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

We got x64 ELF binary ‘license’


Decompile result of main function starts with opening a file _a\nb\tc_, since it is inconvenient to create file with such name, we replace the filename with following command

hexdump -ve '1/1 "%.2X"' license | sed 's/5F610A6209635F/6B657966696C65/g' | xxd -r -p > license_patch

now we create a file name “keyfile” and get following error.


from next logic, we can see that size of keyfile should satisfy some equation.


which is

44242*X^5 - 45235*X^4 - 1256*X^3 + 14392*X^2 - 59762*X - 1949670109068 = 0

we solve this using z3 solver and found out that size should be 34 byte. next, the file should contain 5 newlines


the file contents needs to be separated with newline and each line has to contain 6bytes which will be compared after XORing with hardcoded XORed key:



As a result, the following equation should be satisfied.

s.add(l4 == 0x686779477857)
s.add(l1 ^ l2 == 0x694b576f5a4c)
s.add(l2 ^ l4 ^ 0x232323232323 == 0x5663344c5479)
s.add(l3 ^ l4 == 0x477243526564)
s.add(l3 ^ (l4 ^ l5 ^ 0x232323232323) == 0x506866456e69)

solving this equation with z3 yields

l1 = 128008166266177
l2 = 32055189049101
l3 = 51768215280947
l4 = 114793625647191
l5 = 57419521861678

generating keyfile with these keys concatenated with newline we get following result

[email protected]:~/tmp# ./license
program successfully registered to ASIS{8d2cc30143831881f94cb05dcf0b83e0}
[email protected]:~/tmp#

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

Steps to solve this challenge:

