Skip to content

Commit

Permalink
Add twctf2018 writeups.
Browse files Browse the repository at this point in the history
  • Loading branch information
rickyz committed Sep 9, 2018
1 parent edc9f4b commit 59bbbaa
Show file tree
Hide file tree
Showing 42 changed files with 4,609 additions and 0 deletions.
1 change: 1 addition & 0 deletions twctf2018/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Writeups for TokyoWesterns 2018 CTF, held from September 1 to 3, 2018.
85 changes: 85 additions & 0 deletions twctf2018/REVersiNG/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# REVersiNG

## Beginning

As the name might suggest, REVersiNG is a binary reversing problem. However the name gives us another hint beyond just the problem's category. As we open the binary, we notice that it is beyond large. In fact, when faced with trying to generate its control-flow graph, Hopper simply died. This is because it was generated using `revng`, a tool for translating binaries from one architecture into another.

As the size would suggest, trying to reverse this monstrosity is enormously difficult. Initially, we began investigaating options for trying to rewrite the binary, as many of its constructs were relatively straightforward and could likely have been reduced to something simpler.

Fortunately for us, however, it became readily apparent that we would not need to do this. Suspiciously, the bytes at `0x400000` begin with `ELF`, indicating that maybe their is another binary located in the file. After extracting it and relocating it to `0x400000`, we can see that this was the original executable, a `mips` program with only a few interesting functions.

## Reversing

Without going into too much detail about the actual implementation, the binary first reads a file called `key` (which we assumed to be the flag), then `12` from `/dev/urandom`. After this initialization, it prompts the user for 1 byte, then another 4. Afterwards, it spits out those `12` bytes it read from `/dev/urandom` as well as two identical 128-byte strings.

Diving in a little deeper into the crypto portion of the code, we see that its doing a series of adds, rotates, and xors. In all likelihood, this means that it is likely some form of ARX hash. By drawing out the exact execution flow of the function, we see that it is identical to the Chacha20 hash.

Now that we know what the operation being performed is, we need to determine what the attack should be. Looking back at out inputs, we see that the crypto function is being called twice with nearly identical arguments. The only bit that changes is the last argument, which iis initially `0`, but becomes `1` during the second pass. Furthermore, that input is being compared to the byte we entered initially. If they are the same, then it looks at the next 4 bytes. It treats those bytes as a pointer, and nulls out whatever is at that byte.

Now, we have something interesting, we can null out exactly one byte during the process execution. Clearly, we should use this to attack the cipher. At a high-level, what the cipher does is (in 20 iterations), take the input, perform the ARX actions on it, add it to the original input, and return that as the output. Thus, if we can null out the corresponding input byte after its already been mixed as part of the ARX actions, we will then have at that byte just the mixed result. If we consider the `i`th byte of the flag, `Ai` to be the `i`th byte of the input, and `Mi` to be the `i`th byte of the mixed input, then from the first pass we know `Ai+Mi`, and from the second pass wit know `0+Mi`. Thus, if we simply subtract the second result from the first, we get back the original input!

In practice, this is very easy to do, because the point at which we have the overwrite occurs after the data is copied out for the `M` computation, but before it is mixed with `A`. Thus, at that point we can null out the byte we want to extract!

Here is the code that does so

