|
| 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