吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 3855|回复: 13
收起左侧

[原创] 逆向笔记之函数与指针(三)

[复制链接]
冰露㊣神 发表于 2020-6-4 18:56

title: 逆向笔记之函数与指针(三)
tags:

  • reverse
  • 逆向

逆向笔记之函数与指针(三)

本文笔记均来自于对滴水逆向教程的学习,通过学习教程记录的笔记,个人所见所得。

大纲:

  1. 堆栈图学习
  2. 函数
  3. 函数调用
  4. 调用约定
  5. 入口点
  6. 函数分析

windows堆栈

  1. 先进后出
  2. 向低地址扩展

什么是堆栈平衡: 简单的说就是还原现场

问: cmp ebp,esp后,如果ebp!=esp是否可以跳转

答: 可以,因为z标志位改了就行

堆栈图练习

堆栈图练习1

单层函数的堆栈调用图

****

堆栈图练习2

带子函数的堆栈调用图

堆栈图练习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

至于堆栈图,不画了,我有点基础,大概懂这些了

函数分析

空函数分析

无参数、无返回值的函数格式

void function()
{
    //代码 每行以;结尾
}

两个数加法函数分析
int func(int x,int y)
{
    return x+y;
}

三个整数加法操作
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;
}

5个整数加法并要求用前两个函数
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;
}

递归反汇编分析练习
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;
}

大概函数逆向出来了,接着编写代码,运行一下就知道了?

我跟踪了下整个过程,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

参数和返回值以及局部变量

//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[3] = {1,2,3};与 char arr[4] = {1,2,3,4};哪个更节省空间,从反汇编的角度来说明你的观点

  3. 找出下面赋值过程的反汇编代码

    void Function()
    {
       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];
    }
  4. 选做题: 桶排序,学校c语言老师教过了,不搞了

第一题解答

高位存在edx, 低位存在eax里

实验证明

c语言代码:

__int64 test()
{
    __int64 x = 0x1234567890;
    return x;
}

int main(int argc, char* argv[])
{
    test();
    return 0;
}

汇编代码:

第二题解答

char arr[4] = {1,2,3,4};更节省空间,因为char arr[3]虽然只用了三个空间,而实际还是占用一个单元,4个字节,剩余的一个字节不会被使用,浪费了

第三题解答
# int x =1;
mov         dword ptr [ebp-4],1 
# int y=2;
mov         dword ptr [ebp-8],2 
# int arr[10] = {1,2,3,4,5,6,7,8,9,10};
mov         dword ptr [ebp-34h],1
mov         dword ptr [ebp-30h],2
mov         dword ptr [ebp-2Ch],3
mov         dword ptr [ebp-28h],4
mov         dword ptr [ebp-24h],5
mov         dword ptr [ebp-20h],6
mov         dword ptr [ebp-1Ch],7
mov         dword ptr [ebp-18h],8
mov         dword ptr [ebp-14h],9
mov         dword ptr [ebp-10h],0Ah
# r = arr[1];
mov         eax,dword ptr [ebp-30h]
mov         dword ptr [ebp-0Ch],eax
# r = arr[x];
mov         ecx,dword ptr [ebp-4]
mov         edx,dword ptr [ebp+ecx*4-34h]
mov         dword ptr [ebp-0Ch],edx
#r = arr[x+y];
0041073C   mov         eax,dword ptr [ebp-4]
0041073F   add         eax,dword ptr [ebp-8]
00410742   mov         ecx,dword ptr [ebp+eax*4-34h]
00410746   mov         dword ptr [ebp-0Ch],ecx
# r = arr[x*2+y];
00410749   mov         edx,dword ptr [ebp-4]
0041074C   mov         eax,dword ptr [ebp-8]
0041074F   lea         ecx,[eax+edx*2]
00410752   mov         edx,dword ptr [ebp+ecx*4-34h]
00410756   mov         dword ptr [ebp-0Ch],edx

对比分析结果:

  1. 越先定义的越靠近栈底
  2. 数组寻址,如果是一个常量,他直接通过ebp-几完成
  3. 数组寻址,如果是一个变量,他通过计算这个变量的值后,通过[ebp-34+变量值4]寻址,解释,ebp-34是数组的第一个元素地址,而 4是因为这是int型数组,一个int占4个字节
第四题解答

多维数组跟一维数组在内存中是一样的

调用约定

调用约定 参数压栈顺序 平衡堆栈
__cdecl 从右至左入栈 调用者清理栈
__stdcall 从右至左入栈 自身清理堆栈
__fastcall ECX/EDX传送前两个                                    剩下:从右至左入栈 自身清理堆栈

__cdecl

外平衡

push 1
push 2
call 
add esp,8
int __cdecl Plus(int a,int b)
{
    return a+b;
}

__stdcall

内平衡

push 1
push 2
call //call里平衡 ret
int __stdcall Plus(int a,int b)
{
    return a+b;
}

__fastcall

利用寄存器传参

mov edx,2
mov ecx,1
call 
int __fastcall Plus(int a, int b)
{
    return a+b;
}
练习

将下列代码裸函数实现

int plus(int x, int y, int z)
{
    int a=2;
    int b=3;
    int c=4;
    return x+y+z+a+b+c;
}
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:[ebp-0x40]
        rep stosd

        mov eax,2
        add eax,3
        add eax,4
        add eax,dword ptr ds:[ebp+0x8]
        add eax,dword ptr ds:[ebp+0xc]
        add eax,dword ptr ds:[ebp+0x10]

        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:[0x12345678]

全局变量就是所谓的基址

局部变量的特点:

  1. 局部变量在程序编译完成后并没有分配固定的地址.

  2. 在所属的方法没有被调用时,局部变量并不会分配内存地址,只有当所属的程序被调用了,才会在堆栈中分配内存.

  3. 当局部变量所属的方法执行完毕后,局部变量所占用的内存将变成垃圾数据.局部变量消失.

  4. 局部变量只能在方法内部使用,函数A无法使用函数B的局部变量.

  5. 局部变量的反汇编识别:[ebp-4],[ebp-8],[ebp-0xc]

函数参数确认

步骤1: 观察调用处代码

push 3

push 2

push 1

call 0040100f

步骤2: 找到平衡堆栈的代码继续论证

call 0040100f

add esp,0Ch

或者函数内部

ret 4/8/0xc/0x10

最后,两者一综合,函数的参数个数基本确定

  1. 参数传递未必都是通过堆栈,还可能通过使用寄存器
  2. 函数调用处的代码无法查看.

函数分析步骤

练习

自行分析如下代码,并还原成c语言

num = 0;
void func(int num1, int num2)
{
    if(num1 > num2)
    {
        num += num2;
    }
}
以下两题找出原因,并解释

有趣的返回地址

void HelloWorld()
{
    printf("Hello World");
    getchar();
}
void fun()
{
    int arr[5]={1,2,3,4,5};
    arr[6] = (int)HelloWorld;
}
int main()
{
    fun();
    return 0;
}

永不磨灭的Hello World

void Fun()
{
    int i;
    int arr[5] = {0};
    for(i=0; i<=5; i++)
    {
        arr[i] = 0;
        printf("Hello World\n");
    }
}

这个原因我就不自己测试了

第一题,arr[6]覆盖了返回地址,傻逼了,因为arr[5]就能覆盖ebp,arr[6] 肯定能覆盖返回地址啊

第二题, arr[i]覆盖了i,至于为什么先定义的能被覆盖,想想堆栈,先定义的,就是说我需要一块一个这个的内存空间,所以int i肯定是比较靠近ebp的,接下来才是arr[5],所以覆盖得到i

指针

指针基础学习

大纲:

  1. 宽度
  2. 声明
  3. 赋值
  4. ++ --
  5. 加/减 一个整数
  6. 求差值
  7. 比较

这些我都学过了,c语言基础相对较好,略过

char**** a;
char**** b;
a = (char****)200;
b = (char****)100;
int x=a-b;

结果就是砍掉一个*,在除以数据宽度就行