```python
import subprocess
from binascii import hexlify, unhexlify
import random
import sys
from pwn import *

i = 0
p = 0

known = "KNOWN_PLAIN_TEXT" * 8

def random_str():
global i,p
#return chr(random.randint(0, 1)) + "\x00\x41".join([chr(random.randint(0, 255)) for i in range(4)])
if p == 0:
p = 1
if p == 1:
p = 0
i += 1
return chr(1) + "\x00\x41" + chr(i / 256 + 0x20) + chr(i % 256)



def trial(num):
# input_data = subprocess.Popen("/file/rev", stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
pipe = remote("pwn1.chal.ctf.westerns.tokyo", 16625)
input_word = "\x00\x00\x41\x23" + chr(0x20 + num)
pipe.send(input_word)
data = pipe.recvall().split("\n")
if len(data[2]) == 0:
return "\x00"

print hexlify(input_word)

data1_new = []
data2_new = []

for it in range(0, len(data[1]), 2):
elt = int(data[1][it] + data[1][it+1], 16) ^ ord(known[it/2])
data1_new.append(elt)
for it in range(0, len(data[2]), 2):
elt = int(data[2][it] + data[2][it+1], 16) ^ ord(known[it/2])
data2_new.append(elt)

thing = (data1_new[num] - data2_new[num] + 256) % 256
print(data1_new[num], data2_new[num], thing, hex(thing), chr(thing))
return chr(thing)


final = [trial(i) for i in range(0x10, 0x31)]

str = "".join(final)
print(hexdump(str))
print(repr(str))
with open("final_{}.txt".format(sys.argv[1]), "w") as f:
f.write(str)

```

Note, that it is possible for this to fail, as the addition could cause a carry which we have no way to track. This is why we run it multiple times and save the output. However in practice, this was not an issue and our initial execution found the flag.
104 changes: 104 additions & 0 deletions twctf2018/darts/darts.code
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
main() {
flag = new File("flag").readAsBytesSync()
data = new QK2kqq(100, 100).fhghlm(flag)
data.forEach(_Closure)
}

_Closure(K, V) {
/* send UDP packets to K with data V */
hdvley(K, V)
}

/* i4RgAL = random.nextInt */
QK2kqq.mps5g0(int arg) {
v20 = new List();
int i = 0;
while(arg > 0) {
int v = ke3oum.i4RgAL();
if(v20.contains(v) || v == 0)
continue;
v20[i] = v;
i++;
arg--;
}
return v20;
}

QK2kqq.fhghlm(List<> arg) {
if(arg.length == 0) {
throw ArgumentError();
}
/* not sure */
if(any(not (0 <= i && i <= 255) for i in arg)) {
throw ArgumentError();
}
v30 = new HashMap<int, List<int>>;
/* generate ports list */
v38 = this.mps5g0(this.sa6em2);
for(i in v38) {
v30[i] = new List(arg.length);
}

v40 = this.hph9df - 1;
for(v48 = 0; v48 < arg.length; v48++) {
v58 = new Y9u8Iu(v40);
v58.o09ha7(arg[v48], this.ke3oum);
for(j in v38) {
v38[j][v48] = v58.l75g4v(j)
}
}
}

Y9u8Iu.enxs4e(OlZeuV random) {
return random.i4RgAL();
}

Y9u8Iu.o09ha7(int arg, OlZeuV random) {
if(arg > 255 || arg < 0) {
throw ArgumentError();
}
if(random == null) {
throw ArgumentError();
}
this.gaeym7 = new List(this.awx1n6 + 1);
this.gaeym7[0] = arg;
for(r28 = 1; r28 < this.gaeym7.length; r28++) {
this.gaeym7[r28] = this.enxs4e(random);
}
while(this.gaeym7.last == 0) {
this.gaeym7[this.gaeym7.length - 1] = this.enxs4e(random);
}
this.oozkuu = true;
}

e5v35l(int a, int b) {
return a ^ b;
}

zhl9i7(int a, int b) {
/* a @ 0x18, b @ 0x10 */
if(a == 0 || b == 0) {
return 0;
}

return fjke0l[sfyc9a[a] + sfyc9a[b]];
}

Y9u8Iu.l75g4v(int arg) {
if(arg > 255 || arg < 0) {
throw ArgumentError();
}
if(this.oozkuu == false) {
throw ArgumentError();
}

if(arg == 0) {
return this.h4jcj4;
}

v28 = 0;
for(v30 = this.gaeym7.length - 1; v30 >= 0; v30--) {
v28 = e5v35l(zhl9i7(v28, arg), this.gaeym7[v30]);
}
return v28;
}
Loading

0 comments on commit 59bbbaa

Please sign in to comment.