본문 바로가기

CTF

[Codegate final 2018] G0Crack

코드게이트 본선 대회장에서 가장 먼저 풀었던 첫번째 리버싱 문제 입니다 ㅎㅎ

제목으로 볼 수 있듯이 문제는 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 = [0x4a0x690x180x610x750x66]
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 = [0x4a0x690x180x610x750x66]
main_check_array = [0x8c0x10xf00xa70xa50xd80xb80x90xf20x850xcb0xae0xae0xd0x700xbb0xf50xe6]
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