문제 설명

    누군가 만든 매우 쉬운 wasm 문제

    Files

    ubuntu@WSL2:~/CTF/dreamhack.io$ tree very-easy-wasm
    very-easy-wasm
    ├── index.html
    └── simple.wasm
    
    0 directories, 2 files
    
    ubuntu@WSL2:~/CTF/dreamhack.io$ file very-easy-wasm/index.html
    very-easy-wasm/index.html: HTML document, ASCII text
    
    ubuntu@WSL2:~/CTF/dreamhack.io$ file very-easy-wasm/simple.wasm
    very-easy-wasm/simple.wasm: WebAssembly (wasm) binary module version 0x1 (MVP)

    simple.wasm 파일 하나와 index.html 파일 하나가 보인다.

    Decompile

    https://cluberwsoh9812.tistory.com/17

    https://www.hahwul.com/2018/10/06/hacking-security-analysis-web-assembly/

    https://github.com/WebAssembly/wabt

    위 링크를 참고해서 wabt 프로젝트를 이용해서 simple.wasm 파일을 디컴파일 할 수 있다.

    ubuntu@WSL2:~/CTF/dreamhack.io/very-easy-wasm$ wasm-decompile ./simple.wasm -o simple.dcmp
    
    ubuntu@WSL2:~/CTF/dreamhack.io/very-easy-wasm$ file simple.dcmp
    simple.dcmp: Java source, ASCII text
    import memory imports_memory;
    
    table T_a:funcref(min: 3, max: 0);
    
    function f_a(a:int):int {
      return (a ^ 192) + 175 & 255
    }
    
    function f_b(a:int):int {
      return (a ^ 191) + 236 & 255
    }
    
    function f_c(a:int):int {
      return (a ^ 196) + 171 & 255
    }
    
    export function something(a:int) {
      var d:int = 0;
      var b:int = d;
      loop L_a {
        var c:int = b[0]:ubyte;
        b[0]:byte = call_indirect(c, d % 3);
        d = c;
        b = b + 1;
        if (b < a) continue L_a;
      }
    }

    분석

    그냥 html 파일을 열면, CORS 문제로 wasm 파일이 로드되지 않는다.

    따라서 vscode로 열어서 Live Server 플러그인을 이용하면 된다.

    다음은 index.html 파일에서 암호화할 문자열을 입력하고 encrypt 버튼을 눌렀을때 작동하는 일부 코드이다.

    <script>
        const btn = document.getElementById("btn")
        const input = document.getElementById("input")
        const out = document.getElementById("out")
        const memory = new WebAssembly.Memory({
            initial: 1
        });
        const u8Arr = new Uint8Array(memory.buffer)
        WebAssembly.instantiateStreaming(fetch('simple.wasm'), {
            "imports": {
                memory
            }
        }).then(obj => {
            btn.addEventListener("click", function() {
                const flag = input.value
                const res = new TextEncoder().encodeInto(flag, u8Arr)
                const len = res.written
                for (let i = 0; i < 9; i++) {
                    obj.instance.exports.something(len)
                    for (let j = 0; j < len / 2 | 0; j++) {
                        let t = u8Arr[len - j - 1]
                        u8Arr[len - j - 1] = u8Arr[j]
                        u8Arr[j] = t
                    }
                }
                out.innerText = Array.from(u8Arr.slice(0, len), function(byte) {
                    return ('0' + (byte & 0xFF).toString(16)).slice(-2);
                }).join('')
            })
        })
    </script>

    Chrome 브라우저로 브레이크포인트를 걸어 하나씩 확인해봤을때
    다음과 같이 작동한다는 것을 알 수 있었다.

    1. u8Arr에는 암호화할 문자열의 문자 하나씩 배열에 아스키 코드값으로 하나의 요소에 들어간다.
    2. 문자열 길이인 len 매개변수와 함께 simple.wasm 파일로부터 로드된 something 함수를 호출해
      u8Arr에 있는 각 요소의 값을 변형시킨다.
    3. 각 요소의 값들이 있는 u8Arr 배열을 역순으로 뒤집는다.
    4. 이런 짓거리(2~3번 과정)를 9번 반복한다.
    5. 마지막으로 u8Arr 배열에 저장된 바이트 데이터를 16진수 문자열로 변환시켜 결과값을 나타나게 한다.

    something 함수는 다음과 같이 리스트에 값들을 변형시킨다는 것을 알 수 있었다.

    이를 테면, CDE 문자열을 넣어 something 함수를 살펴봤을때
    각 배열 요소들은 다음과 같은 변화가 있었다.

    CDE = [67, 68, 69]
    (67 ^ 192) + 175 & 255 = 50	//w2c__f0 -> 0%3=0
    (68 ^ 191) + 236 & 255 = 231	//w2c__f1 -> 67%3=1
    (69 ^ 196) + 171 & 255 = 44	//w2c__f2 -> 68%3=2
    [44, 231, 50]
    
    (44 ^ 192) + 175 & 255 = 155	//w2c__f0 -> 0%3=0
    (231 ^ 196) + 171 & 255 = 206	//w2c__f2 -> 44%3=2
    (50 ^ 192) + 175 & 255 = 161	//w2c__f0 -> 231%3=0
    [161, 206, 155]
    
    (161 ^ 192) + 175 & 255 = 16	//w2c__f0 -> 0%3=0
    (206 ^ 196) + 171 & 255 = 181	//w2c__f2 -> 161%3=2
    (155 ^ 196) + 171 & 255 = 10	//w2c__f2 -> 206%3=2
    [10, 181, 16]
    
    (10 ^ 192) + 175 & 255 = 121	//w2c__f0 -> 0%3=0
    (181 ^ 191) + 236 & 255 =	246	//w2c__f1 -> 10%3=1
    (16 ^ 191) + 236 & 255 = 155	//w2c_f1 -> 181%3=1
    [155, 246, 121]
    
    ~(생략)
    
    (195 ^ 192) + 175 & 255 = 178	//w2c__f0 -> 0%3=0
    (78 ^ 192) + 175 & 255 = 61	//w2c__f0 -> 195 % 3 = 0
    (195 ^ 192) + 175 & 255 = 178	//w2c__f0 -> 78%3=0
    [178, 61, 178]

    call_indirect (param i32) (result i32)
    에서 호출될 수 있는 f0, f1, f2(각각 f_a, f_b, f_c와 같음)가 각각 존재하는데,
    호출되는 함수는 각 배열에 있는 요소의 값을 3을 1로 나눈 나머지에 따라 달라진다.

    처음에는 무조건 0으로 시작해서 f0을 호출하다가,
    0번째 요소가 67이면 67 % 3 = 1이므로 f1 호출
    1번째 요소가 68이면 68 % 3 = 2이므로 f2 호출이 된다.

    만약 n(단, 0은 아님)번째 요소가 100이면,
    100 % 3 = 1이므로 f1을 호출해서 n+1 번째 요소의 값을 변형시킨다.

    따라서 Python3 코드로 암호화시키는 코드를 작성하면 다음과 같다.

    def convert_val(val, choice):
        if(choice == 0):
            return (val ^ 192) + 175 & 255 
        if(choice == 1):
            return (val ^ 191) + 236 & 255
        if(choice == 2):
            return (val ^ 196) + 171 & 255
    
    flag = "DH{THIS_IS_FAKE_FLAG}"  #암호화시킬 문자열
    
    flag = [ord(char) for char in flag]
    saved_flag = flag.copy()
    
    for i in range(9):
        choice = 0
        for j in range(len(flag)):
            # print("choice: " + str(choice), end=", ")
            flag[j] = convert_val(flag[j], choice)
            choice = saved_flag[j] % 3
        flag = flag[::-1]
        saved_flag = flag.copy()
        # print(flag)
    
    enc_flag_list = [hex(item)[2:].zfill(2) for item in flag]
    enc_flag = ''.join(enc_flag_list)
    # print(enc_flag_list)
    print(enc_flag)
    PS Microsoft.PowerShell.Core\FileSystem::\\wsl.localhost\Ubuntu\home\ubuntu\CTF\dreamhack.io\very-easy-wasm> python3 simple.py
    4cd86ad5eda212f25c1558d858148eb6598f0ca579

    Solution

    간단한 XOR 방식으로 암호화가 이루어졌기에
    역으로 계산하면 풀린다. (나에게 역산은 항상 어렵다…)

    def convert_val(val, choice):
        if(choice == 0):
            return ((val - 175) ^ 192) & 255
        if(choice == 1):
            return ((val - 236) ^ 191) & 255
        if(choice == 2):
            return ((val - 171) ^ 196) & 255
        
    enc_flag = "aea9ee71d339c19b0e9c504213b105ebd8c78b9205d46ef347fe70ce4df8dcf4cd7c9f79"
    enc_flag_list = [int(enc_flag[i:i+2], 16) for i in range(0, len(enc_flag), 2)]
    saved_enc_flag_list = enc_flag_list.copy()
    
    for i in range(9):
        choice = 0
        # print(enc_flag_list)
        # enc_flag_list = enc_flag_list[::-1]
        saved_enc_flag_list = enc_flag_list.copy()
        for j in range(len(enc_flag_list)-1, -1, -1):
            # print("choice: " + str(choice), end=", ")
            enc_flag_list[j] = convert_val(enc_flag_list[j], choice)    
            choice = enc_flag_list[j] % 3
        
        enc_flag_list= enc_flag_list[::-1]
    
    # print(enc_flag_list[::-1])
    
    for i in range(len(enc_flag_list)):
        print(chr(enc_flag_list[i]), end='')

    FLAG

    DH{279E4EE960ECB3D0968BDA91C40662EF}

    답글 남기기

    이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다