Introduction
上一个lab的主要内容为__data pointer__(指向数据的指针)可能在Linux系统中造成的__segmentation fault__。本次lab将考虑__function pointer__(指向函数/代码的指针)可能造成的错误:segfault或其他exceptions。
函数指针 Function Pointers
一个函数指针可以像函数一样被调用,包括传递参数和获得返回结果。函数指针的一些用途是用于编写泛型generic函数,有时是一种面向对象的样式,也用于实现回调。
- 在函数中,有物理内存地址可以赋值给指针,而一个函数的函数名就是一个指针,指向函数的代码;
- 一个函数的地址就是该函数的进入点,也是调用函数的地址;
- 函数的调用可以通过函数名,也可以通过指向函数的指针;
- 函数指针还允许将函数作为变元传递给其他函数;
- 没有括号和变量列表的函数名也可以表示函数的地址(数组中,不带下标的数组名表示数组的首地址)
定义形式
类型 (*指针变量名) (参数列表);
如, int (*p)(int i, int j);
→ p是一个指针,它指向一个函数,该函数有两个整型参数,返回类型为int;p首先和*结合,表明p是一个指针,再与( )结合,表明它指向的是一个函数。
调用函数指针
(*p) (argument)
p (argument)
例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
#include <stdio.h> #define GET_MAX 0 #define GET_MIN 1 int get_max( int i, int j) { return i>j?i:j; } int get_min( int i, int j) { return i>j?j:i; } int compare( int i, int j, int flag) { int ret; / / 这里定义了一个函数指针,就可以根据传入的flag,灵活地决定其是指向求大数或求小数的函数 / / 便于方便灵活地调用各类函数 int ( * p)( int , int ); if (flag = = GET_MAX) p = get_max; else p = get_min; ret = p(i,j); return ret; } int main() { int i = 5 ,j = 10 ,ret; ret = compare(i,j,GET_MAX); printf( "The MAX is %d\n" ,ret); ret = compare(i,j,GET_MIN); printf( "The MIN is %d\n" ,ret); return 0 ; } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
#include <stdio.h> #include <string.h> void check(char * a,char * b, int ( * cmp )(const char * ,const char * )); main() { char s1[ 80 ],s2[ 80 ]; int ( * p)(const char * ,const char * ); / / 将库函数strcmp的地址赋值给函数指针p p = strcmp; printf( "Enter two strings.\n" ); gets(s1); gets(s2); check(s1,s2,p); } void check(char * a,char * b, int ( * cmp )(const char * ,const char * )) { printf( "Testing for equality.\n" ); / / 表达式( * cmp )(a,b)调用strcmp,由 cmp 指向库函数strcmp(),由a和b作调用strcmp()的参数。 / / 调用时,与声明的情况类似,必须在 * cmp 周围使用一对括号,使编译程序正确操作, / / 同时这也是一种良好的编码风格,指示函数是通过指针调用的,而不是函数名。 if (( * cmp )(a,b) = = 0 ) printf( "Equal\n" ); else printf( "Not Equal\n" ); } |
int *f(int i, int j);
int (*p)(int i, int j);
前者是返回值是指针的函数;后者是一个指向函数的指针。
注意 本实验用到的技巧也将会在JIT编译器(如,浏览器中的JavaScript编译器)中出现,因为它们也是将数据转换为代码,然后再调用。请注意,试图执行代码的攻击可能会使用本实验室的变体。这个实验也说明了一个常见的错误,可能是不确定的。
Exercise 1:qsort中的函数指针
-
为什么 q s o r t ( ) qsort() qsort() 使用函数指针?
- q s o r t ( ) qsort() qsort() 是对任何类型的数组数据的通用排序例程(generic sorting routine ),因此,不同的数据类型需要不同的比较方法,这是由用户提供的比较函数 c o m p a r e ( ) compare() compare() 提供的。简单说就是对数组进行排序。
- 声明:
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))
-
参数:
- base – 指向要排序的数组的第一个元素的指针。
- nitems – 由 base 指向的数组中元素的个数。
- size – 数组中每个元素的大小,以字节为单位。
- compar – 用来比较两个元素的函数。如果 compar 返回值< 0,那么第一个参数p1 所指向元素会被排在第二个p2所指向元素的前面;如果 compar 返回值= 0,那么 p1 所指向元素与 p2 所指向元素的顺序不确定;如果 compar 返回值> 0,那么 p1 所指向元素会被排在 p2 所指向元素的后面。
- 返回值 – 无
代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
#include <stdlib.h> #include <string.h> #include <strings.h> #include <stdio.h> / / example of qsort using function pointer for comparison function ( * compar)() / / see man qsort char * num[] = { "000000001" , "1" , "1000" , " 100 " , "1 " , }; / / compare p1 & p2 as strings / / call with the address of the array element, e.g. &(char * ) = char * * int string_comp(const void * p1, const void * p2) { / / be careful that address of element is passed so there is an / / extra * needed here given that it is already (char * ) / / return strcmp((char * ) p1, (char * ) p2); / * a bug as needs deref * / return strcmp( * ((char * * ) p1), * ((char * * ) p2)); } / / compare p1 & p2 as integers int int_comp(const void * p1, const void * p2) { int i1, i2; / / i1 = atoi((char * ) p1); / * bug: same reason as line in string_comp() * / i1 = atoi( * ((char * * ) p1)); / * correct * / / / i2 = atoi((char * ) p2); / * bug: same reason as line in string_comp() * / i2 = atoi( * ((char * * ) p2)); / * correct * / / / printf( "comp(%s,%s)\n" , p1, p2); / * bug: for debugging * / / / printf( "comp(\"%s\", \"%s\")\n" , * (char * * ) p1, * (char * * ) p2); / * correct: for debugging * / if (i1 < i2) return - 1 ; else if (i1 = = i2) return 0 ; else return 1 ; } void print_array(char * a[], int n) { int i; for (i = 0 ; i < n; i + + ) printf( "\"%s\" " , a[i]); } int main() { printf( "Original array\n" ); print_array(num, 5 ); printf( "\n" ); qsort(num, 5 , sizeof(char * ), int_comp); printf( "sorted array as int\n" ); print_array(num, 5 ); printf( "\n" ); qsort(&num, 5 , sizeof(char * ), string_comp); printf( "sorted array as string\n" ); print_array(num, 5 ); printf( "\n" ); return ( 0 ); } |
一般形式:strcmp(字符串1,字符串2)
说明:当s1<s2时,返回为负数 注意不是-1
当s1==s2时,返回值= 0
当s1>s2时,返回正数 注意不是1
即:两个字符串自左向右逐个字符相比(按ASCII值大小相比较),直到出现不同的字符或遇'\0'为止。如:
“A”<“B” “a”>“A” “computer”>“compare”
特别注意:strcmp(const char *s1,const char * s2)这里面只能比较字符串,不能比较数字等其他形式的参数。
Exercise 2:
代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
#include <stdio.h> #include <string.h> #include <strings.h> #include <errno.h> #include <sys/mman.h> #define L (32) int addnum( int a) { return a + 255 ; } int main( int argc, char * argv[], char * envp[]) { int a, ( * f)( int ), * code_buf; char * p, data[] = { 0x0f , 0x0b }; / * Try uncommenting out * / / * f = NULL; a = f( 10 ); printf( "0: f(10)=%d f=%p\n\n" , a, f); * / f = addnum; a = f( 10 ); / / LINE1 printf( "1: f(10)=%d f=%p\n\n" , a, f); code_buf = ( int * ) mmap( 0 , 4096 , PROT_EXEC|PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, - 1 , 0 ); / * Try swapping the two mmap lines * / / / code_buf = ( int * ) mmap( 0 , 4096 , PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, - 1 , 0 ); printf( "code_buf %p\n" , code_buf); memcpy(code_buf, addnum, L); f = ( int ( * )( int )) code_buf; a = f( 10 ); / / LINE2 printf( "2: f(10)=%d f=%p\n\n" , a, f); p = index((char * ) code_buf, 0xff ); printf( "before *p=%hhx\n" , * p); * p = 100 ; printf( "after *p=%hhx\n" , * p); a = f( 10 ); / / LINE3 printf( "3: f(10)=%d\n\n" , a); * ((char * ) code_buf) = 0xc3 ; a = f( 10 ); / / LINE4 printf( "4: f(10)=%d\n\n" , a); memcpy(code_buf, data, 2 ); printf( "before last call\n" ); a = f( 10 ); / / LINE5 return 0 ; } |
-
mmap() 函数:
-
用途:
- 将一个普通文件映射到内存中,通常在需要对文件进行频繁读写时使用,这样用内存读写取代I/O读写,以获得较高的性能;
- 将特殊文件进行匿名内存映射,可以为关联进程提供共享内存空间;
- 为无关联的进程提供共享内存空间,一般也是将一个普通文件映射到内存中。
-
头文件:
#include <sys/mman.h>
-
原型:
void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offsize);
-
参数说明:
- start:指向欲映射的内存起始地址,通常设为 NULL,代表让系统自动选定地址,映射成功后返回该地址。
- length:代表将文件中多大的部分映射到内存。
-
prot:映射区域的保护方式。可以为以下几种方式的组合:
- PROT_EXEC 映射区域可被执行
- PROT_READ 映射区域可被读取
- PROT_WRITE 映射区域可被写入
- PROT_NONE 映射区域不能存取
-
flags:影响映射区域的各种特性。在调用mmap()时必须要指定MAP_SHARED 或MAP_PRIVATE。
- MAP_FIXED:如果参数start所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不鼓励用此旗标。
- MAP_SHARED:对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。
- MAP_PRIVATE:对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on write)对此区域作的任何修改都不会写回原来的文件内容。
- MAP_ANONYMOUS:建立匿名映射。此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。
- MAP_DENYWRITE:只允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝。
- MAP_LOCKED:将映射区域锁定住,这表示该区域不会被置换(swap)。
-
用途:
fd:要映射到内存中的文件描述符。如果使用匿名内存映射时,即flags中设置了MAP_ANONYMOUS,fd设为-1。
offset:文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是分页大小的整数倍。
返回值:若映射成功则__返回映射区的内存起始地址__,否则返回MAP_FAILED(-1),错误原因存于errno 中。
错误代码:
EBADF 参数fd 不是有效的文件描述词
EACCES 存取权限有误。如果是M
P_PRIVATE 情况下文件必须可读,使用MAP_SHARED则要有PROT_WRITE以及该文件要能写入。
EINVAL 参数start、length 或offset有一个不合法。
EAGAIN 文件被锁住,或是有太多内存被锁住。
ENOMEM 内存不足。
-
memcpy() 函数:
- 用途:内存复制。
- 原型:void *memcpy(void *dest, const void *src, size_t n);
- 功能:从src的开始位置拷贝n个字节的数据到dest。如果dest存在数据,将会被覆盖。
- 返回值:dest的指针。
- 头文件:string.h
-
index() 函数:
- 用途:找地址。
- 原型:char * index(const char *s, int c);
- 功能:用来找出参数s 字符串中第一个出现的参数c 地址,然后将该字符出现的地址返回。字符串结束字符(NULL)也视为字符串一部分。返回值:如果找到指定的字符则返回该字符所在地址,否则返回0。
- 头文件:#include <string.h> Exercise 3
代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
#include <stdio.h> #include <string.h> #include <strings.h> #include <errno.h> #include <sys/mman.h> int encrypt( int a) // simple encryption with a (secret) constant { return a ^ 255; } int main( int argc, char *argv[], char *envp[]) { int (*f)( int ); int secret, x, y; // test encrypt f = encrypt; printf ( "1: enter test data\n" ); scanf ( "%d" , &x); y = f(x); printf ( "1: test original encrypt(): f(%x)=%x\n" , x, y); printf ( "enter new key\n" ); scanf ( "%d" , &secret); // create a new function by using encrypt's code-bytes as a template // which is similar to encrypt() except that it is xor with the secret // key which has been input // let f point to the new function created by the code below // LINE1 /* your code goes here */ // LINE2 secret = 255; // erase secret, set it back to the original key // test if it works printf ( "2: value to encrypt\n" ); scanf ( "%d" , &x); y = f(x); // should use the input secret key above printf ( "2: f(): f(%x)=%x\n" , x, y); printf ( "erased secret %d\n" , secret); // test if f() is modifiable *(( int *)f) = 0; // LINE3: must segfault here return 0; } |
-
要求:
- 加密方法只是让数据XOR亦或整常数密码(key,secret)。原始密码已知,为255。
- 目标:从源代码中移除密码的依赖性,尽管有人知道源代码也无法知道密码(只考虑整数)。
- 只修改LINE1和LINE2之间的代码。
-
限制:
- 不能使用新函数或者新包。
- 需要在运行时,动态地创建f()所指向的新代码。(f是随输入变化的)
- 新函数实现了与encrypt()相同的功能,除了常量来自输入(读入为secret)。
- 加密是密码的异或,采用(a ^ c)形式,其中c是常数而不是c变量。
- 此外,尽管f()是在运行时动态创建的,但在使用时不应该修改。在LINE3测试,即出现segfault。 hint:看看mprotect,它补充了mmap
总结
本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注服务器之家的更多内容!
原文链接:https://blog.csdn.net/weixin_43831311/article/details/120474586