Windows 逆向-指针数组和结构体指针

Oyst3r 于 2024-01-20 发布

前言

看到这里了,我感觉其实再看到这些新的名词也不害怕了,比如上节笔记其实就已经提到了指针数组,那结构体指针终究还是一个指针呗,一个指向结构体的指针,这个如果有些 php 以及 python 的开发基础那就更好懂了,就和操作一个类一样

课堂

指针数组

char* arr[5] = {0};   //占4 * 5 = 20字节

arr[0] = (char*)1;    //可以将int强转成char*类型,就可以存储在char*类型的数组中

char a1 = 'A';
char a2 = 'B';
char a3 = 'C';
char a4 = 'D';
char a5 = 'E';

char* p1 = &a1;	    //因为一个char类型变量加&,表示取此变量的地址,类型为char加一个*号,即char*类型
char* p2 = &a2;
char* p3 = &a3;
char* p4 = &a4;
char* p5 = &a5;

char* arr1[5] = {p1,p2,p3,p4,p5};   //char*类型就可以直接赋值

指针数组存储字符串首地址

因为字符串存储在常量区中,如果使用指针指向常量区字符串,那么这个这个指针就是char*类型的,自然可以存在指针数组中。按照这个道理,也可以直接将字符串存储在char*类型的指针数组中

上面这点是不是就是上篇文章所涉及的

char* str[] = {"if","while","a"};

上面这段代码其实就等价于

char* str1 = "if";
char* str2 = "while";
char* str3 = "a";
char* str[] = {str1,str2,str3};

结构体指针

又引入了一个新的类型,指针这块每引入一个新的类型,咱们就从像第一节探测带星号的类型一样开始学它,就能把它给学透彻

由于之前就说过所有带星号的类型的宽度都是 4 个字节,这里就不再多说了,那就直接从运算开始说起

#include "stdafx.h"
struct Student{  //根据字节对齐,可以判断此结构体宽度为(2+2) + 4 + (1+3) = 12
    short a;
    int b;
    char c;
};
int main(int argc,char* argv[]){
    Student stu;
    printf("%d",sizeof(stu));  //12
    Student* pstu = (Student*)100;   //正常情况下使用Student* p = &stu,用来获取结构体变量stu的首地址,这里为了方便查看++结果
    pstu++;    100 + 12 = 112
    return 0;
}
#include "stdafx.h"
struct Student{  //根据字节对齐,可以判断此结构体宽度为(2+2) + 4 + (1+3) = 12
    short a;
    int b;
    char c;
};
int main(int argc,char* argv[]){
    Student stu;
    Student* pstu = (Student*)100;  //强转。这就是结构体指针
    pstu = pstu + 10;    //100 + 12 * 10 = 220
	printf("%d",pstu);  //220
	return 0;
}
#include "stdafx.h"
struct Student{  //根据字节对齐,可以判断此结构体宽度为(2+2) + 4 + (1+3) = 12
    short a;
    int b;
    char c;
};
int main(int argc,char* argv[]){
    Student stu;
    Student* pstu = (Student*)100;  //强转
    Student* pstu2 = (Student*)200;
	printf("%d",pstu2 - pstu);  //(200 - 100) // 12 = 8
	return 0;
}

通过结构体指针操作数据

这个其实就可以类比操作一个一维数组,这里还不能将它和数组指针类比,因为数组指针指向的是一个数组,而这个数组其实是一个地址,所有还得指一次才能到数据,而结构体指针就不一样了,指一次就到数据了,这两者的比较咱们仔细想想,其实早就在函数传参那里就有所体现出不一样了

结构体变量前加&,得到结构体的首地址,类型为结构体*。所以定义结构体指针去接收这个结果,那么此指针中存的就是此结构体变量的首地址,通常我们说指针指向结构体变量

那么这个结构体指针就可以通过p->结构体中变量的方式来读取结构体中的变量(原理是根据宽度从结构体起始地址偏移,而不是根据变量的名字来查找的!!!)

#include "stdafx.h"
struct Student{  //根据字节对齐,可以判断此结构体宽度为(2+2) + 4 + (1+3) = 12
    short a;
    int b;
    char c;
};
int main(int argc,char* argv[]){
   	//创建结构体并赋值
   	Student stu;
    stu.a = 1;
    stu.b = 2;
    stu.c = 3;

    //现在声明并赋值结构体指针。即结构体*类型的变量
    Student* pstu = &stu;   //pstu中存储的就是stu结构体首地址!!!

    //通过 pstu-> 的方式来访问结构体中的变量的值
    printf("%d %d %d\n",pstu->a,pstu->b,pstu->c);  //1 2 3

    //通过结构体指针来修改结构体中变量的值
    pstu->a = 10;
    pstu->b = 20;
    pstu->c = 30;
    printf("%d %d %d",pstu->a,pstu->b,pstu->c);  //10 20 30
    return 0
}

