pwn常见函数简记

pwn题做多了,总是会遇到一些奇形怪状的函数,有的时候久了没看又会忘掉,就开篇专门来记一下这些东西吧

strtoul()

头文件:#include <stdlib.h>

strtoul() 函数源自于“string to unsigned long”,用来将字符串转换成无符号长整型数(unsigned long),其原型为:

unsigned long strtoul (const char* str, char** endptr, int base);

【参数说明】str 为要转换的字符串,endstr 为第一个不能转换的字符的指针,base 为字符串 str 所采用的进制。

【函数说明】strtoul() 会将参数 str 字符串根据参数 base 来转换成无符号的长整型数(unsigned long)。参数 base 范围从2 至36,或0。参数 base 代表 str 采用的进制方式,如 base 值为10 则采用10 进制,若 base 值为16 则采用16 进制数等。

strtoul() 会扫描参数 str 字符串,跳过前面的空白字符(例如空格,tab缩进等,可以通过 isspace() 函数来检测),直到遇上数字或正负符号才开始做转换,再遇到非数字或字符串结束时(‘\0’)结束转换,并将结果返回。

两点注意:
当 base 的值为 0 时,默认采用 10 进制转换,但如果遇到 ‘0x’ / ‘0X’ 前置字符则会使用 16 进制转换,遇到 ‘0’ 前置字符则会使用 8 进制转换。
若 endptr 不为NULL,则会将遇到的不符合条件而终止的字符指针由 endptr 传回

若 endptr 为 NULL,则表示该参数无效,或不使用该参数。

【返回值】返回转换后的无符号长整型数

如果不能转换或者 str 为空字符串,那么返回 0;

如果转换得到的值超出unsigned long int 所能表示的范围,函数将返回 ULONG_MAX(在 limits.h 头文件中定义),并将 errno 的值设置为 ERANGE。

如果 我们在调用这个函数的时候,有这么一段的程序

1
2
3
4
5
6
fgets(&s, 24, stdin);
v0 = strtoul(&s, 0LL, 0);

如果我们的输入为“ -1”,由于stroul函数扫描str的时候会跳过空格
那么v0是会被赋值为-1的,对于unsigned的数值来说,会造成整数溢出
而如果将v0作为一个数组的下标,就会造成数组下标的溢出

snprintf()

snprintf(),函数原型为int snprintf(char str, size_t size, const char format, …)
将可变个参数(…)按照format格式化成字符串,然后将其复制到str中
(1) 如果格式化后的字符串长度 < size,则将此字符串全部复制到str中,并给其后添加一个字符串结束符(‘\0’);
(2) 如果格式化后的字符串长度 >= size,则只将其中的(size-1)个字符复制到str中,并给其后添加一个字符串结束符(‘\0’),返回值为欲写入的字符串长度。

补充一下,snprintf的返回值是欲写入的字符串长度,而不是实际写入的字符串度。如:

1
2
3
4
5
6
7
char test[8];
int ret = snprintf(test,5,"1234567890");
printf("%d|%s/n",ret,test);

运行结果为:
10|1234
可以看到ret的值实际上可以大于test[8]的长度的,如果将它作为数组的下标,则会造成漏洞

signal()

C 库函数 void (signal(int sig, void (func)(int)))(int) 设置一个函数来处理信号,即带有 sig 参数的信号处理程序

下面是 signal() 函数的声明:

void (signal(int sig, void (func)(int)))(int)

简单来说,这个函数的作用是,在出现signal信号(比如中断)的时候进行处理,一般放在初始化函数里面,有的时候需要细心留意signa函数中的处理函数参数,说不定程序的解题思路正是造成崩溃后执行其他函数

这里贴几个常见的signal信号含义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Linux支持的信号列表如下。很多信号是与机器的体系结构相关的
信号值 默认处理动作 发出信号的原因
SIGHUP 1 A 终端挂起或者控制进程终止
SIGINT 2 A 键盘中断(如break键被按下)
SIGQUIT 3 C 键盘的退出键被按下
SIGILL 4 C 非法指令
SIGABRT 6 C 由abort(3)发出的退出指令
SIGFPE 8 C 浮点异常
SIGKILL 9 AEF Kill信号
SIGSEGV 11 C 无效的内存引用
SIGPIPE 13 A 管道破裂: 写一个没有读端口的管道
SIGALRM 14 A 由alarm(2)发出的信号
SIGTERM 15 A 终止信号
SIGUSR1 30,10,16 A 用户自定义信号1
SIGUSR2 31,12,17 A 用户自定义信号2
SIGCHLD 20,17,18 B 子进程结束信号
SIGCONT 19,18,25 进程继续(曾被停止的进程)
SIGSTOP 17,19,23 DEF 终止进程
SIGTSTP 18,20,24 D 控制终端(tty)上按下停止键
SIGTTIN 21,21,26 D 后台进程企图从控制终端读
SIGTTOU 22,22,27 D 后台进程企图从控制终端写

setvbuf

函数setvbuf()用来设定文件流的缓冲区,其原型为:

​ int setvbuf(FILE stream, char buf, int type, unsigned size);

【参数】stream为文件流指针,buf为缓冲区首地址,type为缓冲区类型,size为缓冲区内字节的数量。

参数类型type说明如下:

  • _IOFBF (满缓冲):当缓冲区为空时,从流读入数据。或当缓冲区满时,向流写入数据。
  • _IOLBF (行缓冲):每次从流中读入一行数据或向流中写入—行数据。
  • _IONBF (无缓冲):直接从流中读入数据或直接向流中写入数据,而没有缓冲区。

