해킹에 지친 우리를 위해 실험을 도와주면 flag를 준다고 했습니다... 믿지 않습니다..
주어진 링크로 소스코드를 확인해보면 다음과 같은 소스코드가 준비되어있습니다.
// 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;
}
주의 깊게 봤는데 모르는 함수들부터 점검해 나가겠습니다.
rdtsc는 전에 한번 윈도우 시간 측정 찾다가 찾아보았었는 데 사용해본 적은 없던 코드여서 그런지 기억에 잊혔었습니다. 정확한 시간 측정을 위해 사용되는 함수중 하나입니다.
unsigned long long rdtsc(){
asm("rdtsc");
}
movdqa와 movntps를 통해서 fast memcpy를 하고 있습니다. 둘은 128bit(16byte)씩 xmm register에 쓰고, xmm register에서 읽어오며 빠르게 메모리에 복사할 수 있게 해 줍니다. movdqa에 대한 설명은 잘 풀어써준 블로그가 있어서 참고했습니다. movntps에 대해서는 밑에서 다시 한번 보겠습니다.
__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;
}
위에서 movdqa와 movntps에 사용되는 xmm register도 블로그를 참조했습니다. 128비트 크기의 레지스터라고 합니다.
그리고 코드에서 눈에 띄는 부분이 있었습니다.
바로 dest를 malloc 해주는데 free 없이 반복해서 malloc 해준다는 것입니다.
char* dest;
// 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");
}
처음에는 일단 접속부터 해보자 해서 readme에 있는 데로 nc 0 9022로 접속해서 해봤습니다.
나오다 말고 fast_memcpy()를 하지 않고 멈췄습니다.
큰 수로 넣어도 마찬가지로 나오다가 fast_memcpy() 앞에서 멈췄습니다. 이건 무적권 movdqa와 movntps문제라고 생각했습니다. 일단, 둘 다 16바이트씩 복사한다는 것은 알고 있습니다.
그런데 movntps를 보면 다음과 같은 설명이 있습니다.
dest는 128bit memory location이라고 합니다. 즉 128bit 단위의 메모리 주소라는 건데, 16바이트는 0x10이기 때문에 0x10으로 끝나야 한다는 것 (목적지의 메모리 주소가 0x10의 배수 형태라는 것)입니다.
gdb로 분석하기 위해 malloc() 이후 dest의 주소를 확인하기 위해서 break point를 걸고 확인해봤습니다.
0x08048ac4 <+696>: sub $0xc,%esp
0x08048ac7 <+699>: push %eax
0x08048ac8 <+700>: call 0x80485d0 <malloc@plt>
0x08048acd <+705>: add $0x10,%esp
0x08048ad0 <+708>: mov %eax,-0x4c(%ebp)
dest의 위치는 ebp-0x4c입니다!
specify the memcpy amount between 8 ~ 16 : 8
specify the memcpy amount between 16 ~ 32 : 16
specify the memcpy amount between 32 ~ 64 : 32
specify the memcpy amount between 64 ~ 128 : 64
specify the memcpy amount between 128 ~ 256 : 128
specify the memcpy amount between 256 ~ 512 : 256
specify the memcpy amount between 512 ~ 1024 : 512
specify the memcpy amount between 1024 ~ 2048 : 1024
specify the memcpy amount between 2048 ~ 4096 : 2048
specify the memcpy amount between 4096 ~ 8192 : 4096
81ok, lets run the experiment with your configuration
experiment 1 : memcpy with buffer size 8
Breakpoint 1, 0x08048ad3 in main ()
(gdb) x/x $ebp-0x4c
0xffffdbac: 0x0804c410
(gdb) c
Continuing.
ellapsed CPU cycles for slow_memcpy : 1918
ellapsed CPU cycles for fast_memcpy : 158
experiment 2 : memcpy with buffer size 16
Breakpoint 1, 0x08048ad3 in main ()
(gdb) x/x $ebp-0x4c
0xffffdbac: 0x0804c420
(gdb) c
Continuing.
ellapsed CPU cycles for slow_memcpy : 244
ellapsed CPU cycles for fast_memcpy : 298
experiment 3 : memcpy with buffer size 32
Breakpoint 1, 0x08048ad3 in main ()
(gdb) x/x $ebp-0x4c
0xffffdbac: 0x0804c438
보면 다음과 같이 첫 malloc후 주소는 0x0804c410입니다. 두 번째 malloc 후의 주소는 0x0804c420입니다. 세 번째 때에는 0x084c438 이었습니다. 세 번째의 input은 세 번째 주소부터 dest가 갖는 크기이며, 세 번째의 주소 자체에 영향을 미친 거는 두 번째의 사이즈 때문이기 때문에 8바이트를 더해서 두 번째 input때 24를 넣어줍니다.
specify the memcpy amount between 8 ~ 16 : 8
specify the memcpy amount between 16 ~ 32 : 24
specify the memcpy amount between 32 ~ 64 : 32
specify the memcpy amount between 64 ~ 128 : 64
specify the memcpy amount between 128 ~ 256 : 128
specify the memcpy amount between 256 ~ 512 : 256
specify the memcpy amount between 512 ~ 1024 : 512
specify the memcpy amount between 1024 ~ 2048 : 1024
specify the memcpy amount between 2048 ~ 4096 : 2048
specify the memcpy amount between 4096 ~ 8192 : 4096
ok, lets run the experiment with your configuration
experiment 1 : memcpy with buffer size 8
Breakpoint 1, 0x08048ad3 in main ()
(gdb) x/x $ebp-0x4c
0xffffdbac: 0x0804c410
(gdb) c
Continuing.
ellapsed CPU cycles for slow_memcpy : 1942
ellapsed CPU cycles for fast_memcpy : 216
experiment 2 : memcpy with buffer size 24
Breakpoint 1, 0x08048ad3 in main ()
(gdb) x/x $ebp-0x4c
0xffffdbac: 0x0804c420
(gdb) c
Continuing.
ellapsed CPU cycles for slow_memcpy : 308
ellapsed CPU cycles for fast_memcpy : 288
experiment 3 : memcpy with buffer size 32
Breakpoint 1, 0x08048ad3 in main ()
(gdb) x/x $ebp-0x4c
0xffffdbac: 0x0804c440
(gdb) c
Continuing.
ellapsed CPU cycles for slow_memcpy : 458
ellapsed CPU cycles for fast_memcpy : 384
experiment 4 : memcpy with buffer size 64
Breakpoint 1, 0x08048ad3 in main ()
(gdb) x/x $ebp-0x4c
0xffffdbac: 0x0804c468
보면 세 번째의 주소는 0x10의 배수로 자리 잡은 것을 확인할 수 있습니다. 하지만 네 번째 주소 역시 끝자리가 8로 떨어지게 되어 8을 더해줍니다.
몇 번 반복해본 결과 (n+1)번 째의 주소 = (n)번 째의 주소 + malloc size + 8 인 것을 확인할 수 있었습니다. 그래서 malloc size에 16의 배수 + 8을 넣어주어 모두 끝자리가 0x10으로 맞게 할 수 있었습니다.
입력을 바꿔주니 코드가 끝까지 돌고 flag를 볼 수 있었습니다.
난생처음 듣고 처음 보는 명령어나 함수들이 많아서 신기합니다..!
<참고>
https://3dmpengines.tistory.com/1807
https://3dmpengines.tistory.com/1800?category=774908
잘못된 점이나 부족한 점 지적해주시면 감사하겠습니다
'pwnable > pwnable.kr' 카테고리의 다른 글
[pwnable.kr Toddler's Bottle] unlink write up (0) | 2019.08.28 |
---|---|
[pwnable.kr Toddler's Bottle] asm write up (0) | 2019.08.27 |
[pwnable.kr Toddler's Bottle] uaf write up (0) | 2019.08.22 |
[pwnable.kr Toddler's Bottle] blackjack write up (0) | 2019.08.22 |
[pwnable.kr Toddler's Bottle] coin1 write up (0) | 2019.08.18 |