Inc0gnito CTF 2015 Write-Up | By RevDev
Reversing - Anti Hexray [100pt]
1. Ready to Solve
Problem
Find the key
ssh anti_hexray@ssh.inc0gnito.com anti_hexray
anti_hexray_c878be02ae603ac59c793d58d06cc54489ff8251
GNU bash (version 4.3.11)
IDA Pro 6.6 With HexRay
File List
anti_hexray@incognito1:~$ ls -al
total 36
drwx--xr-x 4 root root 4096 Aug 23 04:10 .
drwxr-x--x 13 root root 4096 Aug 22 09:14 ..
-rwxr-sr-x 1 root anti_hexray_root 8228 Aug 23 04:07 anti_hexray
---------- 1 root root 0 Aug 23 04:10 .bash_history
drwx------ 2 root root 4096 Aug 23 04:01 .cache
-r--r----- 1 root anti_hexray_root 18 Aug 23 04:07 flag
drwx------ 2 root root 4096 Aug 23 03:54 .ssh
-rw------- 1 root root 1782 Aug 23 04:03 .viminfo
Disassembled Code
Dump of assembler code for function main:
0x000000000040073c <+0>: push rbp
0x000000000040073d <+1>: mov rbp,rsp
0x0000000000400740 <+4>: sub rsp,0xb0
0x0000000000400747 <+11>: mov DWORD PTR [rbp-0xa4],edi
0x000000000040074d <+17>: mov QWORD PTR [rbp-0xb0],rsi
0x0000000000400754 <+24>: mov rax,QWORD PTR fs:0x28
0x000000000040075d <+33>: mov QWORD PTR [rbp-0x8],rax
0x0000000000400761 <+37>: xor eax,eax
0x0000000000400763 <+39>: mov QWORD PTR [rbp-0x90],0x40099c
0x000000000040076e <+50>: mov QWORD PTR [rbp-0x88],0x4009ab
0x0000000000400779 <+61>: mov QWORD PTR [rbp-0x80],0x4009b4
0x0000000000400781 <+69>: mov QWORD PTR [rbp-0x78],0x4009bd
0x0000000000400789 <+77>: mov QWORD PTR [rbp-0x70],0x4009c6
0x0000000000400791 <+85>: mov QWORD PTR [rbp-0x68],0x4009cf
0x0000000000400799 <+93>: mov QWORD PTR [rbp-0x60],0x4009d8
0x00000000004007a1 <+101>: mov QWORD PTR [rbp-0x58],0x4009e1
0x00000000004007a9 <+109>: mov QWORD PTR [rbp-0x50],0x4009ea
0x00000000004007b1 <+117>: mov QWORD PTR [rbp-0x48],0x4009f3
0x00000000004007b9 <+125>: mov QWORD PTR [rbp-0x40],0x4009fc
0x00000000004007c1 <+133>: mov QWORD PTR [rbp-0x38],0x400a05
0x00000000004007c9 <+141>: mov DWORD PTR [rbp-0x9c],0xbb8
0x00000000004007d3 <+151>: mov DWORD PTR [rbp-0x98],0x1388
0x00000000004007dd <+161>: push rsp
0x00000000004007de <+162>: mov r15,rsp
0x00000000004007e1 <+165>: xor rsp,rsp
0x00000000004007e4 <+168>: mov rsp,r15
0x00000000004007e7 <+171>: lea rax,[rbp-0x30]
0x00000000004007eb <+175>: mov edx,0x20
0x00000000004007f0 <+180>: mov esi,0x0
0x00000000004007f5 <+185>: mov rdi,rax
0x00000000004007f8 <+188>: call 0x4005c0 <memset@plt>
0x00000000004007fd <+193>: mov DWORD PTR [rbp-0xa0],0x0
0x0000000000400807 <+203>: jmp 0x40081c <main+224>
0x0000000000400809 <+205>: mov eax,DWORD PTR [rbp-0x9c]
0x000000000040080f <+211>: add DWORD PTR [rbp-0xa0],eax
0x0000000000400815 <+217>: add DWORD PTR [rbp-0xa0],0x1
0x000000000040081c <+224>: mov eax,DWORD PTR [rbp-0xa0]
0x0000000000400822 <+230>: cmp eax,DWORD PTR [rbp-0x9c]
0x0000000000400828 <+236>: jb 0x400809 <main+205>
0x000000000040082a <+238>: mov esi,0x800
0x000000000040082f <+243>: mov edi,0x400a0e
0x0000000000400834 <+248>: mov eax,0x0
0x0000000000400839 <+253>: call 0x400610 <open@plt>
0x000000000040083e <+258>: mov DWORD PTR [rbp-0x94],eax
0x0000000000400844 <+264>: xor rsp,rsp
0x0000000000400847 <+267>: mov rsp,r15
0x000000000040084a <+270>: mov eax,DWORD PTR [rbp-0x94]
0x0000000000400850 <+276>: lea rcx,[rbp-0x30]
0x0000000000400854 <+280>: mov edx,0x20
0x0000000000400859 <+285>: mov rsi,rcx
0x000000000040085c <+288>: mov edi,eax
0x000000000040085e <+290>: call 0x4005e0 <read@plt>
0x0000000000400863 <+295>: push rsp
0x0000000000400864 <+296>: mov rax,QWORD PTR [rbp-0xb0]
0x000000000040086b <+303>: add rax,0x8
0x000000000040086f <+307>: mov rax,QWORD PTR [rax]
0x0000000000400872 <+310>: mov rdi,rax
0x0000000000400875 <+313>: call 0x4005a0 <strlen@plt>
0x000000000040087a <+318>: mov rdx,rax
0x000000000040087d <+321>: mov rax,QWORD PTR [rbp-0xb0]
0x0000000000400884 <+328>: add rax,0x8
0x0000000000400888 <+332>: mov rcx,QWORD PTR [rax]
0x000000000040088b <+335>: lea rax,[rbp-0x30]
0x000000000040088f <+339>: mov rsi,rcx
0x0000000000400892 <+342>: mov rdi,rax
0x0000000000400895 <+345>: call 0x400600 <memcmp@plt>
0x000000000040089a <+350>: mov DWORD PTR [rbp-0x9c],eax
0x00000000004008a0 <+356>: cmp DWORD PTR [rbp-0x9c],0x0
0x00000000004008a7 <+363>: je 0x4008c0 <main+388>
0x00000000004008a9 <+365>: mov eax,DWORD PTR [rbp-0x94]
0x00000000004008af <+371>: mov edi,eax
0x00000000004008b1 <+373>: call 0x4005d0 <close@plt>
0x00000000004008b6 <+378>: mov edi,0x1
0x00000000004008bb <+383>: call 0x400620 <exit@plt>
0x00000000004008c0 <+388>: push rbx
0x00000000004008c1 <+389>: mov eax,DWORD PTR [rbp-0x94]
0x00000000004008c7 <+395>: mov edi,eax
0x00000000004008c9 <+397>: call 0x4005d0 <close@plt>
0x00000000004008ce <+402>: mov eax,0x0
0x00000000004008d3 <+407>: mov rdx,QWORD PTR [rbp-0x8]
0x00000000004008d7 <+411>: xor rdx,QWORD PTR fs:0x28
0x00000000004008e0 <+420>: je 0x4008e7 <main+427>
0x00000000004008e2 <+422>: call 0x4005b0 <__stack_chk_fail@plt>
0x00000000004008e7 <+427>: leave
0x00000000004008e8 <+428>: ret
End of assembler dump.
3. Solve
말 그대로 HexRay로 분석하지 못하도록 Anti HexRay 기법이 걸려있다.
HexRay 플러그인으로 열어보려고 하면, positive sp value has been found라면 Decompilation failure가 뜨게 된다.
어쩔 수 없이 HandRay를 수행해야한다. IDA의 강점 중 하나인 Graph View를 이용하여 전체적인 분기문의 모습을 살펴보도록 하자.
![](https://t1.daumcdn.net/cfile/tistory/243C923455DB1BD42C)
생각보다 간단한 풀이가 될 듯하다. 좀 더 세부적으로 살펴보면서 찬찬히 HandRay를 하도록 하자.
![](https://t1.daumcdn.net/cfile/tistory/256B063455DB1BD512)
일단 가장 먼저 나오는 분기문 부분이다. 크게 중요한 부분은 없는 듯 하고, loc_40081C
와 loc_400809
로 이어지는 반복 문 또한, 메인 루틴에서 쓰이지 않으므로 무시해도 된다. 여기서 그나마 의미 있는 부분은 위 어셈블리 문에서
0x00000000004007e7 <+171>: lea rax,[rbp-0x30]
0x00000000004007eb <+175>: mov edx,0x20
0x00000000004007f0 <+180>: mov esi,0x0
0x00000000004007f5 <+185>: mov rdi,rax
0x00000000004007f8 <+188>: call 0x4005c0 <memset@plt>
부분에 해당하는 memset함수 부분이다. 이 부분의 인자들과 기타 변수들을 적절히 해석하여 코드로 바꾸어 주자.
memset(unk_1, 0, 32);
본인의 경우에는 아직 rbp-0x30
이 어떠한 변수인지 모르므로 unk_1이라는 식별자를 붙여주었다. 계속해서 다음 부분을 살펴보도록 하자.
![](https://t1.daumcdn.net/cfile/tistory/2751303455DB1BD721)
이 부분이 가장 중요한 부분이다. 찬찬히 살펴보도록 하자. 가장 먼저 파일 경로로 보이는 문자열이 눈에 보인다. 내친김에 이 파일 경로로 무엇을 하는지 확인하자. Disassemble Dump에서는 다음 부분이다.
0x000000000040082a <+238>: mov esi,0x800
0x000000000040082f <+243>: mov edi,0x400a0e
0x0000000000400834 <+248>: mov eax,0x0
0x0000000000400839 <+253>: call 0x400610 <open@plt>
0x000000000040083e <+258>: mov DWORD PTR [rbp-0x94],eax
0x0000000000400844 <+264>: xor rsp,rsp
0x0000000000400847 <+267>: mov rsp,r15
0x000000000040084a <+270>: mov eax,DWORD PTR [rbp-0x94]
0x0000000000400850 <+276>: lea rcx,[rbp-0x30]
0x0000000000400854 <+280>: mov edx,0x20
0x0000000000400859 <+285>: mov rsi,rcx
0x000000000040085c <+288>: mov edi,eax
0x000000000040085e <+290>: call 0x4005e0 <read@plt>
여기서 rbp-0x94
가 들어간 인자 위치로 보나, 두 함수에서 중복되어 쓰였다는 것을 보나, rbp-0x94
는 fd
(File Descriptor)라는 것을 알 수 있다. 또, open
에서 얻어낸 fd값이 rbp-0x94
즉, fd
에 들어가고 그 값을 다시 read에서 File Descriptor로 사용하는 것으로 말미암아, /home/anti_hexray/flag
라는 파일에서 무언가를 읽어온다는 것 또한 짐작할 수 있다. 여기서 입력된 값이 들어가는 버퍼는 위에서 unk_1이라고 명명한 rbp-0x30
이다. 이제 이 정보들을 토대로 계속 코드를 재구성 해보자.
memset(buf, 0, 32);
fd = open("/home/anti_hexray/flag" 0x800, 32);
read(fd, buf, 32);
좋다, 지금까지 우리가 알아낸 사실은 32byte짜리 buf
배열을 0으로 초기화 한 뒤, 그 buf
에 /home/anti_hexray/flag
의 값을 32byte만큼 읽어온다는 것이다. 계속 진행하도록 하자. 그 다음으로 살펴 볼 코드는 다음 부분이다.
0x0000000000400863 <+295>: push rsp
0x0000000000400864 <+296>: mov rax,QWORD PTR [rbp-0xb0]
0x000000000040086b <+303>: add rax,0x8
0x000000000040086f <+307>: mov rax,QWORD PTR [rax]
0x0000000000400872 <+310>: mov rdi,rax
0x0000000000400875 <+313>: call 0x4005a0 <strlen@plt>
0x000000000040087a <+318>: mov rdx,rax
0x000000000040087d <+321>: mov rax,QWORD PTR [rbp-0xb0]
0x0000000000400884 <+328>: add rax,0x8
0x0000000000400888 <+332>: mov rcx,QWORD PTR [rax]
0x000000000040088b <+335>: lea rax,[rbp-0x30]
0x000000000040088f <+339>: mov rsi,rcx
0x0000000000400892 <+342>: mov rdi,rax
0x0000000000400895 <+345>: call 0x400600 <memcmp@plt>
0x000000000040089a <+350>: mov DWORD PTR [rbp-0x9c],eax
0x00000000004008a0 <+356>: cmp DWORD PTR [rbp-0x9c],0x0
0x00000000004008a7 <+363>: je 0x4008c0 <main+388>
전체 Graph View에서 본, 마지막 분기 부분 전까지 해석해보도록 하자. main+296
에서 main+313
까지를 보면, rbp-0xb0
에서 0x8만큼을 더한 곳의 문자열을 strlen함수의 인자로 넘겨주고 있다. 그 다음, 그 값을 rdx
를 통해 memcmp함수의 마지막 인자로 전달한다. 여기서 우리는 아직 rbp-0xb0+0x8
의 정체를 모르기 때문에 일단은 unkStr
이라 놓고 보겠다. memcmp함수에는 unkStr
문자열의 길이를 구하여 그 만큼의 unkStr
문자열을 위의 buf
배열과 비교한다. 그럼 대충 짐작이 가지 않는가? unkStr
은 유저가 건네준 Input이라는 것이? 그러나 이전까지의 함수 진행에서 별도의 입력 함수가 없었기에 본인은 unkStr
이 Argv가 아닐까하고 예상했다. 그리고 나중에 GDB로 디버깅 결과 실제로 그러하였다.
뭐 이렇게 해서 unkStr
은 Argv[1]
이라는 것이 확정되었다. 자 그럼 조금 더 내려가면 memcmp의 결과값이 0, 즉 두 값이 같을 경우에만 0x4008c0
주소로 점프하게 된다. 이는 전형적인 if문이다. 계속 코드를 복원하자.
memset(buf, 0, 32);
fd = open("/home/anti_hexray/flag" 0x800, 32);
read(fd, buf, 32);
lenArg = strlen(argv[1]);
if(memcmp(buf, argv[1], lenArg))
이제 남은 마지막 부분까지 해독해보도록 하자.
0x00000000004008a9 <+365>: mov eax,DWORD PTR [rbp-0x94]
0x00000000004008af <+371>: mov edi,eax
0x00000000004008b1 <+373>: call 0x4005d0 <close@plt>
0x00000000004008b6 <+378>: mov edi,0x1
0x00000000004008bb <+383>: call 0x400620 <exit@plt>
0x00000000004008c0 <+388>: push rbx
0x00000000004008c1 <+389>: mov eax,DWORD PTR [rbp-0x94]
0x00000000004008c7 <+395>: mov edi,eax
0x00000000004008c9 <+397>: call 0x4005d0 <close@plt>
0x00000000004008ce <+402>: mov eax,0x0
0x00000000004008d3 <+407>: mov rdx,QWORD PTR [rbp-0x8]
0x00000000004008d7 <+411>: xor rdx,QWORD PTR fs:0x28
0x00000000004008e0 <+420>: je 0x4008e7 <main+427>
0x00000000004008e2 <+422>: call 0x4005b0 <__stack_chk_fail@plt>
0x00000000004008e7 <+427>: leave
0x00000000004008e8 <+428>: ret
main+365
에서 main_383
까지를 해석하면, fd
를 close함수를 통해 닫은 뒤, 인자인 edi에 0x1을 넣은 채로 exit함수를 실행해 프로그램을 종료하게 된다. 그 밑으로 main+388
부터는 main+363
의 조건분기문에 의해 분기된 부분으로서, 이 부분 또한 fd
를 닫는 것 까지 같다. 다만 다른 점이라면 Stack Check를 수행하는 것과, eax의 값이 0인 채로 리턴된다는 것 정도이다. 자, 이제 전체 코드를 복원해보자.
memset(buf, 0, 32);
fd = open("/home/anti_hexray/flag" 0x800, 32);
read(fd, buf, 32);
lenArg = strlen(argv[1]);
if(memcmp(buf, argv[1], lenArg)){
close(fd);
exit(1);
}
close(fd);
return 0;
이 코드를 분석해보자. 사실상 이 코드가 뜻하는 바는 간단하다. 프로그램 호출시 Argv로 준 값과 파일에서 읽어온 값을 Argv의 길이만큼 비교해, 같다면 0을, 틀리다면 1을 리턴하게 된다. 그런데 여기서 의문이 생길 수 있을 것이다. 그래서, Auth값을 어떻게 구할건가? 물론 방법은 있다. 다음 화면을 보자.
![](https://t1.daumcdn.net/cfile/tistory/2642913455DB1BDA2B)
실제 문제 서버 상에서 프로그램을 실행시킨 결과이다. 물론 코드와 같이 별도의 출력이라던가, 기타 Auth Key유추에 필요한 정보는 프로그램 상에서 주지 않는다. 그러나, 리눅스에 대해 어느 정도 다루어 본 사람들은 echo명령어를 떠올릴 수 있을 것이다. echo $?
는 가장 나중에 실행되었던 프로그램의 리턴 값을 출력해주는 리눅스 쉘 명령어이다. 이를 응용하면 쉽게 문제를 풀 수 있을 것이다. 조금만 더 확인해보자.
![](https://t1.daumcdn.net/cfile/tistory/27296D3455DB1BDC39)
보다시피, 특정한 값을 입력시 0으로 리턴되는 것을 볼 수 있다. memcmp
함수는 어디까지나 1byte씩 비교하는 함수이므로…
뭐, 결론만 말하자면 0이 리턴 값으로 나오는 문자를 하나하나 브루트 포싱으로 구하면 완성된 문자열이 Auth Key이다.
4. Flag
![](https://t1.daumcdn.net/cfile/tistory/234A433455DB1BDE1E)