# code_runner
# Summary
After a PoW check, the server generates a MIPS binary, sends it base64-encodedto us, and then runs it. The binary checks 64 bytes we supply to its stdin, andin case it's happy, it lets us give it some shellcode to run. The catch is thatgenerates binaries are kind of different, and there is a time limit.
# Analysis
Even though there is PoW, it's still possible to download a significant amountof samples. I let the script to run in the background, and it ended up collectingabout 1000. The `main()` function is always like this:
```int main() {  gettimeofday(&dt, NULL);  if (checker()) {    gettimeofday(&t1, NULL);    dt.tv_usec = ((t1.tv_sec - dt.tv_sec) * 1000000 + t1.tv_usec) - dt.tv_usec;    if (dt.tv_usec / 100000 < 13)      read(0, shellcode, 0x34);    (*(code *)shellcode)();```
This means we have 1.3 seconds to find something that the checker would accept.The first two layers are always the same:
```uint checker() {  puts("Faster > ");  read(0,input,0x100);  checker_trampoline(input);```
followed by
```uint checker_trampoline(uchar *input) {  return checker0(input);```
What follows are 16 chained checkers, each of which inspects 4 bytes:
```uint checker0(uchar *input) {  if ((input[1] == input[3]) && (input[0] == input[2]) &&      (input[0] == -0x26) && (input[3] == 0x1a))    return checker1(input + 4);  else    return 0;}```
and the final checker is always the same:
```uint checker16(uchar *input) {  return 0xc001babe;}```
# Attempt 1: angr
This looks simple enough for [angr to solve](https://github.com/mephi42/ctf/blob/master/2020.05.02-De1CTF_2020/code_runner/angrit.py). Indeed, angr cuts throughmost checkers like a hot knife through butter. What gives it pause are nonlinearcheckers like this one:
```uint checker3(uchar *input) {  x1 = input[2] * input[2] - input[1] * input[1];  if (x1 < 0) x1 = -x1;  x2 = input[3] * input[3] - input[0] * input[0];  if (x2 < 0) x2 = -x2;  if (x2 <= x1) {    x1 = input[3] * input[3] - input[2] * input[2];    if (x1 < 0) x1 = -x1;    x2 = input[0] * input[0] - input[1] * input[1];    if (x2 < 0) x2 = -x2;    if (x1 < x2)      return checker4(input + 4);    }  }  return 0;}```
The good news is that there is always a solution, where inputs are either `0`or `1`, so trying each checker with such an extra constraint first lets us getpast those without adding any explicit signatures.
This puts us at 15 seconds under pypy3 though, and despite the presence of someglaring inefficiencies, like starting symbolic execution at the very entrypoint, it's highly unlikely that we can get to 1.3s. So this approach has tobe scrapped.
# Attempt 2: grouping and signatures
Since `checker_trampoline()` is always the same, and each checker follows thesame pattern:
```if (!cond1) goto fail;if (!cond2) goto fail;...return checker_n+1(input + 4);  # jal ADDRfail:return 0;                       # jr ra```
we can very quickly identify and extract every single one of them from allsamples, and then do a pairwise [`difflib.SequenceMatcher().ratio()`](https://docs.python.org/3/library/difflib.html#difflib.SequenceMatcher.ratio) onthem in order to find similar groups.
There are certain immediately noticeable thresholds: 98% and 70%. Using 98%produces groups that differ only by constant values (good), but there are waytoo many of them to further handle manually. Using 70% produces a moremanageable amount of groups, however, the differences within each group arequite significant - it's not only register numbers, but sometimes also sequencesof instructions.
I'm pretty sure an AV guru could've make it work, but having stared at samplesfor quite some time, I knew that I would have more luck with the next approach.
# Solution: signatures + DIY femtoangr
angr has a lot of nice features, like using a proper IR and forking states, butthis challenge doesn't really need them, and they appear slow us down. What weneed is converting an essentially linear sequence of checks into a constraintand letting z3 solve it.
There are only ~20 various MIPS instructions in collected samples, so it wasfairly easy to write [such a convertor](https://github.com/mephi42/ctf/blob/master/2020.05.02-De1CTF_2020/code_runner/emu.py). There are a bunch of placeswhere we can cheat and get away with it: not handling overflows, considering all"branch taken" events as lava and matching `abs()` by signature.
Combined with nearly instantaneous extraction of checkers from the secondapproach this puts us at 0.2 seconds, which is more than enough.
There are plenty of MIPS shellcodes floating on the internet, I ended up used[this one](https://packetstormsecurity.com/files/105611/MIPS-execve-Shellcode.html),because it fit somewhat tight space requirements. The [combined exploit](https://github.com/mephi42/ctf/blob/master/2020.05.02-De1CTF_2020/code_runner/pwnit.py) worked!
# Flag
`De1ctf{9d94bc3d3f57f1b33aee728b5d32d6d473df8df3}`