西安推荐企业网站制作平台,网站建设培训哪家好,中国建设网官方网站企业登录,wordpress教育模版ollydbg
原版下载#xff0c;网上有很多的版本#xff0c;下载及测试后发现还是原版的比较好#xff0c;但是原版没有各种各样的插件#xff0c;这就需要自己把插件导入进去了#xff0c;插件及配色界面下载 全局变量、局部变量对应反汇编 ollydgb 函数连接线消失 解决办…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~127bl为 -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 eaxb 第三步再次把eax的值传送到a的栈中
寄存器是CPU的手内存中任何要通过CPU进行计算的数据都要通过寄存器进行中转。 以下用汇编的形式实现 a - b 下面是 a - b 的汇编优化写法 下面实现 a - b 汇编的优化写法原码及反汇编
int main()
{int a 2;int b 3;//__asm{mov eax, bsub a, eax}
} CMP 指令
cmp是比较指令cmp的功能相当于减法指令只是不保存结果。cmp指令执行后将对标志寄存器产生影响。其它相关指令通过识别被影响的标志寄存器位来得知比较结果。
cmp 指令格式 cmp 操作对象 1操作对象 2 功能计算操作对象1 -减操作对象 2但是并不保存结果仅仅根据计算结果对寄存器进行设置。
例如cmp axax 做ax - ax的运算结果为0但是结果并不保存在ax中仅仅影响flag相关标志位zf 1、pf 1、sf 0、of 0.标志寄存器详解
SFsigned flag
在看完cmp指令后很有必要先介绍flag寄存器中各个位的说明。
sf位作用上一条计算指令对两个操作数计算结有影响结果为正sf 0结果为负sf 1。
小二上菜。
int a 1;
int b 127;
if (a b)
{printf(a b);
}
printf(a b); 源码与上图片对比 执行步骤看标号上图片中flag寄存器红色部分都是被影响的值。
OFOverFlow Flag
进行两个操作数有符号运算的时候结果超出了范围存储操作数的寄存器所表示的有符号范围称为溢出。
int main()
{__asm{mov al, 1mov bl, -127sub al, bl}return 0;
} albl都是8为的8位寄存器表示有符号范围 -128~1271、 1 - -127 128显然这个结果是错误的已经超出8位寄存器有符号范围而128在8位有符号中的值应为 -128。
2、所以标号2的OF值为1.
3、因为计算的结果为 -128所以标号3 SF位的值为 1.
DFdirection Flags
JE/JZ 指令条件转移指令
jejump equalzf1说明上一条参与运算的指令如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 指令条件转移指令
jnejump when not equal检查zf0则跳转既然zf0说明上一条执行运算指令的两个操作数不相等所以该指令的作用是判断两个操作数不相等进行跳转。
jnzjump when not equal 指令用法同jne相同
JMP无条件跳转指令
mov 指令不能用于设置CS、IP的值原因很简单因为8086CPU没有提供这样的功能。
若想改变它们的值就要用到JMP指令格式为JMP 段地址偏移地址例如 jmp 2AE33此时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有符号小于跳转指令
格式jljump 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, 1mov bl, -127// 判断 al bl 那么在反汇编中判断小于等cmp al, bljl end}printf(a b);getchar();return 0;
end:printf(a b);getchar();return 0;
} JLE有符号指令 JLEjump 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, bcmp a, eaxjle end}printf(a b);
end:printf(a b);return 0;
}
JNGjump when not greater有符号指令
jng指令同jle用法一样。
有符号小于等于总结 JGjump when greater有符号指令
jg有符号大于跳转
int main()
{int a 3;int b 1;if (a b){printf(a b);}else{printf(a b);}
} JNLEjump when not less or equal有符号指令
jnle 指令用法同 jg一样
有符号大于小结 JAjump 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
JNBEjump when not below or equal无符号指令
jnbe不低于等于那么也就是大于则跳转。
jnbe 用法同 ja相同。
JNBjump 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 IPjump IP。 ②、把call指令的下一条指令入栈push IP。 ③、IP 002F1030已经入栈。 ④、jump IP。注意ollydgb光标处是待执行命令此时已经执行完call指令了但是并没有执行被调用函数内部的汇编指令。 ①、此时已经进入函数内部并且执行完第一条指令执行push ebp此时的ebpmain函数的栈底指针目的是要保存main函数的栈底指针保存到栈中。 ②、main函数的栈底指针已经保存到栈中。 ①、此时把栈顶esp当作被调用函数的栈底。 ②、新的栈底产生被调用的函数的栈底。 ①、sub esp0x8已经执行完了目的是栈顶栈顶指针上移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],0x3ollydbg智能的把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 IPcall下一条指令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的大小。 ②、如果ab跳转实质ab所以程序继续向下执行。 ③、④、压栈字符串 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。
第二种情况当case4个时候编译器生成跳转表。
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而它把0xb11放到栈中目的是为了后续的③做减法用。
②打印、③做减法。
④只要不等于继续跳转。
通过上诉发现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 esiesi用esi作为增量毕竟值在寄存器中做运算是最快的并且优化的汇编代码都看不到在栈中开辟空间这就是编译器智能优化。
while
int main()
{printf(while);int i 0;while (i 10){printf(%d, i);i;}return 0;
} SETESETZ
取ZF标识位的值保存。
SETNESETNZ
取ZF标识位的值取反保存。
NOT
按位取反。
XOR
异或指令按位运算相同为 0不同为 1
AND
按位运算
REPNZ/REPNE
REPNE----repeat when not equal 当比较结果不相等,且CX/ECX0时重复
REPNZ----repeat when zero flag 当ZF0,且CX/ECX0时重复
SCASB 自定义汇编禁止编译器添加寄存器保护及堆栈平衡
STD/CLDset 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);