OverTheWire Advent 2019 Day0 - Challenge Zero - Writeup

Posted on

On https://advent2019.overthewire.org/challenge-zero we are presented with a fireplace gif.

I first thought it's a forensic challenge with the fireplace gif, but couldn't find anything abnormal in the image. Then I checked the source code of the page.

<html>
    <head>
    <title>Fireplace</title>
    <!-- browser detected: chrome -->
    </head>
    <body>
    <img style="width:400px;" src="/flames.gif"><pre>Fox! Fox! Burning bright! In the forests of the night!

Hint: $ break *0x7c00</pre>
    </body>
</html>

We see that the server detects our browser and responds accordingly. Which is a hint for using another browser, or no browser at all to access the page.

So I ran

curl https://advent2019.overthewire.org/challenge-zero

and got the jumping flame in text :) curl-fireplace

If we observe the flame carefully, we can see that the text constructing the flame is repeating itself. By using Shift+PageUp of our terminal emulator we are able to reconstruct the whole text from the scrollback buffer.

In the end we can construct a text file. Let's use a simple python script to get rid of the # background.

with open('flame.txt') as f:
    data =  f.read()
data = data.replace("#", "").replace("\n", "")

data becomes

YmVnaW4gNjQ0IGJvb3QuYmluCk1eQydgQ01CLlAoWzBPYCFcMGBeQiNSI2BAXiNbQFxAIiNSK2AjUiNAIzBgJiNSK0BPTzlcWitYYDlAXloKTVgxRVMzO1g4Pz5CUWArXGA/QydgUzE4XCM3MDovYEFVI1gnX2AnWV5bS1kmPz5CNmAkX0tZOkpUI0xUMApNWl1aIV9RIV49PFwvKmA7UD8wXEgnQCFeWiMwYDlAX08nTiFdOUBcWCVdTVQiTk5TT0I1XVomMGBaX1peCk00J1QvKmA4YD9AXEgnLkAvYGBcSScoLyYkKCdeWCdVVVo+Rk1gJjgvW10pRiNeXzhOXjRWTScvIVpgP1YKTVxYQEZPR1FGI1NLP1IkNUYjVyMpX1BfJlQhIUYjXl8iQCM7Jz8pUVhcNjgvW1wkWF8nMCc5QFxYVy1DSwpNU0Y4Ly4tVzhQWzAuSyMxIj1gOFFWXFQwWl8vIzNUQDUpVihXLEI0UChSOEcpRihELCJUTzhBYCE9RihWCk0qQkxROENMRyhTIUMwRF0oJEIsUSwzNE0sIjlYOEQpLzJgJE0rUj1CKCIsQSo2KFUqUzhKOEItQitSVEYKTSlTYEw4QCQyJVYpWCREKSo4REkiRCkiMEQpIjBUKC9LQlFeIU9aLFAsITE5RVlIVSQhUSIwXjBeKz84PQpNTEdZWicsS18iND4nK05fVUsmPUEtRjsqJUNcJVw9PSgvM0tUYFosMlo5SCMiIichITheUiRANztdQi1YCk0sIUlCIV4wR15cIktWSkE7QjNMWltVV0gqWiZOW1wxQz5CST4hKjpdPEsvSCUrMywiO1tCQD47RTcySVYKTT4kWiU/KlE0YEZfUSdCIktEV1guMkJeWUBaOEYtMiQ4LzBNRFYnLl1dX1g6QCM5MS4pIkAtISVNLCVZMgoxNSZVWUArRkUiSTxEIzJXXC1AVjU1OkhgCmAKZW5kCg==YmVnaW4gNjQ0IGJvb3QuYmluCk1eQydgQ01CLlAoWzBPYCFcMGBeQiNSI2BAXiNbQFxAIiNSK2AjUiNAIzBgJiNSK0BPTzlcWitYYDlAXloKTVgxRVMzO1g4Pz5CUWArXGA/QydgUzE4XCM3MDovYEFVI1gnX2AnWV5bS1kmPz5CNmAkX0tZOkpUI0xUMApNWl1aIV9RIV49PFwvKmA7UD8wXEgnQCFeWiMwYDlAX08nTiFdOUBcWCVdTVQiTk5TT0I1XVomMGBaX1peCk00J1QvKmA4YD9AXEgnLkAvYGBcSScoLyYkKCdeWCdVVVo+Rk1gJjgvW10pRiNeXzhOXjRWTScvIVpgP1YKTVxYQEZPR1FGI1NLP1IkNUYjVyMpX1BfJlQhIUYjXl8iQCM7Jz8pUVhcNjgvW1wkWF8nMCc5QFxYVy1DSwpNU0Y4Ly4tVzhQWzAuSyMxIj1gOFFWXFQwWl8vIzNUQDUpVihXLEI0UChSOEcpRihELCJUTzhBYCE9RihWCk0qQkxROENMRyhTIUMwRF0oJEIsUSwzNE0sIjlYOEQpLzJgJE0rUj1CKCIsQSo2KFUqUzhKOEItQitSVEYKTSlTYEw4QCQyJVYpWCREKSo4REkiRCkiMEQpIjBUKC9LQlFeIU9aLFAsITE5RVlIVSQhUSIwXjBeKz84PQpNTEdZWicsS18iND4nK05fVUsmPUEtRjsqJUNcJVw9PSgvM0tUYFosMlo5SCMiIichITheUiRANztdQi1YCk0sIUlCIV4wR15cIktWSkE7QjNMWltVV0gqWiZOW1wxQz5CST4hKjpdPEsvSCUrMywiO1tCQD47RTcySVYKTT4kWiU/KlE0YEZfUSdCIktEV1guMkJeWUBaOEYtMiQ4LzBNRFYnLl1dX1g6QCM5MS4pIkAtISVNLCVZMgoxNSZVWUArRkUiSTxEIzJXXC1AVjU1OkhgCmAKZW5kCg==

