Win32 反汇编

ollydbg 原版下载,网上有很多的版本,下载及测试后发现还是原版的

Win32 反汇编

ollydbg

原版下载,网上有很多的版本,下载及测试后发现还是原版的比较好,但是原版没有各种各样的插件,这就需要自己把插件导入进去了,插件及配色界面下载

全局变量、局部变量对应反汇编

ollydgb 函数连接线消失

解决办法 一:
首先,把ollydbg关闭掉
其次,把如图文件夹内当前调试出现问题程序的.bak、.udd文件删除即可,
或者删除.UDD后缀文件。

解决方法 二:

打开ollydbg弹窗问题

上面的解决方法一同样适用打开ollydbg时,弹出一些注意或是警告对话框。

visual studio release模式禁用代码优化

因为编译器很智能,visual studio在release模式下,代码会有很多被优化(没有实质意义的代码会被删除掉),便于学习,所以先把优化关闭掉,如下图。

Command

? 寄存器

例如(? eax/ax/ah/al)可以看到该寄存器的值

 ? ebp

看到的是epb的地址值,而不是ebp指向内存的值 

 ? ebp-0xC

意思同上,这次截图使能看到ax的值为0X4321

? 寄存器:得到寄存器的值
? 地址:    得到内存地址值(注意不是内存地址所存储的值)

dd 内存地址

查看的是(data dword) 4个字节的内存数据

dw 内存地址

查看的是(data word)   2个字节的内存数据

db 内存地址

查看的是(data byte)    1个字节的内存数据

MOVSX

movsx 为有符号传送

格式如下:
MOVSX reg32, reg/mem8
MOVSX reg32, reg/mem16
MOVSX reg16, reg/mem8

0x4321
‭0100 0011 0010 0001‬
    4      3       2      1
以上是十六进制与二进制的对应关系
mov ax, 0
mov bx, 0x4321// bx = 0x4321
bh = 0x43, bl = 0x21
movsx(s:signed)有符号传送指令
bl = 0x21 = 33 因为执行movsx是有符号传送
有符号byte范围 -128~127,所以bl为正数
由于bl是无符号数,所以执行movsx指令后,未被赋值的二进制位设置为 0
movsx ax, bl//执行完这条指令后 ax的值为 0X0021
0x43e1
‭0100 0011 1110 0001‬
    4       3       E      1
以上是十六进制与二进制的对应关系
mov ax, 0
mov bx, 0x43E1
bl = 0xe1 = 225 因为执行movsx是有符号传送
byte有符号范围 -128~127,bl为 -31(-31的补码为 ‭1110 0001,补码转十六进制为 0XE1‬)
由于bl是有符号数,所以执行movsx指令后,未被赋值的二进制位设置为 1
ax的数据的二进制形式为 1111 1111 ‭1110 0001
                                            F     F       E     1
movsx ax, bl//执行完这条指令后 ax的值为 0XFFE1

MOVZX

格式如下:
MOVZX reg32, reg/mem8
MOVZX reg32, reg/mem16
MOVZX reg16, reg/mem8

movzx 为无符号传送

mov ax, 0
mov bx, 0x43e1
movzx ax, bl

进行反汇编后代码如下



上图 bl为 0xE1 为 -31, 但是传送后ax中的高4位仍然都为 0,并没有把符号传递

LEA

LEA是微机8086/8088系列的一条指令,取自英语Load effect address——取有效地址,也就是取偏移地址。

int main()
{
	int i = 0x1234;
	int* p = &i;
	//
	return 0;
}

 以上源代码对应下面的反汇编代码

在一个函数中所有新定义的变量,都在栈中。
1 第一步是先取栈地址(i 的地址),把变量 i 的地址赋值给 eax(操作数A必须是寄存器)。
2 第二步把寄存器eax中地址,传送给栈中的变量 p。

以下用C++嵌入汇编,用汇编通过 i 的地址,给 i 赋值。

int main()
{
	int i = 0;
	// 以下是用汇编的形式给 i 的内存地址赋值
	__asm
	{
		// 先取得 i 的地址
		lea eax, i
	    // 通过 i 的地址 给i赋值
		mov dword ptr [eax], 0x12345678
		// 执行完后 i 的值为 0x12345678
	}
	//
	return 0;
}

ollydbg 使用小节

断点:双击反汇编代码创口第二列(Hex dump)所在行,可以快速的下断点。

Enter键

①、如是在此行敲enter(此行是push一个地址),那么就会在②的数据窗口显示处此地址下内存中存储的数据。

