Skip to content

Commit 45a83d4

Browse files
committed
authenticator writeup
1 parent 53dea62 commit 45a83d4

File tree

2 files changed

+203
-0
lines changed

2 files changed

+203
-0
lines changed

2018-11-8-defcamp-finals/README.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Defcamp finals CTF 2018
2+
3+
Team: Eternal, msm, shalom, des, monk
4+
5+
### Table of contents
6+
7+
* [authenticator (pwn)](authenticator)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
# authenticator (pwn, 2 solved)
2+
3+
This task was categoried as pwn, but in fact it was RE and simple crypto.
4+
The hardest part of this task was to reverse engineer the binary.
5+
6+
main calls following functions:
7+
8+
`000000000406950` is sha1 function from boost library. The proof is the string `/usr/include/boost/uuid/sha1.hpp`.
9+
10+
11+
`0000000000406410` is also a cryptography function.
12+
Let's call it `crypto_function` now, we will look at it closely later.
13+
14+
The pseudocode of main() function is below:
15+
16+
```C
17+
int main()
18+
{
19+
sha = boost_sha1(); //buffer of 16 bytes
20+
21+
if (strcmp(user_input(),"HELLO")) return 0;
22+
23+
bytes = read("/dev/urandom",16); //buffer of 16 bytes
24+
print_bytes_hex(bytes);
25+
bytes2 = read("/dev/urandom",16); //buffer of 16 bytes
26+
print_bytes_hex(bytes2);
27+
28+
print_some_strange_data(); //idk what is this, it's not needed anyway :)
29+
30+
if(crypto_function(bytes2, user_input(), 16, some_bytes) == bytes1)
31+
print crypto_function(bytes2, flag, 16, some_bytes);
32+
}
33+
```
34+
35+
`user_input` is a function which reads data from user.
36+
37+
Now let's investigate `crypto_function`.
38+
I was sure that this isnt any new crypto but just from some library.
39+
We can see that it calls various methods from vtable.
40+
After nawigating to the memory of them we can gain more information.
41+
42+
For example above offset `73F500` there is following string:
43+
44+
```
45+
.data.rel.ro:000000000073F4F8 dq offset _ZTIN8CryptoPP14CTR_ModePolicyE ; `typeinfo for'CryptoPP::CTR_ModePolicy
46+
.data.rel.ro:000000000073F500 unk_73F500 db 0
47+
```
48+
49+
```
50+
.data.rel.ro:0000000000741BC8 dq offset _ZTIN8CryptoPP8Rijndael4BaseE ; `typeinfo for'CryptoPP::Rijndael::Base
51+
.data.rel.ro:0000000000741BD0 unk_741BD0 db 0 ; DATA XREF: sub_406410+3CD↑o
52+
```
53+
54+
55+
So I just concluded that this is AES-128 in CTR mode.
56+
I've set a breakpoint at this function and printed their arguments:
57+
58+
- 1: random data, different at every time.
59+
- 2: user input
60+
- 3: int 16
61+
- 4: 0x0d00000f0d0a0af7, 0x0d00000f0d0a0a0b
62+
- 5: where encrypted data is saved
63+
64+
I came to the conclusion that the first argument is ctr, 4 is the key.
65+
I checked if my theory about this cipher function is correct - I copied arguments and output abd wrote the following python script:
66+
67+
```python
68+
from pwn import *
69+
from crypto_commons.symmetrical import aes
70+
71+
def aes_encode(input):
72+
global key
73+
global ctr
74+
AES = aes.AES()
75+
AES.init(key)
76+
w = AES.encrypt(ctr)
77+
w = xor(input, w)
78+
return w
79+
80+
def aes_decode(input):
81+
global key
82+
global ctr
83+
AES = aes.AES()
84+
AES.init(key)
85+
w = AES.encrypt(ctr)
86+
w = xor(input, w)
87+
return w
88+
89+
key = "f70a0a0d0f00000d0b0a0a0d0f00000d" #wytestowac nowe kombinacje
90+
ctr = "f3 e9 cf 98 bb 8d 94 58 43 61 21 f4 f8 e3 19 ad"
91+
input = "a"*16
92+
93+
ctr = ctr.replace(" ","").decode("hex")
94+
key = key.replace(" ","").decode("hex")
95+
96+
x = aes_encode(input)
97+
print "encrypted user input: "+x.encode("hex")
98+
print "the output from binary: 9bcefda8b86570db6d8330986472ac5e"
99+
```
100+
101+
if we want to pass `if(crypto_function(bytes2, user_input(), 16, some_bytes) == bytes1)` it's obvious, that user input needs to be equal the output of the decryption function AES-128 CTR mode with `bytes1` as data to decrypt
102+
103+
104+
The key to `crypto_function` was the same at every run when ASLR was switched off.
105+
When ASLR was switched on, only the first byte of the key was different at every run.
106+
I also checked this on different linux systems and it was the same.
107+
So I wrote the exploit that connects to the server and tries to brute-force all possibilities of the first byte of the key:
108+
109+
```
110+
from pwn import *
111+
from crypto_commons.symmetrical import aes
112+
113+
def recvall(r):
114+
d = ""
115+
n = "a"
116+
117+
while n:
118+
n = r.recv(timeout = 0.2)
119+
d += n
120+
121+
return d
122+
123+
def aes_decode_(key,input,ctr):
124+
print ctr
125+
ctr = hex(ctr)[2:]
126+
ctr = ctr.rjust(32,"0")
127+
ctr = ctr.decode("hex")
128+
129+
AES = aes.AES()
130+
AES.init(key)
131+
132+
w = AES.encrypt(ctr)
133+
w = xor(input, w)
134+
return w
135+
136+
def split_string(string, split_string):
137+
return [string[i:i+split_string] for i in range(0, len(string), split_string)]
138+
139+
def aes_decode(key,input,ctr):
140+
ctr = ctr.encode("hex")
141+
ctr = int(ctr,16)
142+
input = split_string(input, 16)
143+
decrypted = ""
144+
for i in input:
145+
decrypted += aes_decode_(key, i, ctr)
146+
ctr += 1
147+
return decrypted
148+
149+
def try_key(key):
150+
r = remote("46.101.180.78", 13031)
151+
r.sendline("HELLO")
152+
153+
data = recvall(r)
154+
print data
155+
random1 = data.split("\n")[0]
156+
random2 = data.split("\n")[1]
157+
print "-----------"
158+
print random1
159+
print random2
160+
161+
random1 = random1.replace(" ","").decode("hex") #to ma wyjsc
162+
random2 = random2.replace(" ","").decode("hex") #ctr
163+
164+
key = key.decode("hex")
165+
166+
inp = aes_decode(key, random1, random2)
167+
print inp.encode("hex")
168+
169+
r.send("1"+inp+"\n")
170+
171+
print "encrypted flag:"
172+
173+
encrypted_flag = r.recv()
174+
print len(encrypted_flag)
175+
print encrypted_flag
176+
print "###"
177+
decrypted = aes_decode(key,encrypted_flag,random2)
178+
print decrypted
179+
if "DCTF" in decrypted:
180+
exit()
181+
r.close()
182+
183+
for brut_byte in range(0x00,0x100):
184+
gg = hex(brut_byte)[2:]
185+
gg=gg.rjust(2,"0")
186+
print gg
187+
try_key(gg+"0a0a0d0f00000d0b0a0a0d0f00000d")
188+
```
189+
190+
191+
and the flag is:
192+
193+
```
194+
DCTF{a1fee34f2a3e6e010d786f02865dc39896faa6b589d1f57f565bac9bd1d85cae}
195+
```
196+

0 commit comments

Comments
 (0)