It's clear that data is base64 encoded, so we run

import base64
base64decode_data = base64.b64decode(data)

base64decode_data is something like

begin 644 boot.bin\nM^C\'`CMB.P([0O`!\\0`^B#R#`@^#[@\\@"#R+`#R#@#0`&#R+@OO9\\Z+X`9@^Z\nMX1ES3;X8?>BQ`+\\`?C\'`S18\\#70:/`AU#X\'_`\'Y^[KY&?>B6`$_KY:JT#LT0\nMZ]Z!_Q!^=<\\/*`;P?0\\H\'@!^Z#0`9@_O\'N!]9@\\X%]MT"NNSOB5]Z&0`Z_Z^\nM4\'T/*`8`?@\\H\'.@/``\\I\'(/&$(\'^X\'UUZ>FM`&8/[])F#^_8N^4VM\'/!Z`?V\nM\\X@FOGQF#SK?R$5F#W#)_P_&T!!F#^_"@#;\'?)QX\\68/[\\$X_\'0\'9@\\XW-CK\nMSF8/.-W8P[0.K#1"=`8QV\\T0Z_/#3T@5)V(W,B4P(R8G)F(D,"TO8A`!=F(V\nM*BLQ8CLG(S!C0D]($B,Q,34M,"9X8D)/2`$M+R=B(",A*6(U*S8J8B-B+RTF\nM)S`L8@$2%V)X$D)*8DI"D)"0D)"0T(/KBQ^!OZ,P,!19EYHU$!Q"0^0^+?8=\nMLGYZ\',K_"4>\'+N_UK&=A-F;*%C\\%\\==(/3KT`Z,2Z9H#""\'!!8^R$@7;]B-X\nM,!IB!^0G^\\"KVJA;B3LZ[UWH*Z&N[\\1C>BI>!*:]<K/H%+3,";[B@>;E72IV\nM>$Z%?*Q4`F_Q\'B"KDWX.2B^Y@Z8F-2$8/0MDV\'.]]_X:@#91.)"@-!%M,%Y2\n15&UY@+FE"I<D#2W\\-@V55:H`\n`\nend\n

No idea what this is, but by searching the file header “begin 644” we can find out that this is uuencoded. Because the python uu.decode API requires a file input, we first write base64decode_data to a file called uuencoded_data.