咱们看一下它的反汇编,可以发现这个并不是通过比如 ebp 减多少多少去操作的,而是保留了一个基地址,然后根据内存对齐的知识去改它

这里再回顾一下基地址吧,我写了个比较易懂的代码

你看,编译器还是分的很清楚的嘛,有 8 有 4 有 3,所有结构体指针通过这种基地址加偏移来读取数值还是很准确的

作业

1.

#include "stdafx.h"
int main(int argc,char* argv[]){
    int a,b,c,d,e;
    int* arr[5] = {0};
    *arr = &a;
    *(arr + 1) = &b;
    *(arr + 2) = &c;
    *(arr + 3) = &d;
    *(arr + 4) = &e;
    return 0;
}
//或者
int main(int argc, char* argv[])
{
	int num[5] = {1,2,3,4,5};
	int* arr[5] = {num,&num[1],&(*(num+2)),num+3,&num[4]};
	for(int i = 0;i < 5;i++){
		printf("%x ",*(arr+i));  //打印arr数组中的值
	}
	printf("\n");
	for(int j = 0;j < 5;j++){
		printf("%d ",**(arr+j));  //打印arr数组中的地址值对应的内存单元中的数值,即num中的值
	}
	return 0;
}

2.这个还是好好想想吧,当时一做完这个小项目,就感觉这不就和数组指针差不多嘛,都是指向了一个数组,不过数组指针的话,多加了一个可以定义的数组的长度,这样在操作数据的时候就更加灵活了,比如*p++就是加数组的数据宽度,*p 相当于得到了数组的首地址,那么**p 就能读取数据了,但不同就在这里,这里的*p 就能得到数据了,**p 可以继续得数据,下面会验证

// 1208.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
int main(int argc,char* argv[]){
    char* keyword[] = {
			"auto",  //每个常量区中的字符串结尾都有'\0'
			"short",
			"int",
			"long",
			"float",
			"double",
			"char",
			"struct",
			"union",
			"enum",
			"typedef",
			"const",
			"unsigned",
			"signed",
			"extern",
			"register",
			"static",
			"volatile",
			"void",
			"if",
			"else",
			"switch",
			"case",
			"for",
			"do",
			"while",
			"goto",
			"continue",
			"break",
			"default",
			"sizeof",
			"return",
			0
    };
	char** p = keyword;
	while(*p != 0){
		printf("%c\n",*p);
		p++;
	}

	return 0;

}

咱们在此基础上做个小实验哈,证明它和数组指针还是有那么一丝丝联系的,不同的可能就是*p++的宽度不一样,数组指针是可以自定义的,所有它操作数据也就有更多的手段,我们上面代码的 char **p = keyword 何尝不是 p 指向一个数组,然后*p 得到了具体的字符串存储的地址(这步在数组指针得**P 才能做到),然后**p 得具体值

先看原本的**p,就直接得到了常量区的值,这里不能用%s 打印,我就用%c 意思一下

然后再看数组指针的,我稍微改了一下代码

#include "stdafx.h"
int main(int argc,char* argv[]){
    char* keyword[] = {
		"auto",  //每个常量区中的字符串结尾都有'\0'
			"short",
			"int",
			"long",
			"float",
			"double",
			"char",
			"struct",
			"union",
			"enum",
			"typedef",
			"const",
			"unsigned",
			"signed",
			"extern",
			"register",
			"static",
			"volatile",
			"void",
			"if",
			"else",
			"switch",
			"case",
			"for",
			"do",
			"while",
			"goto",
			"continue",
			"break",
			"default",
			"sizeof",
			"return",
			0
    };
	int (*p)[1];
	p = (int(*)[1])keyword;
	printf("%s\n",**p);

	printf("-------------------");
		while(**p != 0){
 			printf("%s\n",**p);
			p++;
		}

		return 0;

}

然后下面是运行结果

欸怎么说还是得对%s 理解深刻,它是给个地址,然后开始从这个地址开始打印,大家一定要注意

3.这个还是模拟 CE 查找的功能,但是明显这两个数据是不一样的,所以定义一个结构体指针最合适不过,然后把数据的首地址给这个指针,那么就可以很随心所欲的操作数据了

下面给出我的代码

char data[100] = {
	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 figure{
	int id;
	int level;
}Player;

void search_struct(int id,int level){

	Player* search;

	for(int i=0;i<100-7;i++){

		search = (Player*)(data+i);
		if(search->id == id&&search->level == level){
			printf("%x\t%d\t%d",search,id,level);
		}
	}

}

int main(int argc,char* argv[]){

	Player player;
	player.id = 1;
	player.level = 2;
	search_struct(player.id,player.level);
	return 0;
}

作者奔溃

啊啊啊啊好累,明天要早睡啊啊啊啊啊啊啊,呜呜呜裂开,讨厌 study

奔溃了