如果在一个call行enter,那么进入的是一个函数,数据窗口不再变化。

+、-

+、-:在单步(F7、F8)执行过,才能用-、+操作,查看执行过的反汇编位置

+、-:在某一个敲enter键后也可以用-、+操作,但是此时只可以用于查看执行的位置,并不能看到相关值的变化,原因是相当于预先让你看下执行流程

*:作用在反汇编代码窗口栈窗口

 函数内声明变量、定义变量,对应反汇编

int main()
{
	int a;// 声明一个变量
	int b = 0;// 定义一个变量
	//
	return 0;
}

1 在下面的反汇编中看出,声明的变量a消失了。
2 声明一个变量若是在函数的开始到结束没有被赋值,那么也就不会在栈中开辟空间,那么这个变量就会在编译器编译阶段被删除掉。
3 在函数内所有定义的变量,都放到了栈里面。

 减法对应反汇编sub指令

int main()
{
	int a = 2;
	int b = 1;
	a = a - b;
}

第一步:首先把栈中a的值,传送到eax(寄存器中)
第二步:其次执行sub eax,b
第三步:再次把eax的值传送到a的栈中

寄存器是CPU的手,内存中任何要通过CPU进行计算的数据,都要通过寄存器进行中转。
以下用汇编的形式实现 a - b

 下面是 a - b 的汇编优化写法

 下面实现 a - b 汇编的优化写法原码及反汇编

int main()
{
	int a = 2;
	int b = 3;
	//
	__asm
	{
		mov eax, b
		sub a, eax
	}
}

CMP 指令

cmp是比较指令,cmp的功能相当于减法指令,只是不保存结果。cmp指令执行后,将对标志寄存器产生影响。其它相关指令,通过识别被影响的标志寄存器,来得知比较结果。

cmp 指令格式: cmp 操作对象 1,操作对象 2
功能:计算操作对象1 -(减)操作对象 2,但是并不保存结果,仅仅根据计算结果对寄存器进行设置。

例如:cmp ax,ax 做ax - ax的运算,结果为0,但是结果并不保存在ax中,仅仅影响flag相关标志位,zf = 1、pf = 1、sf = 0、of = 0.标志寄存器详解

SF(signed flag)

在看完cmp指令后,很有必要先介绍flag寄存器中各个位的说明。

sf位作用:上一条计算指令对两个操作数计算结有影响,结果为正,sf = 0,结果为负,sf = 1。

小二,上菜。

int a = 1;
int b = 127;
if (a > b)
{
	printf("a > b");
}
printf("a < b");

源码与上图片对比, 执行步骤看标号,上图片中flag寄存器红色部分都是被影响的值。

OF(OverFlow Flag) 

进行两个操作数有符号运算的时候,结果超出了范围(存储操作数的寄存器,所表示的有符号范围),称为溢出。

int main()
{
	__asm
	{
		mov al, 1
		mov bl, -127
		sub al, bl
	}
	return 0;
}

 al,bl都是8为的,8位寄存器表示有符号范围 -128~127
1、 1 - (-127)= 128,显然这个结果是错误的,已经超出8位寄存器有符号范围,而128在8位有符号中的值应为 -128。

2、所以标号2的OF值为1.

3、因为计算的结果为 -128,所以标号3 SF位的值为 1.

DF(direction Flags)

JE/JZ 指令(条件转移指令)

je(jump equal)zf=1(说明上一条参与运算的指令如:cmp、sub等,两个操作数相等) 相等则跳转。
jz(当两个操作数相减的结果等于0时跳转),用法同je一样。
je指令与cmp指令是配合使用,flag寄存器中zf位为 1,则跳转。

原码

int main()
{
	int a = 3;
	if (3 != a)
	{
		printf("a != 3");
	}
	else
	{
		printf("a == 3");
	}
	return 0;
}

反汇编 

1、执行cmp指令 栈中值 [local.1] - 3

2、通过cmp指令执行后,值为 0,影响flag寄存器,修改flag寄存器ZF位的值为 1

3、执行je指令,je指令检查ZF位的值(1 跳转,0不跳转继续向下执行)。

4、ZF的值为 1,执行跳转。

5、通过4的内存地址跳转到指定的内存地址。

6、蓝色的指令是 push(看不太清楚),这行入栈,入栈的值为一个字符串 “a == 3”,因为调用函数printf需要参数,所以需要push入栈参数,然后执行下一行的call指令进入printf函数。

需要注意的是,源码中的if语句内判断不相等,而反汇编后是用的je指令,为什么会这样呢?

