prettify code

2016年12月21日 星期三

[Write-up] WhiteHat Grand Prix 2016 - pwn500 Bun bo Nam Bo / note


Basic Info

Attachments are two 32bit ELF note, note_client and glibc  libc-2.19




Normal protection, no PIE and Partial RELRO.

Directly run binary note will face a punch of hex:



While it looks like as normal menu challenge in IDA:



Why there's hex code when running?


After analysis, all I/O in  note has been encrypt/decrypt in some way:


Let's skip here, and see what does note_client do first:

Directly run will get:


With IDA we found it want to read a file named config.txt, and connect the host:port described in config.txt.

So we create config.txt with content  127.0.0.1 31337, and use ncat to create a socket service of note listen at 127.0.0.1:31337, then note_client:



It works!

Actually note_client just do the I/O encrypt/decrypt to communicate with note. So we can skip the enc/dec algorithm in note now and just use note_client to interact with note.

Info of note
  • Initialize: pool = (Pool*)malloc(0x1000), with structure:
    struct Pool
    {
      int limit;
      int alive_amount
      Note note[255];
    };
    struct Note
    {
      int inuse;
      int sz;
      char *content;
    };
    

    Don't care limit, alive_amount, they are used for limit the amount of note not exceed 255.

Commands:
  • list: print the content of notes with inuse is true 
  • read:
    1. check index is valid
    2. check note[index]->inuse is true
    3. show its  date/name/body
  • add: create a new note, steps show as follows:
    1. loop pool to find the first note with inuse is false 
    2. Input a size,  32 <= size <= 1024
    3. note->content = malloc(size+48)
    4. Input name:readn(note->content, 31)
    5. Input date:readn(note->content+32, 15)
    6. Input body: readn(note->content+48, size)
    7. note->inuse = true
    8. note->sz = size
  • free: specific a index
    1. check index is valid
    2. check note[index]->inuse is true
    3. free(note[index]->content)
    4. note->inuse = false
  • edit: specific a index
    1. check index is valid
    2. check note[index]->inuse is true
    3. Input name:readn(note->content, 31)
    4. Input date:readn(note->content+32, 15)
    5. Input body: readn(note->content+48, note->sz)
Vulnerability

