[pwnable.kr] memcpy

2018. 7. 27. 16:08

오늘도 pwnable.kr 문제풀이 탐방~~



음... 대체 이 일러들은 누가 그린것인지 의문이 들기 시작했다..



해킹에 지치지않았냐면서 좀 쉬고가라고 한다.

해킹문제가 아니라는데... 과연....?


ssh로 접속해보면 readme라고 있다. 읽어보면 아래와 같다.


memcpy@ubuntu:~$ cat readme

the compiled binary of "memcpy.c" source code (with real flag) will be executed under memcpy_pwn privilege if you connect to port 9022.

execute the binary by connecting to daemon(nc 0 9022).



memcpy.c를 real flag과 컴파일해서 memcpy_pwn 권한으로 9022포트에서 실행할 수 있다고 한다.



접속해보니, 간단한 실험만 해주면 플래그를 주겠다고 한다.

해킹이 아니라고 하면서 하라는데;;



입력하라는데로 입력했는데, 5번째에서 죽어버린다;

일단 소스코드를 보자.


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(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);
        ifsize < 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+1size);
        dest = mallocsize );
 
        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;
}
 
cs


프로그램은 memcpy를 실행하는데, slow_memcpy와 fast_memcpy를 실행하는 시간(cycle)을 측정하여 출력해준다.

이 사이클을 측정하는데 rdtsc라는 어셈블리 명령어를 사용하는데, rdtsc는 Read Time Stamp Counter로 time stamp counter를 읽어내는 명령어이다. 즉 사이클을 측정하는 것인데, 이는 컴퓨터 사이클로 시간과는 조금 다르다.


어쨋든 중요한 부분은 아니고, 원래라면 10번 실행되고 플래그가 출력되야하는데, 5번째에서 죽어버린다.

소스코드가 있으니, 다른 곳에서 컴파일해서 실행해보았다.



5번째에서 slow_memcpy의 결과가 나오고, fast_memcpy가 나오기전에 Segmentation fault가 뜨면서 죽어버렸다.

gdb로 확인해보자.



fast_memcpy의 movntps XMMWORD PTR [edx], xmm0 명령을 수행하는 과정에서 죽어버렸다. 

movntpsmovdqa가 뭐하는 명령인지 몰라서 한번 찾아보았다.


  • MOVDQA

Move Aligned Double Quadword

데이터 전송 명령어로, 128bits의 데이터를 한 번에 xmm 레지스터로 이동하거나, 반대로 xmm레지스터의 데이터를 메모리로 전송하는 처리를 한다.

 This instruction can be used to move a double quadword to and from an XMM register and a 128-bit memory location, or between two XMM registers.

  • MOVNTPS

Store Packed Single-Precision Floating-Point Values Using Non-Temporal Hint

두 번째 오퍼랜드로 입력받은 XMM 레지스터에 있는 float형 데이터 4개를 캐시를 통하지 않고 첫 번째 오퍼랜드로 입력받은 메모리 주소로 복사한다.

The destination operand is a 128-bit memory location.


여기서 중요한 것은 목적지(destination)이 128-bit memory location이여야 한다는 것이다.

여기서 xmm레지스터라는 것을 처음보아서 xmm레지스터가 뭔지 검색을 한번 해보았다.


CPU에서 정보를 처리하기 위해서는 반드시 레지스터라고 하는 CPU내부의 임시 기억장소에 데이터를 적재해야 한다. 일반 범용 레지스터로 EAX, EBX, ECX, EDX 같은 게 있고 특수 용도 레지스터로 ESP, EBP 같은게 있는데 이런 것들이 모두 SISD용 레지스터다. SIMD용으로도 레지스터가 존재하며 여러 데이터를 한번에 처리하기 위해 일반 범용 레지스터보다 용량이 크다. XMM0, XMM1 같은 이름이 붙어있고 CPU가 지원하는 기술이 뭐냐에 따라 이 레지스터도 달라진다. (xmm0~xmm7)


xmm0~xmm7까지 있고, SIMD용 레지스터로 128비트 레지스터이고 연산 시 32비트 정수 네 개의 배열로 간주하고 연산한다.

즉, xmm 레지스터에 메모리를 16바이트씩 한번에 올려서 연산하므로 빠른것이다.


그리고 movntps를 수행하는데 있어, 목적지가 128-bit memory location으로 정렬되어있어야하는데, 여기서는 그게 안되서 죽은것이다.



그러므로 목적지 주소가 모두 128bit(0x10 byte)의 배수가 되도록 맞춰주면 Segmentation fault없이 작동할 것이다.


몇번의 시행착오끝에 플래그를 획득할 수 있었다.


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
from pwn import *
 
= ssh(user='memcpy',host='pwnable.kr',port=2222,password='guest')
conn = s.connect_remote('localhost'9022)
#conn = process("./memcpy")
 
def specify(amount):
    conn.recvuntil("specify the memcpy amount between")
    conn.sendline(amount)
 
conn.recvuntil("No fancy hacking, I promise :D")
 
specify("8")    #1
specify("16")    #2
specify("32")    #3
specify("72")    #4
specify("136")    #5
specify("264")    #6
specify("520")    #7
specify("1032")    #8
specify("2056")    #9
specify("4096")    #10
 
 
conn.interactive()
cs




굿!


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

[pwnable.kr] blukat  (0) 2018.08.20
[pwnable.kr] unlink  (0) 2018.08.01
[pwnable.kr] asm  (0) 2018.07.27
[pwnable.kr] leg  (0) 2018.06.08
[pwnable.kr] cmd2  (0) 2018.06.08

+ Recent posts