原因是:如果if语句内的条件结果为不相等,那么也就不需要跳转,继续执行下一条指令,只有if语句内的条件结果相等才跳转。

JNE、JNZ 指令(条件转移指令)

jne(jump when not equal)检查zf=0则跳转,既然zf=0,说明上一条执行运算指令的两个操作数不相等,所以该指令的作用是:判断两个操作数不相等,进行跳转。

jnz(jump when not equal )指令用法同jne相同

JMP(无条件跳转指令)

mov 指令不能用于设置CS、IP的值,原因很简单,因为8086CPU没有提供这样的功能。

若想改变它们的值,就要用到JMP指令,格式为JMP 段地址:偏移地址,例如 jmp 2AE3:3,此时,CS = 2AE3、IP = 3。

若是只想修改IP的值,格式为”JMP 某一个合法寄存器“,例如,ax = 1000H,执行jmp ax后,IP的值为1000H。

 以下源码对应反汇编

int main()
{
	goto end;
	printf("no execute");
end:
	printf("program end");
	return 0;
}

 JL(有符号小于跳转指令)

格式:jl(jump if less) 标号地址
功能:小于时跳转

通过上一条计算指令执行后,根据计算结果正、负,修改SF。
注意,SF = 1 且 OF = 0,此时计算结果没有溢出,是准确的
如果,SF = 1 且 OF = 1,此时计算结果有溢出,其结果是不准确
JL指令检查SF符号位的值为,1且OF = 0,(负)则跳转,0(非负)则不跳转

int main()
{
	int a = 2;
	int b = 3;
	if (a >= b)
	{
		printf("a > b");
	}
	printf("a < b");
	return 0;
}

 源码反汇编比较,if (a >=b) 反汇编对应的时,用的是jl指令(小于则跳转,和源码判断方向正好想相反),在条件判断时,反汇编看到的汇编代码是与源码判断相反。

再看下面的示例,
思考
为什么SF 为1?既然SF为1,那么为什么却不跳转呢?

int main()
{
	__asm
	{
		mov al, 1
		mov bl, -127
		// 判断 al > bl 那么在反汇编中,判断小于等
		cmp al, bl
		jl end
	}
	printf("a > b");
	getchar();
	return 0;
end:
	printf("a <= b");
	getchar();
	return 0;
}

 JLE(有符号指令)

 JLE(jump if less or equal)有符号指令 <=。

int main()
{
	int a = 2;
	int b = 3;
	if (a > b)
	{
		printf("a > b");
	}
	printf("a < b");
	return 0;
}

JLE指令只是比JL多了一个E,而这个E代表的就是equal(相等),通过JL源码与JLE源码对比会发现,不大于一个数,那么也就是小于等于,反汇编很直观看出区别。

下面用源代码嵌入汇编,再次解释:逻辑思想,想判断 a > b,那么在汇编中要判断 a <= b,因为判断跳转条件不成立,自然执行下一行汇编代码。

int main()
{
	int a = 2;
	int b = 3;
	/*if (a > b)
	{
		printf("a > b");
	}
	printf("a < b");*/
	//以上逻辑用汇编代码写
	__asm
	{
		// 判断 a > b 那么在反汇编中,判断小于等
		mov eax, b
		cmp a, eax
		jle end
	}
	printf("a > b");
end:
	printf("a <= b");
	return 0;
}

JNG(jump when not greater)有符号指令 

jng指令同jle用法一样。

有符号小于等于总结

 JG(jump when greater)有符号指令

jg:有符号大于跳转

int main()
{
	int a = 3;
	int b = 1;
	if (a <= b)
	{
		printf("a <= b");
	}
	else
	{
		printf("a > b");
	}
}

 JNLE(jump when not less or equal)有符号指令

jnle 指令用法同 jg一样

有符号大于小结

 JA(jump when above)无符号指令

ja:高于,则跳转。

int main()
{
	unsigned int a = 4;
	unsigned int b = 3;
	if (a <= b)
	{
		printf("a <= b");
	}
	else
	{
		printf("a > b");
	}
	return 0;
}


 CF = 0 && ZF = 0

JNBE(jump when not below or equal)无符号指令

jnbe:不低于等于(那么也就是大于),则跳转。 

jnbe 用法同 ja相同。

JNB(jump when not below)无符号指令

jnb:不低于,则跳转。

EBP、ESP

ebp(Base Pointer)        寄存器        储存栈底指针
esp                               寄存器        储存栈顶指针

