深度剖析堆栈中ShellCode的利用原理
2022-8-17 08:42:45 Author: 0x00实验室(查看原文) 阅读量:19 收藏

    "人生若只如初见,何事秋风悲画扇。"


文章概览

001-关于堆栈ShellCode操作:基础理论002-利用fs寄存器寻找当前程序dll的入口:从动态运行的程序中定位所需dll003-寻找大兵LoadLibraryA:从定位到的dll中寻找所需函数地址004-被截断的shellCode:加解密,解决shellCode的零字截断问题

前言

在进入正题之前,需要先了解一下函数调用的堆栈变化。

//代码内容:一个简单的函数调用//环境:vs2022 debug x86#include <iostream>
void fun() {
}
int main() { fun(); return 0;}
;fun函数调用的汇编代码;在执行该汇编语句时,会将该代码的下一个指令的地址进行压栈(0x006C1886;目的是为了回跳,继续执行后续代码006C1881  call        006C10F0006C1886  xor         eax,eax

在执行完call指令之后,正式进入函数内部,进行一系列初始化及环境保存操作。

;提升堆栈006C1800  push        ebp  006C1801  mov         ebp,esp  006C1803  sub         esp,0C0h ;堆栈默认提升0xc0大小;保存函数调用前的寄存器状态006C1809  push        ebx  006C180A  push        esi  006C180B  push        edi  ;初始化提升的新堆栈006C180C  mov         edi,ebp  006C180E  xor         ecx,ecx  006C1810  mov         eax,0CCCCCCCCh  006C1815  rep stos    dword ptr es:[edi]  006C1817  mov         ecx,6CC066h  006C181C  call        006C1311  
;一般功能代码在中间附近出现
;结束功能代码后,恢复现场006C1821 pop edi 006C1822 pop esi 006C1823 pop ebx 006C1824 add esp,0C0h 006C182A cmp ebp,esp 006C182C call 006C123A 006C1831 mov esp,ebp ;恢复调用前的堆栈006C1833 pop ebp ;跳转回地址0x006C1886处006C1834 ret

函数调用中的堆栈情况如下:

正文

  • 对上述两个堆栈状态有个印象,接下来继续进行代码分析。

  • 在用vs环境进行代码编写的时候,遇见类似strcpy,strcmp等函数调用时,如果不将设置中的安全检查取消,那么将判定上述函数为危险函数。其危险的根本就是没有对拷贝、比较等操作限制字符个数,如果无法预见“\0”字符或比较出差值,那么将一直执行下去。

#报错如下D:\Code\CPP\CPP控制台程序\CPP控制台程序\main.cpp(7,2): error C4996: 'strcpy': This function or variable may be unsafe. Consider using strcpy_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.
//代码内容:一个简单的函数调用//环境:vs2022 debug x86#include <iostream>
void fun(const char * c ) { char arr[4]; strcpy(arr, c);}
int main() { char c[1024] = { 0 }; scanf("%s"); fun(c); return 0;}/*程序开始后输入:11111111111111111111*/
;提升堆栈00141EC0  | 55              | push ebp                          | main.cpp:500141EC1  | 8BEC            | mov ebp,esp                       |;sub esp,CC这句表示提升栈空间0xCC大小,十进制也就是204字节00141EC3  | 81EC CC000000   | sub esp,CC                        |;保存现场00141EC9  | 53              | push ebx                          |00141ECA  | 56              | push esi                          | esi:__enc$textbss$end+2300141ECB  | 57              | push edi                          |;初始化新堆栈00141ECC  | 8D7D F4         | lea edi,dword ptr ss:[ebp-C]      |00141ECF  | B9 03000000     | mov ecx,3                         |00141ED4  | B8 CCCCCCCC     | mov eax,CCCCCCCC                  | eax:"11111111111111111111"00141ED9  | F3:AB           | rep stosd                         |00141EDB  | B9 66C01400     | mov ecx,_A0FF1F1C_CPP             | main.cpp:1573248000141EE0  | E8 2CF4FFFF     | call cpp控制台程序.141311          |;拷贝功能代码的汇编形式00141EE5  | 8B45 08         | mov eax,dword ptr ss:[ebp+8]      | main.cpp:7, [ebp+8]:"11111111111111111111"00141EE8  | 50              | push eax                          | eax:"11111111111111111111"00141EE9  | 8D4D F8         | lea ecx,dword ptr ss:[ebp-8]      |00141EEC  | 51              | push ecx                          |00141EED  | E8 C9F4FFFF     | call cpp控制台程序.1413BB          | strcpy;结束函数调用,进行堆栈平衡00141EF2  | 83C4 08         | add esp,8                         |00141EF5  | 52              | push edx                          | main.cpp:800141EF6  | 8BCD            | mov ecx,ebp                       |00141EF8  | 50              | push eax                          | eax:"11111111111111111111"00141EF9  | 8D15 1C1F1400   | lea edx,dword ptr ds:[<>]         |00141EFF  | E8 D7F2FFFF     | call cpp控制台程序.1411DB          |;恢复现场00141F04  | 58              | pop eax                           | eax:"11111111111111111111"00141F05  | 5A              | pop edx                           |00141F06  | 5F              | pop edi                           |00141F07  | 5E              | pop esi                           | esi:__enc$textbss$end+2300141F08  | 5B              | pop ebx                           |00141F09  | 81C4 CC000000   | add esp,CC                        |00141F0F  | 3BEC            | cmp ebp,esp                       |00141F11  | E8 24F3FFFF     | call cpp控制台程序.14123A          |00141F16  | 8BE5            | mov esp,ebp                       |00141F18  | 5D              | pop ebp                           |;结束当前函数调用00141F19  | C3              | ret                               |

下图是对fun函数调用时,堆栈栈底值含义的分析

开始分析功能代码部分

;将输入的字符串的地址入栈00141EE5  | 8B45 08      | mov eax,dword ptr ss:[ebp+8]            | main.cpp:7, [ebp+8]:"11111111111111111111"00141EE8  | 50           | push eax                                | eax:"11111111111111111111";将目标内存地址入栈,根据ebp-8我们可以清楚地知道,char arr[4]的空间位置00141EE9  | 8D4D F8      | lea ecx,dword ptr ss:[ebp-8]            |00141EEC  | 51           | push ecx                                |;调用strcpy函数00141EED  | E8 C9F4FFFF  | call cpp控制台程序.1413BB                | strcpy

执行strcpy函数后,堆栈图如下:

我们可以清楚地看见,一个strcpy函数的执行,直接将原来的堆栈环境打乱

  • 旧栈底地址值被更改

  • 函数结束的回跳地址被更改(改成shellCOde的起始地址)

  • 更改地址的堆栈也可以被随意更改(改写成shellCode具体内容

利用

  • 思考:如果我们输入的内容,恰好将函数的回跳地址重新覆盖,覆盖成我们shellCode地址的开始地址,那么接下来继续执行的代码就是我们的恶意代码

  • 问题:程序每次执行或操作系统重启,地址都会发生改变,如果将回跳地址写死,那么费力编写的恶意字符串也就变成了无用的字符串

  • 输入恶意拼接的字符串,就从左侧的正常堆栈变成右侧被shellCode填充的堆栈

根据上边的问题,我们需要一个动态定位ShellCode地址的方法。让我们详细分析下函数调用恢复的过程。

;恢复寄存器现场00141F04  | 58                 | pop eax                       | eax:"11111111111111111111"00141F05  | 5A                 | pop edx                       |00141F06  | 5F                 | pop edi                       | edi:"111111111111"00141F07  | 5E                 | pop esi                       | esi:__enc$textbss$end+23;将旧栈底重新赋给栈底指针,但是旧栈底会在输入恶意字符串时被破坏00141F08  | 5B                 | pop ebx                       |;esp栈顶寄存器向高位移动,栈提升使用了sub esp,cc,栈顶恢复与此相反;重点1:此时的esp指向了老ebp的存储空间00141F09  | 81C4 CC000000      | add esp,CC                    |00141F0F  | 3BEC               | cmp ebp,esp                   |00141F11  | E8 24F3FFFF        | call cpp控制台程序.14123A      |00141F16  | 8BE5               | mov esp,ebp                   |00141F18  | 5D                 | pop ebp                       |;最后利用ret将回跳地址pop并跳转;重点2:此时ebp指向了shellCode的首地址00141F19  | C3                 | ret                           |

在考虑更改eip寄存器的常用指令:

  • ret

  • jmp系列指令

经过两次跳转,成功的将eip指向了shellCode位置,进行继续执行shellCode

利用跳转的原因

  • 动态定位shellCode

  • shellCode复用

总结

  • 尽量寻找系统文件dll中的jmp esp。因为DLL文件有个默认的载入地址,在没有其他dll占用着位置的时候,dll就会装入默认的位置,否则就会装载到随机的位置,并重新计算函数地址。而操作系统的dll是先于其他应用加载的,因此地址更变得概率不大,成功率高。

  • 相同的系统,shellCode才有通用的可能。因为版本不同,dll加载位置可能不同,最终寻找到的jmp命令位置也不同

  • 全文的操作前提是,能够将栈中的数据当成了代码来进行执行。如果不限制权限,那么就可以利用ret + jmp esp来进行跳转,达到更改eip的效果,进而执行shellcode


文章来源: http://mp.weixin.qq.com/s?__biz=Mzg5MDY2MTUyMA==&mid=2247488819&idx=1&sn=6d4c23f83f48835a8b30c722aa50bcd7&chksm=cfd86accf8afe3da6b572e72d7e77403dee8ebba046093d5ec8f9c9fb8712e5683289fb90eed#rd
如有侵权请联系:admin#unsafe.sh