1、很简单的一段代码,功能就是打开Windows自带的计算器程序。而我要实现的shellcode的功能就是这个。
#include
int main()
{
LoadLibraryA("kernel32.dll");
WinExec("calc.exe", SW_SHOW);
return 0;
}
2、将WinExec("calc.exe", SW_SHOW);改成汇编后的样子。
#include
int main()
{
char str[]="calc.exe";
char* p = str;
LoadLibraryA("kernel32.dll");
__asm
{
push 0;
mov eax,p;
push eax;
mov eax,0x7c86250d; //WinExec的地址
call eax;
}
return 0;
}
3、在堆栈中构造字符串,经典!这招是我在书上学来的,并非本人原创 -_-。
/*
* Author: Leng_que
* Date: 2009年10月12日11:02:40
* E-mail: leng_que@yahoo.com.cn
* Description: 一段通过WinExec运行calc.exe的shellcode雏形I
* Comment: 在WindowsXP SP3 + VC6.0环境下调试通过
*/
#include
int main()
{
//因为WinExec这个API函数在kernel32.dll这个动态链接库里,所以要先加载它
LoadLibraryA("kernel32.dll");
__asm
{
push ebp;
mov ebp,esp;
//在堆栈中构造字符串:calc.exe
xor eax,eax;
push eax;
sub esp,08h;
mov byte ptr [ebp-0ch],63h;
mov byte ptr [ebp-0bh],61h;
mov byte ptr [ebp-0ah],6Ch;
mov byte ptr [ebp-09h],63h;
mov byte ptr [ebp-08h],2Eh;
mov byte ptr [ebp-07h],65h;
mov byte ptr [ebp-06h],78h;
mov byte ptr [ebp-05h],65h;
//执行WinExec,启动计算器程序
push 0;
lea eax,[ebp-0ch];
push eax;
mov eax,0x7c86250d; //WinExec的地址
call eax;
//平衡堆栈
mov esp,ebp;
pop ebp;
}
return 0;
}
4、具有shellcode特点的汇编代码完成了!
值得一提:在这一步我这么也得不到LoadLibrary(在原来的代码中我是写这个的)的地址,于是只好先去吃饭,在来回饭堂的校道上,我突然想到了,原来自己一时忘记,于是犯了一个低级错误,其中在Kernel32.dll里是没有LoadLibrary这个函数的,只有LoadLibraryA和LoadLibraryW,它们的区别在于传入的参数是ANSI编码的还是Unicode编码的,但是为什么我们在平时的编程中写LoadLibrary又可以正常运行呢?那是因为其实这个LoadLibrary只是一个宏而已啦,在VC6.0下选中这个LoadLibrary函数(当然,应该叫宏更准确些),然后右键->"Go To Definition"一下就知道了。
/*
* Author: Leng_que
* Date: 2009年10月12日15:15:32
* E-mail: leng_que@yahoo.com.cn
* Description: 一段通过WinExec运行calc.exe的完整shellcode雏形
* Comment: 在WindowsXP SP3 + VC6.0环境下调试通过
*/
#include
int main()
{
//因为WinExec这个API函数在kernel32.dll这个动态链接库里,所以要先加载它
__asm
{
push ebp;
mov ebp,esp;
//在堆栈中构造字符串:kernel32.dll
xor eax,eax;
push eax;
sub esp,0Ch;
mov byte ptr [ebp-10h],6Bh;
mov byte ptr [ebp-0Fh],65h;
mov byte ptr [ebp-0Eh],72h;
mov byte ptr [ebp-0Dh],6Eh;
mov byte ptr [ebp-0Ch],65h;
mov byte ptr [ebp-0Bh],6Ch;
mov byte ptr [ebp-0Ah],33h;
mov byte ptr [ebp-09h],32h;
mov byte ptr [ebp-08h],2Eh;
mov byte ptr [ebp-07h],64h;
mov byte ptr [ebp-06h],6Ch;
mov byte ptr [ebp-05h],6Ch;
lea eax,[ebp-10h];
push eax;
mov eax,0x7c801d7b; //LoadLibraryA的地址
call eax; //相当于执行LoadLibraryA("kernel32.dll");
//平衡堆栈
mov esp,ebp;
//在堆栈中构造字符串:calc.exe
xor eax,eax;
push eax;
sub esp,08h;
mov byte ptr [ebp-0Ch],63h;
mov byte ptr [ebp-0Bh],61h;
mov byte ptr [ebp-0Ah],6Ch;
mov byte ptr [ebp-09h],63h;
mov byte ptr [ebp-08h],2Eh;
mov byte ptr [ebp-07h],65h;
mov byte ptr [ebp-06h],78h;
mov byte ptr [ebp-05h],65h;
push 05h;
lea eax,[ebp-0Ch];
push eax;
mov eax,0x7c86250d; //WinExec的地址
call eax; //相当于执行WinExec("calc.exe", SW_SHOW);
//平衡堆栈
mov esp,ebp;
pop ebp;
}
return 0;
}
5、然后就可以在VC6.0下用Debug功能提取出机器码:
55 8B EC 33 C0 50 83 EC 0C C6
45 F0 6B C6 45 F1 65 C6 45 F2
72 C6 45 F3 6E C6 45 F4 65 C6
45 F5 6C C6 45 F6 33 C6 45 F7
32 C6 45 F8 2E C6 45 F9 64 C6
45 FA 6C C6 45 FB 6C 8D 45 F0
50 B8 7B 1D 80 7C FF D0 8B E5
33 C0 50 83 EC 08 C6 45 F4 63
C6 45 F5 61 C6 45 F6 6C C6 45
F7 63 C6 45 F8 2E C6 45 F9 65
C6 45 FA 78 C6 45 FB 65 6A 05
8D 45 F4 50 B8 0D 25 86 7C FF
D0 8B E5 5D
6、接着实际执行一下这段shellcode,看看效果,你会发现真的打开了Windows计算器程序。
//作者:冷却
//在WindowsXP SP3 + VC6.0环境下运行成功
unsigned char shellcode[] =
"/x55/x8B/xEC/x33/xC0/x50/x83/xEC/x0C/xC6/x45/xF0/x6B/xC6/x45/xF1/x65/xC6/x45"
"/xF2/x72/xC6/x45/xF3/x6E/xC6/x45/xF4/x65/xC6/x45/xF5/x6C/xC6/x45/xF6/x33/xC6"
"/x45/xF7/x32/xC6/x45/xF8/x2E/xC6/x45/xF9/x64/xC6/x45/xFA/x6C/xC6/x45/xFB/x6C"
"/x8D/x45/xF0/x50/xB8/x7B/x1D/x80/x7C/xFF/xD0/x8B/xE5/x33/xC0/x50/x83/xEC/x08"
"/xC6/x45/xF4/x63/xC6/x45/xF5/x61/xC6/x45/xF6/x6C/xC6/x45/xF7/x63/xC6/x45/xF8"
"/x2E/xC6/x45/xF9/x65/xC6/x45/xFA/x78/xC6/x45/xFB/x65/x6A/x05/x8D/x45/xF4/x50"
"/xB8/x0D/x25/x86/x7C/xFF/xD0/x8B/xE5/x5D";
void main()
{
__asm
{
lea eax,shellcode;
jmp eax;
}
}
7、最后,我写了个小程序,把那些十六进制的shellcode转换为纯二进制的机器码。大家看!这就是CPU老兄眼中的代码了。感觉很神奇吧,0和1在计算机中真的很有魔力。
01010101100010111110110000110011110000000101000010000011111011000000110011000110010001011111000001101011110001100100010111110001011001011100011001000101111100100111001011000110010001011111001101101110110001100100010111110100011001011100011001000101111101010110110011000110010001011111011000110011110001100100010111110111001100101100011001000101111110000010111011000110010001011111100101100100110001100100010111111010011011001100011001000101111110110110110010001101010001011111000001010000101110000111101100011101100000000111110011111111110100001000101111100101001100111100000001010000100000111110110000001000110001100100010111110100011000111100011001000101111101010110000111000110010001011111011001101100110001100100010111110111011000111100011001000101111110000010111011000110010001011111100101100101110001100100010111111010011110001100011001000101111110110110010101101010000001011000110101000101111101000101000010111000000011010010010110000110011111001111111111010000100010111110010101011101
后记:
大家所看到的在计算机里的种种五彩缤纷的一切,在CPU看来不过是0和1……
更新补充:
针对评论中有位朋友指出运行时会弹出错误提示框的问题,我做了修正,解决了这个问题。
下面简单说说道理吧,是这样的:直接通过jmp指令跳到指定的函数去执行,这种做法是“有去无回”的,函数运行完后无法正确的返回原来的位置继续执行,所以修正的方式就是通过call指令来调用函数,然后在函数中通过ret指令(这条指令的机器码:C3)返回,这样就OK了。(详细的还是去看看汇编方面的东东吧)
//作者:冷却
//在WindowsXP SP3 + VC6.0环境下运行成功
//修正:运行后弹出错误提示框 @ 2011-5-15 11:24:16
unsigned char shellcode[] =
"/x55/x8B/xEC/x33/xC0/x50/x83/xEC/x0C/xC6/x45/xF0/x6B/xC6/x45/xF1/x65/xC6/x45"
"/xF2/x72/xC6/x45/xF3/x6E/xC6/x45/xF4/x65/xC6/x45/xF5/x6C/xC6/x45/xF6/x33/xC6"
"/x45/xF7/x32/xC6/x45/xF8/x2E/xC6/x45/xF9/x64/xC6/x45/xFA/x6C/xC6/x45/xFB/x6C"
"/x8D/x45/xF0/x50/xB8/x7B/x1D/x80/x7C/xFF/xD0/x8B/xE5/x33/xC0/x50/x83/xEC/x08"
"/xC6/x45/xF4/x63/xC6/x45/xF5/x61/xC6/x45/xF6/x6C/xC6/x45/xF7/x63/xC6/x45/xF8"
"/x2E/xC6/x45/xF9/x65/xC6/x45/xFA/x78/xC6/x45/xFB/x65/x6A/x05/x8D/x45/xF4/x50"
"/xB8/x0D/x25/x86/x7C/xFF/xD0/x8B/xE5/x5D/xC3";
void main()
{
__asm
{
lea eax,shellcode;
call eax;
}
}