总结:

  1. 带有 的变量类型的标准写法:变量类型 变量名
  2. 任何类型都可以带 加上 以后是新的类型
    • 可以是任意多个
  3. 带 * 类型的变量赋值时只能使用“完整写法”.
  4. 类型的变量宽度永远是4字节、无论类型是什么,无论有几个.
  5. 不带*类型的变量,++或者--  都是假1 或者减1
  6. 带*类型的变量,可是进行++ 或者 --的操作
  7. 类型的变量,++  或者  --  新增(减少)的数量是去掉一个后变量的宽度
  8. 带*类型的变量可以加、减一个整数,但不能乘或者除.
  9. 带*类型变量与其他整数相加或者相减时:
    • 类型变量  + N  =  带 类型变量 + N(去掉一个后类型的宽度)
    • 类型变量 - N  =  带 类型变量 - N(去掉一个后类型的宽度)
  10. 两个类型相同的带 * 类型的变量可以进行减法操作.
  11. 想减的结果要除以去掉一个 * 的数据的宽度.
  12. 带 * 的变量,如果类型相同,可以做大小的比较。
练习

练习全都是针对32位程序

  1. char类型占几字节?char*类型占几字节?int*****占几字节?
  2. char** arr[10] 占多少个字节?
练习1解答

1个字节,char 4个字节,int 4个字节

练习2解答

40个字节,就是定义了一个数组,存的类型为char**,所以就是10 × 4

练习3解答

带*结构体的加减法

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

void test1()
{
    Student**** s1;
    Studnet**** s2;
    int x;
    s1 = (Student****)200;
    s2 = (Student****)100;
    x = s1 - s2; //x=?
}

先盲猜,x=(200-100)/4=25

void test2()
{
    Student* s;
    s = (Student*)100;
    s++; //s=?
    s = s + 2; //s=?
    s = s - 3; //s=?
}

盲猜,s=108, s=124,s=100

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代码的反汇编

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;
}

利用堆栈图,分析下,相对不怎么难,二级**而已

分析下列代码反汇编

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;

}

结果就是p7->p6->p5->p4->p3->p2->p1->p

p7存p6地址,p6存p5地址,一直存下去

还行,逆向起来好像并不怎么复杂

完成代码,实现数组值的互换

void Function()
{
    int arr[5]={1,2,3,4,5};
    //添加代码,使用指针,将数组得到值倒置

    //打印,不需要修改
    for(int k=0; k<5; k++)
    {
        printf("%d\n", *(p+k));
    }
}
void Function()
{
    int arr[5]={1,2,3,4,5};
    //添加代码,使用指针,将数组得到值倒置
    int *p = arr;
    int *q = &arr[4];
    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));
    }
}

指针与数组

编写一个函数,能够打印任意整形数组的值。

void PrintArray(int arr[],int nLength)
{
    for(int i=0;i<nLength;i++)
    {
        printf("%d\n",arr[i]);
    }
}

观察反汇编

通过这个顺便复习了for循环

练习

模拟实现CE的数据搜索功能:这一堆数据中存储了角色的血值信息,假设血值的类型为int类型,值为100(10进制)

请列出所有可能的值以及该值对应的地址.

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[0]);
    int i=0;
    while(i< length-type)
    {
        if(type == 4)
        {
            if(*(int*)&data[i] == num)
            {
                printf("address:%x , data:%d\n",&data[i],(int)num);
            }
        }else if(type == 2)
        {
            if(*(short*)&data[i] == (short)num)
            {
                printf("address:%x , data:%d\n",&data[i],(short)num);
            }
        }else
        {
            if(*(char*)&data[i] == (char)num)
            {
                printf("address:%x , data:%d\n",&data[i],(char)num);
            }
        }
        i += 1;

    }
}

int main(int argc, char* argv[])
{
    printf("data start from:%x\n", data);
    search(4,100);
    return 0;
}

对其进行解耦,接口不改变

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[i] == num)
        {
            printf("address:%x , data:%d\n",&data[i],(int)num);
        }
        i++;
    }
}
void search_short(int num, int length)
{
    int i=0;
    while(i<length-2)
    {
        if(*(short*)&data[i] == num)
        {
            printf("address:%x , data:%d\n",&data[i],(short)num);
        }
        i++;
    }
}

void search_char(int num, int length)
{

    int i=0;
    while(i<length-1)
    {
        if(*(char*)&data[i] == num)
        {
            printf("address:%x , data:%d\n",&data[i],(char)num);
        }
        i++;
    }
}
void search(int type, int num)
{
    int length = sizeof(data)/sizeof(data[0]);
    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;
}

