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 carelimit, alive_amount
, they are used for limit the amount of note not exceed 255.
Commands:
- list: print the content of notes with
inuse
istrue
- read:
- check
index
is valid - check
note[index]->inuse
istrue
- show its
date/name/body
- add: create a new note, steps show as follows:
- loop pool to find the first note with
inuse
isfalse
- Input a
size
,32 <= size <= 1024
note->content = malloc(size+48)
- Input
name:readn(note->content, 31)
- Input
date:readn(note->content+32, 15)
- Input
body: readn(note->content+48, size)
note->inuse = true
note->sz = size
- free: specific a
index
- check
index
is valid - check
note[index]->inuse
istrue
free(note[index]->content)
note->inuse = false
- edit: specific a
index
- check
index
is valid - check
note[index]->inuse
istrue
- Input
name:readn(note->content, 31)
- Input
date:readn(note->content+32, 15)
- 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
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:
Then free the first note(free(0)), and trigger overflow when editing the second note(edit(1)):
Since the chunk size of note 2 has been changed to 0x100, which implys its
So we create a large freed chunk with size
Next we add(36):
Now read(1) will leak the
Overwrite GOT
After information leak, our target turn to overwrite the
We can do this with the unlink function when merge two smallbin chunk.
free(0); add(108); add(52)
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
Visualization:
edit(1): fake a chunk in note 1 and change the size and prev_size of note 2.
free(2):
In the meanwhile will trigger
That is, the content pointer at
Edit again(edit(1)) can overwrite the char *content of
In the exploit script we overwrite
BTW, because
BTW2, the exploit flow is same even if the binary harden with PIE and Full RELRO, just change the last step to overwrite the
Exploit Script
Flag:
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
fd
, bk
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
¬e[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.Flag:
WhiteHat{24b3e07a4494d4cd3ad973ee7d5fadca390df5bb}
沒有留言:
張貼留言