通过Hash值计算API的名字
 
2007.11.15
 
by marxixing@tom.com
 
 
 
在分析shellcode的过程中,我们经常会遇到被编码后的API的hash值,因为hash算法是不可逆算法,我们不可能通过一个DWORD值得知原始的api,为了知道shellcode都调用了哪些api,你就得去不断的去调试,这个工作量是比较大的。我昨天就深有体会。今天,我写了个小工具,只要输入一个DOWRD值和dll文件的名字,就能计算出来api的名字了,呵呵,裤罢.
 
你可以这样来使用它: ByHashName EC0E4E8E kernel32 ,其中ByHashName是这个程序的名字,EC0E4E8E是api的hash值,kernel32就是该api所在的库了,玩shellcode的大家应该都知道吧。针对上面这个命令行,输出结果应该是LoadLibraryA.
 
本文所列出的hash值的算法是 ror edi,0d, 也就是将每个字符都右移13位,名字的起始指针放在esi寄存器中。下面我列出了常见的hash值对应的函数名称,当你遇到了他们,就不用在程序中运算了,直接使用他们就是了。对于一个特定的shellcode,这个算法是最常见的,如果你发现了其他的hash算法,只需要更改dll文件的输出函数的hash算法就可以了,shellcode的hash算法不会很难,最多10行汇编代码就能搞定。
 
2007.11.21晚上改进了一下,做了个纯汇编版本的,不用带个dll尾巴了。但原文件仍然保留。汇编版本附在本文后面。附件中是汇编版本的可执行文件。
 
; 0EC0E4E8Eh kernel32.dll LoadLibraryA
; 016B3FE72h kernel32.dll CreateProcessA
; 07c0dfcaah kernel32.dll GetProcAddress
; 00c0397ech kernel32.dll GlobalAlloc
 
 
; 702F1A36h urlmon.dll URLDownloadToFileA
; 054FEF4Fh urlmon.dll URLDownloadToCacheFileA
 
 
 
这是个命令行工具,我使用了汇编语言编写marxixing.dll文件,供c语言编写的主程序调用。 其中dll文件的内容如下:
 
.386
.model flat, stdcall
option casemap:none
 
 
include windows.inc 
include kernel32.inc 
includelib kernel32.lib 
 
.code
 
DllEntry proc _hInst: HINSTANCE, _reason: DWORD, _reserved: DWORD
mov eax, TRUE
ret
DllEntry Endp
 
 
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;得到kernel32在内存中的加载地址,无参数
;eax返回最终的加载地址
MXX_GetKernelBase proc
 
ASSUME FS:nothing
mov eax, fs:[30h]
mov eax, [eax + 0ch]
mov esi, [eax + 1ch]
lodsd
mov eax, [eax + 8h]
ret
 
MXX_GetKernelBase endp
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;通过hash值得到转换前的API的名字
;参数DllBase: dll文件的加载地址
;参数ApiHash: api字符串的Hash值
;返回值:eax返回API名字字符串
 
MXX_GetFunctionAddress proc DllBase:dword, ApiHash:dword
 
mov edx, DllBase
mov ebx, [edx + 3ch]
mov ebx, [edx + ebx + 78h]
add ebx, edx ;export table address
 
mov ecx, [ebx + 18h] ;函数数目
mov edi, [ebx + 20h] ;api名字数组首地址
add edi, edx
 
;;计算每个函数的hash值,获得我们需要的函数 
NextFunc:
jecxz error
dec ecx
mov esi, [edi + ecx * 4h] ;初始时指向最后一个函数的指针
add esi, edx
 
push edi 
push edx
 
mov edx, esi ;先保存一下
 
xor edi, edi
cld
Counthash: ;开始计算hash值
xor eax, eax
lodsb
cmp al, ah
jz Apiend
 
ror edi, 0dh
add edi, eax
 
jmp Counthash
Apiend:
cmp edi, ApiHash ;hash值计算结束
mov esi, edx ;恢复api名字指针,用来返回
pop edx 
pop edi
jnz NextFunc
 
mov eax, esi
ret 
 
error:
mov eax, 0
ret
 
MXX_GetFunctionAddress endp
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 
end DllEntry
 
 
 
该文件输出了两个函数,因此我们还需要给它加上一个def文件,命名为marxixing.def,内容如下:
 
LIBRARY marxixing
EXPORTS MXX_GetKernelBase
MXX_GetFunctionAddress
 
 
之后,就可以用如下的命令进行编译和链接了,最终生成两个文件,marxixing.lib和marxixing.dll
 
