逆向笔记之C基础(二)
本文笔记均来自于对滴水逆向教程的学习,通过学习教程记录的笔记,个人所见所得。
内存图
数据类型与数据存储
C语言数据类型 | 汇编数据宽度 |
---|---|
char | byte |
short | word |
int | dword |
long | dword |
无符号跟有符号,在内存中存储的是一样的,根据使用的人来决定
默认有符号,类型转换–比较大小–数学运算要注意
有符号跟无符号,比较会改掉,jbe跟jle 低于等于,小于等于
浮点类型
这部分就是计算机组成原理
float的存储方法
12.5 在内存中存储为
整数部分: 12
计算 | 余数 |
---|---|
12/2=6 | 0 |
6/2=3 | 0 |
3/2=1 | 1 |
1/2=0 | 1 |
从下往上: 1100
小数部分: 0.5
计算 | 小数 |
---|---|
0.5*2=1.0 | 1 |
从上往下:1
1100.1=1.1001*2的3次方
1位符号位 中间8位指数 后面23位尾数
尾数从左往右写就行
中间8位开头,左移为1,右移为0,
后面7位,存储指数-1,比如3次方,就是存10,如果是-2次方就是-3,也就是fd, 11111101,然后只取后7位
127 +(3) = 130 = 0x82 = 1000 0010 这里直接加指数
0 10000010 10010000000000000000000
0100 0001 0100 1000 0000 0000 0000 0000
41480000
如果是-12.5呢
就变成
1100 0001 0100 1000 0000 0000 0000 0000
C1480000
练习
将CallingConvention.exe逆向成C语言
1 | int __fastcall Add5(int num1, int num2, int num3, int num4, int num5) |
编码
ASCII 英文字符
GB2312或GB2312-80 中文字符,两个字节一个汉字
练习
截取字符串
比如 china中国verygood天朝nice
fn(5) = china
fn(6) = china中
fn(8) = china中国v
1 |
|
指针做乘法运算
1 |
|
原来强转int *老是说信息丢失报错,改成long就不报错了
数组练习
- 假设现在有5个班,每个班10个人,设计一个二维数组存储这些人的年龄.
- 如果想知道第二个班的第6个人的年龄,应该如何获取?编译器该如何获取?
- 打印所有班级,所有学生的年龄(每个班级打印一行).
- 将第二个班级的超过20岁的学生的年龄修改为21岁.
- 打印出每个班级学生的年龄的和.
- 数组一:[3,5,7,9,12,25,34,55] 数组二:[4,7,9,11,13,16] 将两个数组中所有数据进行从小到大的排序,存储到另一个数组中.
解答
1-5
1 | int age[5][10] = |
6 严格意义并不算归并排序,这里已经有序了,所以只是归并排序的一部分
1 | int array2[8]={3,5,7,9,12,25,34,55}; |
函数
函数的概念
CC是int 3,防止缓冲区溢出,CPU会断下
函数入口
函数的出口:
MOV EAX,DWORD PTR SS:[EBP+8] 上面的这个函数将计算结果存储到EAX中
ADD EAX,DWORD PTR SS:[EBP+C] 我们称为返回值
思考:
函数的计算结果除了放在寄存器中,还可以放在什么地方?
还可以通过寄存器传参
裸函数
1 | void __declspec(naked) Plus() |
跟空函数不一样
1 | void Plus() |
裸函数依旧是个call,不过里面没有内容
1 | void __declspec(naked) Plus() |
这样的就可以正常运行,因为这样会retn
手写汇编实现Add函数
1 | int __declspec(naked) Plus(int x, int y) |
if语句逆向分析
if语句判定
来个影响标志位语句,然后jcc,极有可能就是if语句
c语言与汇编指令是反着来的,比如<=在汇编里就是jg
if_else判断
if_else练习
- 参数 [ebp+8]=num1, [ebp+0xc]=num2, [ebp+0x10]=num3
- 局部变量[ebp-4]=0, [ebp-8]=1, [ebp-0xc]=2
- 全局变量无
- 功能分析直接分析代码
- 返回值存在eax里,有
1 | int func(int num1, int num2, int num3) |
类型转换
movsx 先符号扩展,在传送 (有符号用)
1 | mov al,0ff |
movzx 先零扩展,在传送 (无符号用)
1 | mov al,0ff |
1 | void test() |
1 | void test() |
大转小,利用宽度就行,想想内存中如何存值
1 | void test() |
0x0: 78563412 实际存储
short的话,肯定就是5678
char的话肯定就是78
有符号跟无符号相加 变成有符号
无符号跟有符号相加 变成无符号
是以自身为基准
结构体
1 | struct AA |
返回结构体
1 | lea eax,[ebp-30h] |
结构体基础练习
- 定义一个结构体Gamer用来存储一个游戏中的角色的信息,包括血值、等级、坐标等信息
- 具体包含哪些信息自由设计
- 但这些包含的类型中,必须要有一个成员是结构体类型
- 定义一个函数,用来给这个结构体变量赋值
- 定义一个函数,用来显示这个结构体变量的所有成员信息
c语言都没经常写,复习下吧,练练手
1 | struct Point |
结构体对齐
1 |
|
对齐原则:
- 数据成员对齐规则:结构的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储).
- 结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。
- 该成员大小的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储).(struct a里存有struct b,b里有char,int,double等元素,那b应该从8的整数倍开始存储.)
- 对齐参数如果比结构体成员的sizeof值小,该成员的偏移量应该以此值为准.
结构体对齐练习
- 定义一个结构体Monster,能够存储怪的各种信息(至少有一个成员是结构体类型)。
- 声明一个Monster类型的数组,长度为10.
- 编写一个函数,为第二题中的数组赋值.
- 编写一个函数,能够通过怪物ID,打印当前这个怪物的所有信息.
1 | struct Point |
分析下面结构体的内存分配
1 | struct S1 |
S1结构体
结果是16,c按一字节对齐,是c,然后结构体内最大是double,8个字节,所以按8字节补全,合起来16个字节,也就是0x10字节
S2结构体
结果是16+2*8=32=0x20
首先char都是按1字节对齐了,然后S1分析过了,是16字节,整体按double补全,所以开头一个char 补全7个字节,后面两个char,补全6个字节,中间16个字节
S3结构体
结果是16+2*8+8=40=0x28
S1依旧16字节,char c1是1字节,按最大8字节补齐,c2旁边没有,也按8字节补齐,double接上
S4结构体
结果是4+8+4 = 16
char类型数组相当于叠了10个char,所以一个个叠下去最后剩下两个,两个按int补齐,所有就是16字节
总结
大胆猜想,小心论证,做完后实验,与理论相同
Switch语句逆向
通过这些问题进行学习Switch语句
break加与不加有什么特点?default语句可以省略吗?
答: 不加,每个都会执行,可以省略,不过怕会出错
添加case后面的值,一个一个增加,观察反汇编代码的变化(何时生成大表).
将3中的常量值的顺序打乱,观察反汇编代码(观察顺序是否会影响生成大表).
答: 不会
将case后面的值改成从100开始到109,观察汇编变化(观察值较大时是否生成大表).
答: 会,减的常数不同
将连续的10项中抹去1项或者2项,观察反汇编有无变化(观察大表空缺位置的处理).
答: 无,空缺位置填充了default分支
在10项中连续抹去,不要抹去最大值和最小值(观察何时生成小表).
答: 大表+小表,达到一定数量会生成小表,小表用来查偏移,直接跳,节省内存
将case后面常量表达式改成毫不连续的值,观察反汇编变化.
答:搜索二叉树,骚啊
总结:
- switch分支少于4个没意思,因为会生成类似if_else的结构
- sub ecx,常数,是为了跳转表转向,将其转换成index
- 4个分支上会生成大表,直接计算地址,而大表需要连续的数值判断,如果相隔太大,会生成if_else结构
练习
写一个switch语句,不生产大表也不生产小表,贴出对应的反汇编
这个分支少于4个就会生成if_else结构
1 | 8: switch(num) |
写一个switch语句,只生成大表,贴出对应的反汇编.
这个分支多于4个就可以生成
1 | 8: switch(num) |
还有一种大表,带default的
1 | 8: switch(num) |
004010F1 5B 10 40 00 [.@.
004010F5 D3 10 40 00 ..@.
004010F9 D3 10 40 00 ..@.
004010FD 6A 10 40 00 j.@.
00401101 79 10 40 00 y.@.
00401105 88 10 40 00 ..@.
00401109 97 10 40 00 ..@.
0040110D A6 10 40 00 ..@.
00401111 B5 10 40 00 ..@.
这是生成的大表,中间被我注释掉的全部生成了default语句
写一个switch语句,生成大表和小表,贴出对应的反汇编.
11个注释掉6个后终于生成了小表
1 | 8: switch(num) |
这就是小表
004010CD 00 04 04 04 ….
004010D1 04 04 04 01 ….
小表查找的语句
1 | 040104D mov eax,dword ptr [ebp-4] |
小表就是个存了偏移的表,通过比较,取出相对应的偏移位置,然后在jmp
循环语句
- do..while
- while
- for
练习
为do..while语句生成的反汇编填写注释.
1 | 9: do{ |
do while先执行,后面cmp跳转回去,明显特征,只有往上跳,没有往下跳的
总结:
- 根据条件跳转指令所跳转到的地址,可以得到循环语句块的起始地址。
- 根据条件跳转指令所在的地址,可以得到循环语句块的结束地址。
- 条件跳转的逻辑与源码相同。
为while语句生成的反汇编填写注释.
1 | 9: while(i++ < 10) |
while语句特征,先cmp比较,然后在执行,最后往上跳,特征,cmp后才会执行
总结:
- 根据条件跳转指令所跳转到的地址,可以得到循环语句块的结束地址;
- 根据jmp 指令所跳转到的地址,可以得到循环语句块的起始地址;
- 在还原while 比较时,条件跳转的逻辑与源码相反。
为for语句生成的反汇编填写注释.
1 | 9: for(i=0; i<10; i++) |
for循环的话,开头跳去比较,比较过后执行,最后自增
总结:
- 第一个jmp 指令之前为赋初值部分.
- 第一个jmp 指令所跳转的地址为循环条件判定部分起始.
- 判断条件后面的跳转指令条件成立时跳转的循环体外面
- 条件判断跳转指令所指向的地址上面有一个jmp jmp地址为表达式3的起始位置
本文作者:NoOne
本文地址: https://noonegroup.xyz/posts/806264cf/
版权声明:转载请注明出处!