[pwnable.kr] Toddler’s Bottle – memcpy
이 문제를 풀기 위해서 어떤 exploit 스킬을 쓰는 게 아닌 것 같습니다.
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 |
// 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; }
|
코드가 기네요.
설명해보면, slow_memcpy, fast_memcpy가 있습니다.
slow_memcpy 함수는 한 바이트씩 len만큼 하나하나 src에서 dest로 옮깁니다.
fast_memcpy 함수는 64byte씩 나눠서 xmm 레지스터에 넣고 옮깁니다. 남는 바이트는 slow_memcpy를 사용해서 옮깁니다.
확실히 레지스터를 사용해서 옮기는 게 훨씬 빠를 것입니다.
그리고 main에서는 어떤 범위 안의 size를 10번 입력 받고, malloc 후 slow_memcpy, fast_memcpy를 해줍니다. 여기서 rdtsc는 저 두 함수를 실행하기 전과 후의 시간 차를 이용해서 얼마나 걸렸는지 알아내기 위해서 사용되었습니다.
위에 컴파일이 어떻게 되었는지 제시해줬기 때문에 코드를 긁어와서 컴파일을 합시다.
보면 5번째에서 세폴이 뜬 걸 확인할 수 있습니다.
즉, 이 문제는 10번까지 잘 끌고 가면 flag를 주는 문제라는 것입니다.
그럼 왜 5번째 fast_memcpy에서 세폴이 뜬 것일까요? gdb로 디버깅 해봅시다.
fast_memcpy에 bp를 걸었고, 64 byte 아래로는 fast_memcpy에서 레지스터를 통하지 않고 slow_memcpy만 동작하기 때문에 exp 4와 5를 비교해보겠습니다.
이 부분에서 세폴이 나는데, movntps 명령어를 찾아보니 이런 말이 있었습니다.
The destination operand is a 128-bit memory location.
128-bit면 16byte입니다.
하지만 EDX를 보면 0xa8 16byte의 배수가 아닙니다.
그럼 잘 실행이 되던 exp 4를 한 번 볼까요?
명령어가 잘 실행이 되는데, EDX를 보면 16byte의 배수인 것을 알 수 있습니다.
그렇다면 왜 세폴이 나는지 알게 되었으니, 오류를 고쳐 나가 봅시다!
문제가 나던 128부분을 0xa8에서 8을 더해 16byte의 배수로 만들어주고 확인을 해봤습니다.
하지만 EDX가 그대로인데요, 여기서 아차 싶더라고요. 어디에 할당되는지는 그 전에 할당한 size에 따라서 달라지는 것인데, 저는 128을 136으로만 바꿔줬기 때문에 시작 위치는 그대로인 것입니다.
그렇다면 64를 72로 바꿔서 주도록 하겠습니다.
이렇게 주면 128 부분, exp 5는 통과하지만 다시 6에서 막히게 되는데요.
다시 EDX를 보면,
또 128에 8을 더해줘야 하겠네요. 하지만 여기서 왜 16byte의 배수를 할당했는데 주소 마지막이 8로 끝나는 이유를 생각해보면, prev_size와 size 때문이 아닐까 생각됩니다.
그럼 그 뒤로도 주소 마지막이 8로 끝날 것이고, 모두 8을 더해주면 될 것입니다.
8, 16, 32, 64+8, 128+8, 256+8, 512+8, 1024+8, 2048+8, 4096+8
마지막 4096은 사실 시작 주소와 관련 없기 때문에 8을 더해주지 않아도 되지만, 스크립트를 짤 때 수월하도록 8을 더했습니다.
1 2 3 4 5 6 7 8 9 10 11 12 |
from pwn import *
s = process('./memcpy')
for i in range(3, 13): # 2^3 ~ 2^12 if i < 6: s.sendline(str(2**i)) else: s.sendline(str(2**i + 8))
sleep(5) print s.recv() |
2^5까지, 64가 되기 전까지는 n제곱해서 그대로 보내고, 2^6 이상은 8을 더해서 주소가 16byte의 배수가 되도록 해줬습니다.
스크립트를 돌려주면 플래그가 나옵니다.
이제 pwnable.kr 서버로 바꿔서 실행해보겠습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
from pwn import *
#s = process('./memcpy') s = remote('pwnable.kr', 9022)
for i in range(3, 13): # 2^3 ~ 2^12 if i < 6: s.sendline(str(2**i)) else: s.sendline(str(2**i + 8))
sleep(5) print s.recv() |
flag가 출력됩니다. 감사합니다.
'System&Write up > Pwnable.kr' 카테고리의 다른 글
[pwnable.kr] Rookiss - echo1 (0) | 2018.04.01 |
---|---|
[pwnable.kr] Toddler's Bottle - unlink (0) | 2018.03.31 |
[pwnable.kr] Toddler's Bottle - asm (0) | 2018.01.31 |
[pwnable.kr] Toddler's Bottle - uaf (0) | 2018.01.31 |
[pwnable.kr] Toddler's Bottle - input (0) | 2018.01.20 |