문제 링크
https://dreamhack.io/wargame/challenges/17
rev-basic-3
Reversing Basic Challenge #3 이 문제는 사용자에게 문자열 입력을 받아 정해진 방법으로 입력값을 검증하여 correct 또는 wrong을 출력하는 프로그램이 주어집니다. 해당 바이너리를 분석하여 correct를 출
dreamhack.io
풀이
이번 문제는 바로 정답을 찾기보다는 좀 더 메모리 구조와 흐름을 이해해보기 위해 자세히 포스팅 해보았다.
먼저, Bp를 아래 그림 1을 기준으로 scanf를 받은 후 나오는 call 함수에 걸었다.
[rsp+32]가 가리키는 주소에는 사용자가 입력한 문자열이 위치해 있다. 이때, 위 그림 1과 같이 lea rcx, [rsp+32]를 통해 레지스터 rcx에 해당 주솟 값을 저장하게 된다.
위 그림 2는 그림 1에서 F7 한후, mov [rsp+8], rcx 를 한 후의 결과이다. rcx 레지스터에 저장된 값을 [rsp+8]이 위치하는 곳에 mov 시킨다.
그래서 현재 rsp가 00000027A86FFC08 인데, 스택의 00000027A86FFC10 주소를 보면, rcx에 저장된 값인 00000027A86FFC30가 담겨진 것을 확인 가능하다.
위 그림 2에서 rsp가 00000027A86FFC08 였고, 0x18 뺐을 때, 위 그림 3과 같이 rsp가 00000027A86FFBF0 가 됨을 확인했다.
위 그림 3에서 rsp의 주소( 00000027A86FFBF0 ) 에 저장된 값은 "00 00 01 6E 00 00 00 01"이다. 이때 위 그림 4와 같이 mov dword ptr [rsp], 0 연산을 수행하면 x86-64 특성(리틀엔디안)에 따라 하위 4바이트인 "00 00 00 01" 인 값이 "00 00 00 00" 으로 변경되게 된다.
위 그림 5에서는 movsxd rax, dword ptr [rsp]에 대한 결과를 살펴볼 수 있다. 기존의 rax = 0x1(앞의 0 생략)이였는데, rax = 0으로 변경 되었다.
위 그림 6에서는 아직 lea rcx(레지스터), unk_7FF7DAA83000(메모리) 연산을 하기 이전 상황이며, 현재 rcx 레지스터에 저장된 메모리 주소 값을 확인할 수 있다.
unk_7FF7DAA83000 (메모리 주소) 를 위 그림 7에서 확인한다.
lea rcx, unk_7FF7DAA83000 후 rcx 레지스터에 위 그림 8과 같이 해당 메모리 주소( unk_7FF7DAA83000 )값이 저장되었다.
movzx eax, byte ptr [rcx+rax] 연산을 수행 한 후인데, 기존에 rax 레지스터에 저장된 값이 0이였다. 그래서 movzx eax, byte ptr [rcx + 0]과 같으며, rcx 레지스터가 가리키는 주소의 첫 번째 바이트인 "I"(49)를 가져와 eax에 이동시켰다.
그래서 위 그림 9에서는 rax = 0000000000000049 임을 확인할 수 있다.
movsxd 를 통해서 rsp 메모리에 들어있는 값을 DWORD 형으로 rax에 저장하게 된다.
mov rdx, [rsp+0x20] 수행 후 rdx 레지스터에 주소 값 00000027A86FFC30가 저장되었다.
위 그림 12는 movzx ecx, byte ptr [rdx+rcx] 연산을 수행한 후이다. rcx 값이 0인 상태에서 진행했으므로 movzx ecx, byte ptr [rdx]과 같다.
이때, byte ptr [rdx]( byte ptr [00000027A86FFC30] )의 첫번째 바이트 값인 0x43("C")가 ecx에 저장되어진다.
lea ecx, [rcx+rdx*2] 연산을 한 결과가 RCX에 저장되어 있다.
정답이 아닌 입력 값을 입력했을 때의 flow는 위 그림 1 ~ 15와 같았다.
아래 과정은 flag를 얻는 정답 풀이 과정이다.
해당 파일을 디컴파일 할 시, 위 그림 16과 같고, 문제의 중점인 sub_7FF787931000 함수 내부로 들어가 본다.
위 그림 17의 디컴파일 된 함수(sub_7FF787931000) 로직을 보고, 사용자의 입력 값이 그대로 기존의 비교 대상 값과 일치할 수 없음을 알 수 있었다.
정리하자면, ( 사용자 입력 배열[i] ^ i ) +2 * i 를 한 값과 byte_7FF787933000[i] 가 비교 대상이다. 해당 byte_7FF787933000 배열이 가리키는 주소를 가보면 아래와 같다.
위의 문제 상황을 종합하여, 아래와 같은 파이썬 코드를 작성하였다.
byte_7FF787933000 = [
73, 96, 103, 116, 99, 103, 66, 102, 128, 120, 105, 105,
123, 153, 109, 136, 104, 148, 159, 141, 77, 165, 157, 69
]
input_list = []
for i in range(len(byte_7FF787933000)):
result = i ^ (byte_7FF787933000[i] - 2 * i)
input_list.append(chr(result))
print("".join(input_list))
같은 수를 두번 XOR하면 원래 수로 돌아오기 때문에 위와 같은 코드가 작성되었다.
참고
Byte = 1바이트 | AH : 1 바이트 AL : 1 바이트
Word = 2바이트 | AX : 2바이트
Dword = 4바이트 | EAX : 4바이트
Qword = 8바이트 | RAX : 8바이트
MOVZX란?
https://blog.naver.com/stop2y/221049321800
[리버싱] 기본 어셈블리어 - MOV / MOVZX / MOVSX /MOVS
MOV / MOVZX / MOVSX /MOVSMOV형식 : MOV [OPER1][OPER2] 의미 : OPER...
blog.naver.com
MOVSXD란?
https://stackoverflow.com/questions/56565510/x86-what-does-movsxd-rdx-edx-instruction-mean
X86: What does `movsxd rdx,edx` instruction mean?
I have been playing with intel mpx and found that it adds certain instructions that I could not understand. For e.g. (in intel format): movsxd rdx,edx I found this, which talks about a similar
stackoverflow.com
IDA에서 특정 주소의 바이트 값 리스트화 하기
Python>list(get_bytes(0x00007FF787933000, 32))
[0x49, 0x60, 0x67, 0x74, 0x63, 0x67, 0x42, 0x66, 0x80, 0x78, 0x69, 0x69, 0x7b, 0x99, 0x6d, 0x88, 0x68, 0x94, 0x9f, 0x8d, 0x4d, 0xa5, 0x9d, 0x45, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]