逆向个人学习笔记(2)C语言

基础部分

  1. 裸函数:编译器不管,不自动生成ret
void __declspec(naked) Function()  
{
    __asm 
    {
        ret
    }
}
  1. 常见的几种调用约定:
    在这里插入图片描述

  2. 程序入口
    main 函数的识别:main 函数被调用前要先调用的函数如下
    GetVersion()
    _heap_init()
    GetCommandLineA()
    _crtGetEnvironmentStringsA()
    _setargv()
    _setenvp()
    _cinit()
    这些函数调用结束后就会调用main 函数,根据main函数调用的特征,寻找将3个参数压入栈内作为参数的函数。

  3. 学习数据类型的三个要素:
    (1)存储数据的宽度
    (2)存储数据的格式
    (3)作用范围(作用域)

  4. 整数类型:char short int long
    char 8BIT 1字节
    short 16BIT 2字节
    int 32BIT 4字节
    long 32BIT 4字节
    整数类型分为有符号(signed)和无符号(unsigned)两种,在内存中存储的方式完全一样;在做运算和比较的时候需要注意

  5. 浮点类型:float double
    在这里插入图片描述将一个float型转化为内存存储格式的步骤为:
    (1)先将这个实数的绝对值化为二进制格式
    (2)将这个二进制格式实数的小数点左移或右移n位,直到小数点移动到第一个有效数字的右边。
    (3)从小数点右边第一位开始数出二十三位数字放入第22到第0位。
    (4)如果实数是正的,则在第31位放入“0”,否则放入“1”。
    (5)如果n 是左移得到的,说明指数是正的,第30位放入“1”。如果n是右移得到的或n=0,则第30位放入“0”。
    (6)如果n是左移得到的,则将n减去1后化为二进制,并在左边加“0”补足七位,放入第29到第23位。
    如果n是右移得到的或n=0,则将n化为二进制后在左边加“0”补足七位,再各位求反,再放入第29到第23位。
    在这里插入图片描述
    练习:
    1.用__declspec(naked)裸函数实现下面的功能

int plus1(int x, int y, int z)
{
    int a = 2;
    int b = 3;
    int c = 4;
    return x+y+z+a+b+c;
}

2.将float类型的12.5 转换成16进制

  1. 英文字符存储
    ASCII 码使用指定的 7 位或 8 位二进制数组合来表示 128 或 256 种可能的字符。
    标准 ASCII 码使用 7 位二进制数来表示所有的大写和小写字母,数字 0 到 9、标点符号,以及在美式英语中使用的特殊控制字符。
    扩展 ASCII 码允许将每个字符的第 8 位用于确定附加的 128 个特殊符号字符、外来语字母和图形符号。
  2. 中文字符存储
    char* x = “啊”;
    天朝专家把那些127号之后的奇异符号们(即EASCII)取消掉,规定:一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到 0xF7,后面一个字节(低字节)从0xA1到0xFE,这样我们就可以组合出大约7000多个简体汉字了。

if语句

  1. 内存图
    在这里插入图片描述
  2. 全局变量的反汇编识别
    MOV 寄存器,byte/word/dword ptr ds:[0x12345678]
    通过寄存器的宽度,或者byte/word/dword 来判断全局变量的宽度
    全局变量就是所谓的基址
  3. 局部变量的反汇编识别
    如: [ebp-4]
    [ebp-8]
    [esp+4]
  4. 如何判断函数有几个参数,以及分别是什么

    可能存在的问题:
    1.参数传递未必都是通过堆栈,还可能通过使用寄存器.
    2.函数调用处的代码无法查看.
  5. IF语句的反汇编判断:
    执行各类影响标志位的指令
    jxx xxxx
  6. IF…ELSE…语句的反汇编判断:
    跳转执行一部分代码,不跳转执行另外一部分代码
    第一个jxx跳转的地址前面有一个jmp ,可以判断是if…else…语句
  7. IF…ELSE IF…ELSE IF…多分支语句的反汇编判断:
    当每个条件跳转指令要跳转的地址前面都有jmp 指令
    这些jmp指令跳转的地址都是一样的
    如果某个分支没有条件判断,则为else部分

正向基础

  1. 类型转换
    MOVSX 先符号扩展,再传送。(有符号数)如:
    MOV AL,0FF
    MOVSX CX,AL
    MOV AL,80
    MOVSX CX,AL
    MOVZX 先零扩展,再传送。(无符号数)如:
    MOV AL,0FF
    MOVZX CX,AL
    MOV AL,80
    MOVSX CX,AL
    大转小,截取低位,高位舍弃
  2. 表达式
    表达式无论多么复杂,都只有一个结果。
    只有表达式,可以编译通过,但并不生成代码,需要与赋值或者其他流程控制语句一起组合的时候才有意义。
    当表达式中存在不同宽度的变量时,结果将转换为宽度最大的那个。
    当表达式中同时存在有符号和无符号数的时候,表达式的结构将转换为无符号数。
    扩展的时候按自身类型扩展。

循环

  1. do…while语句反汇编
    在这里插入图片描述

例:
在这里插入图片描述