if (md5($username) == $user_data[0]) {
    return array($username, base64_decode($user_data[1])); 
  • Process the user data and search for vulnerable username
  • Find
{ user = "adm2salwg", userMd5 = "0e004561083131340065739640281486", email = "[email protected]", active = "1" }
  • Find an other MD5 “collision” (as a lot of already known collision found on the web are “already used”)
  • Our collision was:
MD5("D8WKOXN880XR") == "0e299238785153218472769311512731"
  • Register a new user with this username (“D8WKOXN880XR”)
  • The get_user function will match adm2salwg’s user info instead of our new user’s thus leaking out adm2salwg’s password: [email protected]$%rq0
  • Login with adm2salwg / [email protected]$%rq0
  • Profit:

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

There was a server on which there was a message and there were about 1300 users (My solution used only one).

When a client connected to the server a random prime was generated (e <= 65537).

Each user had a modulus (N).

There were two permitted operations:

  • GET(user_id, msg_id): the server replied with [N_{user_id}, (msg_{msg_id}**e) % N_{user_id}]
  • ADD(user_id, new_msg): the server replied with [N_{user_id}, (new_msg**e) % N_{user_id}]

The task was to recover msg_0.


  • ADD some random message m to user_0. We can simply bruteforce e by raising our message to every possible power.
  • GET msg_0 with user_0, this way we know e and c = (msg_0**e) % N_0
  • Reconnect to the server (this generates a new e2), and repeat the previous steps to get e2 and c2 = (msg_0**e2) % N_0
  • Find x and y integers such that xe + ye2 = 1 (with the Extended Euclidean Algorithm)
  • Calculate (c**x * c2**y) % N_0 = (msg_0**(e*x) * msg_0**(e2*y)) % N_0 = (msg_0**(e*x+e2*y)) % N_0 = msg_0 % N_0 = msg_0
  • msg_0 contained the flag.

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

The task was to break a TLS connection where DHE was used but not with a safe prime.

I extracted the group parameters and the public keys from Wireshark.

To get the private keys I had to compute the discrete logarithm of the public keys, Sage has a builtin function discrete_log() for this.

That function internally uses the Pohlig-Hellman algorithm, the problem’s name was a reference to this.

After I had the private keys, I computed the shared secret (= server private key ** client public key = client private key ** server public key).

TLS uses this shared secret as the Pre-Master Secret.

Recent Wireshark versions support PMS_CLIENT_RANDOM lines in SSL key log files, which means that I could give it the Client Random and the PMS.

Unfortunately this didn’t work, Wireshark didn’t properly parsed that line.

I fixed this issue in Wireshark, and later submitted the fix for them, it has been merged into the main Wireshark repo:

After this patch it successfully decrypted the TLS session and I got the flag.

KT’s notes

Wireshark did not recognised the SSL stream for me by default, so I had to use the Decode As… > SSL option to make this work.

While NGG patched the Wireshark, I used Bouncy Castle to generate the master secret from the pre-master secret as Wireshark could decode the packet by using the master secret (but NOT with the pre master secret).

But I had to use the new PRF function which is used by TLS v1.2, the SHA256 one. I first tried to use the legacy (MD5+SHA1) one which did not work of course.

The decrypted stream was gziped too, so I had to decompress it manually.

Here is my python code to calculate the pre master secret from the private key primes (provided by NGG):

g = 15
p = 120154561482812169366431552175315487829790853533514455083187141150401404579723989466386713554692826031183462112641793395815695957964420754471645865010199918851008631038679685035857813488382170765657628252079464574576993595350214255290554756868269962991079417299897885957935578968328491235168443836989742332343
serverPub = 15369826910045798505944602235292509575421896014593476253460520436999422262976808201559207167484626647165429162578239702208851445339474080566111254064294894686179067829420678898877680468207865053420154070753622998449913849113660665890563557345041388819744669458524582116729348785032868594899974751301992211942
clientPriv = 60718865604238636792448303494365716882164666056259649436621448321833526997406139737129717809752411282395923980538280108384420161312200897847011832376271391686584918997410839768394294177900987398210617282873117230335140878980614031856353751625633843722321910793427876767949814642081894104118067451970498867957
serverPriv = 59006032085539066654738961800014267746790599707391816715596169525214790059448996782118611368019640695755528975737064604557552323199458051510791227341575474224365096322734016366705156141900274852582650051394166907890512962805910611488014074434283648737771547999795442960002220587672342309646529188635103348462
clientPub = 105015090494516026290331080495860554512811478600077917407214638372179160992689979146469996516559216693457065339039865100354916844456254305946233030576312061960323729497136060115320306445494733252851807759941954458152508254981769624193022726156751468585330721382626827982843354097562826035890756697793638835353
assert(serverPub == pow(g, serverPriv, p))
assert(clientPub == pow(g, clientPriv, p))
preMasterSecret = pow(serverPub, clientPriv, p)

And this is my C# code to calculate the master secret from the premaster secret:

var premaster = new BigInteger("97767226768431843909574565470123616032917083203013628903095612670005753675445500525763667655689728731939280038960341016957753437419456873567523867888108036157325629926777593214675498958231293240650034816836615958992638881953048254604005620980597958229509174130519560051229345937360583880913807554453333987854");
var masterSecret = TlsUtilities.PRFHack(premaster, "master secret", TlsUtilities.Concat(
    HexToBytes("63e2b53d65a8e860794ff15e90e7a369b179843f349b68df4b59d1ac53003735")), 48);
var masterSecretHex = BitConverter.ToString(masterSecret).Replace("-", "").ToLower();

Setting this file in File > Preferences > Protocols > SSL as the (Pre)-Master-Secret log filename I could follow the SSL with the unpatched Wireshark too:

CLIENT_RANDOM 7712ca85fcd84204104909836b7c92cec6094277f1c0efe885102d74a30b6dcc 599dae45bea108da405cebfa1fd4414acccdc24ab4262c1c1e86af60da2de9712933343d3c6f92486db4bdbda9d693f8


After decompressing the HTTP response body I got the file’s content:

Flag is ASIS{f702d759801533096be29291fd6e82c3}

A website showed the world map, clicking on your country (depending on your IP) gave you a part of the flag:


Clicking on other country said you cannot do that:


Solution was: using a few proxies, TOR, Hola VPN, etc the gather the 6 parts of the flag and then figure out in which order should we concatenate them (from the 6! = 720 possibilites). We know that the SHA256(SHA256(“ASIS{…}”)) hash should match with the hash of the real flag (which was used by the ASIS scoreboard in a client-side javascript code).

Usually on every ASIS CTF we have to use this trick, so if you haven’t know it yet, then don’t forget for next year. :)