学习笔记(逆向汇编)Day11-Day15
2017-08-28 22:19
267 查看
Day 11 《存储与判断语句》
(一)字符存储
1.字符存储:2.ASCII码:
ASCII码使用指定的7位或8位二进制数组合来表示128或256种可能;标准的只有7位;
扩展ASCII码:
由于标准ASCII码字符集字符数目有限,在实际应用中往往无法满足要求。为此,国际标准化组织又将ASCII码字符集扩充为8位扩展ASCII码的扩充。这样,ASCII码的字符集可以扩充128个字符,也就是实用8位扩展ASCII码能为256个字符提供编码,这些扩充字符的编码均为高位1的8位代码(即十进制数128~255),称为扩展ASCII码。扩展ASCII码所增加的字符包括加框文字、圆圈和其他图形符号;
3.GB2312:
GB2312是用两个ASCII码表示,即16位;
(二)内存
1.内存图:所谓的可读可写限制是对正向开发人员而言的。我们的源代码都在代码区中,数值保存在对应的区,比如int x = 2;这个代码放在代码区中,但是x对应的数值存储在全局变量区;
2.全局变量的特点:
1. 全局变量在程序编译完成后地址就已经确定下来了,只要程序启动,全局变量就已经存在;是否有值取决于声明时是否给定了初始值,如果没有,默认为0;
2. 全局变量的值可以被所有函数所修改,里面存储的是最后一次修改的值;
3. 全局变量所占内存会一直存在,直到整个进程结束;
4. 全局变量的反汇编识别:
mov 寄存器,byte/word/dword ptr ds:[0x12345678]
通过寄存器的宽度,或者byte/word/dword来判断全局变量的宽度
全局变量就是所谓的基址;
注意:函数名也是全局变量;
3.局部变量的特点:
1. 局部变量在程序编译完成后并没有分配固定的地址;
2. 在所属的方法没有被调用时,局部变量并不会分配内存地址,只有当所属的程序被调用时,才会在堆栈中分配内存;
3. 当局部变量所属的方法执行完毕后,局部变量所占用的内存将变成垃圾数据,局部变量消除;
4. 局部变量只能再方法内部使用,函数A无法使用函数B的局部变量;
5. 局部变量的反汇编识别:
[ebp-4]这个是局部变量,即ebp-多少,
但是这个有时候也不是绝对的,有的编译器使用ESP+多少之类的;
4.判断函数的参数个数:
步骤一:
步骤二:参数的传递未必都是通过堆栈,还能使用寄存器
当函数调用处的代码无法查看:
步骤一:查看函数内用于赋值的寄存器:eax、ecx、edx、ebx、esi、edi
不考虑ebp、esp,找到用于赋值的寄存器后,查看其来源
如果该寄存器的值不是再函数内存赋值的,那一定是传进来的参数;
步骤二:
观察ebx-x之类的代码
查看ret返回时的操作数
寄存器 + ret x = 参数个数
寄存器 + [ebp + x] + … = 参数个数;
5.函数内部功能分析:
1. 分析参数:
2. 分析局部变量
3. 分析全局变量
4. 功能分析
练习:
(三)if语句的反汇编判断
1.执行各类影响标志位的指令:jxx xxxxmov eax,dword ptr [ebp+8] cmp eax,dword ptr [ebp+0Ch] jle 00401059 分析:cmp指令 影响标志位 jle: 小于或等于就跳转到00401059 mov eax,dword ptr [ebp+8] cmp eax,dword ptr [ebp+0Ch] jl 00401059 分析:cmp指令 影响标志位 jl: 小于则跳转
2017/5/20 23:39:08
Day 12 《反汇编C语法结构》
(一)练习
1.从堆栈的角度理解这个程序为什么可以执行:答案:
2.分析此循环为什么永远无法停止:
答案:
(二)MOVSX 和 MOVZX
MOVSX (move with sign-extend) 符号位扩展适用于有符号整数,将源操作数内容复到目标操作数,用较小操作数的最高位填充所有扩展位(根据符号位使用0或1扩展)
MOVZX (move with zero-extend) 零扩展传送
适用于无符号整数,将源操作数内容复制到目标操作数,用0扩展到16位/32 位,适用于无符号整数
(三)反汇编C基本语法
1.++/–2.数组
数组是按顺序存于栈中的;
3.循环语句
执行效率如图,由上至下递减;
(四)练习
#include<stdio.h> int Found(int arr[], int count) { int i = 0; int _max = arr[0]; do { if (arr[i] < arr[i + 1]) { _max = arr[i + 1]; } ++i; } while (i<count-1); return _max; } int Sum(int arr[], int count) { int i = 0; int _sum=0; do { _sum += arr[i]; ++i; } while (i<count); return _sum; } int Prime(int x) { int i, k; k = (int)sqrt(x); for (i = 2; i <= k; ++i) { if(x%i==0) break; } return i == k; } int main() { //1.将两个变量的值交换 int i = 5; int j = 4; int temp = i; i = j; j = temp; //2.将一个数组中的数倒序输出 i = 4; int arr[5] = { 1,2,3,4,5 }; do { printf("%d ", arr[i]); --i; } while (i>=0); //3.找出数组里面最大的值,并返回 int _max = Found(arr, 5); //4.将数组所有的元素相加,将结果返回 int _sum = Sum(arr, 5); //5.将两个等长数组相同位置的值相加,存储到另外一个等长的数组中 i = 4; int arr2[5] = { 9,8,7,6,5 }; int arr3[5]; do { arr3[i] = arr[i] + arr2[i]; ++i; } while (i<=4); //6.写一个函数int prime(int x),如果x是素数返回值为1,否则为0. int j = Prime(9); //7.俩俩比较数组的值,获取最大的一个值。 int _max = 0; for (i = 0; i < 5; i++) { if (arr[i] > arr2[i]) { j = arr[i]; } else { j = arr2[i]; } if (_max < j) _max = j; } }
2017/5/23 17:50:04
Day 13 《函数缓冲与变量内存》
(一)练习
1.long long类型在C中如何存储2.char arr[3]={1,2,3} 与 char arr[4]={1,2,3,4}哪个更省空间?
3.找出下面赋值过程的反汇编代码
void Funcition() { int x = 1; int y = 2; int r; int arr[10] = {1,2,3,4,5,6,7,8,9,10}; r = arr[1]; r = arr[x]; r = arr[x+y]; r = arr[x*2+y]; }
(二)函数内缓冲
1.函数调用传入实参换成char或short,会节省空间吗?2.参数和局部变量的区别
参数在函数调用的时候分配空间,位于函数栈的ebp下;局部变量是在函数执行的时候分配空间,位于函数栈的ebp上面(即函数缓冲区内);
小于32位的局部变量,在空间分配时,按照32位的数据规格来分配,根据编译器的不同,可能每增加一个4字节及以下的变量会增加8个字节,有的会增加12(Ch)个字节;使用时按实际的宽度使用。
3.数组
4.数组的赋值
#include<stdio.h> int Add(int x, int y) { return x + y; } int main() { int x = 1; int y = 2; int r; int arr[5] = { 1,2,3,4,5 }; r = arr[1]; r = arr[x]; r = arr[x + y]; r = arr[x * 2 + y]; r = arr[arr[1] + arr[2]]; r = arr[Add(1, 2)]; r = arr[100]; return 0; }
反汇编结果:
每条赋值语句都能够执行;
数组下标不但可以越界,而且可以做到一些不可思议的事情。堆栈图必须过硬。
5.二维数组
其内存的存储上,完全一样,并无区别。
但其取值上有区别:
6.缺省一维数组个数的二维数组定义
2017/5/24 17:49:57
Day 14 《结构体》
(一)结构体的存储与传递
1.反汇编结构体查看反汇编,发现结构体和数组的存储几乎一样,难以区分;
数组和游戏中的地图有关,结构体和游戏中的角色有关;
所以我们逆向的时候,只需要实现相同的功能就行;
2.结构体作为参数时的传递方式
这里没有使用push的堆栈方式,而是直接提升堆栈,在堆栈中使用变址寄存器完成结构体数据的复制,或者直接movs数据以传递参数;
3.结构体作为返回值
注意:
开发中,最好不要使用结构体当参数,或结构体当返回值,这样将大量的浪费空间;
(二)结构体的内存对齐
1.#pragma pack的基本用法#pragma pack(n) struct MyStruct { }; #pragma pack()
n为字节对齐数,其取值为1,2,4,默认是8;
如果这个n值比结构体成员的sizeof值小,那么该成员的偏移量应该以此n值为准,即是说,结构体成员的偏移量应该取二者的最小值;
2.深入结构体内存对齐
对齐原则:
1. 结构的数据成员,第一个成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小的证书倍开始(比如int再32位机为4字节,则要从4的整数倍地址开始存储);
2. 结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐;
3. 如果一个结构里有某些结构体成员,则结构成员要从其内部最大元素大小的整数倍地址开始存储(比如struct a里有struct b,b里有char int double等元素,那b应该从8的整数倍开始存储);
4. 对齐参数如果比结构体成员的sizeof值小,该成员的偏移量应该以此值为准,也就是说,结构体成员的偏移量应该取二者的最小值;
建议:按照数据类型由小到大的顺序进行书写;
3.练习
1.分析结构体大小
struct S1 { char c; double i; }; struct S2 { char c1; struct S1 s; char c2; char c3; }; struct S3 { char c1; struct S1 s; char c2; double i; }; struct S4 { int c1; char c2[10]; };
答案:
int main() { //8+8=16字节 int i = sizeof(struct S1); struct S1 s = { 1,1 }; //8+16+8=32字节(根据S1里成员最大的数据类型为double,倍数为8) int j = sizeof(struct S2); //8+16+8+8=40字节 int k = sizeof(struct S3); //8+8=16字节(此处是先填满前8个字节再填后面的) int l = sizeof(struct S4); return 0; }
2.递归函数反汇编分析
#include<stdio.h> int test(int i) { if (i < 1) return 1; int k = test(--i); return k; } int main() { int i = test(3); return 0; }
main中调用test(3)后,传入3,在第一次test中,i 不小于 1,则调用函数test(2),同样,再调用test(1),第3次test中,i=1,i不小于1,同样,调用test(0),则此第4次,0<1,则返回1;第3次中,k=1,返回k;在第2次中,k=1,返回k;在第1次中,k=1,返回k;在main中,i = 1;
2017/5/25 15:33:21
Day 15 《逆向switch》
(一)switch
1.switch的反汇编switch 在游戏中和技能配合使用最多, 比如 F 加血等等
上方为什么要减去1再比较? (图中已说明)
Switch 会建立一个大的表,把我们要执行的语句对应的地址都保存起来。
2.switch的地址表
情况一:3个分支
情况二:4个分支
情况三:分支常量值顺序打乱
当case分支少于4个时,并不会生成case分支地址表,因为编译器会生成类似if…else之类的反汇编;
case后面的常量可以是无序的,并不会影响表的生成;且其表内的元素排列会按照case分支的值从小到大排列,表是有序的;
3.switch的case分支语句地址的寻址方式
1. 情况一:
2. 情况二:
从上面两种情况来看,在编译器的处理中,如果switch的case常量之间差值太大的话,会采取情况二的方法(大表+小表)。如果差值不大的话,会采取情况一(大表)的方式去处理;
情况一:差值之间的数据统一都使用default语句的EIP地址填充;
情况二:该方法有利于节省空间;
(二)switch练习
1.写一个switch语句,不产生大表与小表2.写一个switch语句,只产生大表
3.写一个switch语句,产生大表与小表
#include<stdio.h> char Fun1(char c) { char j = 0; switch (c) { case 'a': j = 'a'; break; case 'b': j = 'b'; break; case 'c': j = 'c'; break; default: break; } return j; } char Fun2(char c) { char j = 0; switch (c) { case 'a': j = 'a'; break; case 'b': j = 'b'; break; case 'c': j = 'c'; break; case 'd': j = 'd'; break; case 'e': j = 'e'; break; case 'f': j = 'f'; break; case 'g': j = 'g'; break; case 'h': j = 'h'; break; case 'n': j = 'n'; break; default: break; } } char Fun3(char c) { char j = 0; switch (c) { case 'a': j = 'a'; break; case 'b': j = 'b'; break; case 'c': j = 'c'; break; case 'n': j = 'n'; break; default: break; } return j; } int main() { int i = Fun1('n'); int j = Fun2('n'); int k = Fun3('n'); return 0; }
(三)分析循环语句
2017/5/26 12:44:11
Day 16 《标题》
(一)小节
相关文章推荐
- android逆向学习,笔记(四)ARM汇编基础
- 汇编学习笔记43
- 汇编入门学习笔记 (七)—— dp,div,dup
- 汇编语言学习笔记(2)
- 【学习笔记之汇编语言】【二】寄存器
- 汇编语言学习笔记-按指定的字体输出文本
- 汇编语言学习笔记10
- 汇编--学习笔记(十二)-子程序(二)-子程序数据传递
- 王爽汇编语言学习笔记(五)--loop与[bx]
- 32位汇编语言学习笔记(12)--分析switch语句的汇编代码
- windows下32位汇编语言学习笔记 第四章 第一个窗口程序 (windows的消息机制)
- 汇编语言学习笔记(二)
- 汇编学习笔记----8086CPU的段寄存器
- 汇编学习笔记---3内存访问
- c&c++反汇编与逆向分析学习笔记(5)--加法的求值过程和编译器优化
- 汇编语言学习笔记(五)
- TQ2440 学习笔记—— 13、GPIO 接口【实验:用汇编语言实现】
- 朱老师ARM裸机学习笔记(七):汇编写启动代码之调用C语言
- 汇编语言学习笔记2(王爽)
- android逆向学习,笔记(一)