0
点赞
收藏
分享

微信扫一扫

pikachu靶场第八关——XSS(跨站脚本)之DOM型xss(附代码审计)

文章目录

一、预定义符号

 二、#define定义常量

三、 #define 定义宏 

四、 带有副作用的宏参数

五、 宏替换的规则

六、宏和函数的对比

七、 #和##

7.1  #运算符

7.2 ##运算符 

八、 命名约定

九、 #undef

十、 命令行定义

十一、 条件编译 

十二、 头文件的包含 

12.1 本地头文件的包含

12.2 库文件的包含

12.3 二者的区别

 十三、 其他预处理指令


 一、预定义符号

 C语言设置了⼀些预定义符号,可以直接使用,预定义符号也是在预处理期间处理的

 //__FILE__    //进行编译的源文件
 //__LINE__    //文件当前的行号
 //__DATE__    //文件被编译的日期
 //__TIME__    //文件被编译的时间
 //__STDC__    //如果编译器遵循ANSI C,其值为1,否则未定义

例如:

#include<stdio.h>

int main()
{
	printf("%s\n", __FILE__);
	printf("%d\n", __LINE__);
	printf("%s\n", __DATE__);
	printf("%s\n", __TIME__);
	//printf("%d", __STDC__);
	return 0;
}

 由于Visual Studio 2019编译器不遵循ANSI C,是未定义的。所以我们注释掉该行,如果是在gcc编译器,则__STDC__就为1

结果如下所示:

 二、#define定义常量

 #define定义常量的语法如下:

#define name  stuff

#define name stuff 将创建一个名为 name 的符号常量,其为 stuff。 

 因为在预处理阶段,文件会将#define展开和删除,所以我们可以通过gcc编译器来观察

 C1,C2和Num都被替换了

那在使用#define的时候,我们需要注意几个点

1.用来省略for循环判断部分的时候,循环条件的判断恒为真,这个循环是死循环

例如:

#define forever for(;;)

 2.最好不用加分号

例如:

#define MAX 1000; 
#define MAX 1000

会将MAX替换成1000;这样会导致在语句后本来就有分号的情况下,替换后导致多出了一个分号 

 出现语法错误

三、 #define 定义宏 

#define机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏 (definemacro) 

下面是宏的声明方式: 

#define name( parament-list ) stuff

其中的  parament-list 是⼀个由逗号隔开的符号表,它们可能出现在stuff中。
注意:
参数列表的左括号必须与name紧邻,如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。 

 例如:

#include<stdio.h>
#define SQUARE( x )  x * x

int main()
{
	int a = SQUARE(5);
	printf("%d\n", a);
	return 0;
}

 这个宏接收一个参数  x
如果在上述声明之后,你把SQUARE( 5 ); 置于程序中,预处理器就会用下面这个x*x表达式替换上面的表达式

但是这个宏存在一定的问题

假设我们传入的是5+2

SQUARE(5+2);

那么这个式子就会被替换成5+2*5+2

原意我们是想把这个式子,算成7*7,但是结果却是十七,

那为了解决这个问题,我们就加括号

这样才能正确计算表达式的值

所以对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的 操作符或邻近操作符之间不可预料的相互作用。

四、 带有副作用的宏参数

当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。

那么什么是副作用呢?

副作用就是表达式求值的时候出现的永久性效果

例如:

int a = 10;    //a = 10
int b = a + 1; //b = 11,a = 10

int a = 10;    //a = 10
int b = ++a;   //b = 11,a = 11

也就是在使用这些运算符的时候,原变量会因为这些操作符而改变本身的值,这样就算有副作用了。就像上述代码我们使用了++a,从而导致a先加后用,让a本身的值改变成了11.而第一个代码却不会改变本身 

MAX宏可以证明具有副作用的参数所引起的问题。

 我们知道宏在预处理阶段会展开替换,所以MAX会变成

五、 宏替换的规则

在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
1.在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
2.替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
3.最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。 

注意:

1. 宏参数和#define定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。

2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。 

六、宏和函数的对比

 现在有一个函数max

#include<stdio.h>
#define MAX(a, b) ((a) > (b)?(a):(b))

int max(int a, int b)
{
	return a > b ? a : b;
}

int main()
{
	int x = 15;
	int y = 9;
	//int z = MAX(x++, y++);
	int z = max(x++, y++);

	printf("x=%d y=%d z=%d\n", x, y, z);
	return 0;
}

但是宏和函数到底哪个更有优势在这样的环境下?

答案是宏,宏通常被应用于执行简单的运算。

 但是宏本身也存在一些缺点:

宏和函数的对比:

七、 #和##

7.1  #运算符

#include<stdio.h>

int main()
{
	int x = 15;
	printf("the value of x = %d\n", x);

	int y = 9;
	printf("the value of y = %d\n", y);
	
	float z = 3.14f;
	printf("the value of z = %f\n", z);

	return 0;
}

 假设现在我们有这些代码,我们可以用函数封装

#include<stdio.h>

void Print(int n)
{
	printf("the value of n = %d\n", n);
}

int main()
{
	int x = 15;
	Print(x);
	int y = 9;
	Print(y);
	float z = 3.14f;
	Print(z);
	return 0;
}

我们发现我们只能打印n ,无法打印,而且受制类型限制,那如果用宏定义来呢?

#include<stdio.h>

#define Print(n,format)	printf("the value of n is "format"\n",n);

int main()
{
	int x = 15;
	Print(x,"%d");

	int y = 9;
	Print(y,"%d");
	
	float z = 3.14f;
	Print(z,"%f");

	return 0;
}

