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腳本里留下了許多不行的數據: