0x00 前言
Win32 API 是windows平台下的最基本的API接口,面向Windows编程
以学习底层的角度去学习Windows操作系统
r3是一定要学明白的!
0x01 多字节字符
前言
多字节字符,又叫窄字节字符
ASCII码表:0111 1111
拓展ASCII码表:1111 1111
一个字节有8位,两个拓展ASCII码(2字节)拼成一个汉字
问题
- 有可能和韩文、日文撞了,就出现了乱码
- 当判断
city5
时,正常的返回应该是5,但是实际返回6,因为ASCII
是按照字节去判断的,一个汉字是2字节,所以返回6
Unicode
针对以上的问题,Unicode表应运而生,进行标准的统一
Unicode表中每一个符号都有2字节
0x02 C语言&宽字符
前言
中
字的编码:
ASCII:d6 d0
UNICODE:4e 2d
示例1_一个汉字
当我们用char去存储中
时,编译和运行都不会报错
但是,char只可以存储一个字节,会有字节丢失,断点执行看内存
当我们用wchar_t
去存储中
时,继续断点执行看内存
wchar_t
确实可以存储两字节,但是我们发现编译器使用的还是Ascii表
告诉编译器我们使用的时宽字符,让它使用Unicode表
wchar_t x2 = L'中';
继续看内存
示例2_两个汉字
char x[] = "中国";
//d6 d0 b9 fa 00使用拓展ASCII编码表 以00(\0)结尾
wchar_t x1[] = L"中国";
//2d 4e fd 56 00 00 使用UNICODE编码表 以00 00(\0\0)结尾
断点看内存
示例3_控制台打印
char x[] = "中国";
wchar_t x1[] = L"中国";
printf("%s\n",x); //使用控制台默认的编码
wprintf(L"%s\n",x1);//默认使用英文
我们可以看到编译器不认识我们写的宽字符打印,所以没有显示出来
稍作修改:
1、包含头文件 #include <locale.h>
2、setlocale(LC_ALL,""); //告诉编译器我们的地域
示例4_字符串长度
包含头文件
#include<string.h>
char x[] = "中A国";
wchar_t x1[] = L"中A国";
strlen(x); //5 //取得多字节字符串中字符长度,不包含 00
wcslen(x1); //3 //取得宽字节字符串中字符长度,不包含 00
示例5_字符串复制
两者本质都是调用一个函数
strcpy:字符串复制,全复制,上层多了一个容量的判断处理
memcpy:内存复制,按多少字节复制
char x[] = "china";
char x1[] = "123";
strcpy(x,x1);
wchar_t y[] = L"中国";
wchar_t y1[] = L"好";
wcscpy(y,y1);
0x03 C语言&宽字符<-->多字符
多字节字符类型 宽字符类型
charwchar_t
printf wprintf 打印到控制台函数
strlen wcslen 获取长度
strcpy wcscpy 字符串复制
strcat wcscat 字符串拼接
strcmp wcscmp 字符串比较
strstr wcsstr 字符串查找
0x04 Win32 API&宽字符
前言
主要是存放在C:\WINDOWS\system32
下面所有的dll
Windows是使用C语言开发的,Win32 API同时支持宽字符与多字节字符.
重要DLL
Kernel32.dll:最核心的功能模块,比如管理内存、进程和线程相关的函数等.
User32.dll:是Windows用户界面相关应用程序接口,如创建窗口和发送消息等.
GDI32.dll:全称是Graphical Device Interface(图形设备接口),包含用于画图和显示文本的函数
比如要显示一个程序窗口,就调用了其中的函数来画这个窗口
注:其实DLL都只是外壳,其中的内核函数才是关键
当我们对DLL特别了解的时候,可以绕过DLL,直接调用内核函数
字符类型
char CHAR
wchar_t WCHAR
宏TCHAR
注:这里的TCHAR,宏是这么理解的,根据当前项目
编译器使用Ascii表:char
编译器使用Unicode表:wchar_t
所以,它是一种宏,推荐使用,因为适用性好
字符串指针
PSTR(LPSTR):指向多字节字符串
PWSTR(LPWSTR):指向宽字符串
宏:PTSTR(LPTSTR)
字符数组赋值
CHAR cha[] = "中国";
WCHAR chw[] = L"中国";
TCHAR cht[] = TEXT("中国");
字符串指针赋值
PSTR pszChar = "china"; //多字节字符
PWSTR pszWChar = L"china"; //宽字符
PTSTR pszTChar = TEXT("china"); //如果项目是ASCII的 相当于"china" UNICODE 相当于L"china"
各种版本的MessageBox
Windows提供的API 凡是需要传递字符串参数的函数,都会提供两个版本和一个宏.
MessageBoxA(0,"内容多字节","标题",MB_OK); //多字节字符
MessageBoxW(0,L"内容宽字节",L"标题",MB_OK); //宽字符
MessageBox(0,TEXT("根据项目字符集决定"),TEXT("标题"),MB_OK); //它也是一个宏
0x05 Win32主函数分析
头文件
头文件会帮我们添加
#include <windows.h>
__stdcall
继续看主函数
#define APIENTRYWINAPI
#define WINAPI __stdcall
Win32程序使用的都是__stdcall
调用约定
__stdcall
特点:内平栈、参数的压栈顺序是从右到左
灵格斯翻译
hInstance
#include "stdafx.h"
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
// TODO: Place code here.
return 0;
}
其实hInstance参数就是ImageBase
我们修改一下编译器的ImageBase
十六进制:500000
十进制:5242880
进行修改
我们可以打印看看
总结
#include "stdafx.h"
int APIENTRY WinMain(HINSTANCE hInstance, //ImageBase
HINSTANCE hPrevInstance, //永远为0
LPSTR lpCmdLine, //命令行执行内容,允许我们在程序启动时,进行传值
int nCmdShow) //以什么方式显示,最大化,最小化,隐藏等
{
// TODO: Place code here.
return 0;
}
0x06 Win32&打印信息
前言
它只能在输出窗口中打印文本信息
使用
OutputDebugString("xxx");
打开输出窗口
成功输出信息
改造&打印参数
tools.cpp
#include "stdafx.h"
#include "tools.h"
void __cdecl OutputDebugStringF(const char *format, ...)
{
va_list vlArgs;
char*strBuffer = (char*)GlobalAlloc(GPTR, 4096);
va_start(vlArgs, format);
_vsnprintf(strBuffer, 4096 - 1, format, vlArgs);
va_end(vlArgs);
strcat(strBuffer, "\n");
OutputDebugStringA(strBuffer);
GlobalFree(strBuffer);
return;
}
tools.h
// tools.h: interface for the tools class.
#if !defined(AFX_TOOLS_H__1C580EE6_D5B3_40B3_9BBD_A164B5C714F9__INCLUDED_)
#define AFX_TOOLS_H__1C580EE6_D5B3_40B3_9BBD_A164B5C714F9__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
//
void __cdecl OutputDebugStringF(const char *format, ...);
#ifdef _DEBUG
#define DbgPrintf OutputDebugStringF
#else
#define DbgPrintf //当项目改为Release版本时,所有的DbgPrintf都自己消失了
#endif
//
#endif // !defined(AFX_TOOLS_H__1C580EE6_D5B3_40B3_9BBD_A164B5C714F9__INCLUDED_)
stdafx.h
#include <windows.h>
#include <stdio.h>
使用DbgPrintf
,即可打印参数
DbgPrintf("%d %d", 10, 20);
0x07 Win32&错误返回
前言
使用GetLastError()
函数,放到出问题的那行代码下面,返回值是DWORD类型
使用
返回DWORD类型
#include "stdafx.h"
#include "tools.h"
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
// TODO: Place code here.
//明显的使用有误
MessageBox((HWND)1, 0, 0, 0);
DWORD errorCode = GetLastError();
return 0;
}
调试的时候,鼠标放到errorCode
值上面
报错编号:显示1400
MSDN
搜索GetLastError
1400 Invalid window handle. ERROR_INVALID_WINDOW_HANDLE
#无效的窗口句柄
0x08 事件&消息
前言
Windows中的事件是一个动作
,这个动作可能是用户操作应用程序产生的,也可能是Windows自己产生的.
而消息,就是用来描述这些动作
的,动作被封装成了消息
Windows为了能够准确的描述这些信息,提供了一个结构体:MSG
MSG
这个结构体里面记录的事件的详细信息
typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG, *PMSG;
参数解析
1、hwnd:窗口句柄
表示消息所属的窗口
一个消息一般都是与某个窗口相关联的
在Windows中 HWND 类型的变量通常用来标识窗口
2、message:消息类型
在Windows中,消息是由一个数值来表示的
但是由于数值不便于记忆,所以Windows将消息对应的数值定义为WM_XXX宏(WM == Window Message)
鼠标左键按下 WM_LBUTTONDOWN 键盘按下 WM_KEYDOWN
3、wParam 和 lParam:message(消息类型)的附加消息,消息的进一步描述
32位消息的特定附加信息,具体表示什么处决于message
4、time:消息创建时的时间
5、pt:消息创建时的鼠标位置,通过坐标(x, y)
屏幕左上角是(0 ,0)
0x09 消息处理
前言
系统消息队列与应用程序消息队列
示意图
整体流程解析
1、用户输入:用户触发了一个动作,统称为用户输入,这个时候有一个事件触发了
2、保存消息:把事件封装到消息结构(MSG)
3、系统队列:把消息结构放到系统队列,队列里面的消息是一个挨着一个的,先进先出
4、应用消息队列:开始分流,窗口一的消息给窗口一,窗口二的消息给窗口二,每一个窗口都会有自己的窗口队列
5、通过消息循环,从队列里取出消息
5、处理消息:判断消息类型
是我们关注的,就处理
不是我们关注的(拖过来拖过去、点击空白处等等),就让Windows去处理
0x10 图形界面
创建窗口类的对象
//窗口的类名,这是我们自定义的
//Windows本身也有窗口的类名
TCHAR className[] = "My First Window";
//创建窗口类的对象
WNDCLASS wndclass = {0}; //一定要先将所有值赋值,进行初始化
wndclass.hbrBackground = (HBRUSH)COLOR_MENU; //窗口的背景色
wndclass.lpfnWndProc = WindowProc; //窗口过程函数(指定回调函数)
wndclass.lpszClassName = className; //窗口类的名字
wndclass.hInstance = hInstance; //定义窗口类的应用程序的实例句柄
typedef struct _WNDCLASS {
UINT style;
WNDPROClpfnWndProc; //窗口的消息处理函数
intcbClsExtra;
intcbWndExtra;
HINSTANCE hInstance; //窗口属于的应用程序
HICON hIcon; //窗口的图片标识
HCURSORhCursor; //窗口鼠标形状
HBRUSH hbrBackground; //窗口背景色
LPCTSTRlpszMenuName; //菜单名
LPCTSTRlpszClassName; //窗口名
} WNDCLASS, *PWNDCLASS;
它是一个结构体,包含窗口类的属性
需要使用RegisterClass
函数进行注册
注册窗口类
RegisterClass(&wndclass); //注册窗口类
这里注意:
在我们调用RegisterClass
之前,要先把WNDCLASS
结构体中的成员全部赋值,不需要的值赋值为0
消息处理函数
WNDPROClpfnWndProc; //窗口的消息处理函数
LRESULT CALLBACK WindowProc(
HWND hwnd, // 窗口句柄
UINT uMsg, // 消息类型
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
多选择的时候,switch的效率是很高的
每一个case中,其实都是一个个的宏
LRESULT CALLBACK WindowProc(
IN HWND hwnd,
IN UINT uMsg,
IN WPARAM wParam,
IN LPARAM lParam
)
{
switch(uMsg)
{
//窗口消息
case WM_CREATE:
{
DbgPrintf("WM_CREATE %d %d\n",wParam,lParam);
CREATESTRUCT* createst = (CREATESTRUCT*)lParam;
DbgPrintf("CREATESTRUCT %s\n",createst->lpszClass);
//表示这个消息已被处理
return 0;
}
case WM_MOVE:
{
DbgPrintf("WM_MOVE %d %d\n",wParam,lParam);
POINTS points = MAKEPOINTS(lParam);
DbgPrintf("X Y %d %d\n",points.x,points.y);
return 0;
}
case WM_SIZE:
{
DbgPrintf("WM_SIZE %d %d\n",wParam,lParam);
int newWidth = (int)(short) LOWORD(lParam);
int newHeight = (int)(short) HIWORD(lParam);
DbgPrintf("WM_SIZE %d %d\n",newWidth,newHeight);
return 0;
}
case WM_DESTROY:
{
DbgPrintf("WM_DESTROY %d %d\n",wParam,lParam);
PostQuitMessage(0);
return 0;
}
//键盘消息
case WM_KEYUP:
{
DbgPrintf("WM_KEYUP %d %d\n",wParam,lParam);
return 0;
}
case WM_KEYDOWN:
{
DbgPrintf("WM_KEYDOWN %d %d\n",wParam,lParam);
return 0;
}
//鼠标消息
case WM_LBUTTONDOWN:
{
DbgPrintf("WM_LBUTTONDOWN %d %d\n",wParam,lParam);
POINTS points = MAKEPOINTS(lParam);
DbgPrintf("WM_LBUTTONDOWN %d %d\n",points.x,points.y);
return 0;
}
}
return DefWindowProc(hwnd,uMsg,wParam,lParam);
}
创建窗口
HWND hwnd = CreateWindow(
className, //窗口的类名
TEXT("我的第一个窗口"),//窗口的标题
WS_OVERLAPPEDWINDOW,//窗口外观样式
10, //相对于父窗口的X坐标
10, //相对于父窗口的Y坐标
600,//窗口的宽度
300,//窗口的高度
NULL, //父窗口句柄,为NULL
NULL, //菜单句柄,为NULL
hInstance, //当前应用程序的句柄,一个应用程序有很多窗口
NULL); //附加数据一般为NULL
if(hwnd == NULL)//判断是否创建成功,成功不为0
return 0;
WS_OVERLAPPEDWINDOW,//窗口外观样式
WS_OVERLAPPEDWINDOW Creates an overlapped window with the WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, WS_THICKFRAME, WS_MINIMIZEBOX, and WS_MAXIMIZEBOX styles.
显示窗口
ShowWindow(hwnd, SW_SHOW); //显示窗口
消息循环
MSG msg;
while(GetMessage(&msg, NULL, 0, 0)) //从消息队列中获取消息,放到我们定义的Msg中,然后开始加工消息
{
TranslateMessage(&msg); ////翻译消息
DispatchMessage(&msg); //分发消息,把消息转回操作系统,告诉操作系统它分发完成了
}
回调函数
前言
WindowProc
函数我们只管提供好,放到这里,是由操作系统去调用的
WindowProc
函数会被系统一直调用,一直在发消息,但是我们只处理自己关注的消息
注意
1、窗口回调函数处理过的消息,必须传回0.
2、窗口回调不处理的消息,由DefWindowProc来处理.
#return DefWindowProc(hwnd,uMsg,wParam,lParam);
回调函数结构
LRESULT CALLBACK WindowProc(
IN HWND hwnd,
IN UINT uMsg,
IN WPARAM wParam,
IN LPARAM lParam
);
回调函数的堆栈
代码示例
// test6.cpp : Defines the entry point for the application.
//
#include "stdafx.h"
#include "tools.h"
//声明
LRESULT CALLBACK WindowProc(
IN HWND hwnd,
IN UINT uMsg,
IN WPARAM wParam,
IN LPARAM lParam);
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
//窗口的类名,这是我们自定义的
//Windows本身也有窗口的类名
TCHAR className[] = "My First Window";
//创建窗口类的对象
WNDCLASS wndclass = {0}; //一定要先将所有值赋值,进行初始化
wndclass.hbrBackground = (HBRUSH)COLOR_MENU; //窗口的背景色
wndclass.lpfnWndProc = WindowProc; //指定窗口过程函数
wndclass.lpszClassName = className; //窗口类的名字
wndclass.hInstance = hInstance; //定义窗口类的应用程序的实例句柄
RegisterClass(&wndclass); //注册窗口类
//创建窗口
HWND hwnd = CreateWindow(
className, //窗口的类名
TEXT("我的第一个窗口"),//窗口的标题
WS_OVERLAPPEDWINDOW,//窗口外观样式
10, //相对于父窗口的X坐标
10, //相对于父窗口的Y坐标
600,//窗口的宽度
300,//窗口的高度
NULL, //父窗口句柄,为NULL
NULL, //菜单句柄,为NULL
hInstance, //当前应用程序的句柄
NULL); //附加数据一般为NULL
if(hwnd == NULL)//判断是否创建成功,成功不为0
return 0;
ShowWindow(hwnd, SW_SHOW); //显示窗口
//消息循环
MSG msg;
while(GetMessage(&msg, NULL, 0, 0)) //从消息队列中获取消息,放到我们定义的Msg中,然后开始加工消息
{
TranslateMessage(&msg); ////翻译消息
DispatchMessage(&msg); //分发消息,把消息转回操作系统,告诉操作系统它分发完成了
}
return 0;
}
//消息处理函数
LRESULT CALLBACK WindowProc(
IN HWND hwnd,
IN UINT uMsg,
IN WPARAM wParam,
IN LPARAM lParam
)
{
switch(uMsg)
{
//窗口消息
case WM_CREATE:
{
DbgPrintf("WM_CREATE %d %d\n",wParam,lParam);
CREATESTRUCT* createst = (CREATESTRUCT*)lParam;
DbgPrintf("CREATESTRUCT %s\n",createst->lpszClass);
return 0;
}
case WM_MOVE:
{
DbgPrintf("WM_MOVE %d %d\n",wParam,lParam);
POINTS points = MAKEPOINTS(lParam);
DbgPrintf("X Y %d %d\n",points.x,points.y);
return 0;
}
case WM_SIZE:
{
DbgPrintf("WM_SIZE %d %d\n",wParam,lParam);
int newWidth = (int)(short) LOWORD(lParam);
int newHeight = (int)(short) HIWORD(lParam);
DbgPrintf("WM_SIZE %d %d\n",newWidth,newHeight);
return 0;
}
case WM_DESTROY:
{
DbgPrintf("WM_DESTROY %d %d\n",wParam,lParam);
PostQuitMessage(0);
return 0;
}
//键盘消息
case WM_KEYUP: //键盘抬起来
{
DbgPrintf("WM_KEYUP %d %d\n",wParam,lParam);
return 0;
}
case WM_KEYDOWN: //键盘按下去
{
DbgPrintf("WM_KEYDOWN %d %d\n",wParam,lParam);
return 0;
}
//鼠标消息
case WM_LBUTTONDOWN: //鼠标点击
{
DbgPrintf("WM_LBUTTONDOWN %d %d\n",wParam,lParam);
POINTS points = MAKEPOINTS(lParam);
DbgPrintf("WM_LBUTTONDOWN %d %d\n",points.x,points.y);
return 0;
}
}
return DefWindowProc(hwnd,uMsg,wParam,lParam);
}
窗口的最小化,最大化,关闭
我们并没有去写代码,都是操作系统替我们完成的
return DefWindowProc(hwnd,uMsg,wParam,lParam);
0x11 事件分析
WM_CREATE
前期
窗口创建会触发事件
代码示例
LRESULT CALLBACK WindowProc(
IN HWND hwnd,
IN UINT uMsg,
IN WPARAM wParam,
IN LPARAM lParam
)
{
//输出消息类型
DbgPrintf("%x\n", uMsg);
switch(uMsg)
{
//窗口消息
case WM_CREATE:
{
//输出窗口创建的消息类型
DbgPrintf("WM_CREATE:%x\n", uMsg); //返回1,它是WM_CREATE的宏
//表示这个消息已被处理
return 0;
}
}
}
wParam
&lParam
当消息是WM_CREATE
时,wParam
和lParam
两个参数的意义
wParam:并不使用这个参数
lParam:CREATESTRUCT
类型指针包含窗口的创建信息
LRESULT CALLBACK WindowProc(
IN HWND hwnd,
IN UINT uMsg,
IN WPARAM wParam,
IN LPARAM lParam
)
{
//输出消息类型
DbgPrintf("%x\n", uMsg);
switch(uMsg)
{
//窗口消息
case WM_CREATE:
{
CREATESTRUCT* p = (CREATESTRUCT* ) lParam;
//打印类名
DbgPrintf("WM_CREATE:%x\n",p->lpszClass);
//表示这个消息已被处理
return 0;
}
}
}
WM_MOVE
前言
窗口移动会触发事件
代码示例
LRESULT CALLBACK WindowProc(
IN HWND hwnd,
IN UINT uMsg,
IN WPARAM wParam,
IN LPARAM lParam
)
{
//输出消息类型
DbgPrintf("%x\n", uMsg);
switch(uMsg)
{
//窗口消息
case WM_MOVE:
{
//输出窗口移动的消息类型
DbgPrintf("WM_MOVE:%x\n", uMsg); //返回3,它是WM_MOVE的宏
return 0;
}
}
}
wParam
&lParam
当消息是WM_MOVE
时,wParam
和lParam
两个参数的意义
wParam:并不使用这个参数
lParam(4字节指针):包含了x、y坐标
低位word:x坐标,使用宏LOWORD
高位word:y坐标,使用宏HIWORD
LRESULT CALLBACK WindowProc(
IN HWND hwnd,
IN UINT uMsg,
IN WPARAM wParam,
IN LPARAM lParam
)
{
//输出消息类型
DbgPrintf("%x\n", uMsg);
switch(uMsg)
{
//窗口消息
case WM_MOVE:
{
DWORD xPos = (int)(short) LOWORD(lParam); //x坐标
DWORD yPos = (int)(short) HIWORD(lParam); //y坐标
//输出窗口创建的消息类型
DbgPrintf("%d,%d\n", xPos, yPos); //返回3,它是WM_MOVE的宏
return 0;
}
}
}
WM_SIZE
前言
改变窗口大小会触发事件
代码示例
LRESULT CALLBACK WindowProc(
IN HWND hwnd,
IN UINT uMsg,
IN WPARAM wParam,
IN LPARAM lParam
)
{
//输出消息类型
DbgPrintf("%x\n", uMsg);
switch(uMsg)
{
//窗口消息
case WM_SIZE:
{
//输出窗口大小的消息类型
DbgPrintf("WM_SIZE:%x\n", uMsg); //返回5,它是WM_SIZE的宏
return 0;
}
}
}
wParam
&lParam
当消息是WM_SIZE
时,wParam
和lParam
两个参数的意义
wParam:可能是下面这5种情况中的一种
SIZE_MAXHIDE -- 0
SIZE_MAXIMIZED -- 2
SIZE_MAXSHOW
SIZE_MINIMIZED -- 1
SIZE_RESTORED
lParam(4字节指针):
低位word:当前调整后的窗口的宽度
高位word:当前调整后的窗口的高度
LRESULT CALLBACK WindowProc(
IN HWND hwnd,
IN UINT uMsg,
IN WPARAM wParam,
IN LPARAM lParam
)
{
//输出消息类型
DbgPrintf("%x\n", uMsg);
switch(uMsg)
{
//窗口消息
case WM_SIZE:
{
DbgPrintf("%d %d\n", wParam, lParam);
DWORD xW = (int)(short) LOWORD(lParam); //x坐标
DWORD xH = (int)(short) HIWORD(lParam); //y坐标
//输出窗口创建的消息类型
DbgPrintf("%d,%d\n", xW, xH); //窗口的宽和高
return 0;
}
}
}
0x12 Win32应用程序入口识别
前言
要根据主函数中的第一个参数hInstance,就是ImageBase
实操
GetModuleHandleA:用来获取函数模块的句柄
它是间接Call,地址是405024
注意:这个地址已经修正过了
那么我跳过去
dd 405024
这个地址就是GetModuleHandleA的地址
0x13 ESP寻址特点
前言
EBP是不变的,但是ESP是一直在变的
实操
因为Win32的程序都是__stdcall
,内平栈,从右往左压栈
所以ImageBase是最后一个参数压栈
继续往下看,E8直接Call
按回车跟进去看看
注意到:
RETN 10
初步判断是4个参数
但是注意:当我们看它传入几个参数的时候,要考虑寄存器传参
我们往上看
寄存器传参:没给寄存器赋值,直接使用,那么就是从外面传进来的
在提升堆栈的位置
SUB ESP,54
F2下断点,然后执行
注:F8是步过,F7是步入
调用的下一个地址
0012FF38 0040122E RETURN to test6.<ModuleEntryPoint>+0CE from test6.00401000
继续看这个RETURN 是哪里来的
回车或者Follow in Disassembler
压进去的值就是下一条指令的地址
有4个参数
4 ImageBase 0012FF3C 00400000 test6.00400000
3 0012FF40 00000000
2 0012FF44 00141F18
1 0012FF48 0000000A
开栈是54,那么取第一个参数
ESP+58
我们开始F8步过
当我们走到这里的时候,找到压栈的ImageBase
MOV ESI,DWORD PTR SS:[ESP+5C] ; test6.00400000
注意这里:上面
PUSH ESI
push了一个,ESP的值又会-4
在栈中,可以看到ESP的值
双击一下
我们要找的第一个参数00400000
就是+5C
找到$ ==>
,双击即可回去
继续F8步过,往下走
PUSH EDI
MOV DWORD PTR SS:[ESP+C],ECX
同样是push了一下,ESP-4
注意到ESP 在栈中,继续双击一下
找第一个参数00400000
可以看到是
$+60 >|00400000 test6.00400000
0x14 窗口回调函数的定位
前言
我们的思路是:
RegisterClass-->WNDCLASS-->wndclass.lpfnWndProc
实操
参数类型是指针,并且只有一个参数
RegisterClass(&wndclass); //注册窗口类
E8直接Call
按回车跟进去看看
往下看,根据RegisterClass
那么这个参数EDX
就是WNDCLASS
F2下断点,然后执行
注意:断点,在push上面就行
F8步过
PUSH EDX
EDX是结构体的首地址
0012FF10
右键EDX,Follow in Stack,放到栈里面
我们现在看到这个结构体里面还没有值
0012FF10---0012FF34
然后:锁住堆栈
右键-->Look stack
F8步过,赋值完成
第二个参数,就是我们要找的回调函数
选中这一行,回车,看一下它的反汇编
然后下一个断点
按B查看我们下的断点有哪些
空格暂停断点,只留我们找到的回调函数开始的位置
按C回来
事件_鼠标左键的处理函数定位
#define WM_LBUTTONDOWN 0x0201
在我们回调函数的断点位置,编辑条件
右键-->Edit condition
进行编辑
esp+8:存储的就是消息的ID
[esp+8] == WM_LBUTTONDOWN
双击进入断点
开始运行
现在我们只有点击左键,窗口才会有变化
抓到,鼠标左键的消息的处理函数
0x15 子窗口(按钮)
前言
Windows中所有的组件,其实都是窗口
函数要放到创建窗口之后,因为它参数有子窗口ID
void CreateButton(HWND hwnd)
{
HWND hwndPushButton;
hwndPushButton = CreateWindow (
TEXT("button"), //类名
TEXT("普通按钮"), //标题
//WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_DEFPUSHBUTTON,
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_DEFPUSHBUTTON, //按钮属性
10, 10, //坐标
80, 20, //宽度高度
hwnd, //父窗口句柄
(HMENU)1001, //子窗口ID,强转为菜单类型
hAppInstance, //当前应用程序句柄
NULL); //附件程序,一般为NULL
}
整体代码
// test6.cpp : Defines the entry point for the application.
//
#include "stdafx.h"
#include "tools.h"
HINSTANCE hAppInstance; //应用程序句柄定义为全局变量
//声明
LRESULT CALLBACK WindowProc(
IN HWND hwnd,
IN UINT uMsg,
IN WPARAM wParam,
IN LPARAM lParam);
//按钮函数
void CreateButton(HWND hwnd)
{
//定义句柄
HWND hwndPushButton;
HWND hwndCheckBox;
HWND hwndRadio;
hwndPushButton = CreateWindow (
TEXT("button"), //类名
TEXT("普通按钮"), //标题
//WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_DEFPUSHBUTTON,
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_DEFPUSHBUTTON, //按钮属性
10, 10, //坐标
80, 20, //宽度高度
hwnd, //父窗口句柄
(HMENU)1001, //子窗口ID,强转为菜单类型
hAppInstance, //当前应用程序句柄
NULL); //附件程序,一般为NULL
hwndCheckBox = CreateWindow (
TEXT("button"),
TEXT("复选框"),
//WS_CHILD | WS_VISIBLE | BS_CHECKBOX | BS_AUTOCHECKBOX,
WS_CHILD | WS_VISIBLE | BS_CHECKBOX |BS_AUTOCHECKBOX ,
10, 40,
80, 20,
hwnd,
(HMENU)1002,//子窗口ID
hAppInstance,
NULL);
hwndRadio = CreateWindow (
TEXT("button"),
TEXT("单选按钮"),
//WS_CHILD | WS_VISIBLE | BS_RADIOBUTTON | BS_AUTORADIOBUTTON,
WS_CHILD | WS_VISIBLE | BS_RADIOBUTTON ,
10, 70,
80, 20,
hwnd,
(HMENU)1003,//子窗口ID
hAppInstance,
NULL);
}
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
hAppInstance = hInstance;
//窗口的类名,这是我们自定义的
//Windows本身也有窗口的类名
TCHAR className[] = "My First Window";
//创建窗口类的对象
WNDCLASS wndclass = {0}; //一定要先将所有值赋值,进行初始化
wndclass.hbrBackground = (HBRUSH)COLOR_MENU; //窗口的背景色
wndclass.lpfnWndProc = WindowProc; //指定窗口过程函数
wndclass.lpszClassName = className; //窗口类的名字
wndclass.hInstance = hInstance; //定义窗口类的应用程序的实例句柄
RegisterClass(&wndclass); //注册窗口类
//创建窗口
HWND hwnd = CreateWindow(
className, //窗口的类名
TEXT("我的第一个窗口"),//窗口的标题
WS_OVERLAPPEDWINDOW,//窗口外观样式
10, //相对于父窗口的X坐标
10, //相对于父窗口的Y坐标
600,//窗口的宽度
300,//窗口的高度
NULL, //父窗口句柄,为NULL
NULL, //菜单句柄,为NULL
hInstance, //当前应用程序的句柄
NULL); //附加数据一般为NULL
if(hwnd == NULL)//判断是否创建成功,成功不为0
return 0;
CreateButton(hwnd);
ShowWindow(hwnd, SW_SHOW); //显示窗口
//消息循环
MSG msg;
while(GetMessage(&msg, NULL, 0, 0)) //从消息队列中获取消息,放到我们定义的Msg中,然后开始加工消息
{
TranslateMessage(&msg); ////翻译消息
DispatchMessage(&msg); //分发消息,把消息转回操作系统,告诉操作系统它分发完成了
}
return 0;
}
//消息处理函数
LRESULT CALLBACK WindowProc(
IN HWND hwnd,
IN UINT uMsg,
IN WPARAM wParam,
IN LPARAM lParam
)
{
switch(uMsg)
{
//窗口消息
case WM_CREATE:
{
DbgPrintf("WM_CREATE %d %d\n",wParam,lParam);
CREATESTRUCT* createst = (CREATESTRUCT*)lParam;
DbgPrintf("CREATESTRUCT %s\n",createst->lpszClass);
return 0;
}
case WM_MOVE:
{
DbgPrintf("WM_MOVE %d %d\n",wParam,lParam);
POINTS points = MAKEPOINTS(lParam);
DbgPrintf("X Y %d %d\n",points.x,points.y);
return 0;
}
case WM_SIZE:
{
DbgPrintf("WM_SIZE %d %d\n",wParam,lParam);
int newWidth = (int)(short) LOWORD(lParam);
int newHeight = (int)(short) HIWORD(lParam);
DbgPrintf("WM_SIZE %d %d\n",newWidth,newHeight);
return 0;
}
case WM_DESTROY:
{
DbgPrintf("WM_DESTROY %d %d\n",wParam,lParam);
PostQuitMessage(0);
return 0;
}
//键盘消息
case WM_KEYUP: //键盘抬起来
{
DbgPrintf("WM_KEYUP %d %d\n",wParam,lParam);
return 0;
}
case WM_KEYDOWN: //键盘按下去
{
DbgPrintf("WM_KEYDOWN %d %d\n",wParam,lParam);
return 0;
}
//鼠标消息
case WM_LBUTTONDOWN: //鼠标点击
{
DbgPrintf("WM_LBUTTONDOWN %d %d\n",wParam,lParam);
POINTS points = MAKEPOINTS(lParam);
DbgPrintf("WM_LBUTTONDOWN %d %d\n",points.x,points.y);
return 0;
}
}
return DefWindowProc(hwnd,uMsg,wParam,lParam);
}
子窗口事件的处理
button按钮,是系统给我们预定义好的,我们并没有去创建
那么我们去获取button的属性,使用GetClassName
函数用来:获取类名
TCHAR szBuffer[0x20];
GetClassName(hwndPushButton,szBuffer,0x20);
参数解析:
int GetClassName(
HWND hWnd, // 获取窗口的句柄
LPTSTR lpClassName, // 缓冲区的地址,out类型的参数,会获取到类名会保存在这个指针中
int nMaxCount// 缓冲区的大小
);
使用GetClassInfo
获取函数的其他信息
WNDCLASS wc;
GetClassInfo(hAppInstance,szBuffer,&wc);
//指针打印输出
//打印类名
OutputDebugStringF("-->%s\n",wc.lpszClassName);
//打印消息处理函数的地址
OutputDebugStringF("-->%x\n",wc.lpfnWndProc);
参数分析:
BOOL GetClassInfo(
HINSTANCE hInstance,// 应用程序的ImageBase
LPCTSTR lpClassName,// 类名,in类型参数
LPWNDCLASS lpWndClass // out类型参数,保存到指针中
);
查看输出:
系统预定义的类名:Button
系统预定义的消息处理函数的地址:77d3b036
子窗口&父窗口消息处理
前言
在消息处理函数中添加子窗口的消息处理函数,即可
示意图
WM_COMMAND
case WM_COMMAND:
{
switch(LOWORD(wParam)) //子窗口的ID
{
case 1001:
MessageBox(hwnd,"Hello Button 1","Demo",MB_OK);
return 0;
case 1002:
MessageBox(hwnd,"Hello Button 2","Demo",MB_OK);
return 0;
case 1003:
MessageBox(hwnd,"Hello Button 3","Demo",MB_OK);
return 0;
}
return DefWindowProc(hwnd,uMsg,wParam,lParam);
}
总结
1、在父窗口的消息处理函数中添加单击左键的事件,跟子窗口没关系
2、button按钮,是系统给我们预定义好的
3、按钮是一种特殊的窗体,并不需要提供单独的窗口回调函数.
4、当按钮有事件产生时,消息类型会发生改变,会给父窗口消息处理程序发送一个WM_COMMAND
消息
子窗口回调函数的定位
前言
RegisterClass-->WNDCLASS-->wndclass.lpfnWndProc-->[ESP+8] == WM_COMMAND&&[ESP+0xC] == 某个窗口的ID
实操
回车进来
父窗口的回调函数
004011F0
ctrl+g 跳过去
F2下断点,ESP+8 == uMsg
开始编辑断点
[ESP+8] == WM_COMMAND
开始运行
这个时候,我们只有点击子窗口,才会有反应
点击后,就断进来了
按W,子控件的编号
这个时候呢,我们要精确控制每一个子窗口
编辑断点处,就要多+一个条件
[ESP+8] == WM_COMMAND&&[ESP+0xC] == 0x3EB
重新运行,这个时候,我们只有单击单选框,它才会有反应
0x16 资源文件&对话框
前言
消息断点本质是条件断点
通过资源文件创建对话框,会非常的简单
1、创建窗口
2、提供消息处理函数
操作系统会为我们做很多的事情
实操
创建资源文件
添加头文件
创建Dialog(对话框)
默认会带两个按钮
右键-->cut删掉
首先要修改它的属性
ID:IDD_DIALOG_MAIN
文本框标题:My FirstDialog
F7编译一下
资源的头文件有增加的内容
#define IDD_DIALOG_MAIN 101
这是一个宏,表示刚才的那个对话框编号
创建对话框
使用DialogBox
函数
INT_PTR DialogBox(
HINSTANCE hInstance, // ImageBase
LPCTSTR lpTemplate, // dialog对话框
HWND hWndParent, // 父窗口的句柄
DLGPROC lpDialogFunc // dialog对话框的消息处理函数
);
当前的参数指向以空结尾的字符串
当它是一个数字时
1、可以通过(char* )
把它转换为一个指针
2、可以按照文档中的做法,使用MAKEINTRESOURCE
宏,也是将数字转换为一个(char* )
类型的指针
DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG_MAIN), NULL, DialogProc);
继续,进行添加按钮
双击它
可以自己随意去画
排版
同时选中两个按钮
下面一行都是排版用的按钮
设置按钮属性
F7重新编译,然后重新看资源头文件
替我们生成了编号
将按钮对接到消息处理函数上
case WM_COMMAND :
switch (LOWORD (wParam))
{
case IDC_BUTTON_OK :
MessageBox(NULL,TEXT("IDC_BUTTON_OK"),TEXT("OK"),MB_OK);
return TRUE;
case IDC_BUTTON_NO:
MessageBox(NULL,TEXT("IDC_BUTTON_NO"),TEXT("NO"),MB_OK);
EndDialog(hwndDlg, 0);
return TRUE;
}
继续画一些按钮
静态框:
文本框:
现在呢,想实现功能:点击按钮,打印文本框的输出
1、获取文本框的句柄(通用)
使用GetDlgItem
函数
参数分析:
hDlg:当前文本框的句柄
IDC_EDIT_USER:对话框的编号
示例:
HWND hEditUser = GetDlgItem(hDlg, IDC_EDIT_UserName);
2、通过句柄获取文本框内容
使用GetWindowText
函数
首先要有一个缓冲区
参数分析:
hEditUser:文本框句柄
szUserBuff:输出缓冲区
示例:
TCHAR szUserBuff[0x50];
GetWindowText(hEditUser, szUserBuff, 0x50);
代码示例
// test6.cpp : Defines the entry point for the application.
//
#include "stdafx.h"
#include "resource.h"
//消息处理函数
BOOL CALLBACK DialogProc(
HWND hDlg, // handle to dialog box
UINT uMsg, // message
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
)
{
HWND hEditUser = NULL;
HWND hEditPassWord = NULL;
switch(uMsg)
{
case WM_INITDIALOG :
MessageBox(NULL,TEXT("WM_INITDIALOG"),TEXT("INIT"),MB_OK);
return TRUE ;
case WM_COMMAND :
switch (LOWORD (wParam))
{
case IDC_BUTTON_OK :
//1、获取文本框的句柄
hEditUser = GetDlgItem(hDlg,IDC_EDIT_UserName);
hEditPassWord = GetDlgItem(hDlg,IDC_EDIT_PassWord);
//2、通过句柄获取文本框内容
TCHAR szUserBuff[0x50];
TCHAR szPassBuff[0x50];
GetWindowText(hEditUser, szUserBuff, 0x50);
GetWindowText(hEditPassWord, szPassBuff, 0x50);
MessageBox(NULL,TEXT("IDC_BUTTON_OK"),TEXT("OK"),MB_OK);
return TRUE;
case IDC_BUTTON_NO:
MessageBox(NULL,TEXT("IDC_BUTTON_NO"),TEXT("NO"),MB_OK);
EndDialog(hDlg, 0);
return TRUE;
}
break ;
}
return FALSE ;
}
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG_MAIN), NULL, DialogProc);
return 0;
}
总结
对话框的消息处理函数:
1、处理过的消息,返回TRUE
2、不处理的消息,返回FALSE
0x17 对话框&定位消息处理函数
回车跟进去
间接Call,创建Dialog的地址
定位消息处理函数
Ctrl+g 跟过去
00401000
F2下断点,因为消息在不停的发送,所以我们要添加条件,让它停下来
添加条件
[esp+8] == WM_COMMAND
当我们点击OK,它断进来了
0x18 消息断点
前言
当程序特别复杂的时候,我们一时间找不见消息处理函数
实操
打开DTDebug,拖入exe,开始执行
点击W
刷新一下
当前页面的所有窗口都在这里了
系统为我们指定的消息处理函数,由某个dll提供的
这中间存在一个关系
系统为我们指定的消息处理函数-->最终仍要调用我们自己写的消息处理函数
现在,我们在系统为我们指定的消息处理函数处,下断点
首先跳过来
还有一种更方便的操作,就是我们下一个消息断点
选择消息类型,这里注意:我们找的不应该是WM_COMMAND
要找的是,系统为我们指定的消息处理函数的消息类型
小技巧,我们要确定是鼠标左键点下去,还是鼠标左键抬起来
鼠标左键点下去的时候,消息没有被触发
鼠标左键抬起来的时候,消息没有被触发
所以,我们这里找消息类型,应该是
WM_LBUTTONUP
两个都变色了,因为对于按钮Button,系统为我们指定的消息处理函数的消息类型都是一样的
我们继续去看断点
开始执行
我们一点击OK
,它就断下来了
断到系统为我们指定的消息处理函数
打开内存窗口
代码段
数据段
资源段
我们可以在代码段,下一个断点,它是访问断点
然后我按了F9,它要去调用我们的消息处理函数
我在代码段,下了访问断点,它就停下来了
我们继续看
我们知道进到消息处理函数中时,是WM_COMMAND
,消息类型是111
调用地址
句柄
消息类型
w
l
我们可以看到消息类型是135,并不是我们要找的鼠标左键事件,消息处理函数
那么,怎么定位到我们鼠标左键事件的,消息处理函数
F8,我们一直往下走,往下走
可以看到又回到,7xxxxx
然后F9,我们可以看到找到了鼠标左键的,消息处理函数
111
总结
系统为我们指定的消息处理函数-->最终仍要调用我们自己写的消息处理函数
0x19 图标
前言
注意:VC6.0C++只支持32*32像素256色
实操
首先生成32*32像素256色
的图标
创建图标
修改属性
继续添加一个图标,修改属性
F7编译一下
查看资源头文件
有两个图标的编号
#define IDI_ICON_BIG101
#define IDI_ICON_SMALL 102
具体是这样划分的:
Alt+Tab-->大图标
其他地方-->小图标
加载图标
使用LoadIcon
函数
参数分析:
hAppInstance:应用程序句柄
IDI_ICON:图标编号
MAKEINTRESOURCE:用这个宏的主要原因是有的资源是用序号定义的,而不是字符串.所以要把数字转换成字符串指针
示例
hBigIcon = LoadIcon(hAppInstance, MAKEINTRESOURCE(IDI_ICON_BIG));
hSmallIcon = LoadIcon(hAppInstance, MAKEINTRESOURCE(IDI_ICON_SMALL));
设置图标
SendMessage(hDlg,WM_SETICON,ICON_BIG,(DWORD)hBigIcon);
SendMessage(hDlg,WM_SETICON,ICON_SMALL,(DWORD)hSmallIcon);
代码示例
#include "stdafx.h"
#include "resource.h"
HINSTANCE hAPPhinstance;
BOOL CALLBACK DialogProc(
HWND hwndDlg, // handle to dialog box
UINT uMsg, // message
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
)
{
HWND hEditUser = NULL;
HWND hEditPass = NULL;
HICON hBigIcon;
HICON hSmallIcon;
switch(uMsg)
{
case WM_INITDIALOG :
hBigIcon = LoadIcon(hAPPhinstance, MAKEINTRESOURCE(IDI_ICON_BIG));
hSmallIcon = LoadIcon(hAPPhinstance, MAKEINTRESOURCE(IDI_ICON_SMALL));
SendMessage(hwndDlg,WM_SETICON,ICON_BIG,(DWORD)hBigIcon);
SendMessage(hwndDlg,WM_SETICON,ICON_SMALL,(DWORD)hSmallIcon);
//MessageBox(NULL,TEXT("WM_INITDIALOG"),TEXT("INIT"),MB_OK);
return TRUE ;
case WM_COMMAND :
switch (LOWORD (wParam))
{
case IDC_BUTTON_OK :
// 第一步:先获取文本框的句柄
hEditUser = GetDlgItem(hwndDlg, IDC_EDIT_USERNAME);
hEditPass = GetDlgItem(hwndDlg, IDC_EDIT_PASSWORD);
// 第二步:通过句柄得到里面的内容
TCHAR szUserBuff[0x50];
TCHAR szPassBuff[0x50];
GetWindowText(hEditUser, szUserBuff, 0x50);
GetWindowText(hEditPass, szPassBuff, 0x50);
MessageBox(NULL,TEXT("IDC_BUTTON_OK"),TEXT("OK"),MB_OK);
return TRUE;
case IDC_BUTTON_ERROR:
MessageBox(NULL,TEXT("IDC_BUTTON_ERROR"),TEXT("ERROR"),MB_OK);
EndDialog(hwndDlg, 0);
return TRUE;
}
break ;
}
return FALSE ;
}
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
// TODO: Place code here.
hAPPhinstance = hInstance;
DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG_MAIN), NULL, DialogProc);
return 0;
}
0x20 提取图标
实操
使用ResHacker.exe
把exe拖进来,选中图标
成功提取图标
图标
这里,注意:
当我们拖入一个exe,它展现了很多图标,但是我们并没有添加这么多图标
这是,因为Windows操作系统在处理图标的时候,他必须要考虑到很多情况
考虑,在不同分辨率情况下,展现更好的效果,所以它分了好多份
它是编译器分的
图标组
它是对图标信息的一种描述
总结
这个工具,它会替我们去解析
0x21 更改标题
使用LordPE
点击PE编辑器,拖入我们的exe
对话框:"DLG_ABOUT"
RVA:00007004
偏移:00002A04
大小:00000190
进行保存
打开16进制的编辑器,拖入exe
和保存的.dmp
文件
找到我们exe的标题
这里注意:在资源文件中,所有的字符串都是Unicode
在exe中,Ctrl+f搜索一下
简单修改一下,然后保存
总结
这个工具,对于每一个exe文件,我们都可以找到它的资源文件对于的二进制文件
但是,每一种资源文件它都有自己对应的文件结构,它并没有替我们去解析
0x22 资源表
前言
在PE文件中,资源表是最复杂的
资源目录在PE结构数据目录的数组下标为2的位置,第三个表
#define IMAGE_DIRECTORY_ENTRY_RESOURCE2 // Resource Directory
头文件目录
C:\Program Files\Microsoft Visual Studio\VC98\Include\WINNT.H
每一块都是资源节点:
绿的:资源目录
黄的:资源目录项
示意图
资源目录&资源目录项
资源目录它是资源表中每一层都会有的结构
以名称命名的资源数量 + 以ID命名的资源数量 = 一个节点中的数据项(资源节点中黄色的结构体个数)
typedef struct _IMAGE_RESOURCE_DIRECTORY {
DWORD Characteristics;//资源属性 保留 0
DWORD TimeDateStamp; //资源创建的时间
WORDMajorVersion; //资源版本号 未使用 0
WORDMinorVersion; //资源版本号 未使用 0
WORDNumberOfNamedEntries; //以名称命名的资源数量
WORDNumberOfIdEntries; //以ID命名的资源数量
// IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[];
} IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;
资源目录项:
typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {
//这个联合体要根据最高位0还是1,做不同的事情
//通过判断DWORD NameIsString成员是否为1
//4字节联合体
union { //目录项的名称、或者ID
struct {
//共占4字节,总共32位,目的:是对位的精确控制
DWORD NameOffset:31; //位段或者叫位域,从后往前,它代表0-30,第31位 -
DWORD NameIsString:1; //代表31,第32位(最高位)
};
DWORD Name;
WORDId;
};
//4字节联合体
union {
DWORD OffsetToData; //目录项指针
struct {
DWORD OffsetToDirectory:31;
DWORD DataIsDirectory:1;
};
};
} IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY;
第一层
绿色:资源目录
黄色:它是资源目录项结构体,用来判断资源的类型,Windows共16中预定义类型,
像:光标:1,位图:2,图标:3,菜单:4,对话框:5等
我们要去判断它是Windows的预定义类型,还是我们使用了自定义类型
在第一层中,资源目录项结构体中Name代表:资源类型,又有两种情况
它可以通过字符串去指定,也可以是一个数字
typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {
//这个联合体要根据最高位0还是1,做不同的事情
//通过判断DWORD NameIsString成员是否为1
//4字节联合体
union { //目录项的名称、或者ID
struct {
//共占4字节,总共32位,目的:是对位的精确控制
DWORD NameOffset:31; //位段或者叫位域,从后往前,它代表0-30,第31位 -
DWORD NameIsString:1; //代表31,第32位(最高位)
};
DWORD Name;
WORDId;
};
//4字节联合体
union {
DWORD OffsetToData; //目录项指针
struct {
DWORD OffsetToDirectory:31;
DWORD DataIsDirectory:1;
};
};
} IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY;
看第一个联合体union
当它最高位是1时,当前的资源类型是通过字符串指定的,低31位是一个UNICODE指针
,指向一个结构:
typedef struct _IMAGE_RESOURCE_DIR_STRING_U {
WORDLength; //长度
WCHAR NameString[ 1 ]; //UNICODE起始地址
} IMAGE_RESOURCE_DIR_STRING_U, *PIMAGE_RESOURCE_DIR_STRING_U;
当它最高位是0时,低31位
是一个一个int类型的数字
,是一个编号,表示字段的值作为 ID 使用
判断第一位的值
1、printf("%x\n",(pResourceEntry[i].Name & 0x80000000) == 0x80000000);
2、printf("%x\n",pResourceEntry[i].NameIsString == 1);
继续看第一个联合体union
OffsetToData,它是用来找第二层的地址,它不是一个RVA
公式:资源表地址(RES) + OffsetToData的低31位 = 下一层目录节点的起始位置
最高位如果为1,低31位 + 资源地址 == 下一层目录节点的起始位置
最高位如果为0,指向 IMAGE_RESOURCE_DATA_ENTRY
第一层、第二层全为1,第三层为0
第二层
绿色:资源目录
黄色:它是资源目录项结构体
在第二层中,资源目录项结构体中Name代表:资源编号,又有两种情况
它可以通过字符串去指定,也可以是一个数字,和第一层的判断方法一样
公式:资源表地址(RES) + OffsetToData的低31位 = 下一层目录节点的起始位置
第三层
绿色:资源目录
黄色:它是资源目录项结构体
在第三中,资源目录项结构体中Name代表:代码页,又有两种情况
它可以通过字符串去指定,也可以是一个数字,和第一层的判断方法一样
代码页又会有编号,简体中文:2052
其他编号参考:http://blog.itpub.net/68137/viewspace-687394/
节点指针
第三层又指向了节点指针
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; //当前资源的RVA是多少
DWORD Size; //当前资源的大小是多少
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
代码示例
#include "stdafx.h"
#include <stdio.h>
#include <windows.h>
#include <malloc.h>
#define FilePath_In "C:\\notepad.exe"
//函数声明
//ReadPEFile:将文件读取到缓冲区
DWORD ReadPEFile(IN LPSTR lpszFile,OUT LPVOID* pFileBuffer);
//RvaToFileOffset:将内存偏移转换为文件偏移
DWORD RvaToFileOffset(IN LPVOID pFileBuffer,IN DWORD dwRva);
//ReadPEFile:将文件读取到缓冲区
DWORD ReadPEFile(IN LPSTR lpszFile, OUT LPVOID* pFileBuffer)
{
FILE* pFile = NULL;
//定义一个FILE结构体指针,在标准的stdio.h文件头里面
DWORD fileSize = 0;
LPVOID pTempFileBuffer = NULL;
//打开文件
pFile = fopen(lpszFile,"rb"); //lpszFile是当作参数传递进来
if (!pFile)
{
printf("打开文件失败!\r\n");
return 0;
}
/*
关于在指针类型中进行判断的操作,下面代码出现的情况和此一样,这里解释下:
1.因为指针判断都要跟NULL比较,相当于0,假值,其余都是真值
2.if(!pFile)和if(pFile == NULL), ----> 为空,就执行语句;这里是两个等于号不是一个等于号
3.if(pFile)就是if(pFile != NULL), 不为空,就执行语句;
*/
//读取文件内容后,获取文件的大小
fseek(pFile,0,SEEK_END);
fileSize = ftell(pFile);
fseek(pFile,0,SEEK_SET);
//动态申请内存空间,得到的是内存分配的指针
pTempFileBuffer = malloc(fileSize);
if (!pTempFileBuffer)
{
printf("内存分配失败!\r\n");
fclose(pFile);
return 0;
}
//根据申请到的内存空间,将文件读取到缓冲区
size_t n = fread(pTempFileBuffer,fileSize,1,pFile);
if (!n)
{
printf("读取数据失败!\r\n");
free(pTempFileBuffer); // 释放内存空间
fclose(pFile);// 关闭文件流
return 0;
}
//数据读取成功,关闭文件
*pFileBuffer = pTempFileBuffer; // 将读取成功的数据所在的内存空间的首地址放入指针类型pFileBuffer
pTempFileBuffer = NULL; // 初始化清空临时申请的内存空间
fclose(pFile); // 关闭文件
return fileSize; // 返回获取文件的大小
}
//RvaToFileOffset:将内存偏移转换为文件偏移
DWORD RvaToFileOffset(LPVOID pFileBuffer, DWORD dwRva)
{
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)(pDosHeader->e_lfanew + (DWORD)pFileBuffer + 4);
PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));
PIMAGE_SECTION_HEADER pSectionHeader = \
(PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
// RVA在文件头中或者文件对齐==内存对齐时,RVA==FOA 错!第一句是对的,第二句是错的
if (dwRva < pOptionHeader->SizeOfHeaders)
{
return dwRva;
}
// 遍历节表,确定偏移属于哪一个节
for (int i = 0; i < pPEHeader->NumberOfSections; i++)
{
if (dwRva >= pSectionHeader[i].VirtualAddress && \
dwRva < pSectionHeader[i].VirtualAddress + pSectionHeader[i].Misc.VirtualSize)
{
int offset = dwRva - pSectionHeader[i].VirtualAddress;
return pSectionHeader[i].PointerToRawData + offset;
}
}
printf("找不到RVA %x 对应的 FOA,转换失败\n", dwRva);
return 0;
}
//打印资源表
VOID ResourceTable(LPVOID pFileBuffer)
{
//资源的类型
PCHAR lpszResType[17] = { "未定义", "光标", "位图", "图标", "菜单",
"对话框", "字符串","字体目录", "字体",
"加速键", "非格式化资源", "消息列表", "光标组",
"未定义", "图标组","未定义", "版本信息" };
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)(pDosHeader->e_lfanew + (DWORD)pDosHeader + 4);
PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));
PIMAGE_SECTION_HEADER pSectionHeader = \
(PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
// 定义第一层的指针和长度
PIMAGE_RESOURCE_DIRECTORY pResDir1 = (PIMAGE_RESOURCE_DIRECTORY)((DWORD)pFileBuffer + \
RvaToFileOffset(pFileBuffer, pOptionHeader->DataDirectory[2].VirtualAddress));
PIMAGE_RESOURCE_DIRECTORY_ENTRY pResDirEntry1 = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)((DWORD)pResDir1 + \
sizeof(IMAGE_RESOURCE_DIRECTORY));
int dwNumberOfResDirEntry1 = pResDir1->NumberOfNamedEntries + pResDir1->NumberOfIdEntries;
printf("资源类型数量: %d\r\n", dwNumberOfResDirEntry1);
//上面是将名称和ID相加得出资源类型的数量
// 总共三层,所以需要3层循环,开始遍历第一层:类型
for (int i = 0; i < dwNumberOfResDirEntry1; i++)
{
// 如果高位是1,则低31位就是指针,她是指向一个Unicode字符串
if (pResDirEntry1[i].NameIsString == 1)
{
PIMAGE_RESOURCE_DIR_STRING_U uString =
(PIMAGE_RESOURCE_DIR_STRING_U)((DWORD)pResDir1 + (pResDirEntry1[i].NameOffset & 0x7FFFFFFF));
WCHAR *pName = (WCHAR *)malloc(2 * (uString->Length + 1));
memset(pName, 0, 2 * (uString->Length + 1));
memcpy(pName, uString->NameString, 2 * uString->Length);
wprintf(L"ID: - 资源类型: \"%s\"\n", pName);
free(pName);
}
// 如果最高位是0,则其就是一个序号,此时字段的值作为ID使用,她是预定义的16种资源之一
else
{
if (pResDirEntry1[i].Id <= 16)
printf("ID: %2d 资源类型: %s\n", pResDirEntry1[i].Id, lpszResType[pResDirEntry1[i].Id]);
else
printf("ID: %2d 资源类型: 未定义\n", pResDirEntry1[i].Id);
}
// 定义第二层的指针和长度
PIMAGE_RESOURCE_DIRECTORY pResDir2 = (PIMAGE_RESOURCE_DIRECTORY)((DWORD)pResDir1 + \
(pResDirEntry1[i].OffsetToData & 0x7FFFFFFF));
PIMAGE_RESOURCE_DIRECTORY_ENTRY pResDirEntry2 = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)((DWORD)pResDir2 + \
sizeof(IMAGE_RESOURCE_DIRECTORY));
int dwNumberOfResDirEntry1 = pResDir2->NumberOfNamedEntries + pResDir2->NumberOfIdEntries;
// 开始遍历第二层:编号
for (int j = 0; j < dwNumberOfResDirEntry1; j++)
{
if (pResDirEntry2[j].NameIsString == 1)
{
PIMAGE_RESOURCE_DIR_STRING_U uString =
(PIMAGE_RESOURCE_DIR_STRING_U)((DWORD)pResDir1 + (pResDirEntry2[j].NameOffset & 0x7FFFFFFF));
WCHAR *pName = (WCHAR *)malloc(2 * (uString->Length + 1));
memset(pName, 0, 2 * (uString->Length + 1));
memcpy(pName, uString->NameString, 2 * uString->Length);
wprintf(L"\tName: \"%s\"\n", pName);
free(pName);
}
else
{
printf("\tID: %d\n", pResDirEntry2[j].Id);
}
// 定义第三层的指针和长度
PIMAGE_RESOURCE_DIRECTORY pResDir3 = (PIMAGE_RESOURCE_DIRECTORY)((DWORD)pResDir1 + \
(pResDirEntry2[j].OffsetToData & 0x7FFFFFFF));
PIMAGE_RESOURCE_DIRECTORY_ENTRY pResDirEntry3 = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)((DWORD)pResDir3 + \
sizeof(IMAGE_RESOURCE_DIRECTORY));
int dwNumberOfResDirEntry3 = pResDir3->NumberOfNamedEntries + pResDir3->NumberOfIdEntries;
// 遍历第三层:代码页
// 大多数情况下一个资源的代码页只定义一种,但不是绝对,因此第三层也要循环遍历
//printf("\t\t%d\n", dwNumberOfResDirEntry3); // 真有不是1的
for (int k = 0; k < dwNumberOfResDirEntry3; k++)
{
if (pResDirEntry3[k].Name & 0x80000000)
{
printf("\t非标准代码页\n");
}
else
{
printf("\t代码页: %d\n", pResDirEntry3[k].Id & 0x7FFF);
}
// 资源数据项,通过这个结构可以找到资源的RVA,以及大小
PIMAGE_RESOURCE_DATA_ENTRY pDataEntry = (PIMAGE_RESOURCE_DATA_ENTRY)((DWORD)pResDir1 + \
pResDirEntry3[k].OffsetToData);
DWORD FoaResource = RvaToFileOffset(pFileBuffer, pDataEntry->OffsetToData);
//printf("\tRVA: %#010x\r\n \tFOA: %#010x\r\n \tSIZE: %d ", pDataEntry->OffsetToData,FoaResource,pDataEntry->Size);
printf("\tFOA: %#010x\r\n \tSIZE: %d ", FoaResource,pDataEntry->Size);
printf("\r\n");
//system("pause");
}
printf("\r\n");
}
}
}
//调用上面函数的调用代码
VOID PrintResourceTable()
{
PVOID pFileBuffer = NULL;
DWORD FileBufferSize = 0;
//File-->FileBuffer
FileBufferSize = ReadPEFile(FilePath_In,&pFileBuffer);
if (FileBufferSize == 0 || !pFileBuffer)
{
printf("文件-->缓冲区失败\r\n");
return ;
}
printf("FileBufferSize: %#X \r\n",FileBufferSize);
ResourceTable(pFileBuffer);
free(pFileBuffer);
}
int main(int argc, char* argv[])
{
PrintResourceTable();
return 0;
}
- 本文作者: 略略略
- 本文来源: 奇安信攻防社区
- 原文链接: https://forum.butian.net/share/1386
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!