with open("uuencoded_data", "wb") as f:
    f.write(base64decode_data)
import uu
with open("uuencoded_data", "rb") as f:
    uu.decode(f)

After uu.decode(f) executes, we get a boot.bin file. Running file boot.bin tells us it contains Master Boot Record.

We can run qemu-system-x86_64 boot.bin, but we get

We upgraded from RC4 this year!

Come back with a modern CPU :P

Let's try qemu-system-x86_64 boot.bin -cpu max (max Enables all features supported by the accelerator in the current host, see qemu-system-x86_64 -cpu help). Now it is asking for a password. We don't know it, let's disassemble the file by IDA.

After studying the assembly code, I added all these comments and custom function names.

ida-1 ida-2 ida-3

The idea of the program is that the user needs to type 16 chars (see 0x7C5C), which is stored in 0x7E00-0x7E0F, the predefined word xmmword_7DF0 is moved to xmm0 (see 0x7C62), the user input is moved to xmm3 (see 0x7C67), then the aes_encrypt_xmm0_as_key_xmm3_as_input function is invoked, which stores the aes encrypted result back to xmm3. The final result is compared with xmmword_7DE0. If the comparison succeeds, we jump to giving_flag_start (see 0x7C7A), which decrypts the data chunk starting from 0x7D50. Then the instruction pointer points to 0x7D50, to execute the decrypted code, which supposedly would give us the flag.

So we know the aes key is xmmword_7DF0, the aes encrypt output is xmmword_7DE0. We just need to run aes decrypt with these two values. But I actually encountered some problem there.

As can be seen in this screenshot, the xmmword displayed in IDA shows 54525E306D1134A090385136801AFEF7h and 0AA55950D36FC2D0D24970AA5B980796Dh. So I tried to run aes decrypt with these two inputs, and got some non ascii results. Even if I used

ida-4

After some struggling, I looked at the actual hexdump of the boot.bin file, and found that IDA is representing the word in little endian format.

So I ran aes decrypt with the words shown in the hexdump, and successfully got the password.

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import (
    Cipher, algorithms, modes
)
key = bytes.fromhex("6D7980B9A50A97240D2DFC360D9555AA")
decryptor = Cipher(
    algorithms.AES(key),
    modes.ECB(),
    backend=default_backend()
).decryptor()
pt = decryptor.update(bytes.fromhex("F7FE1A8036513890A034116D305E5254")) + decryptor.finalize()
print(' '.join('%02x' %x for x in pt))
print(' '.join("%d" %x for x in pt))
print(''.join("%c" %x for x in pt))

We get the password MiLiT4RyGr4d3MbR.

After we type these 16 characters in the emulator, we get the flag AOTW{31oct__1s__25dec}.

flag

Yes, 512 bytes certainly is a lot of space. :)


Extra Stuff

Another way to enter the input, which was used when I was trying to enter the incorrect non-ascii values.

At one shell, we run the following script (-s is shorthand for -gdb tcp::1234, -S freezes CPU at startup, which allows us to set breakpoint before the program starts)

qemu-system-x86_64 boot.bin -cpu max -s -S

At another shell, we use gdb to debug and manipulate the memory

gdb
target remote localhost:1234
b *0x7C67 (stop when the program tries to move our input to xmm3 register)
continue
type 16 random chars in the emulator
    We encounter breakpoint *0x7C67
    We modify the user input (0x7E00-0x7E0F) by running 
set {int}0x7E00 = 77
set {int}0x7E01 = 105
set {int}0x7E02 = 76
set {int}0x7E03 = 105
set {int}0x7E04 = 84
set {int}0x7E05 = 52
set {int}0x7E06 = 82
set {int}0x7E07 = 121
set {int}0x7E08 = 71
set {int}0x7E09 = 114
set {int}0x7E0A = 52
set {int}0x7E0B = 100
set {int}0x7E0C = 51
set {int}0x7E0D = 77
set {int}0x7E0E = 98
set {int}0x7E0F = 82
continue

We get the flag.