pwnable的新一题,和堆分配相关。
http://pwnable.kr/bin/memcpy.c
ssh memcpy@pwnable.kr -p2222 (pw:guest)
我觉得主要考察的是堆块分配问题。
推荐《C和C++安全编码》
首先通过阅读源代码,看一下题目大意。
// compiled with : gcc -o memcpy memcpy.c -m32 -lm #include <stdio.h> #include <string.h> #include <stdlib.h> #include <signal.h> #include <unistd.h> #include <sys/mman.h> #include <math.h> unsigned long long rdtsc(){ asm("rdtsc"); } 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 32(%0), %%xmm2\n" "movdqa 48(%0), %%xmm3\n" "movntps %%xmm0, (%1)\n" "movntps %%xmm1, 16(%1)\n" "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){ 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; // allocate memory char* cache1 = mmap(0, 0x4000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); char* cache2 = mmap(0, 0x4000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); src = mmap(0, 0x2000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); size_t sizes[10]; int i=0; // setup experiment parameters for(e=4; e<14; e++){ // 2^13 = 8K low = pow(2,e-1); 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 ); 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个数字,分别位于2的各次幂之间。
输入之后,根据用户输入的数据使用malloc函数,在堆上请求大小为用户输入数据的堆块。
然后,分别用slow_memcpy和fast_memcpy两种方式,对堆块内的数据向另外一个内存地址拷贝,并比较二者时间。
slow_memcpy使用的是最复杂的循环赋值,而fast_memcpy使用的是汇编指令movdqa进行拷贝。
当全部10数字拷贝结束后打印flag。
坑点在于全部的以mmap申请的空间基本对题目没有什么影响。
先运行一下,随意输入10个符合要求的数字,运行终止,出现段错误
并不清楚为什么,使用gdb调试一下看看:
结果显示出错位置在fast_memcpy函数中的movntps汇编语句上
也就是说是movntps执行出了问题,各寄存器值如下:
具体看一下movntps是做什么的
也就是把之前从src中拷贝到XMM寄存器中的数据传递给新申请的栈块。
要求是必须对其16字节
一.什么是字节对齐,为什么要对齐?
现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特 定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
16字节对齐的意思就是地址的末位必须为0,比如0xabcdef10,就是16字节对齐。
所以出错的原因是edx中的数据非16进制对齐数据(0x804c4a8)
因此,这道题的关键是构造每次申请的堆块地址都是16进制对齐数据。
堆块是由链表结构组成的,dlalloc系列的堆块结构图和分配过程可参看《C和C++安全编码》,堆块有一明显特征是堆块除了用户数据外还有堆块大小和标志位共计4字节。而malloc分配的堆块大小是以8字节对其的。
假设用户申请的堆块大小是a的话,malloc(a)分配的堆块大小为 8*(int((a+4)/8)+1)。
因此假设第一个malloc分配地址是16字节对齐的,则每次请求大小为16字节对齐的数据块即可成功运行结束。
以如下脚本可检测是否,malloc分配的字节大小是16字节对齐的
# coidng = utf-8 while(1): a = raw_input() a = int(a) if (a+4)%16>=9 || (a+4)%16==0: print a," is ok" else: print a," is wrong"
因此,只要每次输入数字前先用脚本测试一下,基本就可以通过验证。
服务器上的代码在另外一个文件夹下,用nc命令连接
nc 0 9022后就可以输入了:
在检测的python脚本里留下了许多不行的数据: