基础知识补充
一些基础知识的补充
¶0x00-typedef定义函数指针
¶函数指针
在此之前我们要先了解函数指针,即存放函数首地址的变量,而函数名就是函数的首地址,为了存放函数的首地址就需要定义函数指针
1 | void hello() |
¶typedef与函数指针混合使用
1 | typedef int (*FP)(int,int); |
那么我们在赋值的时候就可以改成
1 | FP fp1=add;//fp1就是返回值为int型,参数为两个int型的函数指针,FP表示函数指针的类型,通过类型名+变量名就可以定义函数指针 |
¶0x01-回调函数
¶定义
如果函数的参数具有函数指针,那么这样的函数就被称为回调函数
函数指针就是当作接口使用的
¶例子
1 | //这里的call就是回调函数,这里传递函数指针和我们传入数组时使用函数指针是类似的,都是使用指针将首地址进行传递,而不是传整个函数 |
¶0x02-复杂函数
定义复杂的回调函数
1 | void *handle(void*arg) |
¶0x03-一维数组内存
1 | int arr[6]={10,20,30,40};//当有初始值时,剩余没定义的默认为0,如果没有定义初始值,则为随机值 |
因为数组长度定义为6,类型为int,所以在内存中分配24个字节
¶0x04-函数声明
返回值+函数名+参数
1 | //函数声明 |
¶0x05-调用约定
为什么要有不同的调用约定——是因为调用函数之后需要清理栈,而不同的调用约定对应不同的清理方式
__cdecl:调用者自己清理栈
1
2
3 //函数外部
call hello;函数
add esp,立即数__stdcall:函数自己清理栈
1
2 //函数内部
retn 立即数
如果使用__cdcel调用方式,因为不同编译器产生的栈不同,所以不能很好地清理栈,而stdcall则可以在函数内部完成清理栈。
所以,在跨(开发)平台的调用中,我们都使用stdcall(有时是以WINAPI的样子出现)。那么为什么还需要_cdecl呢?当我们遇到这样的函数如fprintf()它的参数是可变的,不定长的,被调用者事先无法知道参数的长度,事后的清除工作也无法正常的进行,因此,这种情况我们只能使用cdecl
¶0x06-extern “C”
¶原因
也就是说如果不声明为extern “C”,我们导出的函数名会被修饰
可以看到我们的函数名被修饰了,在调用的时候我们无法通过GetProcAddress通过函数名调用
可以看到我们的函数名没有被修饰
C++函数重载即函数名可以相等,只要该函数的参数类型或者个数不同即可
¶补充
标准文件头
1 |
|
extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用
¶0x07-HMOUDLE、HANDLE、HWND、HINSTANCE
https://www.cnblogs.com/wingsummer/p/15823780.html
这里就不看定义了,越看越晕(
¶HWND
HWND是线程相关的,可以通过HWND找到该窗口所属进程的句柄
¶HANDLE
Handle是代表系统的内核对象,如文件句柄,线程句柄,进程句柄
系统对内核对象以链表的形式进行管理,载入到内存中的内核对象都有一个线性地址,同时相对系统来说,在串列中有一个索引位置,这个索引位置就是内核对象的HANDLE
¶HINSTANCE
HINSTANCE的本质是模块基地址,它仅在同一进程中才有意义,跨进程的HINSTANCE是没有意义的
¶HMODULE
代表应用程序载入的模块,WIN32系统下通常是被载入模块的线性地址,比如exe, dll等模块等
HINSTANCE 在win32下与HMODULE是相同的东西(只有在16位windows上,二者有所不同)
¶0x08-命令行参数
我们知道main函数实际上是有两个参数的,但一般不会进行使用
其原型如下
1 | main(int argc,char*argv[]) |
这两个参数实际上与命令行相关
1 | //argc是一个整数,其代表了命令行参数个数 |
1 |
|
¶0x09-有符号数和无符号数
¶unsigend和signed
unsigned顾名思义就是无符号数,signed是有符号数
我们以char为例,char类型存储时为8位,当声明为signed的时候(不加unsigned的时候),最高位也就是第八位被当作符号位,当最高位为0的时候为正数,1时为负数,那么后七位就是数据位
所以signed char也就是char可以表示-127-128的值(2**7)
当声明为unsigned char时,最高位也是数据位,此时可以表示0-256中的数据(2**8)
¶有符号数与无符号数的移位问题
逻辑移位用于无符号数
算术移位用于有符号数
¶逻辑移位
对于逻辑移位,就是不考虑符号位,移位的结果只是数据所有的位数进行移位,左移时低位补0,右移时高位补0
¶算术移位
首先我们需要知道
C在存储数字的时候都采用补码的形式,而正数的原码、反码、补码都是一样的,左右移位时直接使用原码即可,负数的需要重新计算,补码计算:除符号位外,其他取反加一
算术移位,右移时(未溢出),保持符号位不变,同时用符号位补数值最高位
-65=11000001,转为补码为10111111,>>2变成11101111,再转为原码10010001,为-17
算术移位,左移时(未溢出),直接将数据最高有效位移入符号位,最低位补0
当符号位为1时,为负数,若数据最高位为0(补码的数据最高位),那么此时左移必定溢出,正数也同理
¶0x0A-整型溢出与左移溢出
¶整型溢出
0x7F就是01111111,也就是127,+1之后过渡到0x80,此时为1000000,也就是负数,该值为负数的最小值也就是-128
¶左移溢出
左移和右移运算过程中也会发生溢出,移位位数并不是可以任意。当移位位数超过该数值类型的最大位数时,编译器会用移位位数去模该类型位数,然后按照余数进行移位。
¶0x0B-循环左移与循环右移
循环移位区别于一般的移位时没有数位的丢失
循环左移时,用从左边移出的位填充字的右端;循环右移时,用从右边移出的位填充字的左端
以(unsigned char)0x51(无符号)为例,二进制为01010001,循环左移三位最终应该得到10001010,所以我们要先把前三位取出放置到最后三位,把最后五位前移,最后按位或,实现代码
1 | ((0x51<<3)|(0x51>>5)) |
01010001000+
00000000010
01010001010,因为最后要截断,需要&0xFF
要注意的是有符号数的循环右移时,使用符号位填充
通用,总长度N(8,16,32),循环左移n:(a>>(N-n))|(a<<n),循环右移n:(a<<(N-n))|(a>>n)
¶逆运算
只需要将<<改为>>,>>改为<<即可
¶0x0C-ida生成数组
通过分析直到数组,然后点进去跳转到栈,右键->Array->填写最大大小,然后确定即可
点击数组首元素v17
填写好数组大小
¶0x0D-IDA设置中文编码
有时候逆向题会出现中文提示字符串,这时候就需要设置中文了
OPtions->General->Strings,然后选择第一个UTF-8,双击后Insert一个GBK
¶0x0E-IDA创建结构体
一种是添加IDA已经存在的结构体,适合于那种规范的
在结构体这一栏中右键创建即可