总结:
根据条件跳转指令所跳转到的地址,可以得到循环语句块的起始地址。
根据条件跳转指令所在的地址,可以得到循环语句块的结束地址。
条件跳转的逻辑与源码相同。

  1. while语句反汇编
    WHILE_BEGIN:
    影响标志位的指令
    jxx WHILE_END ;条件成立跳转到循环语句块结尾处

    jmp WHILE_BEGIN ;跳转到条件比较处
    WHILE_END:
    例:

在这里插入图片描述总结:
根据条件跳转指令所跳转到的地址,可以得到循环语句块的结束地址。
根据jmp 指令所跳转到的地址,可以得到循环语句块的起始地址。
在还原while 比较时,条件跳转的逻辑与源码相反。

  1. for语句反汇编
    for(表达式1;表达式2;表达式3)
    例:
    在这里插入图片描述

总结:
第一个jmp 指令之前为赋初值部分.
第一个jmp 指令所跳转的地址为循环条件判定部分起始.
判断条件后面的跳转指令条件成立时跳转的循环体外面
条件判断跳转指令所指向的地址上面有一个jmp ,jmp地址为表达式3的起始位置

参数_返回值_局部变量_数组

  1. 返回值
    本机尺寸:如果本机是32位的,那么对32位的数据支持最好,如果是64位的,那么对64位的支持最好。(?)
    编译器遵守了这个规则: char类型或者short类型的参数不但没有节省空间,反而浪费了多余的操作。
    结论:整数类型的参数,一律使用int类型。
    参数传递的本质:将上层函数的变量,或者表达式的值“复制一份”,传递给下层函数。
  2. 参数和局部变量
    小于32位的局部变量,空间在分配时,按32位(即4字节)分配.
    使用时按实际的宽度使用.
    不要定义char/short类型的局部变量.
    参数与局部变量没有本质区别,都是局部变量,都在栈中分配.
    完全可以把参数当初局部变量使用

练习:

  1. 返回值超过32位时,存在哪里?用long long(__int64)类型做实验。long long类型在VC6中对应的是__int64
    __int64 Function()
    {
    __int64 x = 0x1234567890;
    return x;
    }
    VC6下的反汇编:低位在低位,高位在高位
    在这里插入图片描述
    在这里插入图片描述
  2. char arr[3] = {1,2,3};与 char arr[4] = {1,2,3,4};哪个更节省空间,从反汇编的角度来说明你的观点
    答:都分配了四个字节
  3. 找出下面赋值过程的反汇编代码
void function3()                    
{                    
    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];                
}    

VC6下的反汇编:(眼熟下数组的反汇编)

乘4是因为数组为int型

多维数组

  1. 基于缓冲区溢出的HelloWorld
void HelloWord()            
{            
    printf("Hello World");        
            
    getchar();        
}            
void Fun()            
{            
    int arr[5] = {1,2,3,4,5};        
            
    arr[6] = (int)HelloWord;        
}

解释:arr[6]原本为ebp+4,即函数调用后的返回地址,函数名HelloWord是函数void HelloWord()的地址。

  1. 总结:
    一维数组与多维数组在反汇编上没有区别.
    数量如果不够,其他的值会补零.
    不允许超过维度的最大长度.
    可以省略里面的{ }
    可以省略第一维的值

结构体_字节对齐

  1. 结构体做参数或者返回值实质就是内存之间的大量复制,最好不要直接让结构体做参数或者返回值
  2. 基本类型的sizeof,可以使用类型,也可以使用变量
  3. #pragma pack的基本用法为:
#pragma pack( n )        
结构体       
#pragma pack(  )    

对齐参数:n为字节对齐数,其取值为1、2、4、8,默认是8。

  1. 对齐原则:(对齐参数为默认值8)
    原则一:数据成员对齐规则:结构的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置(起始位置看原则四偏移量)要从该成员大小的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储).
    原则二:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。
    原则三:如果一个结构里有某些结构体成员,则要从子结构体最大成员或者实际对齐单位(还是取两者小的那个)的整数倍地址开始存储。(struct a里存有struct b,b里有char,int,double等元素,那b应该从8的整数倍开始存储) // 但该子结构体所占内存仍为原本所占内存
    原则四:对齐参数如果比结构体成员的sizeof值小,该成员的偏移量应该以此值为准. 也就是说,结构体成员的偏移量应该取二者的最小值.
    参考博客
    举例:
struct S4        
{        
    int c1;     
    char c2[10];     
};

所占的空间大小为16, c2[10]并不是基本数据类型,这里当做10个char型变量来处理,char占1个字节,小于实际对齐字节8,所以这10个char型变量可以直接挨着int c1后面放。

switch语句

  1. switch要求:
    case后面必须是常量表达式
    case后常量表达式的值不能一样
    switch后面表达式必须为整数
  2. 尝试:(有惊喜)
    (1)添加case后面的值,一个一个增加,观察反汇编代码的变化(何时生成大表).
    (2)将3常量值的顺序打乱,观察反汇编代码(观察顺序是否会影响生成大表).
    (3)将case后面的值改成从100开始到109,观察汇编变化(观察值较大时是否生成大表).
    (4)将连续的10项中抹去1项或者2项,观察反汇编有无变化(观察大表空缺位置的处理).
    (5)在10项中连续抹去,不要抹去最大值和最小值(观察何时生成小表).
    (6)将case后面常量表达式改成毫不连续的值,观察反汇编变化.
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值