编写shellcode的全过程

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: [email protected] * 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: [email protected] * 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; } }

 

你可能感兴趣的:(信息安全)