코드게이트 본선 대회장에서 가장 먼저 풀었던 첫번째 리버싱 문제 입니다 ㅎㅎ
제목으로 볼 수 있듯이 문제는 go 언어로 만들어진 바이너리 파일 입니다.
IDA로 보면 일단 주요 함수는 main과, checkinput, checkValid, printFlag, error, init 등 이 있습니다.
먼저 main 에서 메뉴창을 출력하고 입력을 받은 후에 바로 checkInput 함수를 호출 함으로써 입력값을 확인하게 됩니다.
위 코드는 checkInput 함수의 모습인데 &1 연산을 통해 입력 길이가 홀수 인지 확인합니다.
후에 표현할 수 있는 acsii table 33~126 (띄어쓰기 제외) 값인지 확인한 뒤에 checkValid함수을 호출해 유효한 입력값인지 확인 합니다.
이제 checkValid 함수의 코드 모습인데 이 부분은 gdb로 분석하면서 프로그램 동작구조를 파악했습니다.
먼저 위 코드에서 하는 일은 main_key.array를 불러온 후에 shl, xor, add 연산을 하게 됩니다.
위 내용을 정리해서 파이썬 코드로 나타내 보자면 약간 이런 느낌입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | main_key_array = [0x4a, 0x69, 0x18, 0x61, 0x75, 0x66] r14 = 0xf input = raw_input("input : ") if len(input) & 1 == 1: for i in range(len(input)): tmp = input[i] tmp ^= main_key_array[i % 6] tmp += r14 r14 = tmp else: main_error() | cs |
중간에 트레이싱하다가 이 부분에서 main_error 로 가게 되서 분석해봤는데 main_key.len과 연관이 있었습니다.
main_key_array 배열의 크기는 6 length로 위에서 main_key.len로 비교하는 부분이 존재합니다.
만약 사용자 입력 길이가 6의 배수 + 1이 아니라면 main_key.len과 조건을 비교하는 점프문에서 꼬이기 때문에 길이가 다르게 나와 막히게 됩니다.
그러므로 입력할 문자열 길이는 6의 배수 + 1 이 되어야만 합니다.
좀 밑으로 내려가보면 이런 코드가 있는데 전에 했던 연산을 통해 저장했던 r14를 불러와 위와 같은 연산을 하게 됩니다.
마찬가지로 파이썬 코드로 나타내보면 이런 느낌입니다.
1 2 3 4 5 6 7 8 9 10 | rbx = r14 rbp = (r14 * 0x4081020408102041) >> 64 rbp >>= 5 rbx >>= 0x3f rbp -= rbx rbx = rbp rbx *= 0x7f rax = r14 rax -= rbx rax -= 0x17 | cs |
연산했던 r14을 불러와 0x4081020408102041와 곱한 다음에 8byte 이상 값만 추출하여 위와 같은 연산을 하게 됩니다.
lea rbx, [r12+r15]
movzx ebx, byte ptr [rbx]
cmp rbx, rax
다음에 위 어셈에서 비교를 하게 되는데 lea와 movzx 명령어로 불러온 ebx값은 입력했던 input값의 제일 마지막 문자입니다.
이 부분으로 인해 일단 마지막 문자를 구하는 방법을 알게 되었습니다.
좀 더 내려가보면 이런 부분이 존재합니다.
mov rbx, r13을 해주고 rbx에서 배열 값을 추출합니다.
바로 뒤에 cmp rbx, rbp 부분에서 main_check.array과 값을 비교하는데 r13배열이 어디서 만들어 지는지 다시 위로 올라가서 분석해봤더니 아래와 같은 코드를 볼 수 있었습니다.
r13을 gdb 로 봤을때 rsp+40 에 있는 주소를 나타내길래 rsp+40에 넣는 부분을 집중적으로 봤더니 위와 같은 코드가 나왔습니다.
이 부분은 맨 윗 부분에서 설명했던 shl, xor, add 연산을 한 바로 다음 코드입니다.
연산은 다를게 없어보이지만 add r14 가 없다는 차이가 있습니다.
즉 shl, xor만 한 뒤에 rsp+40 에 값을 넣는 걸 알 수 있었습니다.
이제 분석한 내용을 정리해보면 아래와 같습니다.
1. 입력한 문자열의 길이는 홀수 이어야 하며, 6의 배수 + 1 이어야 합니다.
2. 연산한 r14를 가지고 마지막 input값을 알 수 있습니다.
3. 입력한 문자열과 shl 1, xor main_key.array 를 한 뒤에 나온 값과 main_check.array와 비교
저희는 main_key.array 와 main_check.array 값을 알기 때문에 윗 내용을 토대로 역연산 하여 key값을 추출할 수 있습니다.
[Solve]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | main_key_array = [0x4a, 0x69, 0x18, 0x61, 0x75, 0x66] main_check_array = [0x8c, 0x1, 0xf0, 0xa7, 0xa5, 0xd8, 0xb8, 0x9, 0xf2, 0x85, 0xcb, 0xae, 0xae, 0xd, 0x70, 0xbb, 0xf5, 0xe6] key = [] for i in range(len(main_check_array)): tmp = main_check_array[i] ^ main_key_array[i % 6] tmp >>= 1 key.append(tmp) tmp = [] for i in key: tmp.append(i) r14 = 0xf for i in range(len(key)): tmp[i] <<= 1 tmp[i] ^= main_key_array[i % 6] tmp[i] += r14 r14 = tmp[i] rbx = r14 rbp = (r14 * 0x4081020408102041) >> 64 rbp >>= 5 rbx >>= 0x3f rbp -= rbx rbx = rbp rbx *= 0x7f rax = r14 rax -= rbx rax -= 0x17 key.append(rax) flag = '' for i in key: flag += chr(i) print flag #c4tch_y0ur_dr24m@@! | cs |
'CTF' 카테고리의 다른 글
CODEGATE 2018 본선 후기 (1) | 2018.04.06 |
---|---|
[Codegate final 2018] Shall We Dance (0) | 2018.04.06 |
[Codegate final 2018] betting (0) | 2018.04.06 |
[Codegate 2017] Goversing (0) | 2018.03.29 |
[angstromctf 2017] Product Key (0) | 2018.03.18 |