CSysSec注: 本系列文章译自安全自由工作者Sploitfun的漏洞利用系列博客,从经典栈缓冲区漏洞利用堆漏洞利用,循序渐进,是初学者不可多得的好材料,本系列所有文章涉及的源码可以在这里找到。CSysSec计划在原基础上不断添加相关漏洞利用技术以及相应的Mitigation方法,欢迎推荐或自荐文章。
转载本文请务必注明,文章出处:《Linux(X86)漏洞利用系列-绕过ASLR-第三篇章(GOT覆盖与GOT解引用)》
- 0X01 什么是GOT覆盖
- 0X02 什么是GOT解引用
- 0X03 什么是ROP
- 0X04 什么是gadgets
- 0X05 如何在可执行文件中找出合适的gadgets
- 0X06 利用ROP做到GOT覆盖
- 0X07 利用ROP做到GOT解引用
- 0X08 ROP gadget人工搜索
阅读基础:
经典栈缓冲区溢出
绕过ASLR-第一篇章(return-to-plt)
VM Setup: Ubuntu 12.04(x86)
在这篇文章中,我们来看看如何利用GOT覆盖与GOT解引用技术来绕过共享库的随机化。正如在第一篇章提到的那样,尽管可执行文件没有必需的PLT存根代码,攻击者也能利用GOT覆盖和GOT解引用技术来绕过ASLR。
漏洞代码:
|
|
编译命令:
|
|
注意:
- system@PLT并没有出现在我们的可执行文件’vuln’中
- 字符串”sh”也没有出现在我们的可执行文件’vuln’中
0X01 什么是GOT覆盖
这项技术可以帮助攻击者利用另一个libc函数的地址来覆盖一个特定libc函数的GOT表项。举个例子,GOT[getuid]含有getuid的函数地址(在第一次调用之后),当偏移差(offset difference)添加到GOT[getuid]表项时,它可以被execve的函数地址覆盖。我们已经知道,在共享库中,函数从基地址的偏移总是一个常量。因此,如果我们将两个libc函数(execve与getuid)的偏移差添加到getuid的GOT表项中,我们就可以获取execve函数的地址。之后,就可以通过调用getuid来调用execve!!
offset_diff = execve_addr - getuid_addr
GOT[execve] = GOT[getuid] + offset_diff
0X02 什么是GOT解引用
这和GOT覆盖技术类似,但并不是覆盖一个特定libc函数的GOT表项,而是将GOT表项中的值拷贝到一个寄存器中并将偏移差添加到寄存器的内容中。因此现在寄存器就有了必需的libc函数地址。举个例子,GOT[getuid]含有getuid的函数地址,函数地址被拷贝到一个寄存器中。两个libc函数(execve与getuid)的偏移差添加到寄存器的内容中。现在可以跳转到寄存器的值中来调用execve!
offset_diff = execve_addr - getuid_addr
eax = GOT[getuid]
eax = eax + offset_diff
这两种技术看起来很类似,但在运行期间出现缓冲区溢出时如何操作它们呢? 我们需要识别出一个函数(用来执行这些运算并将结果放入寄存器中),然后跳转到那个特定的函数来做到GOT覆盖/GOT解引用。 显然的是,在libc与我们的可执行文件中并没有一个单独的函数专门来做这件事!! 这种情况下,我们就要利用ROP技术了。
0X03 什么是ROP
虽然没有一种直接了当的方法,ROP可以帮助攻击者一旦获取调用栈的控制权后,就可以执行构造好的机器指令来达到自己的目的
。举个例子,在return-to-libc攻击中,我们用system()的地址覆盖返回地址来执行system()。但如果system函数(以及execve函数簇)从libc共享库中脱离,攻击中就不能获取root shell。在这种情况下,ROP就可以用来拯救攻击者了。利用ROP技术,尽管在没有任何必需的libc函数的情况下,攻击者也可以执行一系列的gadgets来模拟必需的libc函数。
0X04 什么是gadgets
gadgets是以’ret’指令结尾的一串指令。攻击中利用一个gadget地址来覆盖返回地址,这个gadget中含有与system()前几个汇编指令类似的汇编指令串。因此,返回到这个gadget地址就能执行system()的一部分功能。system()剩余的功能可以通过返回到其它的gadget中来完成。利用这种方法,链接一系列gadgets就能完整模拟system()的功能。因此,就算system()被脱离,也能照样被执行!
0X05 如何在可执行文件中找出合适的gadgets
事实证明,可以利用gadget工具。有许多工具(比如ropeme,ROPgadget,rp++)可以帮助攻击者在二进制文件中找出gadgets。这些工具的基本思想都是找出’ret’指令,然后往回找出一系列有用的机器指令。
在我们的例子中,我们不必去利用ROP gadgets来模拟任何libc函数的功能。取而代之的是,我们需要覆盖一个libc函数的GOT表项或者保证任一的寄存器指向一个libc函数地址。让我们来看看如何利用ROP gadgets来实现GOT覆盖与GOT解引用吧!
0X06 利用ROP做到GOT覆盖
-Gadget 1: 首先我们需要一个gadget将偏移差添加到GOT[getuid]中。因此,我们要找出一个add gadget用来将结果拷贝到内存区域。
|
|
太棒了! 我们找到了一个’add gadget’用来将结果拷贝到内存区域!
现在,如果我们EBX中含有GOT[getuid]-0x5d5b04c4, EAX中含有偏移差,我们就可以执行GOT覆盖了!
-Gadget 2: 确保EBX中含有getuid的GOT表项。getuid的GOT表项(如下所示)位于0x8041004处。因此,EBX必须要读入0x804a004, 但是在add gadget中一个常量(0x5d5b04c4)添加到了EBX中,我们就从EBX中减去那个常量: ebx =0x804a004 -0x5d5b04c4 = 0xaaa99b40。 现在我们需要找到一个gadget用来将这个值(0xaaa99b40)拷贝到EBX寄存器中。
|
|
太棒了! 我们找到一个’pop gadget’! 因此,在将值(0xaaa99b40)push到栈中之后,再返回到“pop EBX”指令,EBX中就有了0xaaa99b40.
-Gadget 3: 确保EAX含有偏移差。 我们需要找到一个gadget将偏移差拷贝到EAX寄存器中。
|
|
因此,只要将偏移差(0xfffff530)push到栈中,然后返回到“pop EAX”指令,就能将偏移差拷贝到EAX中。但不幸的是,在我们的二进制文件‘vuln’中并不能找到’pop % eax; ret’ gadget。因此,GOT覆盖就变得不可能了。
栈布局: 下图描绘了利用gadget链接来做到GOT覆盖
0X07 利用ROP做到GOT解引用
-Gadget 1: 首先,我们需要一个gadget来讲偏移差添加到GOT[getuid]中,结果需要加载到寄存器中。因此,我们来找出一个’add gadget’将结果拷贝到寄存器中
|
|
太棒了,我们找到了一个’add gadget’用来将结果拷贝到寄存器中!现在,如果我们将EBX含有GOT[getuid]+0xb8a0008,EAX含有偏移差,就可以成功执行GOT解引用了!
-Gadget 2: 正如在GOT覆盖那样,我们已经在可执行文件’vuln’中找到了’pop %ebx;ret’ gadget。
-Gadget 3: 正如在GOT覆盖那样,可执行文件’vuln’中找不到’pop %eax’ret’ gadget。
-Gadget 4: 通过调用寄存器来调用execve函数。因此我们需要一个’call *eax’ gadget
|
|
太棒了!我们找到了’call *eax’ gadget。 但仍然无法找到gadget 3 ‘pop %eax’ret’ ,GOT解引用依然变得不可能。
栈布局: 下图描绘了利用gadget链接来做到GOT解引用
在似乎没有找到其他方法的时候(至少当我开始学习ROP的时候), Reno教我利用人工ROP gadget搜索来达成目的。多谢! 继续阅读下去吧!
0X08 ROP gadget人工搜索
既然ROP gadget工具无法找到’pop eax’ret’ gadget, 我们就试试人工搜索方法来看看是否有任何有趣的gadgets能帮助我们将偏移差拷贝到EAX寄存器中。
使用下面命令反汇编’vuln’二进制文件
$objdump -d vuln > out
-Gadget 4: 将偏移差(0xfffff530)加载到EAX寄存器。反汇编中可以看到,一条mov指令可以将栈中的内容拷贝到EAX中。
|
|
但看起来’ret’(0x80485d0)离这条指令(0x80485b3)很远。因此这里主要的挑战就是在执行’ret’之前,EAX的内容没有被修改。
未被修改的EAX: 我们来看看如何做到在执行’ret’之前,EAX不被修改。这里有条call指令(0x80485bb),所以找出一种方法加载EBX与ESI以便让call指令调用一个不修改EAX的函数。
|
|
_fini调用了_do_global_dtors_aux, 这里当我们将内存区域0x804a028设置为0x1时,EAX不会被修改。
那EBX与ESI中应该是什么值才能调用_fini呢?
- 1.首先,我们需要找到一个含有_fini(0x804861c)地址的内存区域。 如下所示,内存地址0x8049f3x中含有_fini的地址
|
|
- 2.将ESI设置为0x01020101.采用这个值,那是因为我们不能让ESI中为0x0,而这时一个strcpy漏洞代码,0是个不好的字符! 另外,我们要确保存储在EBX中的结果值也不能是0!
- 将EBX设置为下面的值
|
|
这样一来,我们发现要调用_fini,就需要确保EBX和ESI中必须分别加载0x3fc9c18与0x01020101
-Gadget 5: 加载0x3fc9c18到EBX中
|
|
-Gadget 6: 加载0x01020101到ESI,加载0x01020102到EDI
|
|
-Gadget 7: 将0x1拷贝到内存区域0x804a028中
|
|
现在,我们已经完成了gadget搜索! 我们开始这个游戏吧!
Gadget搜索总结:
- 要成功调用gadget 1,我们需要gadget 2和3
- 由于没有gadget 3,我们做了人工搜索并找出了gadget 4,5,6和7.
- 要成功调用gadget4,我们需要gadget 5,6和7
漏洞利用代码:下面的漏洞利用代码用execve函数地址覆盖了GOT[getuid]!!
|
|
执行上面的漏洞利用代码会生成一个core文件。打开core文件,可以看到GOT[getuid]被execve函数地址覆盖了(如下所示)
|
|
太棒了,我们已经成功利用execve函数地址覆盖了getuid的GOT表项。因此从现在开始,任意对getuid的调用都会调用execve~~
触发 root shell: 我们的漏洞利用代码还不完整。我们只执行了GOT覆盖但还没有触发root shell。 为了触发一个root shell, 将下面的libc函数(以及它们的参数)拷贝到栈中
seteuid@PLT | getuid@PLT | seteuid_arg | execve_arg1 | execve_arg2 | execve_arg3
这里
- setuid@PLT - setuid的plt代码地址(0x80483c0)
- getuid@PLT - getuid的plt代码地址(0x80483b0)。 由于已经执行了GOT覆盖,这里会调用execve函数
- setuid_arg必须为0才能获取root shell
- execve_arg1 - 文件名 - 字符串“/bin/sh”的地址
- execve_arg2 - argv - 参数数组的地址,内容为[ “/bin/sh”的地址, NULL]
- execve_arg3 - envp - NULL
在这篇文章中,由于我们没有直接用0来溢出缓冲区(0是个不好的字符), 我们利用strcpy调用链拷贝0用来替代setuid_arg。反由于栈是被随机化的,这种方法在这里并不可行, 找出setuid_arg栈的地址变得困难起来。
如何绕过栈地址随机化
可以利用自定义栈和栈旋转(stack pivoting)技术做到!
什么是自定义栈
自定义栈就是被攻击者控制的栈区域。攻击者拷贝libc函数链以及相应的参数来绕过栈随机化。由于攻击者进程的选择任意非位置独立(non position independent)并可写的内存区域作为自定义栈。在我们的二进制文件’vuln’中,可写并非位置独立的内存区域是从0x804a000到0x804b000(如下所示)
|
|
含有.data和.bss段的内存区域可以用来当做自定义栈。我将自定义站的位置选为0x804a360.
选择选择好自定义栈位置后,我们需要拷贝libc函数链以及相应的参数到自定义栈中。在我们的例子中,将下面的libx函数(以及相应的参数)拷贝到自定义栈中,以用来触发root shell。
seteuid@PLT | getuid@PLT | seteuid_arg | execve_arg1 | execve_arg2 | execve_arg3
为了将这些内容拷贝到自定义栈,我们需要利用一系列strcpy调用来覆盖真实栈中的返回地址。比如,为了将seteuid@PLT (0x80483c0)拷贝到自定义栈中,我们需要
- 四个strcpy调用- 每个十六进制值分别有一个strcpy调用(0x08, 0x04, 0x83, 0xc0)
- strcpy的源参数必须是含有必需的十六进制值的可执行内存区域的地址。另外我们需要确保选择的内存区域的值不能被修改。 - strcpy的目的参数必须是自定义栈的地址
遵守以上原则,我们可以设置完整的自定义栈。一旦设置好了自定义栈,我们需要利用栈旋转技术(stack pivoting)将真实栈移到自定义栈中
什么是栈旋转
栈旋转可以利用’leave ret’指令来做到。我们已经知道“leave”被翻译为
mov ebp, esp
pop ebp
因此,在执行”leave“指令之前,将自定义栈的地址加载到EBP中–当“leave”执行后,将ESP指向EBP! 这样就已经旋转了自定义栈,我们继续执行加载到自定义栈中的一系列libc函数,就可以触发root shell了。
完整漏洞利用代码:
|
|
执行上面的漏洞利用代码就可以得到下面的root shell
|
|
参考
PAYLOAD ALREADY INSIDE: DATA REUSE FOR ROP EXPLOITS
转载本文请务必注明,文章出处:《Linux(X86)漏洞利用系列-绕过ASLR-第三篇章(GOT覆盖与GOT解引用)》与作者信息:CSysSec出品