콘텐츠로 건너뛰기

very-easy-wasm

문제 설명

누군가 만든 매우 쉬운 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}

태그:

답글 남기기