我们发现结果仍然是打印n,但是我们解决了类型限制的问题

我们发现在format前后都是字符串,这时候我们就可以用#这个运算符了,我们将n单独拿出来前后成为一个字符串,并将其搞成#n的形式

#define Print(n,format)	printf("the value of "#n" is "format"\n",n);

结果如下: 

7.2 ##运算符 

这样无数堆叠类型,太过于繁琐,我们可以尝试用宏来解决这个问题 

#define GENERIC_MAX(type) \
type type##max(type x,type y)\
{\
    return x > y?x:y;\
} 
  • 代码解释:

  • #define GENERIC_MAX(type):这行定义了一个宏,宏的名称为GENERIC_MAX,并且带有一个参数type

  • type type##max(type x, type y):这行定义了一个函数模板。由于##是连接记号,这里的type##max将会在宏展开时将type替换进去,所以对于不同的type都会生成不同名字的函数。这个函数模板接受两个参数,类型为type,并且生成一个函数来返回这两个参数中的较大者。

  • \:反斜杠表示换行符,用于将宏定义延续到下一行。这样做是为了将宏定义分成多行以提高可读性。(也叫做续行符

  • {}:大括号内是函数的具体实现,其内容是返回两个参数中的较大者。

总之,这段宏定义的作用是根据所提供的类型type,创建一个名为typemax的函数模板,该函数模板接受两个相同类型的参数,并返回其中的较大者。在代码中调用这个宏并提供不同的类型type时,会生成对应类型的函数模板,可以方便地生成不同类型的最大值函数。这段代码是一个带参数的宏定义,用于创建一个通用的求最大值函数

通过预处理发现,我们生成了两个类型的函数 

 

八、 命名约定

九、 #undef

 使用如下:

#include<stdio.h>

#define M 100

int main()
{
	int x = M;
	printf("%d\n", x);

#undef M
#define M 20

	int y = M;
	printf("%d",y);
	
	return 0;
}

十、 命令行定义

许多C的编译器提供了⼀种能力,允许在命令行中定义符号。用于启动编译过程。
例如:当我们根据同⼀个源文件要编译出一个程序的不同版本的时候,这个特性有点用处。(假定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外⼀个机器内存大些,我们需要一个数组能够大些。)

#include<stdio.h>

int main()
{	
    int i = 0;
	int array[SZ];
	for (i = 0; i < SZ; i++)
	{
		array[i] = i;
	}
	for (i = 0; i < SZ; i++)
	{
		printf("%d ", array[i]);
	}
	printf("\n");
	return 0;
}

输入以下指令我们就可以看到结果 

 

十一、 条件编译 

在编译⼀个程序的时候我们如果要将⼀条语句(⼀组语句)编译或者放弃是很方便的。因为我们有条件编译指令。

比如说:
调试性的代码,删除可惜,保留⼜碍事,所以我们可以选择性的编译。 

#include<stdio.h>
#define FLAG 1
int main()
{
	//条件编译1
#if FLAG == 1
	printf("hehe\n");
#endif
	//条件编译2
	//多分支的条件编译
#if FLAG ==1
	printf("1\n");
#elif  FLAG == 2
	printf("2\n");
#else
	printf("3\n");
#endif
	//条件编译3
	//判断是否被定义
#if defined(MAX)
	printf("MAX defined\n");
#endif

#if !defined(MAX)
	printf("MAX not defined\n");
#endif
//写法一样
#ifdef MAX
	printf("MAX defined\n");
#endif

#ifndef MAX
	printf("MAX not defined\n");
#endif
	//条件编译四
	//嵌套指令
#if defined(MID)
	#if defined(MIN)
	printf("MIN not defined\n");
	#elif defined(MAX)
	printf(xxx);
	#endif

#elif defined(xx)
	#if 
	#endif

#endif
	return 0;
}

十二、 头文件的包含 

12.1 本地头文件的包含

#include "filename.h"

12.2 库文件的包含 

#include <filename.h>

 12.3 二者的区别

 十三、 其他预处理指令

  • #error

    #error 指令用于在预处理阶段生成一个错误消息,并终止程序的编译。通常用于在特定条件下中断编译过程,例如在条件判断中发现不支持的编译选项或条件时,可以使用 #error 指令中断编译并显示自定义的错误消息。

  • #pragma

    #pragma 指令用于向编译器发出特定的实现-defined 的指令,通常用于设定编译的特定行为或者使用特定的扩展特性。它是编译器指令的一种标准方式,不同的编译器可能支持不同的 #pragma 指令。

  • #line

    #line 指令用于改变源代码行号信息,可以在预处理阶段修改行号和文件名,这对于调试和跟踪预处理后的代码很有用。通在代码生成器或者宏定义中使用,可以帮助调试器或者日志工具准确定位到源码中的位置。

  • #error

    • 作用:用于在预处理阶段生成一个编译错误,并显示指定的错误消息。
    • 示例#error "Something went wrong!",这将导致编译器输出错误消息"Something went wrong!"并终止编译过程。
  • #pragma

    • 作用:用于向编译器发出特定的命令或指示,通常用于控制编译器的行为。
    • 示例#pragma warning(disable: 1234),这个指令可以告诉编译器禁用特定的警告。
  • #line

    • 作用:用于修改编译器在报告错误时所使用的行号和文件名。
    • 示例#line 100 "myfile.c",这个指令将当前行号设置为100,并且将当前文件名设置为"myfile.c"。
举报

相关推荐

0 条评论