逆向笔记之函数与指针(三)
---title: 逆向笔记之函数与指针(三)
tags:
- reverse
- 逆向
---
# 逆向笔记之函数与指针(三)
本文笔记均来自于对滴水逆向教程的学习,通过学习教程记录的笔记,个人所见所得。
大纲:
1. 堆栈图学习
2. 函数
3. 函数调用
4. 调用约定
5. 入口点
6. 函数分析
## windows堆栈
1. 先进后出
2. 向低地址扩展
什么是堆栈平衡: 简单的说就是还原现场
问: cmp ebp,esp后,如果ebp!=esp是否可以跳转
答: 可以,因为z标志位改了就行
### 堆栈图练习
#### 堆栈图练习1
单层函数的堆栈调用图
![****](https://gitee.com/NoOne-hub/picture/raw/master/img/20200120232525.png)
#### 堆栈图练习2
带子函数的堆栈调用图
![](https://gitee.com/NoOne-hub/picture/raw/master/img/20200121101329.png)
#### 堆栈图练习3
粗略看了下,是个多层加法计算
第一层 7,6,5,4,3
第二层: int num1=2,num2=3; 子函数传入参数6,5,4,3,计算结果为num1+num2+ 子函数计算结果+7
第三层:int num1=2, 子函数传入参数5,4,3 计算结果为 num1+ 子函数计算结果+6
第四层: 返回5+4+3
所以整体为: 5+4+3+6+2+7+2+3=32
至于堆栈图,不画了,我有点基础,大概懂这些了
## 函数分析
#### 空函数分析
无参数、无返回值的函数格式
```c
void function()
{
//代码 每行以;结尾
}
```
![](https://gitee.com/NoOne-hub/picture/raw/master/img/20200121124211.png)
#### 两个数加法函数分析
```c
int func(int x,int y)
{
return x+y;
}
```
![](https://gitee.com/NoOne-hub/picture/raw/master/img/20200121124844.png)
#### 三个整数加法操作
```c
int Plus1(int x,int y)
{
return x+y;
}
int func(int x,int y,int z)
{
int t,r;
t = Plus1(x,y);
r = Plus1(t,z);
return r;
}
```
![](https://gitee.com/NoOne-hub/picture/raw/master/img/20200121130139.png)
#### 5个整数加法并要求用前两个函数
```c
int Plus1(int x,int y)
{
return x+y;
}
int Plus2(int x,int y,int z)
{
int t,r;
t = Plus1(x,y);
r = Plus1(t,z);
return r;
}
int Plus3(int num1, int num2, int num3, int num4, int num5)
{
int result1 = Plus2(num1, num2, num3);
int result = Plus2(result1, num4, num5);
return result;
}
```
![](https://gitee.com/NoOne-hub/picture/raw/master/img/20200121155031.png)
#### 递归反汇编分析练习
```c
int fcb(int n)
{
if(n==0)
return 0;
else if(n==1)
return 1;
else
return fcb(n-1) +fcb(n-2);
}
int main(int argc, char* argv[])
{
int result = fcb(3);
printf("%d\n", result);
return 0;
}
```
![](https://gitee.com/NoOne-hub/picture/raw/master/img/20200122225336.png)
大概函数逆向出来了,接着编写代码,运行一下就知道了?
我跟踪了下整个过程,fcb(3)
首先他会执行fcb(3-1)->fcb(2-1)->fcb(1)
然后从fcb(1)那一层返回去执行fcb(2-1)+fcb(2-2)
在返回一层执行fcb(3-1)+fcb(3-2)
所以整体就是 fcb(3-2) + fcb(3-1) = fcb(1) +(fcb(0) + fcb(1))
实际结果就是1+0+1=2
#
## 参数和返回值以及局部变量
```c
//8位参数
void func(char x)
{
}
//16位
void func(short x)
{
}
//32位
void func(int x)
{
}
```
1. 本机尺寸:如果本机是32位的,那么对32位的数据支持最好,如果是64位的,那么对64位的支持最好.
2. 编译器遵守了这个规则:char类型或者short类型的参数不但没有节省空间,反而浪费了多余的操作.
结论:整数类型的参数,一律使用int类型
参数传递的本质:将上层函数的变量,或者表达式的值“复制一份”,传递给下层函数.
1. 小于32位的局部变量,空间在分配时,按32位分配.
2. 使用时按实际的宽度使用.
3. 不要定义char/short类型的局部变量.
4. 参数与局部变量没有本质区别,都是局部变量,都在栈中分配.
5. 完全可以把参数当初局部变量使用
赋值的本质: 将某个值存储到变量中的过程就是赋值.
### 练习题
1. 返回值超过32位时,存在哪里?用long long(__int64)类型做实验
2. char arr = {1,2,3};与 char arr = {1,2,3,4};哪个更节省空间,从反汇编的角度来说明你的观点
3. 找出下面赋值过程的反汇编代码
```c
void Function()
{
int x = 1;
int y = 2;
int r;
int arr = {1,2,3,4,5,6,7,8,9,10};
r = arr;
r = arr;
r = arr;
r = arr;
}
```
4. 选做题: 桶排序,学校c语言老师教过了,不搞了
#### 第一题解答
高位存在edx, 低位存在eax里
实验证明
c语言代码:
```c
__int64 test()
{
__int64 x = 0x1234567890;
return x;
}
int main(int argc, char* argv[])
{
test();
return 0;
}
```
汇编代码:
![](https://gitee.com/NoOne-hub/picture/raw/master/img/20200122152314.png)
#### 第二题解答
char arr = {1,2,3,4};更节省空间,因为char arr虽然只用了三个空间,而实际还是占用一个单元,4个字节,剩余的一个字节不会被使用,浪费了
#### 第三题解答
```assembly
# int x =1;
mov dword ptr ,1
```
```assembly
# int y=2;
mov dword ptr ,2
```
```assembly
# int arr = {1,2,3,4,5,6,7,8,9,10};
mov dword ptr ,1
mov dword ptr ,2
mov dword ptr ,3
mov dword ptr ,4
mov dword ptr ,5
mov dword ptr ,6
mov dword ptr ,7
mov dword ptr ,8
mov dword ptr ,9
mov dword ptr ,0Ah
```
```assembly
# r = arr;
mov eax,dword ptr
mov dword ptr ,eax
```
```assembly
# r = arr;
mov ecx,dword ptr
mov edx,dword ptr
mov dword ptr ,edx
```
```assembly
#r = arr;
0041073C mov eax,dword ptr
0041073F add eax,dword ptr
00410742 mov ecx,dword ptr
00410746 mov dword ptr ,ecx
```
```assembly
# r = arr;
00410749 mov edx,dword ptr
0041074C mov eax,dword ptr
0041074F lea ecx,
00410752 mov edx,dword ptr
00410756 mov dword ptr ,edx
```
对比分析结果:
1. 越先定义的越靠近栈底
2. 数组寻址,如果是一个常量,他直接通过ebp-几完成
3. 数组寻址,如果是一个变量,他通过计算这个变量的值后,通过寻址,解释,ebp-34是数组的第一个元素地址,而 *4是因为这是int型数组,一个int占4个字节
#### 第四题解答
多维数组跟一维数组在内存中是一样的
## 调用约定
| 调用约定 | 参数压栈顺序 | 平衡堆栈 |
| ---------- | ------------------------------------------------------------ | ------------ |
| __cdecl | 从右至左入栈 | 调用者清理栈 |
| __stdcall| 从右至左入栈 | 自身清理堆栈 |
| __fastcall | ECX/EDX传送前两个 剩下:从右至左入栈 | 自身清理堆栈 |
### __cdecl
外平衡
```assembly
push 1
push 2
call
add esp,8
```
```c
int __cdecl Plus(int a,int b)
{
return a+b;
}
```
### __stdcall
内平衡
```assembly
push 1
push 2
call //call里平衡 ret
```
```C
int __stdcall Plus(int a,int b)
{
return a+b;
}
```
### __fastcall
利用寄存器传参
```assembly
mov edx,2
mov ecx,1
call
```
```c
int __fastcall Plus(int a, int b)
{
return a+b;
}
```
#### 练习
将下列代码裸函数实现
```c
int plus(int x, int y, int z)
{
int a=2;
int b=3;
int c=4;
return x+y+z+a+b+c;
}
```
```assembly
int __declspec(naked) Plus(int x, int y,int z)
{
__asm
{
push ebp
mov ebp,esp
sub esp,0x40
push ebx
push edi
push esi
mov eax,0xCCCCCCCC
mov ecx,0x10
lea edi,dword ptr ds:
rep stosd
mov eax,2
add eax,3
add eax,4
add eax,dword ptr ds:
add eax,dword ptr ds:
add eax,dword ptr ds:
pop esi
pop edi
pop ebx
mov esp,ebp
pop ebp
ret
}
}
```
## 如何查找修改入口点
查找main方法,可以push 3个参数的
入口点实际不是main
link->entrypoint可以修改入口函数
## 函数分析
### 全局变量与局部变量
全局变量的特点:
1. 全局变量在程序编译完成后地址就已经确定下来了,只要程序启动,全局变量就已经存在了,启动后里面是否有值取决于声明时是否给定了初始值,如果没有,默认为0
2. 全局变量的值可以被所有函数所修改,里面存储的是最后一次修改的值.
3. 全局变量所占内存会一直存在,知道整个进程结束.
4. 全局变量的反汇编识别:MOV 寄存器,byte/word/dword ptr ds:
**全局变量就是所谓的基址**
局部变量的特点:
1. 局部变量在程序编译完成后并没有分配固定的地址.
2. 在所属的方法没有被调用时,局部变量并不会分配内存地址,只有当所属的程序被调用了,才会在堆栈中分配内存.
3. 当局部变量所属的方法执行完毕后,局部变量所占用的内存将变成垃圾数据.局部变量消失.
4. 局部变量只能在方法内部使用,函数A无法使用函数B的局部变量.
5. 局部变量的反汇编识别:,,
### 函数参数确认
步骤1: 观察调用处代码
push 3
push 2
push 1
call 0040100f
步骤2: 找到平衡堆栈的代码继续论证
call 0040100f
add esp,0Ch
或者函数内部
ret 4/8/0xc/0x10
最后,两者一综合,函数的参数个数基本确定
1. 参数传递未必都是通过堆栈,还可能通过使用寄存器
2. 函数调用处的代码无法查看.
![](https://gitee.com/NoOne-hub/picture/raw/master/img/20200121221336.png)
### 函数分析步骤
![](https://gitee.com/NoOne-hub/picture/raw/master/img/20200121222317.png)
### 练习
#### 自行分析如下代码,并还原成c语言
![](https://gitee.com/NoOne-hub/picture/raw/master/img/20200121225625.png)
```c
num = 0;
void func(int num1, int num2)
{
if(num1 > num2)
{
num += num2;
}
}
```
#### 以下两题找出原因,并解释
有趣的返回地址
```c
void HelloWorld()
{
printf("Hello World");
getchar();
}
void fun()
{
int arr={1,2,3,4,5};
arr = (int)HelloWorld;
}
int main()
{
fun();
return 0;
}
```
永不磨灭的Hello World
```c
void Fun()
{
int i;
int arr = {0};
for(i=0; i<=5; i++)
{
arr = 0;
printf("Hello World\n");
}
}
```
这个原因我就不自己测试了
第一题,arr覆盖了返回地址,傻逼了,因为arr就能覆盖ebp,arr 肯定能覆盖返回地址啊
第二题, arr覆盖了i,至于为什么先定义的能被覆盖,想想堆栈,先定义的,就是说我需要一块一个这个的内存空间,所以int i肯定是比较靠近ebp的,接下来才是arr,所以覆盖得到i
## 指针
### 指针基础学习
大纲:
1. 宽度
2. 声明
3. 赋值
4. ++ --
5. 加/减 一个整数
6. 求差值
7. 比较
这些我都学过了,c语言基础相对较好,略过
```c
char**** a;
char**** b;
a = (char****)200;
b = (char****)100;
int x=a-b;
```
结果就是砍掉一个*,在除以数据宽度就行
总结:
1. 带有 * 的变量类型的标准写法:变量类型* 变量名
2. 任何类型都可以带 * 加上 * 以后是新的类型
3. * 可以是任意多个
4. 带 * 类型的变量赋值时只能使用“完整写法”.
5. 带 * 类型的变量宽度永远是4字节、无论类型是什么,无论有几个*.
6. 不带*类型的变量,++或者--都是假1 或者减1
7. 带*类型的变量,可是进行++ 或者 --的操作
8. 带 * 类型的变量,++或者--新增(减少)的数量是去掉一个*后变量的宽度
9. 带*类型的变量可以加、减一个整数,但不能乘或者除.
10. 带*类型变量与其他整数相加或者相减时:
- 带 * 类型变量+ N=带 * 类型变量 + N*(去掉一个*后类型的宽度)
- 带 * 类型变量 - N=带 * 类型变量 - N*(去掉一个*后类型的宽度)
11. 两个类型相同的带 * 类型的变量可以进行减法操作.
12. 想减的结果要除以去掉一个 * 的数据的宽度.
13. 带 * 的变量,如果类型相同,可以做大小的比较。
#### 练习
练习全都是针对32位程序
1. char类型占几字节?char*类型占几字节?int*****占几字节?
2. char** arr 占多少个字节?
##### 练习1解答
1个字节,char* 4个字节,int* 4个字节
##### 练习2解答
40个字节,就是定义了一个数组,存的类型为char**,所以就是10 × 4
##### 练习3解答
带*结构体的加减法
```c
struct Student
{
int x;
int y;
}
void test()
{
Student**** s;
s = (Student****)100;
s++;//s=?
s = s + 2;//s=?
s = s - 3;//s=?
}
```
先盲猜下,第一个s=104,第二个s=112.第三个s=100
```c
void test1()
{
Student**** s1;
Studnet**** s2;
int x;
s1 = (Student****)200;
s2 = (Student****)100;
x = s1 - s2; //x=?
}
```
先盲猜,x=(200-100)/4=25
```c
void test2()
{
Student* s;
s = (Student*)100;
s++; //s=?
s = s + 2; //s=?
s = s - 3; //s=?
}
```
盲猜,s=108, s=124,s=100
```c
void test3()
{
Student* s1;
Student* s2;
int x;
s1 = (Student*)200;
s2 = (Student*)100;
x = s1 - s2;
}
```
盲猜, x=(200-100)/8=12
大胆猜想,小心论证,结果是正确的,没毛病
### 指针的基本使用
大纲:
1. 类型转换
2. &符号的使用
3. “带 * 类型" 求值
4. 用指针操作数组
- &是地址符,类型是其后面的类型加一个“*”,任何变量都可以使用&来获取地址,但不能用在常量上。
- 带 * 类型的变量,可以通过在其变量前加*来获取其指向内存中存储的值
- 在带*类型的变量前面加*,类型是其原来的类型减去一个 *.
#### 练习
分析下列c代码的反汇编
```c
void func()
{
char a = 10;
short b = 0x20;
int c = 30;
char* pa = &a;
short* pb = &b;
int* pc = &c;
char** ppa = &pa;
short** ppb = &pb;
int** ppc = &pc;
}
```
利用堆栈图,分析下,相对不怎么难,二级**而已
![](https://gitee.com/NoOne-hub/picture/raw/master/img/20200123115703.png)
分析下列代码反汇编
```c
void func1()
{
int******* p7;
int****** p6;
int***** p5;
int**** p4;
int*** p3;
int** p2;
int* p1;
int p = 10;
p1= &p;
p2 = &p1;
p3 = &p2;
p4 = &p3;
p5 = &p4;
p6 = &p5;
p7 = &p6;
}
```
![](https://gitee.com/NoOne-hub/picture/raw/master/img/20200123122426.png)
结果就是p7->p6->p5->p4->p3->p2->p1->p
p7存p6地址,p6存p5地址,一直存下去
还行,逆向起来好像并不怎么复杂
完成代码,实现数组值的互换
```c
void Function()
{
int arr={1,2,3,4,5};
//添加代码,使用指针,将数组得到值倒置
//打印,不需要修改
for(int k=0; k<5; k++)
{
printf("%d\n", *(p+k));
}
}
```
```c
void Function()
{
int arr={1,2,3,4,5};
//添加代码,使用指针,将数组得到值倒置
int *p = arr;
int *q = &arr;
while(p<q)
{
//交换数值
int temp = *p;
*p = *q;
*q = temp;
//自增
p++;
q--;
}
p = arr;//恢复指针位置
//打印,不需要修改
for(int k=0; k<5; k++)
{
printf("%d\n", *(p+k));
}
}
```
### 指针与数组
编写一个函数,能够打印任意整形数组的值。
```c
void PrintArray(int arr[],int nLength)
{
for(int i=0;i<nLength;i++)
{
printf("%d\n",arr);
}
}
```
观察反汇编
![](https://gitee.com/NoOne-hub/picture/raw/master/img/20200123165301.png)
通过这个顺便复习了for循环
#### 练习
模拟实现CE的数据搜索功能:这一堆数据中存储了角色的血值信息,假设血值的类型为int类型,值为100(10进制)
请列出所有可能的值以及该值对应的地址.
```c
char data[]= {
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x07,0x09,
0x00,0x20,0x10,0x03,0x03,0x0C,0x00,0x00,0x44,0x00,
0x00,0x33,0x00,0x47,0x0C,0x0E,0x00,0x0D,0x00,0x11,
0x00,0x00,0x00,0x02,0x64,0x00,0x00,0x00,0xAA,0x00,
0x00,0x00,0x64,0x10,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x02,0x00,0x74,0x0F,0x41,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x0A,0x00,
0x00,0x02,0x74,0x0F,0x41,0x00,0x06,0x08,0x00,0x00,
0x00,0x00,0x00,0x64,0x00,0x0F,0x00,0x00,0x0D,0x00,
0x00,0x00,0x23,0x00,0x00,0x64,0x00,0x00,0x64,0x00
};
// type: byte num: data
void search(int type, int num)
{
char *start = data;
int length = sizeof(data)/sizeof(data);
int i=0;
while(i< length-type)
{
if(type == 4)
{
if(*(int*)&data == num)
{
printf("address:%x , data:%d\n",&data,(int)num);
}
}else if(type == 2)
{
if(*(short*)&data == (short)num)
{
printf("address:%x , data:%d\n",&data,(short)num);
}
}else
{
if(*(char*)&data == (char)num)
{
printf("address:%x , data:%d\n",&data,(char)num);
}
}
i += 1;
}
}
int main(int argc, char* argv[])
{
printf("data start from:%x\n", data);
search(4,100);
return 0;
}
```
对其进行解耦,接口不改变
```c
char data[]= {
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x07,0x09,
0x00,0x20,0x10,0x03,0x03,0x0C,0x00,0x00,0x44,0x00,
0x00,0x33,0x00,0x47,0x0C,0x0E,0x00,0x0D,0x00,0x11,
0x00,0x00,0x00,0x02,0x64,0x00,0x00,0x00,0xAA,0x00,
0x00,0x00,0x64,0x10,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x02,0x00,0x74,0x0F,0x41,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x0A,0x00,
0x00,0x02,0x74,0x0F,0x41,0x00,0x06,0x08,0x00,0x00,
0x00,0x00,0x00,0x64,0x00,0x0F,0x00,0x00,0x0D,0x00,
0x00,0x00,0x23,0x00,0x00,0x64,0x00,0x00,0x64,0x00
};
// type: byte num: data
void search_int(int num, int length)
{
int i=0;
while(i<length-4)
{
if(*(int*)&data == num)
{
printf("address:%x , data:%d\n",&data,(int)num);
}
i++;
}
}
void search_short(int num, int length)
{
int i=0;
while(i<length-2)
{
if(*(short*)&data == num)
{
printf("address:%x , data:%d\n",&data,(short)num);
}
i++;
}
}
void search_char(int num, int length)
{
int i=0;
while(i<length-1)
{
if(*(char*)&data == num)
{
printf("address:%x , data:%d\n",&data,(char)num);
}
i++;
}
}
void search(int type, int num)
{
int length = sizeof(data)/sizeof(data);
int i=0;
typedef void (*funcptr)(int,int);
funcptr p;
if(type == 4)
{
p = search_int;
}
else if(type == 2)
{
p = search_short;
}else
{
p = search_char;
}
p(num,length);
}
int main(int argc, char* argv[])
{
printf("data start from:%x\n", data);
search(4,0x64);
return 0;
}
```
### 指针与字符串
```c
char* x = "china"; //china存在常量区
char y[] = "china";
```
常见字符串操作,学过了
#### 练习
模拟实现CE的数据搜索功能:这一堆数据中存储了角色的名字信息(WOW),请列出角色名的内存地址.
```c
char data[]=
{
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x07,0x09,
0x00,0x20,0x10,0x03,0x03,0x0C,0x00,0x00,0x44,0x00,
0x00,0x33,0x00,0x47,0x0C,0x0E,0x00,0x0D,0x00,0x11,
0x00,0x00,0x00,0x02,0x64,0x00,0x00,0x00,0xAA,0x00,
0x00,0x00,0x64,0x10,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x02,0x00,0x74,0x0F,0x41,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x0A,0x00,
0x00,0x02,0x57,0x4F,0x57,0x00,0x06,0x08,0x00,0x00,
0x00,0x00,0x00,0x64,0x00,0x0F,0x00,0x00,0x0D,0x00,
0x00,0x00,0x23,0x00,0x00,0x64,0x00,0x00,0x64,0x00
}
```
1. 编写函数,返回角色名字信息的地址,如果没有返回0
- char* FindRoleNameAddr(char* pData,char* pRoleName)
2. 编写函数,遍历上面数据中所有角色名字.
```c
#include "string.h"
char data[]=
{
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x07,0x09,
0x00,0x20,0x10,0x03,0x03,0x0C,0x00,0x00,0x44,0x00,
0x00,0x33,0x00,0x47,0x0C,0x0E,0x00,0x0D,0x00,0x11,
0x00,0x00,0x00,0x02,0x64,0x00,0x00,0x00,0xAA,0x00,
0x00,0x00,0x64,0x10,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x02,0x00,0x74,0x0F,0x41,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x0A,0x00,
0x00,0x02,0x57,0x4F,0x57,0x00,0x06,0x08,0x00,0x00,
0x00,0x00,0x00,0x64,0x00,0x0F,0x00,0x00,0x0D,0x00,
0x00,0x00,0x23,0x00,0x00,0x64,0x00,0x00,0x64,0x00
};
char* FindRoleNameAddr(char* pData,char* pRoleName)
{
int length = strlen(pRoleName);
int all = sizeof(data)/sizeof(pData);
int i=0;
while(i < all -length)
{
if(strncmp(&pData,pRoleName,length) == 0)
{
return &pData;
break;
}
i++;
}
return NULL;
}
int main(int argc, char* argv[])
{
char *result = FindRoleNameAddr(data,"WOW");
if(result != NULL)
printf("%p\n", result);
else
printf("not found\n");
return 0;
}
```
### 指针数组
#### 练习
1. 创建一个int* arr 数组,并为数组赋值(使用&)
2. 创建一个字符指针数组,存储所有的C的关键词(查资料找),并全部打印出来.
##### 练习1解答
```c
void test()
{
int array = {1,2,3,4,5};
int* arr = {&array,&array,&array,&array,&array};
};
```
##### 练习2解答
```c
void test1()
{
char* arr ={
"auto",
"break",
"case",
"char",
"const"
};
for(int i=0; i<5; i++)
printf("%s", arr);
}
```
##### 练习3解答
查找这些数据中,有几个id=1 level=8的结构体信息。
```c
typedef struct TagPlayer
{
int id;
int level;
}Player;
char data[]={
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x07,0x09,
0x00,0x20,0x10,0x03,0x03,0x0C,0x00,0x00,0x44,0x00,
0x00,0x33,0x01,0x00,0x00,0x08,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x02,0x64,0x00,0x00,0x00,0xAA,0x00,
0x00,0x00,0x64,0x01,0x00,0x00,0x00,0x08,0x00,0x00,
0x00,0x00,0x02,0x00,0x74,0x0F,0x41,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x0A,0x00,
0x00,0x02,0x57,0x4F,0x57,0x00,0x06,0x08,0x00,0x00,
0x00,0x00,0x00,0x64,0x00,0x0F,0x00,0x00,0x0D,0x00,
0x00,0x00,0x23,0x00,0x00,0x64,0x00,0x00,0x64,0x00
};
```
代码编写
```c
typedef struct TagPlayer
{
int id;
int level;
}Player;
char data[]={
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x07,0x09,
0x00,0x20,0x10,0x03,0x03,0x0C,0x00,0x00,0x44,0x00,
0x00,0x33,0x01,0x00,0x00,0x08,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x02,0x64,0x00,0x00,0x00,0xAA,0x00,
0x00,0x00,0x64,0x01,0x00,0x00,0x00,0x08,0x00,0x00,
0x00,0x00,0x02,0x00,0x74,0x0F,0x41,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x0A,0x00,
0x00,0x02,0x57,0x4F,0x57,0x00,0x06,0x08,0x00,0x00,
0x00,0x00,0x00,0x64,0x00,0x0F,0x00,0x00,0x0D,0x00,
0x00,0x00,0x23,0x00,0x00,0x64,0x00,0x00,0x64,0x00
};
void search_Struct()
{
int i=0;
int length = sizeof(data)/sizeof(data);
while(i<length-8)
{
Player* temp = (Player*) &data;
if(temp->id == 1 && temp->level == 8)
{
printf("address:%x id=%d, level=%d\n",&data, temp->id, temp->level);
}
i++;
}
}
int main(int argc, char* argv[])
{
search_Struct();
return 0;
}
```
跟前面的差不多,强转成struct就行
### 多级指针
反汇编并分析下列结果
```c
void test()
{
char* p1;
char** p2;
char*** p3;
char**** p4;
char***** p5;
char****** p6;
char******* p7;
//反汇编 了解*的反汇编
printf("%d\n",*p1);
//反汇编:*p1 = *(p1+0)
printf("%d\n",*(p1+0));
//反汇编:*(p1+0) = p1
printf("%d %d\n",*(p1+0),p1);
printf("%d %d\n",*(p1+2),p1);
//反汇编:了解**的反汇编
//
printf("%d\n",*p2);
printf("%d\n",*(*p2));
printf("%d %d\n",*(*(p2+0)),*(*(p2+0)+0));
printf("%d\n",*(*(p2+1)));
printf("%d\n",*(*(p2+1)+1));
printf("%d\n",*(*(p2+2)+3));
printf("%d %d\n",*(*(p2+0)+0),p2);
//反汇编 了解***的反汇编
printf("%d\n",*p3);
printf("%d\n",*(*p3));
printf("%d\n",*(*(*p3)));
printf("%d %d\n",*(*(*p3)),*(*(*(p3+0)+0)+0));
printf("%d\n",*(*(*(p3+1)+2)+3));
printf("%d %d\n",*(*(*(p3+1)+2)+3),p3);
}
```
这个挺好玩的,挺有意思
\*(p+i) = p
\* (\*(p+i)+k) = p\
\*(\*(\*(p+i)+k)+m) = p\
\*(\*(\*(\*(\*(p+i)+k)+m)+w)+t) = p\\\\
*() 与 []可以相互转换
#### 练习
1.*() 与 []是可以互换的,也就是说: * (* (p+1)+2) 相当与 p\,那*(p+1) 是否一定等于p\呢? 通过反汇编进行论证。
2.使用数组指针遍历一个一维数组.
##### 练习1
不一定,这里有c语言结合顺序的问题
```c
void test()
{
char **p;
printf("%d %d\n", *(p+1), p, (*(p+1)));
}
```
这个就是一样的,而原来的那个,结合顺序是优先数组*((p+1)) 首先是p+1,char **, +1 就是加上他当砍掉一个\*的数据类型,也就是+4, 而p+1是char \*\*类型,所以(p+1)实际就是char *类型 +2 也就是+8,所以实际合起来就是+0xc,也就是p+1+2 ,猜想归猜想,实践证明
```assembly
0040D8B8 mov eax,dword ptr
0040D8BB mov ecx,dword ptr
0040D8BE movsx edx,byte ptr
0040D8C2 push edx
0040D8C3 mov eax,dword ptr
0040D8C6 mov ecx,dword ptr
0040D8C9 movsx edx,byte ptr
0040D8CD push edx
0040D8CE mov eax,dword ptr
0040D8D1 mov ecx,dword ptr
0040D8D4 movsx edx,byte ptr
0040D8D7 push edx
0040D8D8 push offset string "%d %d\n" (00422fb4)
0040D8DD call printf (0040d720)
```
你看它的取法,就是直接+0xc,也就是一样的说法,而结合顺序对的话,结果就是对的
##### 练习2
```c
void test()
{
int array[] = {12345678,345678,3456,56,78,12,34,0};
int (*p);
p = (int (*))array;
for(int i=0; i<4; i++)
printf("%d\n", (*p));
}
```
easy的一件事,注意的是,*p 还是个地址,至于为什么,请看滴水p32 指针第7讲
### 数组指针与函数指针
大纲:
1. 数组指针
2. 函数指针
#### 练习
1. 下列说法是否正确?为什么?
- 指针的指针:就是指向指针的指针
- 结构指针:就是指向结构的指针
- 数组指针:就是指向数组的指针
- 函数指针:就是指向函数的指
2. 将一个函数存储到数据区,通过指针进行访问.
##### 练习1解答
不正确,前面的指针练习已经说明了,结构指针不一定也得指向结构,他可以指向随便一个地址,强制转换后按结构来解析
##### 练习2解答
```c
unsigned char array[]= { 0x55, 0x8b,0xec,0x83,0xec,0x40,0x53,0x56,0x57,0x8d,0x7d,0xc0,0xb9,0x10,0x00,0x00,0x00,0xb8,0xcc,0xcc,0xcc,0xcc,0xf3,0xab,0xb8,0x01,0x00,0x00,0x00,0x5f,0x5e,0x5b,0x8b,0xe5,0x5d,0xc3};
int test(int a)
{
return 1;
}
int main(int argc, char* argv[])
{
int (*p)(int x);
p = (int (__cdecl *)(int))&array;
printf("%d", p(1));
return 0;
}
```
有点小疑惑,为啥要取地址才能搞定,经过测试后,发觉他们的汇编是一样的,而编译器却不通过,array是char []类型也就是char \*类型,而&array是数组指针,也就是char (*p);至于编译器为什么不通过,这里我也不得而知
##### 练习3解答
3、char数组内容如下:
```c
char data[] = {
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x07,0x09,
0x00,0x20,0x10,0x03,0x03,0x0C,0x00,0x00,0x44,0x00,
0x00,0x33,0x00,0x47,0x0C,0x0E,0x00,0x0D,0x00,0x11,
0x00,0x00,0x00,0x02,0x64,0x00,0x00,0x00,0xAA,0x00,
0x00,0x00,0x64,0x10,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x02,0x00,0x74,0x0F,0x41,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x0A,0x00,
0x00,0x02,0x74,0x0F,0x41,0x00,0x06,0x08,0x00,0x00,
0x00,0x00,0x00,0x64,0x00,0x0F,0x00,0x00,0x0D,0x00,
0x00,0x00,0x23,0x00,0x00,0x64,0x00,0x00,0x64,0x00
};
```
不运行说出下面的结果: 指针定义如下:
\*(\*(px+0)+0) int (*px);
\*(\*(px+1)+0) int (*py)\;
\*(\*(px+2)+3) char (*pz);
\*(\*(\*(py+1)+2)+3) char (*pk)\;
\*(\*(pz+2)+3)
\*(\*(\*(pk+2)+3)+4)
这个也挺简单的
px+0
px+(2\*4*1)
px+(2\*4\*2)+(3\*4)
py+(2\*3\*4\*1) + (3\*4\*2) + (4\*3)
pz+(2\*1\*2)+3
pk+(2\*3\*1\*2)+(3\*1\*3)+(1\*4)
论证
```assembly
0040D428 8B 45 FC mov eax,dword ptr
0040D42B 8B 08 mov ecx,dword ptr
0040D42D 89 4D EC mov dword ptr ,ecx
0040D430 8B 55 FC mov edx,dword ptr
0040D433 8B 42 08 mov eax,dword ptr
0040D436 89 45 EC mov dword ptr ,eax
0040D439 8B 4D FC mov ecx,dword ptr
0040D43C 8B 51 1C mov edx,dword ptr
0040D43F 89 55 EC mov dword ptr ,edx
0040D442 8B 45 F8 mov eax,dword ptr
0040D445 8B 48 3C mov ecx,dword ptr
0040D448 89 4D EC mov dword ptr ,ecx
0040D44B 8B 55 F4 mov edx,dword ptr
0040D44E 0F BE 42 07 movsx eax,byte ptr
0040D452 89 45 EC mov dword ptr ,eax
0040D455 8B 4D F0 mov ecx,dword ptr
0040D458 0F BE 51 19 movsx edx,byte ptr
0040D45C 89 55 EC mov dword ptr ,edx
```
实际确实如此,反汇编结果发觉,居然跟char **额外的相似 沙发占楼,虽然我什么也看不懂,可是我占楼快啊{:1_887:}膜拜 写了这么多,辛苦了,冲这热情也得顶一下 辛苦了,了不起 干活啊,辛苦了辛苦了 滴水三期学的妙啊楼主 很详细了,跟着实操一下才有效果 Deuez 发表于 2020-6-4 20:53
滴水逆向三期吧
是的,感觉补基础挺好的
页:
[1]
2