본문 바로가기

pwnable/pwnable.kr

[pwnable.kr Toddler's Bottle] memcpy write up

728x90

 

해킹에 지친 우리를 위해 실험을 도와주면 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");
}

 

movdqamovntps를 통해서 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를 볼 수 있었습니다.

 

 

난생처음 듣고 처음 보는 명령어나 함수들이 많아서 신기합니다..!

 

<참고>

http://www.jaist.ac.jp/iscenter-new/mpc/altix/altixdata/opt/intel/vtune/doc/users_guide/mergedProjects/analyzer_ec/mergedProjects/reference_olh/mergedProjects/instructions/instruct32_hh/vc183.htm

http://www.jaist.ac.jp/iscenter-new/mpc/altix/altixdata/opt/intel/vtune/doc/users_guide/mergedProjects/analyzer_ec/mergedProjects/reference_olh/mergedProjects/instructions/instruct32_hh/vc197.htm

https://3dmpengines.tistory.com/1807

https://3dmpengines.tistory.com/1800?category=774908

 

 

잘못된 점이나 부족한 점 지적해주시면 감사하겠습니다

728x90