有的时候我们需要用shellcode执行一个函数,这个函数中包含一些全局的数据,你会怎么做
(1)这个函数的代码将从0地址开始执行
(2)如何能够找到与0地址相对的偏移地址
解决方案:
(1)方法1:0地址处使用push指令将数据压入栈中,形成栈变量:这种方法适用小量的局部数据,因为大量的数据产生较多指令
(2)方法2:push , call , pop 方法
fun:
push edi ;征用edi寄存器
call entry ;跳转到entry,此时eip入栈(受取指流水线影响,此时的edi正好是data1的值)
data1:
......
entry:
pop edi ;弹出eip到edi,此时edi=data1
mov eax edi ;交给eax
pop edi ;释放对edi的征用
(3)方法2理论上通过的,但实际用C语言怎样编写这样代码呢?
#include "stdafx.h"
#include <Windows.h>
BOOL __stdcall MyStrEqu(char* user, char* data);/注意这个声明,可能导致函数地址排布出现混乱,如没有则未优化的时候static函数都是相对模块线性增序
static BOOL __declspec(naked) CheckUserAndPass2(char* user, char* passwd)///为防止编译器插入其他esp buffer检测代码,函数直接用naked的
{
__asm
{
push ebp;naked的约定下,要仿造cdecl 对页指针保护
mov ebp,esp;
//para ebp+4 ebp+8
//下面开始我们的shellcode
push ebx;//需要征用ebx作为一个临时指针
push edi;
call entry1;
_emit 'a';//_emit等价db(数据可以用其他程序生成_emit形式的代码~~)
_emit 'a';
_emit 'a';
_emit 0;
entry1:
pop edi;
mov ebx, edi;
pop edi;
push ebx;
push dword ptr[user];
call MyStrEqu;
cmp eax, 0;
jz ret0;
push edi;
call entry2;
_emit 'b';
_emit 'b';
_emit 'b';
_emit 'x';
_emit 'x';
_emit 0;
entry2:
pop edi;
mov ebx, edi;
pop edi;
push ebx;
push dword ptr[passwd];
call MyStrEqu;
cmp eax, 0;
jz ret0;
pop ebx;
mov eax, 1;//返回1
pop ebp;
ret ;
ret0:
pop ebx;
mov eax, 0;<span style="font-family: Arial, Helvetica, sans-serif;">//返回0</span>
pop ebp;
ret ;
};
}
static BOOL __stdcall MyStrEqu(char* user, char* data)
{
if(!user || !data)
return FALSE;
while((*user) && (*data) && ((*user) == (*data)))
{
user++;
data++;
}
return (*user) == (*data);
}
static void __empty() {};//__empty必须在代码中出现,否则很容易被优化掉导致计算代码距离失败
BOOL CheckUserAndPass(char* user, char* passwd)
{
if(MyStrEqu(user, "aaa") && MyStrEqu(passwd, "bbbxx"))
{
return TRUE;
}
return FALSE;
}
int _tmain(int argc, _TCHAR* argv[])
{
char bf_user[100] = {};
char bf_pass[100] = {};
printf("test CheckUserAndPass:\ninput User: ");
gets(bf_user);
printf("input Pass: ");
gets(bf_pass);
printf("result: %d\n", CheckUserAndPass(bf_user, bf_pass));
//
printf("test CheckUserAndPass2:\ninput User: ");
gets(bf_user);
printf("input Pass: ");
gets(bf_pass);
printf("result: %d\n", CheckUserAndPass2(bf_user, bf_pass));
//编译器可能最终会优化函数顺序导致我们需要的两个函数位置发生调换,用算法寻找最上面的start
unsigned int fs_empty = ((unsigned int)(void*)__empty);
unsigned int fs_addf = ((unsigned int)(void*)MyStrEqu);
unsigned int fs_addf2 = ((unsigned int)(void*)CheckUserAndPass2);
unsigned int func_entry = ((unsigned int)(void*)CheckUserAndPass2); //入口地址
unsigned int fstart = fs_addf < fs_addf2 ? fs_addf : fs_addf2;
unsigned char* buf = new unsigned char[abs(fs_empty - fstart) + 100];
if(fstart <= fs_empty)
for(unsigned int i = 0; i < (fs_empty - fstart); i++)
{
unsigned char by = ((unsigned char*)fstart)[i];
buf[i + 5] = by; //要在前面插入short jmp
}
else
for(unsigned int i = 0 ; i < (fstart - fs_empty); i++)
{
unsigned char by = ((unsigned char*)fstart)[i];
buf[i + 5] = by;
}
<span style="white-space:pre"> </span>//由于不能确定start就是我们要的入口,插入一个jmp来条转过去
buf[0] = 0xE9; //short jmp
(*(int*)(&buf[1])) = (int)func_entry - (int)fstart;//offset
DWORD dwOldProtect;
::VirtualProtect(buf, abs(fs_empty - fs_addf) + 100, PAGE_EXECUTE_READWRITE, &dwOldProtect);//之后buf就可以调用了
typedef BOOL (__cdecl * PFN_CheckPass)(char* user, char* passwd);
PFN_CheckPass fnCheck = (PFN_CheckPass)buf;
printf("test CheckUserAndPass:\ninput User: ");
gets(bf_user);
printf("input Pass: ");
gets(bf_pass);
BOOL x = fnCheck(bf_user, bf_pass);
printf("result: %d\n", x);
return 0;
}
最后你可以用dump内存把shellcode dump到文件中,以便使用