指针与字符串

char* x = "china"; //china存在常量区
char y[] = "china";

常见字符串操作,学过了

练习

模拟实现CE的数据搜索功能:这一堆数据中存储了角色的名字信息(WOW),请列出角色名的内存地址.

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. 编写函数,遍历上面数据中所有角色名字.
#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[0]);
    int i=0; 
    while(i < all -length)
    {
        if(strncmp(&pData[i],pRoleName,length) == 0)
        {
            return &pData[i];
            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[5] 数组,并为数组赋值(使用&)
  2. 创建一个字符指针数组,存储所有的C的关键词(查资料找),并全部打印出来.
练习1解答
void test()
{
    int array[5] = {1,2,3,4,5};
    int* arr[5] = {&array[0],&array[1],&array[2],&array[3],&array[4]};
};
练习2解答
void test1()
{
   char* arr[5] ={
        "auto",
        "break",
        "case",
        "char",
        "const"
    };
    for(int i=0; i<5; i++)
        printf("%s", arr[i]);
}
练习3解答

查找这些数据中,有几个id=1 level=8的结构体信息。

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               
};

代码编写

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[0]);
    while(i<length-8)
    {
        Player* temp = (Player*) &data[i];
        if(temp->id == 1 && temp->level == 8)
        {
            printf("address:%x id=%d, level=%d\n",&data[i], temp->id, temp->level);
        }
        i++;
    }
}
int main(int argc, char* argv[])
{
    search_Struct();
    return 0;
}

跟前面的差不多,强转成struct就行

多级指针

反汇编并分析下列结果

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[0]               

    printf("%d %d\n",*(p1+0),p1[0]);                

    printf("%d %d\n",*(p1+2),p1[2]);                

    //反汇编:了解**的反汇编              

    //              

    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[0][0]);                

    //反汇编 了解***的反汇编             

    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[1][2][3]);            
}

这个挺好玩的,挺有意思

*(p+i) = p[i]
* (*(p+i)+k) = p[i][k]
*(*(*(p+i)+k)+m) = p[i][m]
*(*(*(*(*(p+i)+k)+m)+w)+t) = p[i][k][m][w][t]
*() 与 []可以相互转换         

练习
  1. () 与 []是可以互换的,也就是说: ( (p+1)+2) 相当与 p[1][2],那(p+1)[2] 是否一定等于p[1][2]呢? 通过反汇编进行论证。
  2. 使用数组指针遍历一个一维数组.
练习1

不一定,这里有c语言结合顺序的问题

void test()
{
    char **p;
    printf("%d %d\n", *(p+1)[2], p[1][2], (*(p+1))[2]);
}

这个就是一样的,而原来的那个,结合顺序是优先数组*((p+1)[2]) 首先是p+1,char *, +1 就是加上他当砍掉一个*的数据类型,也就是+4, 而p+1是char **类型,所以(p+1)[2]实际就是char 类型 +2 也就是+8,所以实际合起来就是+0xc,也就是p+1+2 ,猜想归猜想,实践证明

0040D8B8   mov         eax,dword ptr [ebp-4]
0040D8BB   mov         ecx,dword ptr [eax+4]
0040D8BE   movsx       edx,byte ptr [ecx+2]
0040D8C2   push        edx
0040D8C3   mov         eax,dword ptr [ebp-4]
0040D8C6   mov         ecx,dword ptr [eax+4]
0040D8C9   movsx       edx,byte ptr [ecx+2]
0040D8CD   push        edx
0040D8CE   mov         eax,dword ptr [ebp-4]
0040D8D1   mov         ecx,dword ptr [eax+0Ch]
0040D8D4   movsx       edx,byte ptr [ecx]
0040D8D7   push        edx
0040D8D8   push        offset string "%d %d\n" (00422fb4)
0040D8DD   call        printf (0040d720)

你看它的取法,就是直接+0xc,也就是一样的说法,而结合顺序对的话,结果就是对的

练习2
void test()
{
    int array[] = {12345678,345678,3456,56,78,12,34,0};

    int (*p)[2];

    p = (int (*)[2])array;
    for(int i=0; i<4; i++)
        printf("%d\n", (*p)[i]);

}

