看本篇文章前先看下一点基础知识: C++中函数参数和局部变量的栈位置
简单总结下一般的指令:
add a,b 表示将a+b赋值给a
sub a,b 表示将a-b赋值给a,当a<b的时候,会产生借位,CF=1
详情:这里
cmp a,b 表示比较a和b的大小,之后紧跟着这样的指令:
jne 0x0f0f0f0f 表示a,b不相等的时候,跳转到0x0f0f0f0f 处进行继续执行
或者
je 0x0f0f0f0f 表示a,b相等的时候,跳转到0x0f0f0f0f 处进行继续执行
或者
jle 0x0f0f0f0f 表示a,b小于等于的时候,跳转到0x0f0f0f0f 处进行继续执行
更多cmp后边的操作可以自己搜索一下
lea a,b 将b 的数值赋值给a
test a,b 将a跟b进行与操作,结果的判断与cmp一样,比如:
test a,08h,就是检测a&08h的结果,之后用jne,je等等进行处理
movzx ax,al 表示无符号扩展并传递,也就是将al的字节宽度扩展一下赋值给ax
参考见这里
mov dword ptr[eax],ebx 内存单元操作方法,将寄存器ebx中的数据,保存到eax指向的内存单元中,同理:
mov ebx,dword ptr[eax] 将eax指向的内存单元的数据存储到ebx中
详情见这里
现在开始分析一段汇编代码:
013C1550 push ebp 013C1551 mov ebp,esp 013C1553 sub esp,0E4h 013C1559 push ebx 013C155A push esi 013C155B push edi 013C155C lea edi,[ebp-0E4h] 013C1562 mov ecx,39h 013C1567 mov eax,0CCCCCCCCh 013C156C rep stos dword ptr es:[edi] 013C156E cmp dword ptr [ebp+8],0 013C1572 jne 13C1578h 013C1574 xor eax,eax 013C1576 jmp 13C15BCh 013C1578 mov eax,dword ptr [ebp+8] 013C157B mov dword ptr [ebp-0Ch],eax 013C157E mov eax,dword ptr [ebp-0Ch] 013C1581 movzx ecx,word ptr [eax] 013C1584 cmp ecx,5A4Dh 013C158A je 13C1590h 013C158C xor eax,eax 013C158E jmp 13C15BCh 013C1590 mov eax,dword ptr [ebp-0Ch] 013C1593 mov ecx,dword ptr [ebp-0Ch] 013C1596 add ecx,dword ptr [eax+3Ch] 013C1599 mov dword ptr [ebp-10h],ecx 013C159C mov eax,dword ptr [ebp-10h] 013C159F mov ecx,dword ptr [eax+00000080h] 013C15A5 mov dword ptr [ebp-08h],ecx 013C15A8 mov eax,dword ptr [ebp+0Ch] 013C15AB mov ecx,dword ptr [ebp-10h] 013C15AE mov edx,dword ptr [ecx+00000084h] 013C15B4 mov dword ptr [eax],edx 013C15B6 mov eax,dword ptr [ebp+8] 013C15B9 add eax,dword ptr [ebp-08h] 013C15BC pop edi 013C15BD pop esi 013C15BE pop ebx 013C15BF mov esp,ebp 013C15C1 pop ebp 013C15C2 ret
整体分析思路:
1:调用方式
看到最后是ret,不需要调整栈平衡,所以是_cdecl调用方式,当遇到:
ret number,则是_stdcall或者_fastcall,后两者,区别在于_fastcall调用的时候最多参数可以有4个,因为它会自动将前两个参数用寄存器保存,执行起来更快,参数太多的时候就不行了。
2:函数参数数量
找ebp+i 都有哪些,看到该例子中有:
ebp+8,ebp+0ch,所以是两个参数的函数
3:函数是否有返回值
看最后退出前十都特意将一个数据保存到eax中,因为函数返回值是以该寄存器返回的,该例子有,所以是有返回值的函数
4:分析局部变量有多少
找ebp-i
该例子有:
ebp-0ch,ebp-08h,ebp-10h,ebp-04h,一般是4个参数,但是有时候ebp-4是系统自定义的局部变量,用来交换数据使用的,不是开发者定义的,所以这样可能只有3个局部变量,而不是4个。
5:找找有没有敏感信息,一般里边的硬编码(固定数据)会有些提示(不是ebp,esp,是对其他寄存器的操作),比如该例子:
5A4Dh
eax+3Ch
eax + 00000080h
ecx+00000084h
其实,5A4D是PE文件的标志,3C是PE文件头指针的偏移量,80是导出表偏移量,84是导出表的size,剩下的,就是对里边的一点点理解了:
013C1550 push ebp 013C1551 mov ebp,esp ;所有函数的入口,都要有这两行,因为汇编执行的时候需要寻找下一条执行的地址,需要有定位的变量来时刻去寻找,而ebp指向的始终是栈底,esp指向的是栈顶,这样两头都有了,剩下的无非就是它们去加减相对偏移位数了,函数退出的时候在还原,本例中在013C15BF处。 013C1553 sub esp,0E4h ; ;在这个函数中开辟一块大小为0E4h,也就是12*16+4=196字节的内存空间,以便函数中的变量使用,这个大小是进入函数之前编译器计算的,全部分配在栈上,在函数进入的时候开辟,函数退出的时候自动回收,大小一旦进入函数后就不再改变。所以里边如果有需要动态开辟内存,比如使用malloc、new等等,便会去其他的地方(堆)中开辟。 013C1559 push ebx 013C155A push esi 013C155B push edi ;保存其他有必要的寄存器,以便函数退出的时候还原,本例中在013C15BC处 013C155C lea edi,[ebp-0E4h] 013C1562 mov ecx,39h 013C1567 mov eax,0CCCCCCCCh 013C156C rep stos dword ptr es:[edi] ;寄存器初始化,具体没去深究 013C156E cmp dword ptr [ebp+8],0 ;比较第一个dword区块参数的数据是否为0,0可能是false,NULL等等 013C1572 jne 13C1578h ;不为零,则跳转到13C1578h继续执行 ;否则,返回,返回数值都是存放在eax中 013C1574 xor eax,eax ; 这里xor eax,eax做异或操作后,eax=0,也就是返回0,false,NULL 之类的 013C1576 jmp 13C15BCh ;调到函数结束位置,结束函数 013C1578 mov eax,dword ptr [ebp+8] 上边如果不结束函数,则进行上边这一步,取第一个参数的数据到寄存器eax中 013C157B mov dword ptr [ebp-0Ch],eax ;将寄存器eax中的数据保存到第2个局部变量所在的栈单元 013C157E mov eax,dword ptr [ebp-0Ch] ;由于汇编操作时候,不能取内存嵌套,所以将内存数据用临时存储器保存再操作是经常会用到的 013C1581 movzx ecx,word ptr [eax] ;将eax的数值作为内存地址,之后再取值给ecx,有点编程基础的人都至少会猜到,这是获取指针指向的内存的数值 013C1584 cmp ecx,5A4Dh ;将得到指针指向的数据后与0x5A4Dh进行比较,没有经验的我,开始还以为这个是随便的东西,后来才知道是PE文件的文件头标志:MZ 013C158A je 13C1590h ;当是与0x5A4Dh相等(是PE文件),则跳转到执行位置,否则将返回数值变为0,并跳转到结束位置 013C158C xor eax,eax 013C158E jmp 13C15BCh 013C1590 mov eax,dword ptr [ebp-0Ch] 013C1593 mov ecx,dword ptr [ebp-0Ch] ;表示将相对于第二个局部变量(指针)指向的位置赋值给寄存器 013C1596 add ecx,dword ptr [eax+3Ch] ;取指针指向的位置加上相对于它偏移3Ch位置的数据,实际上是两段便宜地址想加,也就是dword ptr [ebp-0Ch]和dword ptr [eax+3Ch]都是偏移量 013C1599 mov dword ptr [ebp-10h],ecx ;之后将结果赋值给第3个局部变量 013C159C mov eax,dword ptr [ebp-10h] 013C159F mov ecx,dword ptr [eax+00000080h] 013C15A5 mov dword ptr [ebp-08h],ecx ;以上3行表示取第三个局部变量指向的位置的偏移80h的数据给第1个局部变量 013C15A8 mov eax,dword ptr [ebp+0Ch] ;取第二个函数参数到eax 013C15AB mov ecx,dword ptr [ebp-10h] 013C15AE mov edx,dword ptr [ecx+00000084h] 013C15B4 mov dword ptr [eax],edx ;以上4行表示将相对于第3个局部变量指向位置偏移84h的数据存到第二个函数参数,这样的做法一般是回调、指针操作、引用操作,也就是将函数得到的数据通过参数返回去毕竟函数返回数据只有一个,有时候不够用也不想用多余的结构体 013C15B6 mov eax,dword ptr [ebp+8] 013C15B9 add eax,dword ptr [ebp-08h] ;同理,将第一个函数参数的数据取出来,并加上第3个局部变量的数值,将整个和作为地址,取该地址的数据给eax,因为eax是函数返回值保存的位置,所以显然,该函数是有返回值的。 013C15BC pop edi 013C15BD pop esi 013C15BE pop ebx 013C15BF mov esp,ebp 013C15C1 pop ebp 013C15C2 ret
对于PE不了解的时候反汇编的结果:
struct A { DWORD *_1; DWORD *_2; DWORD *_3; }; typedef struct A _A; DWORD * _stdcall showstatic(BYTE *p,DWORD *q)//char s[],DWORD *p)//(_B _b,char *p) { _A a; if (p == NULL) { return NULL; } a._2=(DWORD *)p; if(*(WORD*)a._2!=0x5A4D) { return NULL; } a._1 = (DWORD*)((DWORD)a._2 + *((DWORD*)((DWORD)a._2 + (DWORD)0x3c))); a._3 = (DWORD*)((*(DWORD*)((DWORD)a._1+(DWORD)0x80))); *q = ((*(DWORD*)((DWORD)a._1+(DWORD)0x84))); return (DWORD *)((long)(DWORD *)p+(long)a._3); }对PE了解一些后的结果:
PIMAGE_EXPORT_DIRECTORY _cdecl GetExportTableBaseAddr(BYTE *modBaseAddr,DWORD *size) { PIMAGE_NT_HEADERS _1; PIMAGE_DOS_HEADER _2; DWORD _3; if (modBaseAddr == NULL) { return NULL; } _2=(PIMAGE_DOS_HEADER)modBaseAddr; if(_2->e_magic!=IMAGE_DOS_SIGNATURE)//0x5A4D { return NULL; } _1 = (PIMAGE_NT_HEADERS)((long)_2 + (long)_2->e_lfanew); _3 = _1->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; *size = _1->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size; return (PIMAGE_EXPORT_DIRECTORY)((long)(PIMAGE_DOS_HEADER)modBaseAddr+(long)_3); }当然,这两个运行结果肯定都一样。
整个可执行程序:
#include<windows.h> #include<stdio.h> #include <string> #include <TCHAR.H> #include "psapi.h" #pragma comment (lib, "psapi.lib ") #include "tlhelp32.h" #include <imagehlp.h> #pragma comment (lib, "Dbghelp.lib ") #include <iostream> #include <winnt.h> using namespace std; using namespace std; PIMAGE_EXPORT_DIRECTORY _cdecl GetExportTableBaseAddr(BYTE *modBaseAddr,DWORD *size)//,DWORD *q { PIMAGE_NT_HEADERS _1; PIMAGE_DOS_HEADER _2; DWORD _3; if (modBaseAddr == NULL) { return NULL; } _2=(PIMAGE_DOS_HEADER)modBaseAddr; if(_2->e_magic!=IMAGE_DOS_SIGNATURE)//0x5A4D { return NULL; } _1 = (PIMAGE_NT_HEADERS)((long)_2 + (long)_2->e_lfanew); _3 = _1->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; *size = _1->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size; //PIMAGE_EXPORT_DIRECTORY p = (PIMAGE_EXPORT_DIRECTORY)((long)(PIMAGE_DOS_HEADER)modBaseAddr+(long)_3); return (PIMAGE_EXPORT_DIRECTORY)((long)(PIMAGE_DOS_HEADER)modBaseAddr+(long)_3); } struct A { DWORD *_1; DWORD *_2; DWORD *_3; }; typedef struct A _A; DWORD * _stdcall showstatic(BYTE *p,DWORD *q)//char s[],DWORD *p)//(_B _b,char *p) { _A a; if (p == NULL) { return NULL; } a._2=(DWORD *)p; if(*(WORD*)a._2!=0x5A4D) { return NULL; } a._1 = (DWORD*)((DWORD)a._2 + *((DWORD*)((DWORD)a._2 + (DWORD)0x3c)));//a._2+sizeof(s);//(char*)((DWORD*)a._2 + (DWORD*)(a._2+0x3c)); a._3 = (DWORD*)((*(DWORD*)((DWORD)a._1+(DWORD)0x80))); *q = ((*(DWORD*)((DWORD)a._1+(DWORD)0x84))); return (DWORD *)((long)(DWORD *)p+(long)a._3); } char* wcharTochar(const wchar_t *wchar)//, char *chr, int length) { // WideCharToMultiByte( CP_ACP, 0, wchar, -1 ,chr , length, NULL, NULL ); int len= WideCharToMultiByte( CP_ACP, 0, wchar , wcslen(wchar),NULL, 0 , NULL,NULL); char* m_char =new char[len+1]; WideCharToMultiByte( CP_ACP, 0, wchar , wcslen(wchar),m_char,len,NULL,NULL); m_char[len]=‘\0‘; return m_char; } int main() { //FILE * pp = fopen("C:\\Users\\1\\Desktop\\ntdll.dll","r"); HMODULE hModule = LoadLibrary(L"C:\\Users\\1\\Desktop\\ntdll.dll"); HANDLE h = INVALID_HANDLE_VALUE; h = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,GetCurrentProcessId()); MODULEENTRY32 me32; me32.dwSize = sizeof( MODULEENTRY32 ); if( !Module32First( h, &me32 ) ) { CloseHandle( h ); return( FALSE ); } DWORD size = 0; do{ if(strcmp(wcharTochar(me32.szModule),"ntdll.dll")==0) { PIMAGE_EXPORT_DIRECTORY p = GetExportTableBaseAddr(me32.modBaseAddr,&size); cout << "export table address : 0x"<<hex<<p<<" size:"<<dec<<size<<endl; size = 0; DWORD *p1 = showstatic(me32.modBaseAddr,&size); cout << "export table address : 0x"<<hex<<p1<<" size:"<<dec<<size<<endl; } }while(Module32Next(h,&me32)); CloseHandle( h ); return 0; }