-
-
[分享] pwnable.kr memcpy
-
2021-1-21 20:04 9455
-
memcpy
考察点: 32 位程序中 movntps 16 字节对齐,malloc(n) 实际分配的堆内存是 Header(4 字节) + n 结构且分配的堆块 8 字节对齐
题目
解题过程
1. 查看文件列表,读取 readme
我们通过nc 0 9022
连接题目环境
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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | / / compiled with : gcc - o memcpy memcpy.c - m32 - lm #include <stdio.h> #include <string.h> #include <stdlib.h> #include <signal.h> #include <unistd.h>C #include <sys/mman.h> #include <math.h> unsigned long long rdtsc(){ asm( "rdtsc" ); / / 将处理器的时间标签计数器的当前值加载到 EDX:EAX 寄存器。 } char * slow_memcpy(char * dest, const char * src, size_t len ){ int i; for (i = 0 ; i< len ; i + + ) { dest[i] = src[i]; } return dest; } char * fast_memcpy(char * dest, const char * src, size_t len ){ size_t i; / / 64 - byte block fast copy if ( len > = 64 ){ i = len / 64 ; len & = ( 64 - 1 ); while (i - - > 0 ){ __asm__ __volatile__ ( "movdqa (%0), %%xmm0\n" "movdqa 16(%0), %%xmm1\n" / / MOVDQA 目标操作数 源操作数 移动对齐的双、四字 "movdqa 32(%0), %%xmm2\n" "movdqa 48(%0), %%xmm3\n" "movntps %%xmm0, (%1)\n" / / MOVNTPS 目标操作数 源操作数 使用非时间提示存储打包的单精度浮点值 "movntps %%xmm1, 16(%1)\n" / / 目标操作数是 128 位或 256 位内存位置。内存操作数必须在 16 字节( 128 位版本)或 32 字节(VEX. 256 编码版本)边界上对齐,否则将生成一般保护异常( #GP)。 "movntps %%xmm2, 32(%1)\n" "movntps %%xmm3, 48(%1)\n" :: "r" (src), "r" (dest): "memory" ); / / : 输出运算符列表 : 输入运算符列表 : 被更改资源列表 dest + = 64 ; src + = 64 ; } } / / byte - to - byte slow copy if ( len ) slow_memcpy(dest, src, len ); return dest; } int main(void){ / * int setvbuf( FILE * stream, char * buf, int type , unsigned size); * * stream为文件流指针,buf为缓冲区首地址, type 为缓冲区类型,size为缓冲区内字节的数量。 * * type : _IOFBF (满缓冲):当缓冲区为空时,从流读入数据。或当缓冲区满时,向流写入数据。 * * _IOLBF (行缓冲):每次从流中读入一行数据或向流中写入—行数据。 * * _IONBF (无缓冲):直接从流中读入数据或直接向流中写入数据,而没有缓冲区。 * * 成功返回 0 ,失败返回非 0 。 * / setvbuf(stdout, 0 , _IONBF, 0 ); setvbuf(stdin, 0 , _IOLBF, 0 ); printf( "Hey, I have a boring assignment for CS class.. :(\n" ); printf( "The assignment is simple.\n" ); printf( "-----------------------------------------------------\n" ); printf( "- What is the best implementation of memcpy? -\n" ); printf( "- 1. implement your own slow/fast version of memcpy -\n" ); printf( "- 2. compare them with various size of data -\n" ); printf( "- 3. conclude your experiment and submit report -\n" ); printf( "-----------------------------------------------------\n" ); printf( "This time, just help me out with my experiment and get flag\n" ); printf( "No fancy hacking, I promise :D\n" ); unsigned long long t1, t2; int e; char * src; char * dest; unsigned int low, high; unsigned int size; / * void * mmap(void * start, size_t length, int prot, int flags, int fd, off_t offsize); * * mmap()用来将某个文件内容映射到内存中,对该内存区域的存取即是直接对该文件内容的读写。 * * start 指向欲对应的内存起始地址,通常设为NULL,代表让系统自动选定地址,对应成功后该地址会返回。 * * length 代表将文件中多大的部分对应到内存。 * * prot 代表映射区域的保护方式,有下列组合: * * PROT_EXEC 映射区域可被执行; * * PROT_READ 映射区域可被读取; * * PROT_WRITE 映射区域可被写入; * * PROT_NONE 映射区域不能存取。 * * flags 会影响映射区域的各种特性: * * MAP_FIXED 如果参数 start 所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不鼓励用此 flag。 * * MAP_SHARED 对应射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。 * * MAP_PRIVATE 对应射区域的写入操作会产生一个映射文件的复制,即私人的 "写入时复制" (copy on write)对此区域作的任何修改都不会写回原来的文件内容。 * * MAP_ANONYMOUS 建立匿名映射,此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。 * * MAP_DENYWRITE 只允许对应射区域的写入操作,其他对文件直接写入的操作将会被拒绝。 * * MAP_LOCKED 将映射区域锁定住,这表示该区域不会被置换(swap)。 * * 在调用mmap()时必须要指定MAP_SHARED 或MAP_PRIVATE。 * * fd open ()返回的文件描述词,代表欲映射到内存的文件。 * * offset 文件映射的偏移量,通常设置为 0 ,代表从文件最前方开始对应,offset必须是分页大小的整数倍。 * * 若映射成功则返回映射区的内存起始地址,否则返回MAP_FAILED( - 1 ),错误原因存于errno 中。 * * 错误代码: * * EBADF 参数fd 不是有效的文件描述词 * * EACCES 存取权限有误。如果是 MAP_PRIVATE 情况下文件必须可读,使用 MAP_SHARED 则要有 PROT_WRITE 以及该文件要能写入。 * * EINVAL 参数 start、length 或 offset 有一个不合法。 * * EAGAIN 文件被锁住,或是有太多内存被锁住。 * * ENOMEM 内存不足。 * / / / allocate memory char * cache1 = mmap( 0 , 0x4000 , 7 , MAP_PRIVATE|MAP_ANONYMOUS, - 1 , 0 ); / / 使用了 MAP_ANONYMOUS 建立匿名映射,此时会忽略参数 fd。 char * cache2 = mmap( 0 , 0x4000 , 7 , MAP_PRIVATE|MAP_ANONYMOUS, - 1 , 0 ); / / 7 代表映射区域可被读、写、执行。PROT_READ: 0x1 ,PROT_WRITE: 0x2 / * Page can be written. * / src = mmap( 0 , 0x2000 , 7 , MAP_PRIVATE|MAP_ANONYMOUS, - 1 , 0 ); / / PROT_EXEC: 0x4 ,PROT_NONE: 0x0 size_t sizes[ 10 ]; int i = 0 ; / / setup experiment parameters for (e = 4 ; e< 14 ; e + + ){ / / 2 ^ 13 = 8K low = pow ( 2 ,e - 1 ); / / double pow (double x, double y); x^y high = pow ( 2 ,e); printf( "specify the memcpy amount between %d ~ %d : " , low, high); scanf( "%d" , &size); if ( size < low || size > high ){ printf( "don't mess with the experiment.\n" ); exit( 0 ); } sizes[i + + ] = size; } sleep( 1 ); printf( "ok, lets run the experiment with your configuration\n" ); sleep( 1 ); / / run experiment for (i = 0 ; i< 10 ; i + + ){ size = sizes[i]; printf( "experiment %d : memcpy with buffer size %d\n" , i + 1 , size); dest = malloc( size ); / / 在堆区分配一块指定大小的内存空间(未初始化),用来存放数据。分配成功返回指向该内存的地址,失败则返回 NULL。 / * void * memcpy ( void * dest, const void * src, size_t num ); * * 复制 src 所指的内存内容的前 num 个字节到 dest 所指的内存地址上。逐字节地进行复制,不关心被复制的数据类型。 * * 与 strcpy() 不同的是,memcpy() 会完整的复制 num 个字节,不会因为遇到 "\0" 而结束。 * * 返回指向 dest 的指针。注意返回的指针类型是 void,使用时一般要进行强制类型转换。 * / memcpy(cache1, cache2, 0x4000 ); / / to eliminate cache effect t1 = rdtsc(); slow_memcpy(dest, src, size); / / byte - to - byte memcpy t2 = rdtsc(); printf( "ellapsed CPU cycles for slow_memcpy : %llu\n" , t2 - t1); memcpy(cache1, cache2, 0x4000 ); / / to eliminate cache effect t1 = rdtsc(); fast_memcpy(dest, src, size); / / block - to - block memcpy t2 = rdtsc(); printf( "ellapsed CPU cycles for fast_memcpy : %llu\n" , t2 - t1); printf( "\n" ); } printf( "thanks for helping my experiment!\n" ); printf( "flag : ----- erased in this source code -----\n" ); return 0 ; } |
首先请用户更具提示的范围输入 10 个数字。
输入之后,根据用户输入的数据使用 malloc 函数,在堆上请求大小为用户输入数据的堆块。
然后以 memcpy() 为中介计算、比较 slow_memcpy() 和 fast_memcpy() 这两种方式耗费的时间。
slow_memcpy() 使用的是循环赋值,fast_memcpy() 使用的是汇编指令 movdqa movntps 进行拷贝。
当全部10数字拷贝结束后打印flag。
3. 调试程序
首先利用源码编译二进制文件gcc -o memcpy memcpy.c -m32 -lm
然后用普通的数据测试一下
可以看到程序停止到第五次试验,使用 gdb 调试该程序,使用相同的输入。
可以看到出错位置在 fast_memcpy() 的 movntps 汇编语句上
movntps 在 32 位程序中目标操作数是 128 位即 16 字节
C 中的 malloc(n) 实际分配的堆内存是 Header(4 字节) + n 结构,即分配 n + 4 字节的空间。返回给用户的是 n 部分的首地址。且分配的堆块 8 字节对齐
系统不同 Header 的大小可能不同,详情参考 https://git.busybox.net/uClibc/tree/libc/stdlib/malloc/malloc.h 的第 104 行
程序报错的原因是因为没有做到 16 字节对齐
写一个简单的代码,找出 10 个符合输入范围且可以做到 16 字节对齐的数字
1 2 3 4 5 6 | while True : a = int ( raw_input ()) if (a + 4 ) % 16 > 9 or (a + 4 ) % 16 = = 0 : print ( "Right" ) else : print ( "Wrong" ) # 如果输出 Wrong,当前值 +8 即可 |
我们所得的一串符合预期的值为8 24 40 72 136 264 520 1032 2056 4104
用计算的值带入程序echo "8 24 40 72 136 264 520 1032 2056 4104" | ./memcpy
在本地通过全流程,在 pwnable.kr 要求的环境中测试echo "8 24 40 72 136 264 520 1032 2056 4104" | nc 0 9022
获得 flag 1_w4nn4_br34K_th3_m3m0ry_4lignm3nt
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。