从0开始写ShellCode加载器0x3-C++/C指针详解
这是从0开始写ShellCode加载器的第3篇文章,文章列表,样本demo已上传到GitHub
前言
作为一名计算机专业的毕业生,C/C++是大学必学内容。但是学了之后除了写写数据结构和算法,其它地方基本没用过。用C++写shellcode加载器的过程中,遇到了很多问题,今天系统的来解决一下,也是为以后的工作打下基础。
开始之前先测试一下,如果以下代码每一行你都能理解,那么这篇文章你没必要看。
1 | char buf; |
觉得晕了的请继续往下看。
指针与变量
要理解c语言的指针,就得先理解c语言变量的定义和存储。我们都很熟悉一个概念,即:*
返回指针型变量所指变量的内容 &
返回指定变量存储空间的首地址。这个概念讲的是*
和&
两个运算符的作用,我们先来看看&,取地址符。
1 | char buf = 'a'; |
char类型变量buf,存储的内容是字符a,占空间大小为1字节。buf变量存储的地址为0061FF17,此地址占空间大小为32比特,4字节。
再来看看*
,取内容运算符。首先第一个要注意的地方是指针类型变量的初始化,只能有两种情况:
1 | 指针变量初始化:将已存在的变量地址赋值 或 NULL |
我们继续往下写
1 | char buf = 'a'; |
此时我们发现,变量buf和指针型变量buf1指向的内存地址是一样的,这也就是说,*buf1和buf是等价的(就好比土豆和马铃薯的关系),更改一个另一个也会改变。
指针与数组
了解了一般变量和指针的联系,我们再来看看数组和指针的联系。
1 | int list[5] = {1,2,3,4,5}; |
在数据结构中我们学习过,数组又叫线性表,在内存中是一段连续的地址空间。数组名就是数组存储空间的首地址,同时也是首元素的地址。所以以上代码中,指针l和j所指向的地址是完全一样的。
那么,既然数组名list、指针l、指针j,在内存中地址是相同的,他们的意义自然也是等价的。
1 | printf("j[1] = %d ,l[1] = %d ,list[1] = %d \n",j[1],l[1],list[1]); |
指针对于数组访问还支持加减法
1 | printf("*j = %d \n",*j);//*j = 1 |
其实数组名也支持上述运算,只不过为了保持代码可读性,常常使用下标访问数组元素。
第一次学习指针时我就有一个疑问,既然指针能直接操作内存中的数据,那么能不能用本程序的指针去修改其它程序的数据呢?我写了一个小程序测验一下,看看这段程序会不会引起系统其它程序的崩溃。
1 | int list[5] = {1,2,3,4,5}; |
可以看到程序自己崩溃了,并没有引起系统的崩溃。
指针与函数
指针型参数
C语言中函数采用值传递机制,在函数中对形参进行任何修改,都不会影响到实际参数,所以函数中无法访问和更新外部定义的变量。
指针型参数将外部变量的地址传递给函数,函数可以通过指针访问和更新外部变量。
1 | void swap(char *a,char *b) //a b为形参 |
对于数组型参数,也是一样的效果,因为我们已经知道了,数组名其实就是指向数组存储空间首地址的指针。
1 | void change(int buf[]) |
注意:对于void change(int buf[])
的形式参数定义,不管有没有数组大小的描述,都不是一个数组名,而是一个指针变量,仅保存了数组首元素的地址,而没有保存整个数组。
1 | void change(int buf[]) |
在分离免杀文章中,我有一个疑惑:data的size为什么是4字节,
char *data = new char[length];
创建对象,大小为什么不是length的长度?现在这个问题解决了,data存储
new char[length]
返回的新创建的数组地址,而不是整个数组。
指针型返回值
函数的返回值类型也可以定义为指针,也就是说函数可以把计算结果放在某个存储单元内,把其存储地址作为返回值送回。
1 | int *newlist(int *list,int size) |
学习了这些,函数传参再也不会晕了。
总结
指针型变量,指向内存中的地址,其值是一个地址
数组名也是指针型变量,指向数组存储空间首地址,可用操作指针的方式操作数组名
指针型参数,接收一个地址作为参数
指针型返回值,返回一个地址
指针型变量初始化
1 | char *buf = 这里一定是个地址 或 NULL |
经过本次学习,对于指针有了更清晰的认识,使用起来也更加得心应手。