void fun()
{
	int a = 3;
	int b = 4;
}

int main()
{
	int a = 0x12345678;
	fun();	
}

①、执行call指令时,会隐含执行两条汇编指令(push IP,jump IP)。
②、把call指令的下一条指令入栈(push IP)。
③、IP 002F1030已经入栈。
④、jump IP。
注意,ollydgb光标处待执行命令,此时已经执行完call指令了,但是,并没有执行被调用函数内部的汇编指令。

①、此时已经进入函数内部,并且执行完第一条指令,执行push ebp(此时的ebp:main函数的栈底指针),目的是要保存main函数的栈底指针,保存到栈中。
②、main函数的栈底指针已经保存到栈中。

①、此时把栈顶esp当作被调用函数的栈底。
②、新的栈底产生(被调用的函数的栈底)。

①、sub esp,0x8已经执行完了,目的是栈顶栈顶指针上移(fun函数的栈顶指针,想想内存画面,只有栈顶指针上移,才能向栈中放数据嘛!)。
②、为什么esp要上移8个字节呢?
③、因为我们在函数的内部就定义两个int的变量,所以②中的栈顶上移了8个字节,那么这个数字8是谁给发布的exe程序添加的?答案是:编译器。

编译器:编译器在编译阶段,计算出了函数内需要开辟多少栈空间,需要注意的是,编译器开辟空间都是以4个字节的倍数,原因是会提升CPU的速度。

④、⑤、esp上移后,开辟的两个4个字节的栈空间,观察发现④有值,而⑤呢却全是0,为什么会是这样呢?新开辟的空间没有被使用,都是一些随机值嘛😄。
⑥、没什么好说的,esp = esp - 8 后的值,也就是这个fun函数的栈顶。

自此,fun函数的栈底(EBP)、fun函数的栈顶(ESP)都已经产生,编译器在执行函数内部数据计算时,不会再出现 EBP = EBP +- 数值,但是ESP = ESP +-的指令可能还会产生

①、给栈中第一int 赋值,实质是:mov dword ptr ss:[ebp-0x4],0x3,ollydbg智能的把ebp-0x4翻译成local.1。
②、栈中的值为 0x0000003(十六进制表示)。

①、给栈中第二个int 赋值,实质是:mov dword ptr ss:[ebp-0x8],0x4
②、栈中的值为 0x00000004.

注意:上面两幅图片中的ESP,发现是不变的。
思考:1、为什么上面的偏移地址,一个是epb-0x4,一个是ebp-0x8 ?
是编译器在编译时,计算添加的。
           2、为什么对栈中的数据操作使用ebp,而不是用esp呢?
编译器在执行函数内部数据计算时,不会再出现 EBP = EBP +- 数值,但是ESP = ESP +-的指令可能还会产生,例如:

void fun()
{
	int a = 3;
	int b = 4;
	__asm
	{
		//与上面的源代码相比,只是多写了下面这句
		push 5
	}
}

int main()
{
	int a = 0x12345678;
	fun();	
}

以上源代码及动态图片解释,为什么编译器在编译时,对栈中的数据操作用的是EBP,而不是ESP。

 ①、此时函数内部功能已经执行完成,开始为还原执行调用函数(也就是main函数)栈做准备,先是把被调用函数栈底还原给调用函数的栈顶。
②、此时fun函数栈顶变成栈底,想想内存画面,被调用函数的栈底,不就是调用函数的栈顶吗。

①、还原调用函数的栈底。
②、栈中调用函数的栈底值00D3FE9C已经弹出。
③、从栈中弹出的值00D3FE9C赋给EBP。
④、准备执行ret指令。

①、执行ret指令,相当于执行了 pop IP。
②、对比上一个图发现栈中少了值002F1030,这个值被ret弹出了(pop IP弹出了)。
③、EIP现在的值为002F1030,这也就是在调用call时,向栈中push IP(call下一条指令IP)的值,这么做的目的也是为了还原main函数的执行位置。

ollydbg通过栈窗口查看函数栈的范围

void fun_1()
{
	printf("fun_1");
}

int main()
{
	int a = 0x12345678;
	fun_1();
}

 从上面的源代码可知,是一个很简单的函数调用关系,看了源码代码方便理解反汇编栈窗口。 

每一个中括号,代表一个函数的栈。

有中括号且闭合,代表正在从该函数调用另外一个函数。
没有中括号且有点,代表正在执行本函数。 

