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条)