ml /c /coff marxixing.asm
link /DLL /subsystem:windows /Def:marxixing.def marxixing.obj
 
 
 
注意,我们不想用loadlibrary,getproaddress的方式来调用dll里面的函数,因此我写了下面的头文件marxixing.h,这样,就可以直接使用我们的输出函数了。
 
extern "C" DWORD __stdcall MXX_GetKernelBase();
 
extern "C" char * __stdcall MXX_GetFunctionAddress(HINSTANCE DllBase, DWORD HashSum);
 
 
 
 
现在,该是我们的c语言上场了,我们用它来设计界面:
 
 
 
//输入函数的hash值,求出对应的api名字
#include <stdio.h>
#include <windows.h>
#include "marxixing.h"
 
#pragma comment(lib, "marxixing.lib")
 
int main(int argc, char * argv[])
{
//参数1为api的hash值
//参数2为dll文件的名字
 
HINSTANCE dllbase;
char dllname[32] = {0};
char * alloc = NULL;
DWORD hashsum;
DWORD apiname;
 
if(argc <= 2 )
{
printf("USAGE: ByHashName hashsum dllname.\n");
printf("return: ApiName string\n");
return 0; 
}
sscanf(argv[1], "%x", &hashsum);
strcpy(dllname, argv[2]);
 
dllbase = LoadLibrary(dllname);
if(dllbase == NULL)
{
printf("%s load memory fail!", dllname);
return 0;
}
alloc = MXX_GetFunctionAddress(dllbase, hashsum);
if(alloc == NULL)
{
printf("not find apiname!");
return 0;
}
printf("%s\n",alloc);
 
FreeLibrary(dllbase);
 
 
return 1;
 
}
 
 
 
*************************************************************
*************************************************************
;汇编版本的实现
 
.386
.model flat, stdcall
option casemap:none
 
include windows.inc 
include kernel32.inc 
includelib kernel32.lib 
include user32.inc 
includelib user32.lib 
include masm32.inc
includelib masm32.lib
.data
libfail db "LoadLibrary fail!", 0
NotFindApi db "Not find apiname!", 0
.data?
Hashsum db 256 dup(?)
Libname db 64  dup(?)
ApiName   dd ?
HashDword dd ?
LibDword  dd ?

MXX_GetFunctionName proto :DWORD ,  :DWORD

.code
start:
 invoke GetCL, 1, addr Hashsum
 invoke GetCL, 2, addr Libname
 
 
 invoke htodw, addr Hashsum  ;转换输入字符串为双字
 mov HashDword, eax
 
 invoke LoadLibrary, addr Libname
 .IF eax == NULL
  mov ApiName, offset libfail
  jmp over
 .ELSE
  mov LibDword, eax
 .ENDIF
 
 invoke MXX_GetFunctionName, LibDword, HashDword
 .IF eax == NULL
  mov ApiName, offset NotFindApi
 .ELSE
  mov ApiName, eax
 .ENDIF
 
 
over: 
 invoke MessageBox, NULL, ApiName, addr Libname, MB_OK
 invoke FreeLibrary, LibDword
 invoke ExitProcess, NULL
 
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;通过Hash值得到API的名字
;参数DllBase: dll文件的加载地址
;参数ApiHash: api字符串的Hash值
;返回值:eax返回API名字字符串
MXX_GetFunctionName proc DllBase:dword, ApiHash:dword
 mov edx, DllBase
 mov ebx, [edx + 3ch]
 mov ebx, [edx + ebx + 78h]
 add ebx, edx  ;export table address
 mov ecx, [ebx + 18h]   ;函数数目
 mov edi, [ebx + 20h]  ;api名字数组首地址
 add edi, edx
 
;;计算每个函数的hash值,获得我们需要的函数 
NextFunc:
 jecxz error
 dec  ecx
 mov esi, [edi + ecx * 4h]  ;初始时指向最后一个函数的指针
 add esi, edx
 
 push edi  ;保存原来edi,edx的值,下面要用到它
 push edx
 mov  edx, esi
 xor edi, edi
 cld
Counthash:  ;开始计算hash值
 xor eax, eax
 lodsb
 cmp al,  ah
 jz  Apiend
 ror edi, 0dh
 add edi, eax
 
 jmp Counthash
Apiend:
 cmp edi, ApiHash  ;hash值计算结束
 mov esi, edx     ;恢复api名字指针,用来返回
 pop edx   ;恢复原来edx,edi的值
 pop edi
 jnz NextFunc
 mov eax, esi
 ret 
error:
 mov eax, 0
 ret
 
MXX_GetFunctionName endp
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

end start