①、调用main函数后,首先是把调用main函数的函数栈底保存。
②、此处栈中存放的,是调用main函数的函数栈底值,同时这个值所在的地址⑦位置即是调用main函数的函数栈顶指针(ESP),也是main函数的栈底指针(ESP),从栈窗口⑦位置是两个中括号共用点特点。
④、中括号第二个(中括号从上至下),为EIP的值(ret、retn调用该值,返回该main函数)。
未说明的圈标号是表示执行的顺序流程。 

ollydgb设置local./arg.显示地址

在接下来的内容开始之前,先设置ollydgb关于local./arg.,这样做的目的是有利于学习及观察调用函数、被调用函数,栈低、栈顶变化。

带参数函数调用反汇编

int fun(int a, int b)
{
	return a + b;
}

int main()
{
	int a = 1;
	int b = 2;
	int c = 0;
	c = fun(a, b);
	return 0;
}

 ①、②、现在的EBP是当前函数的栈底指针,然而①、②从栈中取值尽然用了EBP +,说明正在执行的函数使用的值是调用函数栈中的值,由此判定:①、②是调用函数传入的参数。
③、执行①和②值相加,结果给了EAX(返回值,visual studio 通常用EAX作为函数的返回值)。
④、调用函数(当前是main函数)栈顶下移8个字节释放掉传入被调用函数参数所在栈空间)。
⑤、EAX当作返回值的结果,进行栈ss:[ebp-0xC]处赋值。
小结:
EBP +:表示函数的参数。
EBP -: 表示函数的局部变量。
函数的参数入栈的顺序是从右向左。

 

if else

int main()
{
	int a = 3, b = 2;
	if (a > b)
	{
		printf("a > b");
	} 
	else
	{
		printf("a <= b");
	}
	return 0;
}

①、执行比较a与b的大小。
②、如果a<=b跳转,实质a>b,所以程序继续向下执行。
③、④、压栈(字符串 a > b)的地址传参数,调用函数prinf。
⑤、释放掉本函数栈中输入printf的参数("a > b")所存的地址(地址占4个字节)。
⑥、程序被编译后是一行一行连续的,既然执行完了if语句就不能向下执行else语句了,所以⑥的jmp指令进行了跳转。
jle开始到jmp前是一个if语句块,jmp后开始

if else 嵌套

尽管把源码先展示出来了,但是在接下来的分析中,就当作没有源码,便于日后跟好的学习反汇编。

int main()
{
	int a = 1, b = 2, c = 3, d = 4;
	if (b > a)
	{
		if (c > b)
		{
			if (d > c)
			{
				printf("d > c > b > a,%d,%d,%d,%d", d, c, b, a);
			}
		}
		else
		{
			printf("c <= b");
		}
	}
}

上面的if else 嵌套反汇编及分析日后整理

switch case

switch case 反汇编分两种情况。

第一中情况是当case的数量<= 3个的时候

int main()
{
	int i = 4;
	switch (i)
	{
	case 1:
		printf("1");
		break;
	case 4:
		printf("4");
		break;
	case 2:
		printf("2");
		break;
	default:
		printf("default");
		break;
	}
	printf("end");
	return 0;
}

①从上面的源代码得知,只是在栈中开了一个变量,但是看到 sub esp 8,另一个可以看作是一个临时变量。
②、③、④case作比较满足相等则跳转。
⑤case都不满足时,执行默认default。

第二种情况,当case>4个时候,编译器生成跳转表。

int main()
{
	int i = 0x4;
	switch (i)
	{
	case 0x1:
		printf("1");
		break;
	case 0x4:
		printf("4");
		break;
	case 0x2:
		printf("2");
		break;
	case 0x3:
		printf("3");
		break;
	default:
		printf("default");
		break;
	}
	printf("end");
	return 0;
}

①得知传入case的值位4
②编译器生成了数值1,得知case的最小值为1
③编译器会把case中最大值减去最小值的结果放在③处,那么也就是max - 1 = 3,即max = 4,也就是case的最大值为4。此时我们知道了case的最小值是1,最大值为4.
④中的edx要当作以0开始的索引,因为编译器为switch case生成了一个表,而这个表看成是一个数组,数组中的每个元素储存着每个case的执行跳转地址。
举例:当edx = 0时,那么就相当于case 1
⑤跳转表的起始地址。

由上,可以依次推断,当edx = 0时、edx = 1时,对应的case = 1时、case = 2时
所以switch case的判断都是以常数时间完成的,效率非常快。

编译器release优化

① 大小最大化优化,指的是尽可能的减小发布的release程序占用的大小。

②速度最大化优化,也是visual studio默认的。

③综合上诉①、②进行全部优化。

FOR循环

通过上面编译器优化release程序,下面分三种情况看反汇编for循环

第一种:不做任何优化。

int main()
{
	for (int i = 0; i <= 10; ++i)
	{
		printf("abc");
	}
	return 0;
}

①为for循环在栈种开辟空间且赋值为0

②执行jmp跳转到③处,进行比较。

④判断是否大于0xA(10),若是大于则跳转到006310AB,否则向下依次执行。

⑤跳转到for循环。

⑥把栈中的值放到eax寄存器。

⑦对寄存器的值进行 +1.

⑧进行比较。

第二种大小优化

 代码量明显减少。

①编译器智能的计算处我们的循环<=10,而它把0xb(11)放到栈中,目的是为了后续的③做减法用。

②打印、③做减法。

④只要不等于继续跳转。

通过上诉发现,for循环第一次根本不做判断,直接打印,编译器智能的优化了。

第三种速度最优化

计算机速度:寄存器 > 内存 > 硬盘,那么速度越是快,就越是在寄存器中对数据进行操作。

上诉三种情况仔细观察push 寄存器、pop等。

INC、DEC

 do while

int main()
{
	printf("do while");
	int i = 0;
	do
	{
		printf("%d", i);
		++i;
	} while (i <= 10);
	return 0;
}

 release速度最优后反汇编。

源码中 int i = 0 这行被编译器智能的优化后,变成 xor esi,esi,用esi作为增量,毕竟值在寄存器中做运算是最快的,并且优化的汇编代码都看不到在栈中开辟空间,这就是编译器智能优化。

while

int main()
{
	printf("while");
	int i = 0;
	while (i <= 10)
	{
		printf("%d", i);
		++i;
	}
	return 0;
}

SETE(SETZ)

取ZF标识位的值保存。

SETNE(SETNZ)

取ZF标识位的值,取反保存。

NOT

按位取反。

XOR

异或指令,按位运算,相同为 0,不同为 1

AND

按位运算

REPNZ/REPNE

REPNE---->repeat when not equal           当比较结果不相等,且CX/ECX<>0时重复
REPNZ---->repeat when zero flag           当ZF=0,且CX/ECX<>0时重复

SCASB

自定义汇编,禁止编译器添加寄存器保护及堆栈平衡

STD/CLD(set DF/ clear DF)

加载/存储

加载: 硬盘-》内存、内存-》寄存器

存储:寄存器-》内存、内存-》硬盘

 

 定位main

SETZ/SETNZ/SETG/SETL/SETGE/SETLE

 

定位WindowProc

ollydbg-》view-》list of window

Topmost表示主窗口

添加条件断点

上面定位了WindowProc函数

WindowProc函数的原型是

LRESULT CALLBACK WindowProc(
  _In_ HWND   hwnd,
  _In_ UINT   uMsg,
  _In_ WPARAM wParam,
  _In_ LPARAM lParam
);

CALLBACK 回调函数,操作系统调用,消息处理函数。

[arg.2]表示参数2,那么参数2也就是uMsg,此时在[arg.2]下面下了一个条件断点,这个条件是判断消息类型

Ollydbg查看扫雷WindowProc参数

查看弹出菜单的各个功能ID值

 

在代码注入器中 输入

①push 0                        
②push 0x00000209
③push 0x00000111
④push 0x00060930
⑤call    0x01001BC9

①最右边的lParam、②wParam、③uMsg、④hwnd、⑤WindowProc地址

需要注意句柄(④)是个变化的值,每次打开游戏都不一样

Cheat Engine

下载Cheat Engine,及中文包

找基地址

ollydbg内存下断点

验证上面找的基地址

读取进程数据(通过基地址)

HWND hWnd = ::FindWindowW(NULL, L"扫雷");
if (NULL == hWnd)
{
	MessageBoxW(L"未找到扫雷");
	return;
}
//
DWORD dwProcessId = 0;
// 通过窗口句柄,获取进程id
GetWindowThreadProcessId(hWnd, &dwProcessId);
//参数1所有权限,通过进程id获取进程句柄handle
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, dwProcessId);
	
int val = 0;
//通过进程句柄,读取基地址数据,由于预先知道基地址的值为整型,且占用空间是4个字节
ReadProcessMemory(hProcess, (LPCVOID)0x1005194, (LPVOID)&val, 4, NULL);

发布者:admin,转转请注明出处:http://www.yc00.com/news/1700379480a1004018.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信