There're many incorrect bounding check in note, but all of them are few dangers. The only crackable vulnerability is there's a buffer overflow in input function. The implementation of input looks like this: (not exactly because there's decrypt algo. in origin, follows is a simplified version):

void readn(char *output, int len) {
  int i=0, c;
  while((c = getchar()) != '\n') {
    output[i++] = c;
    if(i >= len) break;
  }
  output[i] = c;
}

If the input length is exactly len and the last character is not line break, then will cause one null-byte overflow.

Exploit

Remain is our favorite heap exploit.


Information Leak

Since we can only overflow one null-byte, we must overflow the size of next heap chunk. First use the command add to construct our heap layout:

After add(36); add(36); add(316); add(36); add(36), heap will look like this:

+---------------+---------+---------+------------------+---------+---------+-----------+
| pool: 0x1009  | 0: 0x59 | 1: 0x59 |     2: 0x171     | 3: 0x59 | 4: 0x59 | top_chunk |
+---------------+---------+---------+------------------+---------+---------+-----------+

Then free the first note(free(0)), and trigger overflow when editing the second note(edit(1)):

           |------- 0xb0 ------|
+----------+---------+---------+----------------+-+---------+---------+-----------+
|  0x1009  | 0: 0x59 | 1: 0x58 | 2: 0x100(fake) | | 3: 0x59 | 4: 0x59 | top_chunk |
+----------+---------+---------+----------------+-+---------+---------+-----------+
           |  freed  |         | prev_size(0xb0)| |
           +---------+         +----------------+-+


Since the chunk size of note 2 has been changed to 0x100, which implys its prev_inuse bit is zero, now free it(free(2)) will trigger the chunk merge between note 0 and note 2:

+----------+---------+---------+-----------+------+---------+---------+-----------+
|  0x1009  |            0x1b1              | 0x70 | 3: 0x59 | 4: 0x59 | top_chunk |
+----------+---------+---------+-----------+------+---------+---------+-----------+
                     | 1: 0x58 | <-overlap!   ^ no use
                     +---------+

So we create a large freed chunk with size 0x1b0, which overlap with the chunk of note 1 (great!).

Next we add(36):

+----------+---------+---------------------+------+---------+---------+-----------+
|  0x1009  | 0: 0x59 |     0x159(freed)    | 0x70 | 3: 0x59 | 4: 0x59 | top_chunk |
+----------+---------+---------+-----------+------+---------+---------+-----------+
                     | 1: 0x58 | 
                     +---------+

Now read(1) will leak the fdbk of an unsorted bin chunk. And if we free(3) before read, we can leak libc and heap base simultaneously (unsorted bin double linked list).

                                       +----------+
                                       |          v
+----------+---------+-----------------|---|------+---------+---------+-----------+
|  0x1009  | 0: 0x59 |  fwd: in libc, bck  | 0x70 |  freed  | 4: 0x58 | top_chunk |
+----------+---------+---------+-----------+------+---------+---------+-----------+
                     | 1: 0x58 | 
                     +---------+


Overwrite GOT

After information leak, our target turn to overwrite the char *content of pool. If we success then we can modify it to point to GOT entry and use command edit to overwrite the GOT.
We can do this with the unlink function when merge two smallbin chunk.

free(0); add(108); add(52)

+----------+-----------------+---------+------+------+---------+---------+-----------+
|  0x1009  |    0: 0xa1      | 2: 0x69 | 0xa8 | 0x70 |  freed  | 4: 0x58 | top_chunk |
+----------+---------+-------+-+-------+------+------+---------+---------+-----------+
                     | 1: 0x58 | 
                     +---------+

Notice that now edit note 1 can easily change the size and prev_size of note 2.

The plan is,  we fake a chunk inside note 1 and make its fd, bk point to near the &note[1] in pool, when this fake chunk being unlinked, we can change the char* content in note[1] of pool.

Visualization:

edit(1): fake a chunk in note 1 and change the size and prev_size of note 2.

heap           +----------+----------+
0x0000       : |   0x00   |  0x1009  |
0x0008(pool) : |   0xff   |   0x04   |
                         ...
0x0020       : |   0x24   | h+0x1068 |
                         ...
0x1008       : |   0x00   |   0xa1   |
0x1010(note0): |          |          |
                         ...
0x1068(note1): |   0x00   |   0x41   | <- fake chunk
0x1070       : |  h+0x18  |  h+0x1c  | <- fake fd|bk
                         ...
0x10a8       : |   0x40   |   0x68   |
0x10b0(note2): |          |          |
                         ...

free(2):

heap           +----------+----------+
0x0000       : |   0x00   |  0x1009  |
0x0008(pool) : |   0xff   |   0x04   |
                         ...
0x0020       : |   0x24   | h+0x1068 |
                         ...
0x1008       : |   0x00   |   0xa1   |
0x1010(note0): |          |          |
                         ...
0x1068(note1): |   0x00   |   0x41   |<-+
0x1070       : |  h+0x18  |  h+0x1c  |  | merge
                         ...            | 
0x10a8       : |   0x40   |   0x68   +--+
0x10b0(note2): |          |          |
                         ...

In the meanwhile will trigger unlink(0x1068), and the result is *(h+0x1c+8)=h+0x18
That is, the content pointer at h+0x24 being modified from h+0x1068 to h+0x18!

Edit again(edit(1)) can overwrite the char *content of note[1] itself, and edit again can modify the GOT.

In the exploit script we overwrite got_atoi to libc_system, so next time when prompt to input a index, we can input "sh" to get the shell.



BTW, because note_client will check some length information of input/output, so we cannot use it to communicate with service(will forbid us triggering bug). So we need to reverse what the IO enc/dec algorithm do and implement it ourselves. It's easy and details can be found in the exploit script.

BTW2, the exploit flow is same even if the binary harden with PIE and Full RELRO, just change the last step to overwrite the free_hook function in libc.

Exploit Script 

Flag:  WhiteHat{24b3e07a4494d4cd3ad973ee7d5fadca390df5bb}