-
Notifications
You must be signed in to change notification settings - Fork 117
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
42 changed files
with
4,609 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
Oops, something went wrong.