【返回值】成功返回0,失败返回非0。

setbuf()和setvbuf()函数的实际意义在于:用户打开一个文件后,可以建立自己的文件缓冲区,而不必使用fopen()函数打开文件时设定的默认缓冲区。这样就可以让用户自己来控制缓冲区,包括改变缓冲区大小、定时刷新缓冲区、改变缓冲区类型、删除流中默认的缓冲区、为不带缓冲区的流开辟缓冲区等。

说明:在打开文件流后,读取内容之前,可以调用setbuf()来设置文件流的缓冲区(而且必须是这样)。

【实例】观察缓冲区与流关联后的影响。

1
#include <stdio.h>  char outbuf[BUFSIZ]; int main(void) {     setbuf(stdout, outbuf);  // 把缓冲区与流相连    puts("This is a test of buffered output.\n");    puts(outbuf);    fflush(stdout);  // 刷新    puts(outbuf);  // 输出    return 0; }

输出结果:

This is a test of buffered output..

This is a test of buffered output..

This is a test of buffered output..

This is a test of buffered output..

程序先把outbuf与输出流相连,然后输出一个字符串,这时因为缓冲区已经与流相连,所以outbuf中也保存着这个字符串,紧接着puts函数又输出一遍,所以现在outbuf中保存着两个一样的字符串。刷新输出流之后,再次puts,则又输出两个字符串。

fflush

fflush()用于清空文件缓冲区,如果文件是以写的方式打开 的,则把缓冲区内容写入文件。其原型为:

​ int fflush(FILE* stream);

【参数】stream为文件指针。

【返回值】成功返回0,失败返回EOF,错误代码存于errno 中。指定的流没有缓冲区或者只读打开时也返回0值。

fflush()也可用于标准输入(stdin)和标准输出(stdout),用来清空标准输入输出缓冲区。

stdin 是 standard input 的缩写,即标准输入,一般是指键盘;标准输入缓冲区即是用来暂存从键盘输入的内容的缓冲区。stdout 是 standard output 的缩写,即标准输出,一般是指显示器;标准输出缓冲区即是用来暂存将要显示的内容的缓冲区。

请看下面的代码(代码一):

1
#include <stdio.h>#include <stdlib.h>int main(){    int a;    char c;       scanf("%d", &a);    c = getchar();    printf("a = %d, c = %c \n", a, c);    return 0;}

运行结果:

123abc↙

a = 123, c = a

将上面的代码进行更改(代码二):

1
#include <stdio.h>#include <stdlib.h>int main(){    int a;    char c;       scanf("%d", &a);    fflush(stdin);    c = getchar();    printf("a = %d, c = %c \n", a, c);    return 0;}

运行结果:

123abc↙

xyz↙

a = 123, c = x

对比上面的代码,代码一没有清空输入缓冲区,回车时,将123赋值给a,缓冲区剩下abc,接着执行getchar(),发现缓冲区有内容,就无需等待用户输入,直接读取了,将 ‘a’ 赋给 c。代码二执行到fflush(),清空缓冲区,getchar()发现缓冲区没有内容,需要等待用户输入,所以必须输入两次。

【实例】把输入流与一个缓冲区关联,然后清空缓冲区。

1
#include <stdio.h> char inbuf[BUFSIZ]; int main(void) {     char a[100];    setbuf(stdin, inbuf);    printf("input a string =");    scanf("%s",a);    /*往缓冲区写入数据 */     puts(inbuf);    if(0 ==fflush(inbuf)) /*清空文件缓冲区*/    {        puts(inbuf);    }    puts(inbuf);    return 0; }

运行结果:

input a string = qwe

qwe

程序先把缓冲区与输入流关联,这样输入的内容就在缓冲区中是可见的。提示用户输入一个字符串,用户输入后, 该字符串会在缓冲区中保存,这时我们可以使用puts函数输出一 遍,然后使用fflush函数清空缓冲区,再次输出结果为空。

execlp()

头文件:#include <unistd.h>

定义函数:int execlp(const char file, const char arg, …);

函数说明:execlp()会从PATH 环境变量所指的目录中查找符合参数file 的文件名, 找到后便执行该文件, 然后将第二个以后的参数当做该文件的argv[0], argv[1], …, 最后一个参数必须用空指针(NULL)作结束

返回值:如果执行成功则函数不会返回, 执行失败则直接返回-1, 失败原因存于errno 中.

简单来说,这个函数能直接执行PATH环境变量下的命令,如果配合vim使用得当,甚至可以直接进行cat flag或者getshell的操作


c中的输入输出函数的终止条件:

C语言中的格式控制读入函数scanf(终端读入)和fscanf(文件读入)函数都是以非空白字符开始,以第一个空白字符(空格、换行等)结束,并且系统自动在末尾加上字符串结束标记’\0’;

格式控制输出函数 printf(输出到终端)和fprintf(输出到文件)函数输出时直到遇到’\0’停止输出,并且’\0’不输出

puts函数和fputs函数输出时直到遇到’\0’停止输出,并且’\0’不输出

gets函数遇到’\n’或文件结束标记时停止读入,不读取’\n’,在结尾自动加上’\0’

fgets函数遇到’\n’或文件结束标记时停止读入,并读取’\n’,在结尾自动加上’\0’; strncpy函数遇到’\0’终止

来自:D4rk3r

持续填坑ing。。。。