easy的一件事,注意的是,*p 还是个地址,至于为什么,请看滴水p32 指针第7讲

数组指针与函数指针

大纲:

  1. 数组指针
  2. 函数指针
练习
  1. 下列说法是否正确?为什么?
    • 指针的指针:就是指向指针的指针
    • 结构指针:就是指向结构的指针
    • 数组指针:就是指向数组的指针
    • 函数指针:就是指向函数的指
  2. 将一个函数存储到数据区,通过指针进行访问.
练习1解答

不正确,前面的指针练习已经说明了,结构指针不一定也得指向结构,他可以指向随便一个地址,强制转换后按结构来解析

练习2解答
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)[len];  至于编译器为什么不通过,这里我也不得而知

练习3解答

3、char数组内容如下:           

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)[2];      

*(*(px+1)+0)                   int (*py)[2][3];         

*(*(px+2)+3)                   char (*pz)[2];            

*(*(*(py+1)+2)+3)            char (*pk)[2][3];                     

*(*(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)

论证

0040D428 8B 45 FC             mov         eax,dword ptr [ebp-4]
0040D42B 8B 08                mov         ecx,dword ptr [eax]
0040D42D 89 4D EC             mov         dword ptr [ebp-14h],ecx
0040D430 8B 55 FC             mov         edx,dword ptr [ebp-4]
0040D433 8B 42 08             mov         eax,dword ptr [edx+8]
0040D436 89 45 EC             mov         dword ptr [ebp-14h],eax
0040D439 8B 4D FC             mov         ecx,dword ptr [ebp-4]
0040D43C 8B 51 1C             mov         edx,dword ptr [ecx+1Ch]
0040D43F 89 55 EC             mov         dword ptr [ebp-14h],edx
0040D442 8B 45 F8             mov         eax,dword ptr [ebp-8]
0040D445 8B 48 3C             mov         ecx,dword ptr [eax+3Ch]
0040D448 89 4D EC             mov         dword ptr [ebp-14h],ecx
0040D44B 8B 55 F4             mov         edx,dword ptr [ebp-0Ch]
0040D44E 0F BE 42 07          movsx       eax,byte ptr [edx+7]
0040D452 89 45 EC             mov         dword ptr [ebp-14h],eax
0040D455 8B 4D F0             mov         ecx,dword ptr [ebp-10h]
0040D458 0F BE 51 19          movsx       edx,byte ptr [ecx+19h]
0040D45C 89 55 EC             mov         dword ptr [ebp-14h],edx

实际确实如此,反汇编结果发觉,居然跟char **额外的相似

免费评分

参与人数 12吾爱币 +18 热心值 +11 收起 理由
Hmily + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
mzq123000 + 1 我很赞同!
CraftDeadMRC + 1 + 1 用心讨论,共获提升!
gongcsxue + 1 + 1 谢谢@Thanks!
镇北看雪 + 1 + 1 我很赞同!
媚眼的丹客 + 1 + 1 用心讨论,共获提升!
nihaoz + 1 + 1 我很赞同!
dongfang155 + 1 有滴水三期那味儿了奥
又红又专 + 2 + 1 辛苦
huayugongju + 1 + 1 热心回复!
一只查小渣 + 1 + 1 谢谢@Thanks!
细水流长 + 1 + 1 热心回复!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

tanzhiliang 发表于 2020-6-4 19:00
沙发占楼,虽然我什么也看不懂,可是我占楼快啊膜拜
头像被屏蔽
Deuez 发表于 2020-6-4 23:36
gunxsword 发表于 2020-6-4 19:06
macolma 发表于 2020-6-4 19:23
辛苦了,了不起
pandorazx 发表于 2020-6-4 20:01
干活啊,辛苦了辛苦了
小朋友呢 发表于 2020-6-4 20:03
滴水三期学的妙啊楼主
LoongKing 发表于 2020-6-4 20:33
很详细了,跟着实操一下才有效果
头像被屏蔽
Deuez 发表于 2020-6-4 20:53
提示: 作者被禁止或删除 内容自动屏蔽
 楼主| 冰露㊣神 发表于 2020-6-4 22:37
Deuez 发表于 2020-6-4 20:53
滴水逆向三期吧

是的,感觉补基础挺好的
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-27 16:55

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表