Rating:

# randomware
SECCON 2016 quals
## description
300 points
My PC suddenly got broken. Could you help me to recover it please?
NOTE: The disk can be virus-infected. DO NOT RUN any programs extracted from the disk outside of sandbox.
[disk.qcow2.zip](http://files.quals.seccon.jp/disk.qcow2.zip)
Challenge files is huge, please download it first. Password will release after 60min.
password: h9nn4c2955kik9qti9xphuxti

## Solution
### Boot
First thing I did was boot up the image. I used vmware with
`qemu-img convert -f qcow2 -O vmdk disk.qcow2`. It's some custom bootloader
that starts with
![https://puu.sh/sLf1F.png](https://puu.sh/sLf1F.png)
and then shows a rainbow:
![https://puu.sh/sLf2c.png](https://puu.sh/sLf2c.png)

### Extract
I converted qcow image to a raw disk image:
`qemu-img convert -f qcow2 -O raw disk.qcow2 disk.raw`.
Disk tools didn't work since the MBR was overwritten, so I ended up using
binwalk to extract the files. First interesting thing was `23D70D`, which is an
ELF. Looking at strings suggest that it's a Linux kernel, specifically:
`Linux version 4.2.9-tinycore (tc@box) (gcc version 5.2.0 (GCC) ) #1999 SMP Mon Jan 18 19:42:12 UTC 2016`.
There also looks like a dump of the fs, split across multiple squashfs
directories, but there wasn't anything all that interesting in there (except
that firefox-esr is installed). I ended up doing `grep SECCON . -r` which has
exactly one result: 512CE00, a tar. Extracting that gives `opt/` and the very
interesting `home/` directory.

### Reverse
in `home/tc/` there is an ELF `getflag` and an encrypted
`h1dd3n_s3cr3t_f14g.jpg`. I resisted the temptation to run `getflag` on my host
and instead dropped it into IDA Pro. It's small and not stripped with only 5
non-library functions, and only 3 functions that really matter: `main`,
`recdir`, and `encrypt`.

#### main
![https://puu.sh/sLgRp.png](https://puu.sh/sLgRp.png)

1. Downloads flag from a local server
2. Calls `get_root_dev` (just `stat("/home/", root_dev)` for some reason)
3. Calls `get_key` (sets `encrypt_key` to 1024 bytes from `/dev/urandom`)
4. Calls `recdir("/home/tc")`
5. Writes bootloader from memory to `/dev/sda`

#### recdir
Walks the filesystem from `/home/` and calls `encrypt` on files that have an
extension in the whitelist which is pdf, xml, bin, txt, or various image and
office types.

#### encrypt
Encrypts files with repeated xor key (length 1024). Encryption loop:
![https://puu.sh/sLi7G.png](https://puu.sh/sLi7G.png)

### Decrypt
(Brief description of xor: `1^0==1, 0^1==1, 0^0==0, 1^1==0`.
`ciphertext = key ^ plaintext` but also `key = plaintext ^ ciphertext`)
So, xor block ciphers are vulnerable to known-plaintext attacks, and we just
have to find some plaintext. We know the header of a JPEG, so we can get the
first few bytes of the key, but that's not enough.

#### mime-types
Fortunately, there are also hidden files in the home directory. Searching for
whitelisted file extensions gives some mime-type XML files, and `blocklist.xml`
and `revocations.txt` in a Firefox profile. The mime-type files were all < 1024
bytes, with the longest, `.local/share/mime/packages/user-extension-xhtml.xml`,
being 254 bytes. I happen to have the same file on my computer with a matching
size, so by xoring the two files I got the first 254 bytes of the key (since
`ciphertext ^ plaintext = key`). Around 1/4 of the key still isn't enough to
view the image, though, so I moved on to the Firefox files.
```xml

<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
<mime-type type="application/x-extension-xhtml">
<comment>xhtml document</comment>
<glob pattern="*.xhtml"/>
</mime-type>
</mime-info>
```

#### blocklist.xml
After some research I discovered that `blocklist.xml` is updated once a day.
The one in the challenge was last modified Nov 28, and mine was last modified
Dec 10 with a significantly different size. From `about:config` I found
`https://blocklist.addons.mozilla.org/blocklist/3/%APP_ID%/%APP_VERSION%/%PRODUCT%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/%PING_COUNT%/%TOTAL_PING_COUNT%/%DAYS_SINCE_LAST_PING%/`,
but I don't know valid arguments, and it looks like there's no way to request
historical versions of the file. I spent a fair amount of time on that before
giving up and going the more effort way: since XML has a standard format, you
can guess a few characters after what's decrypted, use that to get a few more
bytes of the key, and repeat for any index multiple of 1024 in the file.

#### Example
We have a plaintext mime-type file and a ciphertext mime-type file which we xor
to get the first 254 bytes of the key. xoring the partial key with the start of
the encrypted `blocklist.xml` gives:
```xml

<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1456184414000">
<emItems>
<emItem blockID="i58" id="webmaster@buzzzzvideos.info">
<versionRange minVersion="0" maxVersion=
```
We assume that the value of `maxVersion` is a string (to match `minVersion` and
because it's that way in the newest version of the file), so we now have 255
plaintext bytes which we can xor with the ciphertext and get 255 bytes of the
key. Next, we shift to index 1024 in the ciphertext (since the xor cipher has a
block size of 1024) and xor 255 bytes with the key to get more plaintext:
```xml
<versionRange minVersion="0" maxVersion="*" severity="1">
</versionRange>
<prefs>
</prefs>
</emItem>
<emItem blockID="i105" id="{95ff02bc-ffc6-45f0-a5c8-619b8226a9de}">

```
(there's a newline and a bunch of spaces at the end there) Sadly, we don't know
what comes next this time, so we move on to index 2048.

We repeat until we get the key to length 1024.

Note: if you're doing this in vim, you'll want these options: `:set binary` and
`:set noendofline`. They will keep vim from messing with tabs and spaces and
from appending a hidden newline to the end of the file.

#### Code
I scripted as much as possible, but it still requires manual intervention on
each iteration.
```python
#!/usr/bin/python2

def xor(xs, ys):
return "".join([chr(ord(x) ^ ord(y)) for x, y in zip(xs, ys)])

def fread(path):
with open(path) as f:
return f.read()

def fwrite(path, data):
with open(path, "w") as f:
return f.write(data)

gooduserxml = fread("user-extension-xhtml.xml")
baduserxml = fread("home/tc/.local/share/mime/packages/user-extension-xhtml.xml")
badflag = fread("home/tc/h1dd3n_s3cr3t_f14g.jpg")
badblocklist = fread("home/tc/.mozilla/firefox/wir5mrmb.default/blocklist.xml")
# If working key is there, use it, else calculate first 254 bytes
try:
key = fread("key")
except IOError:
key = xor(gooduserxml, baduserxml)

i = 0
while len(key) != 1024:
fwrite("partialblocklist.xml", xor(badblocklist[i:i+len(key)], key))
# Append to partialblocklist.xml, and then press enter
raw_input("cur key len: " + str(len(key)))

partialblocklist = fread("partialblocklist.xml")
key = xor(partialblocklist, badblocklist[i:i+len(partialblocklist)])[:1024]
fwrite("key", key)
i = i + 1024 if i < len(badblocklist) - 2048 else 0

fwrite("flag.jpg", xor(badflag, key))
```

### Flag
Picture of some meat with text `SECCON{This is Virtual FAT too}`