前几天笔者在逆向一个易语言写的身份证生成器程序的时候,发现易语言的各种函数都是经过封装过的,包括文本比较,字节集比较等敏感的功能函数全都是经过封装的,并且功能特征码无论哪个版本的易语言还完全相同…
现在网上的一些易语言特征码根本不可靠,只要把代码稍微VM一下,特征码就找不到了,在此,笔者稍微研究了一下易语言库函数调用过程:
测试程序的源码如下:
先看看”信息框”函数是怎么被调用的
可见易语言调用库函数是保留了参数的属性,所以实际传递的参数比代码写的参数要多
跳转到调用库函数的函数的主体
这边是调用库函数,之前设置的EBX就是库函数主体的位置了
终于到库函数的主体部分了…(调用一个函数经历这么多代码,怪不得易语言代码效率这么低)
顺便贴上文本比较的代码部分,可见易语言文本比较也是通过调用库函数实现的
那么只要找到想要Hook的易语言库函数的特征码,就能轻而易举的实现Hook
这里笔者采用DLL注入的方式来实现inlinehook,特征码记录在自定义的配置文件里面
配置定义:
enum EasyLanTypes { zjx, dzsx, zsx, xsx, sjdxsx, wbx, zjj, ljx, noreturn };
typedef struct {
BYTE *pBin;
UINT lenth;
} BIN;
typedef struct {
string MsgText;
UINT OffsetToESP;
EasyLanTypes ArgType;
} ArgInfo;
typedef struct {
BIN SignatureCode;
DWORD ESPOffsetToReturnAddress;
EasyLanTypes ReturnValueType;
string FunName;
vector<ArgInfo> ArgSettings;
DWORD HookId;
DWORD CutByteSize;
BIN RunCode;
} ConfigInfo;
处理:
BOOL Attch(const char *ConfigFile) {
CreateConsole();//创建一个控制台以便调试
HANDLE ProcessHandle = INVALID_HANDLE_VALUE;
LoadConfig(ConfigFile);//载入配置文件
if (!config.size())return false;
int i, j;
DWORD AddressOfSignatureCode;
DWORD BaseAddress = (DWORD)GetModuleHandle(NULL);
IMAGE_DOS_HEADER *pidh = (IMAGE_DOS_HEADER *)BaseAddress;
IMAGE_NT_HEADERS *pinh = (IMAGE_NT_HEADERS *)(BaseAddress + pidh->e_lfanew);
IMAGE_SECTION_HEADER *pish = IMAGE_FIRST_SECTION(pinh);
DWORD old;
DWORD AddressForHookCall = HookCall();
DWORD OffsetOfJmp;
//一般第一个区段就是代码段了,改变代码段的属性
VirtualProtect((LPVOID)(BaseAddress + pish->VirtualAddress), pish->Misc.VirtualSize, PAGE_EXECUTE_READWRITE, &old);
printf("代码块地址:%x 大小:%x\n", BaseAddress + pish->VirtualAddress, pish->Misc.VirtualSize);
printf("Hook回调函数地址:%x\n", AddressForHookCall);
//解析配置文件
/*格式:[特征码][覆盖指令大小][返回地址与ESP的偏移量][返回值的类型][函数名] argconfig: [参数值类型][参数与ESP的偏移量][参数名]
enum EasyLanTypes { 字节型 = 0, 短整数型 = 1, 整数型 = 2, 小数型 = 3, 双精度小数型 = 4, 文本型 = 5, 字节集 = 6, 逻辑型 = 7, 没有返回值 = 8 };*/
for (i = 0; i < config.size(); i++) {
DWORD OffsetOfAddressOfSignatureCode = SeachMemory((const BYTE *)(BaseAddress + pish->VirtualAddress), pish->Misc.VirtualSize, config[i].SignatureCode.pBin, config[i].SignatureCode.lenth);
if (!OffsetOfAddressOfSignatureCode)continue;
AddressOfSignatureCode = BaseAddress + pish->VirtualAddress + OffsetOfAddressOfSignatureCode;
printf("%s 特征码:%s\n", config[i].FunName.data(), BinToText(config[i].SignatureCode).data());
if (AddressOfSignatureCode == 0)continue;
if (config[i].CutByteSize < 5)continue;
printf("Hook地址:%x\n", AddressOfSignatureCode);
UINT emptybytesize = config[i].CutByteSize - 5;
//HookId设置为特征码的位置,Hook处理程序通过函数返回地址来得到HookId,所以特征码一定要找在函数头部的代码
config[i].HookId = AddressOfSignatureCode;
//构造返回函数的代码,执行完Hook的代码之后执行原函数头部的指令,再正常执行函数原有的代码
config[i].RunCode.lenth = config[i].CutByteSize;
config[i].RunCode.pBin = new BYTE[config[i].RunCode.lenth + 6];
memcpy(config[i].RunCode.pBin, (LPVOID)AddressOfSignatureCode, config[i].RunCode.lenth);
BYTE Attchbyte[6] = { 104,0,0,1,0,195 };
DWORD JmpAddress = AddressOfSignatureCode + 5;
memcpy(&Attchbyte[1], &JmpAddress, 4);
memcpy(&config[i].RunCode.pBin[config[i].RunCode.lenth], Attchbyte, 6);
//构造跳转指令(CALL)
OffsetOfJmp = AddressForHookCall - (AddressOfSignatureCode + 5);
BYTE JmpCode[5] = { 232,0,0,0,0 };
memcpy(&JmpCode[1], &OffsetOfJmp, 4);
//写入函数头部
memcpy((LPVOID)AddressOfSignatureCode, JmpCode, 5);
//用NOP填充多余指令字节
while (emptybytesize) {
memset((LPVOID)(AddressOfSignatureCode + 5 + (emptybytesize-- - 1)), 144, 1);
}
}
return true;
}
配置格式:[特征码][覆盖指令大小][返回地址与ESP的偏移量][返回值的类型][函数名] argconfig: [参数值类型][参数与ESP的偏移量][参数名];
参数类型: 字节型 = 0, 短整数型 = 1, 整数型 = 2, 小数型 = 3, 双精度小数型 = 4, 文本型 = 5, 字节集 = 6, 逻辑型 = 7, 没有返回值 = 8 ;
Hook处理程序通过配置文件中参数与现ESP的偏移量来获取参数内容,如果想抓取返回值,则需要修改返回地址处的代码来实现获取函数返回值
效果: