ipwn

[pwnable.kr] memcpy 본문

Write up/Pwnable.kr

[pwnable.kr] memcpy

ipwn 2018. 7. 21. 23:03


바로 소스코드를 보자.


#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(00x40007, MAP_PRIVATE|MAP_ANONYMOUS, -10);
    char* cache2 = mmap(00x40007, MAP_PRIVATE|MAP_ANONYMOUS, -10);
    src = mmap(00x20007, MAP_PRIVATE|MAP_ANONYMOUS, -10);
 
    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개 입력받아서 입력 받은만큼 각각 dest에 malloc으로 사이즈를 힙에 할당해주고, 각 dest에 slow_memcpy함수와 fast_memcpy로 값을 copy한다..


(rdtsc는 시간을 재는 용도로 쓰이는 것 같다.)


다른 함수들을 보자.


slow_memcpy함수를 보면 for 구문으로 인덱스를 하나하나 옮겨준다. 그래서 slow인듯하다.


그리고 fast_memcpy함수를 보면 대충 다 똑같은 코드인데 어셈 딴으로 여러 바이트를 한 번에 옮겨준다.

아마 src의 값을 전부 xmm 레지스터에 담고 그 레지스터에 담긴 값을 다시 dest에 담는듯 하다.


여기서 가장 중요한 xmm레지스터와 movqda, movntps에 대해서 알아보자.


XMM register


xmm0 ~ xmm7 이렇게 8개, 128bit로 이뤄진 레지스터이다.


MOVDQA(Move aligned Double Quad word) instruction


128bit 레지스터 또는 128bit 메모리에 정렬되어 있는 값을 읽어올 때 사용 한다.

movdqa xmm1, xmm2의 명령어가 존재한다고 하면, xmm2의 값을 xmm1에 옮긴다.

 

이 movdqa instruction은 소스 또는 대상 피연산자가 메모리 피연산자인 경우 피연산자는 128bit 정렬되어야하며 그렇지 않으면 에러를 뿜는다.


MOVNTPS instruction


정렬된 128bit의 값을 128bit메모리에 옮길 때 사용한다.


간단하게 옮겨질 메모리를 가진 피연산자는 xmm레지스터가 와야 하고 메모리를 옮겨받을 피연산자는 128bit의 메모리 공간이어야 한다.


이 instruction은 옮겨받을 피연산자가 128bit의 메모리공간이 아니라면 에러를 뿜는듯 하다.


이제 다시 문제로 돌아가서 소스코드를 보면 heap영역에 메모리를 할당해주고, 그 영역에 각각의 memcpy를 해주는 것을 알 수 있다.


일단 바로 파일을 실행시켜보자.



이렇게 정직하게 값을 넣어주면 어느정도 memcpy를 실행시키는 듯 하다가 segmentation fault를 띄우고 종료시킨다.


이게 종료가 되는 이유가 128bit로 정렬하는 데에서의 문제인 것 같아서 소스코드를 받아와서 조금 수정해서 dest의 주소를 나타내주는 코드를 조금 추가하고, 다시 값을 넣어봤다.



보면 아까의 instruction들은 128bit로 정렬이 되어있어야 한다고 했는데, 위 사진을 보면 주소 값들이 128bit로 정렬이 되어있지 않은 걸 알 수 있다.


이 이유는 16의 배수byte로 할당을 해도 heap영역에 할당을 하는 것이라면 부가적인 heap chunk들의 정보들을 담는데에 8byte가 추가적으로 필요해서라고 판단을 했고 첫 할당할 때의 주소는 상관 없으니 첫 할당할 때를 제외, 모든 곳을 할당할 때 8byte를 추가적으로 뒤로 밀어서 할당을 해주면 잘 정렬이 되어 모든 memcpy가 잘 실행될 것이라고 판단했다.



예상대로 정렬이 전부 잘 마무리 되고, flag가 출력된 것을 확인할 수 있다.


'Write up > Pwnable.kr' 카테고리의 다른 글

[pwnable.kr] leg  (0) 2018.07.13
[pwnable.kr] input  (0) 2018.03.13
[pwnable.kr] coin1  (0) 2018.03.12
[pwnable.kr] uaf  (0) 2018.02.19
[pwnable.kr] shellshock  (0) 2018.01.06
Comments