├── README.md ├── exp ├── README.md ├── libc.so.6 └── main.elf ├── r2con2017.pdf └── re ├── README.md └── antir2 /README.md: -------------------------------------------------------------------------------- 1 | # r2con-prequals-rhme3 2 | 3 | ### r2 the Rhme3! 4 | 5 | The RHme (Riscure Hack me) is a low level hardware CTF that comes in the form of an Arduino board (AVR architecture). It involves a set of SW and HW challenges to test your skills in different areas such as side channel analysis, fault injection, reverse-engineering and software exploitation. In our talk we will briefly recap RHme2 and introduce the upcoming RHme3. This year we decided to create a special target called the Riscurino board which features CAN controllers for a real automotive hacking experience! During the r2con we challenge you to solve as many challenges as you can using radare2. Are you up to the task? By the time the r2con takes place the registration for RHme3 will be closed. However, we reserved 5 Riscurino boards for giving away during the conference. Be at r2con and win one of these boards by solving the qualification challenge(s) using radare2! 6 | 7 | Eduardo Novella & Dana Geist 8 | 9 | ### Riscure B.V. 10 | -------------------------------------------------------------------------------- /exp/README.md: -------------------------------------------------------------------------------- 1 | Connect to pwn.rhme.riscure.com:1337 2 | 3 | -------------------------------------------------------------------------------- /exp/libc.so.6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enovella/r2con-prequals-rhme3/3834565614eadeb5395de495ba913f744cd682ab/exp/libc.so.6 -------------------------------------------------------------------------------- /exp/main.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enovella/r2con-prequals-rhme3/3834565614eadeb5395de495ba913f744cd682ab/exp/main.elf -------------------------------------------------------------------------------- /r2con2017.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enovella/r2con-prequals-rhme3/3834565614eadeb5395de495ba913f744cd682ab/r2con2017.pdf -------------------------------------------------------------------------------- /re/README.md: -------------------------------------------------------------------------------- 1 | 2 | During the RadareCON 2017, a pre-qualification binary was provided to obtain a RHme3 board. The binary was an `OLLVM` obfuscated binary with some anti-debugging and anti-hooking techniques to thwart r2 reversers. The binary was named `antir2`. 3 | 4 | The following r2con attendants solved the challenge: 5 | - Cyrill Leutwiler 6 | - Roberto Gutierrez 7 | - Quentin Casasnovas 8 | - Irdeto team (Jonathan Beverley, Colin deWinter, Ben Gardiner) 9 | 10 | Other non-Radare2 write-up that is worth mentioning it: 11 | - Vegard Nossum 12 | 13 | 14 | # Write-up by Quentin Casasnovas 15 | Running the binary shows some plaintext which is then cyphered, printed, 16 | decyphered and printed again. The goal seems to find the AES key: 17 | 18 | ```sh 19 | r2@57f5b63ba13a prequals-rhme3 $ ./antir2 20 | ******************************************* 21 | * Radare2con 2017 rhme3 pre-quals edition * 22 | ******************************************* 23 | 24 | r<< Can you r2 me? 25 | 26 | Plaintext : 47 65 74 20 74 68 65 20 61 65 73 6B 65 79 21 21 27 | Encrypted : FD DA 9B 78 FC F8 E9 BF 33 72 6E 0A 8A E5 F6 8C 28 | Decrypted : 47 65 74 20 74 68 65 20 61 65 73 6B 65 79 21 21 29 | ``` 30 | 31 | OK let's see what rabin2 tells us about the binary: 32 | 33 | ```sh 34 | r2@57f5b63ba13a prequals-rhme3 $ rabin2 -I antir2 35 | arch x86 36 | binsz 1200746 37 | bintype elf 38 | bits 64 39 | canary false 40 | class ELF64 41 | crypto false 42 | endian little 43 | havecode true 44 | lang c 45 | linenum false 46 | lsyms false 47 | machine AMD x86-64 architecture 48 | maxopsz 16 49 | minopsz 1 50 | nx true 51 | os linux 52 | pcalign 0 53 | pic false 54 | relocs false 55 | rpath NONE 56 | static true 57 | stripped true 58 | subsys linux 59 | va true 60 | ``` 61 | 62 | It's statically linked and stripped. Next logical step is to have a look 63 | around at the strings: 64 | 65 | ```sh 66 | r2@57f5b63ba13a prequals-rhme3 $ rabin2 -z antir2 67 | ``` 68 | 69 | Loads of them, as expected since it's statically linked. The first few ones 70 | are interestings and shows the binary probably has some built-in 71 | anti-debugging features, and that it might look for frida as well: 72 | 73 | ```sh 74 | r2@57f5b63ba13a wargames/prequals-rhme3 $ rabin2 -z antir2 | head -n20 0 75 | vaddr=0x004e1b8f paddr=0x000e1b8f ordinal=000 sz=38 len=37 section=.rodata type=ascii string=.Get the aeskey!!r<< Can you r2 me?\n\n 76 | vaddr=0x004e1bb5 paddr=0x000e1bb5 ordinal=001 sz=10 len=9 section=.rodata type=ascii string=Plaintext 77 | vaddr=0x004e1bbf paddr=0x000e1bbf ordinal=002 sz=10 len=9 section=.rodata type=ascii string=Encrypted 78 | vaddr=0x004e1bc9 paddr=0x000e1bc9 ordinal=003 sz=10 len=9 section=.rodata type=ascii string=Decrypted 79 | vaddr=0x004e1bd3 paddr=0x000e1bd3 ordinal=004 sz=45 len=44 section=.rodata type=ascii string=*******************************************\n 80 | vaddr=0x004e1c00 paddr=0x000e1c00 ordinal=005 sz=45 len=44 section=.rodata type=ascii string=* Radare2con 2017 rhme3 pre-quals edition *\n 81 | vaddr=0x004e1c2d paddr=0x000e1c2d ordinal=006 sz=46 len=45 section=.rodata type=ascii string=*******************************************\n\n 82 | vaddr=0x004e1c5b paddr=0x000e1c5b ordinal=007 sz=6 len=5 section=.rodata type=ascii string=%s : 83 | vaddr=0x004e1c61 paddr=0x000e1c61 ordinal=008 sz=6 len=5 section=.rodata type=ascii string=%02X 84 | vaddr=0x004e1c67 paddr=0x000e1c67 ordinal=009 sz=57 len=56 section=.rodata type=ascii string=r2 in debug mode won't help you much! ha ha ha...... :)\n 85 | vaddr=0x004e1ca0 paddr=0x000e1ca0 ordinal=010 sz=31 len=30 section=.rodata type=ascii string=Breakpoint detected @ %p+%x !\n 86 | vaddr=0x004e1cbf paddr=0x000e1cbf ordinal=011 sz=16 len=15 section=.rodata type=ascii string=/proc/self/maps 87 | vaddr=0x004e1ccf paddr=0x000e1ccf ordinal=012 sz=6 len=5 section=.rodata type=ascii string=frida 88 | vaddr=0x004e1cd5 paddr=0x000e1cd5 ordinal=013 sz=49 len=48 section=.rodata type=ascii string=Damn it! You're so shady! h00king isn't allowed\n 89 | vaddr=0x004e1d06 paddr=0x000e1d06 ordinal=014 sz=13 len=12 section=.rodata type=ascii string=aes(partial) 90 | vaddr=0x004e1d20 paddr=0x000e1d20 ordinal=015 sz=39 len=38 section=.rodata type=ascii string=AES part of OpenSSL 1.0.2g 1 Mar 2016 91 | vaddr=0x004e1d47 paddr=0x000e1d47 ordinal=016 sz=11 len=10 section=.rodata type=ascii string=cryptlib.c 92 | vaddr=0x004e1d52 paddr=0x000e1d52 ordinal=017 sz=8 len=7 section=.rodata type=ascii string=dynamic 93 | vaddr=0x004e1d5a paddr=0x000e1d5a ordinal=018 sz=6 len=5 section=.rodata type=ascii string=ERROR 94 | vaddr=0x004e1d60 paddr=0x000e1d60 ordinal=019 sz=16 len=15 section=.rodata type=ascii string=OPENSSL_ia32cap 95 | ``` 96 | 97 | It's statically linked with OpenSSL 1.0.2g, compiled 1 Mars 2016 so we 98 | could probably generate some signatures provided we can find which 99 | distributions it was compiled on to help with sorting out the mess. Well 100 | grepping for "Ubuntu" in the strings gives it away but let's try to dive in 101 | first. 102 | 103 | We fire radare2 in debug mode and run the binary: 104 | 105 | ```sh 106 | r2@57f5b63ba13a wargames/prequals-rhme3 $ radare2 -d antir2 107 | Process with PID 132 started... 108 | = attach 132 132 109 | bin.baddr 0x00400000 110 | Using 0x400000 111 | Warning: Cannot initialize dynamic strings 112 | asm.bits 64 113 | -- Change the graph block definition with graph.callblocks, graph.jmpblocks, graph.flagblocks 114 | [0x004008c0]> dc 115 | ******************************************* 116 | * Radare2con 2017 rhme3 pre-quals edition * 117 | ******************************************* 118 | 119 | r2 in debug mode won't help you much! ha ha ha...... :) 120 | ``` 121 | 122 | As expected we get rejected. Let's have a look at the syscalls the binary 123 | is using: 124 | 125 | ```sh 126 | [0x004008c0]> dcs* 127 | [0x004008c0]> dcs* 128 | ... 129 | 130 | child stopped with signal 133 131 | --> SN 0x00485fba syscall 101 ptrace (0x0 0x0 0x1 0x0) 132 | child stopped with signal 133 133 | --> SN 0x00485fba syscall 101 ptrace (0x0 0x0 0x1 0x0) 134 | ``` 135 | 136 | Ohoh, some `ptrace()` syscalls, looks like the binary is ptracing itself. A 137 | quick look from the ptrace.h header file, we see `0x0` is `PTRACE_ME`, so the 138 | binary tries to ptrace itself, twice. As we're debugging it already, both 139 | calls will fail, and we're told out by antir2. Alright, let's patch this 140 | syscall instruction to clear eax so that it appears to succeed. To do 141 | this, we can use the Visual assembler from radare2 (need to re-open the 142 | binary in write mode for this): 143 | 144 | ```sh 145 | [0x00485fba]> s 0x00485fba 146 | [0x00485fba]> V 147 | ``` 148 | 149 | We can hit `A` and then either `nop;nop`, or `xor eax, eax`. 150 | We run the binary again, and... failure, the binary still complains. 151 | Alright, it checks that the first call succeeds, and the second should 152 | fail. Let's patch the two other call site with NOPs, we can find them by 153 | looking at the cross references: 154 | 155 | ```sh 156 | / (fcn) fcn.00485f70 114 157 | | fcn.00485f70 (); 158 | | ; CALL XREF from 0x0041b5b0 (main) 159 | | ; CALL XREF from 0x0042116a (main) 160 | | ; CALL XREF from 0x0041a1bc (main) 161 | | 0x00485f70 488d442408 lea rax, [rsp + 8] ; 8 162 | | 0x00485f75 448d47ff lea r8d, [rdi - 1] 163 | | 0x00485f79 4c8d5424b0 lea r10, [rsp - 0x50] 164 | | 0x00485f7e 48897424d8 mov qword [rsp - 0x28], rsi 165 | | 0x00485f83 48895424e0 mov qword [rsp - 0x20], rdx 166 | ``` 167 | 168 | That still doesn't work, instead of trying to understand the internal 169 | logic, we can pretend the first ptrace succeeds right after the syscall 170 | opcode by clearing eax, then let it fail as it ought to afterwards. 171 | 172 | ```sh 173 | # I've used equivalent commands on gdb when solving this but am including 174 | # how I would have done in r2land... 175 | [0x00485fba]> dcs ptrace 176 | [0x00485fba]> dr rax = 0 177 | [0x00485fba]> dc 178 | ``` 179 | 180 | Alright, now let's keep looking. We assume from the binary that some 181 | routine cyphers/decyphers the text, so we can look at cross references from 182 | the string Encrypted: 183 | 184 | ```sh 185 | [0x004008c0]> f ~Encrypted # This grep all flags for "Ecrypted" 186 | 0x004e1bbf 10 str.Encrypted 187 | 0x004f46e0 32 str.Microsoft_Encrypted_File_System 188 | ``` 189 | 190 | First one is a match, and we want to find where it's used from: 191 | ```sh 192 | [0x004008c0]> axt `f ~Encrypted:0[0]` 193 | data 0x400c7e mov eax, str.Encrypted in main 194 | ``` 195 | 196 | The syntax above takes the first line of the match, first column in a 197 | subshell, which is then passed to axt to look for cross references. We end 198 | up on `0x400c7e`, around: 199 | 200 | ```sh 201 | | 0x00400c51 41b9b51b4e00 mov r9d, str.Plaintext ; 0x4e1bb5 ; "Plaintext" 202 | | 0x00400c57 4489cf mov edi, r9d 203 | | 0x00400c5a 41b9901b4e00 mov r9d, 0x4e1b90 ; "Get the aeskey!!r<< Can you r2 me?\n\n" 204 | | 0x00400c60 4489ce mov esi, r9d 205 | | 0x00400c63 41b910000000 mov r9d, 0x10 ; 16 206 | | 0x00400c69 4489ca mov edx, r9d 207 | | 0x00400c6c 898550fdffff mov dword [local_2b0h], eax 208 | | 0x00400c72 44898d4cfdff. mov dword [local_2b4h], r9d 209 | | 0x00400c79 e8c20a0100 call 0x411740 ;[2] 210 | | 0x00400c7e b8bf1b4e00 mov eax, str.Encrypted ; 0x4e1bbf ; "Encrypted" 211 | | 0x00400c83 89c7 mov edi, eax 212 | | 0x00400c85 488bb568fdff. mov rsi, qword [local_298h] 213 | | 0x00400c8c 8b954cfdffff mov edx, dword [local_2b4h] 214 | | 0x00400c92 e8a90a0100 call 0x411740 ;[2] 215 | | 0x00400c97 b8c91b4e00 mov eax, str.Decrypted ; 0x4e1bc9 ; "Decrypted" 216 | | 0x00400c9c 89c7 mov edi, eax 217 | | 0x00400c9e 488bb558fdff. mov rsi, qword [local_2a8h] 218 | | 0x00400ca5 8b954cfdffff mov edx, dword [local_2b4h] 219 | | 0x00400cab e8900a0100 call 0x411740 ;[2] 220 | ``` 221 | 222 | Looks like `0x411740` is the function printing the line with the buffer. 223 | Let's find where the buffer is in memory first: 224 | ```sh 225 | drx 1 0x00400c92 x # Add hardware breakpoint to the tracee 226 | dc 227 | dr 228 | ``` 229 | 230 | We use hardware breakpoints in case there are other anti-debugging 231 | techniques looking for int3 in the code (remember the string from running 232 | rabin2 -z about the detected breakpoint!). The two buffers for 233 | encrypted/decrypted are at `0x7fffffffd860` and `0x7fffffffd870`. We can now 234 | add a watchpoints to see when this is being written to: 235 | 236 | ```sh 237 | # Re-open binary 238 | doo 239 | # Setup our watchpoints 240 | drw 1 0x7fffffffd860 8 w 241 | drw 2 0x7fffffffd870 8 w 242 | # Continue 243 | dc 244 | # Look at what's in there 245 | px 32 @ 0x7fffffffd860 246 | dc 247 | ``` 248 | 249 | BTW, `dm` will show us this address is on stack, and we keep hitting it at 250 | the program startup for false positive until finally it gets written the 251 | bytes we expect. At that point we can use `dbt` to get a backtrace or just 252 | print `dr rip` to get the instruction pointer. We appear to be at 253 | 0x0042d17a, which is writing `eax` into `[r9]`, and the last thing that 254 | happened before that is `fcn.0042bff0`... 255 | ```sh 256 | | | 0x0042d166 e885eeffff call fcn.0042bff0 ;[1] 257 | | | 0x0042d16b 4c8b442418 mov r8, qword [rsp + 0x18] ; [0x18:8]=-1 ; 24 258 | | | 0x0042d170 4c8b4c2420 mov r9, qword [rsp + 0x20] ; [0x20:8]=-1 ; 32 259 | | | 0x0042d175 4c8b542428 mov r10, qword [rsp + 0x28] ; [0x28:8]=-1 ; '(' ; 40 260 | | | 0x0042d17a 418901 mov dword [r9], eax 261 | | | 0x0042d17d 41895904 mov dword [r9 + 4], ebx 262 | | | 0x0042d181 41894908 mov dword [r9 + 8], ecx 263 | | | 0x0042d185 4189510c mov dword [r9 + 0xc], edx 264 | | | 0x0042d189 4d8d4010 lea r8, [r8 + 0x10] ; 16 265 | ``` 266 | 267 | Let's have a look at that function: 268 | ```sh 269 | [0x0042bff0 14% 140 ./antir2]> pd $r @ fcn.0042bff0 270 | / (fcn) fcn.0042bff0 591 271 | | fcn.0042bff0 (); 272 | | ; CALL XREF from 0x0042c2d6 (fcn.0042bff0 + 742) 273 | | ; CALL XREF from 0x0042d166 (fcn.0042cd90) 274 | | 0x0042bff0 4d8d86800000. lea r8, [r14 + 0x80] ; 128 275 | | 0x0042bff7 418b7880 mov edi, dword [r8 - 0x80] 276 | | 0x0042bffb 418b68a0 mov ebp, dword [r8 - 0x60] 277 | | 0x0042bfff 458b50c0 mov r10d, dword [r8 - 0x40] 278 | | 0x0042c003 458b58e0 mov r11d, dword [r8 - 0x20] 279 | | 0x0042c007 418b38 mov edi, dword [r8] 280 | | 0x0042c00a 418b6820 mov ebp, dword [r8 + 0x20] ; [0x20:4]=-1 ; 32 281 | | 0x0042c00e 458b5040 mov r10d, dword [r8 + 0x40] ; [0x40:4]=-1 ; '@' ; 64 282 | | 0x0042c012 458b5860 mov r11d, dword [r8 + 0x60] ; [0x60:4]=-1 ; '`' ; 96 283 | | ,=< 0x0042c016 eb08 jmp 0x42c020 ;[1] 284 | | 0x0042c018 0f1f84000000. nop dword [rax + rax] 285 | | | ; JMP XREF from 0x0042c016 (fcn.0042bff0) 286 | | | ; JMP XREF from 0x0042c231 (fcn.0042bff0) 287 | | `-> 0x0042c020 413307 xor eax, dword [r15] 288 | | 0x0042c023 41335f04 xor ebx, dword [r15 + 4] 289 | | 0x0042c027 41334f08 xor ecx, dword [r15 + 8] 290 | | 0x0042c02b 4133570c xor edx, dword [r15 + 0xc] 291 | ``` 292 | 293 | That looks very interesting, it loads many registers and the starts XOR'ing 294 | eax, ebx, ecx, edx with what's pointed to by `r15`, and xoring the key with 295 | clear text is the first step of AES. Let's add a breakpoint there and look 296 | at the registers: 297 | ```sh 298 | dbx 1 0x0042c020 1 x 299 | dr 300 | ``` 301 | 302 | Interestingly, eax, ebx, ecx and edx are loaded with what appears to be 303 | ascii text, and in fact it is "Get the aeskey!!". Now let's look at what's 304 | pointed to by `r15`... 305 | ```sh 306 | ps @ r15 307 | radare2con4ever!>!\x... 308 | ``` 309 | 310 | Looks like we got the flag :) We can make sure in python repl quickly: 311 | ```sh 312 | In [1]: from Crypto.Cipher import AES 313 | 314 | In [2]: key = 'radare2con4ever!' 315 | 316 | In [3]: enc = AES.new(key, AES.MODE_ECB) 317 | 318 | In [4]: enc.encrypt('Get the aeskey!!') 319 | Out[4]: '\xfd\xda\x9bx\xfc\xf8\xe9\xbf3rn\n\x8a\xe5\xf6\x8c' 320 | ``` 321 | 322 | 323 | # Write-up by Cyrill Leutwiler 324 | 325 | The crackme is a single file called `antir2`. What is it? file will tell us: 326 | 327 | ```sh 328 | $ file antir2 329 | antir2: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, stripped 330 | ``` 331 | 332 | It's a stripped and statically linked 64bit ELF binary Let's run it! 333 | 334 | ```sh 335 | # ./antir2 336 | ******************************************* 337 | * Radare2con 2017 rhme3 pre-quals edition * 338 | ******************************************* 339 | 340 | r<< Can you r2 me? 341 | 342 | Plaintext : 47 65 74 20 74 68 65 20 61 65 73 6B 65 79 21 21 343 | Encrypted : FD DA 9B 78 FC F8 E9 BF 33 72 6E 0A 8A E5 F6 8C 344 | Decrypted : 47 65 74 20 74 68 65 20 61 65 73 6B 65 79 21 21 345 | ``` 346 | 347 | We can see that it's probably doing some crypto stuff. The plaintext converted to ascii is: 348 | 349 | ``` 350 | Get the aeskey!! 351 | ``` 352 | 353 | To the r2land! 354 | ```sh 355 | # r2 -AAwd antir2 356 | [...] 357 | [0x004008c0]> dc 358 | ******************************************* 359 | * Radare2con 2017 rhme3 pre-quals edition * 360 | ******************************************* 361 | 362 | r2 in debug mode won't help you much! ha ha ha...... :) 363 | ``` 364 | 365 | 366 | orly? I think you're underestimating the power of r2 but we will talk about this later :) Jokes aside, there are obviously some antidebug measures around. 367 | The most common way to do such stuff is by just calling `ptrace`. Simplified, with the `ptrace` syscall you can debug a program. But a program can only have one debugger attached at the same time. The syscall will fail in that case. So if a program tries to debug itself by calling `ptrace` and it fails, it does know that it is being debugged. We can find out with r2 easily (open it again): 368 | 369 | ```sh 370 | [0x004008c0]> dcs ptrace 371 | Running child until syscalls:101 372 | child stopped with signal 133 373 | --> SN 0x004b5357 syscall 63 uname (0x7ffe3dfcf0f0) 374 | child stopped with signal 133 375 | --> SN 0x004b55b9 syscall 12 brk (0x0) 376 | child stopped with signal 133 377 | --> SN 0x004b55b9 syscall 12 brk (0x1a3c1c0) 378 | child stopped with signal 133 379 | --> SN 0x0043d9ec syscall 158 arch_prctl (0x1002 0x1a3b880 0x0) 380 | child stopped with signal 133 381 | --> SN 0x004bf8af syscall 89 readlink (0x50c343 0x7ffe3dfce220 0x1000) 382 | child stopped with signal 133 383 | --> SN 0x004b55b9 syscall 12 brk (0x1a5d1c0) 384 | child stopped with signal 133 385 | --> SN 0x004b55b9 syscall 12 brk (0x1a5e000) 386 | child stopped with signal 133 387 | --> SN 0x004b5467 syscall 21 access (0x50be7e 0x0) 388 | child stopped with signal 133 389 | --> SN 0x004854a4 syscall 5 fstat (0x1 0x7ffe3dfce7c0) 390 | child stopped with signal 133 391 | --> SN 0x004855b0 syscall 1 write (0x1 0x1a3dbe0 0x2c) 392 | ******************************************* 393 | child stopped with signal 133 394 | --> SN 0x004855b0 syscall 1 write (0x1 0x1a3dbe0 0x2c) 395 | * Radare2con 2017 rhme3 pre-quals edition * 396 | child stopped with signal 133 397 | --> SN 0x004855b0 syscall 1 write (0x1 0x1a3dbe0 0x2d) 398 | ******************************************* 399 | 400 | child stopped with signal 133 401 | --> SN 0x00485fba syscall 101 ptrace (0x0 0x0 0x1 0x0) 402 | ``` 403 | 404 | People are often complaining that r2 cli is hard... `dcs ptrace` simply stands for "Debug Continue Systemcall ptrace" (or something like that). One command and we know where to look, nice. The code around RIP is the following: 405 | 406 | ```sh 407 | 0x00485fb3 b865000000 mov eax, 0x65 ; orax 408 | 0x00485fb8 0f05 syscall 409 | ;-- rcx: 410 | ;-- rip: 411 | 0x00485fba 483d00f0ffff cmp rax, 0xfffffffffffff000 412 | ┌─< 0x00485fc0 7726 ja 0x485fe8 ;[1] 413 | │ 0x00485fc2 4183f802 cmp r8d, 2 ; 2 414 | ┌──< 0x00485fc6 7718 ja 0x485fe0 ;[2] 415 | ││ 0x00485fc8 4885c0 test rax, rax 416 | ┌───< 0x00485fcb 7813 js 0x485fe0 ;[2] 417 | │││ 0x00485fcd 48c7c0d0ffff. mov rax, 0xffffffffffffffd0 418 | │││ 0x00485fd4 64c700000000. mov dword fs:[rax], 0 419 | │││ 0x00485fdb 488b4424b0 mov rax, qword [rsp - 0x50] 420 | └└──> 0x00485fe0 f3c3 ret 421 | │ 0x00485fe2 660f1f440000 nop word [rax + rax] 422 | └─> 0x00485fe8 48c7c2d0ffff. mov rdx, 0xffffffffffffffd0 423 | 0x00485fef f7d8 neg eax 424 | 0x00485ff1 648902 mov dword fs:[rdx], eax 425 | 0x00485ff4 48c7c0ffffff. mov rax, 0xffffffffffffffff 426 | 0x00485ffb c3 ret 427 | ``` 428 | 429 | This might look a bit strange. But you can run `dcs ptrace` again and it will hit again. So its called twice. With debugger: `ptrace` will fail twice. Without debugger: `ptrace` will work the first time and return the PID and fail the second time. To make debugging great again we need to simulate the latter situation. Peace of cake with r2. Reopen the program and: 430 | 431 | ```sh 432 | [0x004008c0]> s 0x00485fb3;wx b827000000;s 0x00485fc2;wx 909090909090;dcu 0x00485fb3;dcu 0x00485fb3;wx b865000000 433 | Continue until 0x00485fb3 using 1 bpsize 434 | ******************************************* 435 | * Radare2con 2017 rhme3 pre-quals edition * 436 | ******************************************* 437 | 438 | hit breakpoint at: 485fb3 439 | Continue until 0x00485fb3 using 1 bpsize 440 | hit breakpoint at: 485fb3 441 | [0x00485fb3]> dc 442 | r<< Can you r2 me? 443 | 444 | Plaintext : 47 65 74 20 74 68 65 20 61 65 73 6B 65 79 21 21 445 | Encrypted : FD DA 9B 78 FC F8 E9 BF 33 72 6E 0A 8A E5 F6 8C 446 | Decrypted : 47 65 74 20 74 68 65 20 61 65 73 6B 65 79 21 21 447 | ``` 448 | 449 | You can chain commands together with ";" as you would in bash. So just copy paste `0x00485fb3;wx b827000000;s 0x00485fc2;wx 909090909090;dcu 0x00485fb3;dcu 0x00485fb3;wx b865000000` after opening the file and your fine. 450 | What it does it simply seeks to the location where we found the `ptrace` syscall, patches it so it becomes a `getpid` syscall, continues until the next call to that location and patches it again so it's a `ptrace` command (that actually is supposed to fail the 2nd time) again. 451 | Side note: I found hints that there might be more antidebug stuff but I did never trigger them and thus not further investigate: 452 | 453 | ```sh 454 | [0x00484b88]> iz | less 455 | vaddr=0x004e1ca0 paddr=0x000e1ca0 ordinal=010 sz=31 len=30 section=.rodata type=ascii string=Breakpoint detected @ %p+%x !\n 456 | vaddr=0x004e1cbf paddr=0x000e1cbf ordinal=011 sz=16 len=15 section=.rodata type=ascii string=/proc/self/maps 457 | vaddr=0x004e1ccf paddr=0x000e1ccf ordinal=012 sz=6 len=5 section=.rodata type=ascii string=frida 458 | vaddr=0x004e1cd5 paddr=0x000e1cd5 ordinal=013 sz=49 len=48 section=.rodata type=ascii string=Damn it! You're so shady! h00king isn't allowed\n 459 | ``` 460 | 461 | So we can start digging around. And prepare to getting headaches, since this binary is clearly obfuscated. The main function is huge and there are several big blocks of binary operations (xor,and,or,...). I tried to navigate around and sniff the aeskey out of memory by stepping but didn't get anywhere and gave up on that quickly. We need to get a higher level understanding of the binary. By looking at the strings again I've found the following (interestingly, right next to the anti debug messages): 462 | 463 | ```sh 464 | vaddr=0x004e1d06 paddr=0x000e1d06 ordinal=014 sz=13 len=12 section=.rodata type=ascii string=aes(partial) 465 | vaddr=0x004e1d20 paddr=0x000e1d20 ordinal=015 sz=39 len=38 section=.rodata type=ascii string=AES part of OpenSSL 1.0.2g 1 Mar 2016 466 | ``` 467 | 468 | Neat, this is very valuable Information. The binary does crypto stuff and this is how it's done. Why using a decompiler if we already have the source? I download said Version of OpenSSL to find the code in question: 469 | 470 | ```sh 471 | root@computer ~/openssl-OpenSSL_1_0_2g # grep -r partial * | grep aes 472 | crypto/aes/aes_misc.c: return "aes(partial)"; 473 | [...] 474 | ``` 475 | 476 | Looks good. The code file is very small and easy to understand. Import part out of it: 477 | 478 | ```c 479 | const char *AES_options(void) 480 | { 481 | #ifdef FULL_UNROLL 482 | return "aes(full)"; 483 | #else 484 | return "aes(partial)"; 485 | #endif 486 | } 487 | 488 | /* FIPS wrapper functions to block low level AES calls in FIPS mode */ 489 | 490 | int AES_set_encrypt_key(const unsigned char *userKey, const int bits, 491 | AES_KEY *key) 492 | { 493 | #ifdef OPENSSL_FIPS 494 | fips_cipher_abort(AES); 495 | #endif 496 | return private_AES_set_encrypt_key(userKey, bits, key); 497 | } 498 | ``` 499 | 500 | Around the function `*AES_options(void)` we should be able to find the function `int AES_set_encrypt_key(...)` nearby! This is a great fact, because: 501 | - Thanks to the cross reference analysis by r2 we have a reference to `*AES_options()` 502 | - The aes key will be in the function arguments (`rdi` will hold the key) 503 | 504 | Lets do this: 505 | 506 | ```sh 507 | [0x004008c0]> pd 1 @ vaddr=0x004e1d06 508 | ;-- str.aes_partial_: 509 | ; DATA XREF from 0x0042bdd0 (main) 510 | 0x004e1d06 .string "aes(partial)" ; len=13 511 | ``` 512 | 513 | Right after the function where the string is being reference comes the next function: 514 | 515 | ```sh 516 | ┌ (fcn) fcn.0042bde0 40 517 | │ fcn.0042bde0 (); 518 | │ ; CALL XREF from 0x00400b50 (main) 519 | │ ; DATA XREF from 0x00400b31 (main) 520 | └ ┌─< 0x0042bde0 e9cb0a0000 jmp loc.0042c8b0 ;[1] 521 | │ 0x0042bde5 90 nop 522 | │ 0x0042bde6 662e0f1f8400. nop word cs:[rax + rax] 523 | ``` 524 | 525 | We might want to debug a bit there: We got a jump to location `0x0042c8b0`. 526 | 527 | ```sh 528 | [0x0042c8b0]> pd 10 @0x0042c8b0 529 | ;-- rip: 530 | 0x0042c8b0 53 push rbx 531 | 0x0042c8b1 55 push rbp 532 | 0x0042c8b2 4154 push r12 533 | 0x0042c8b4 4155 push r13 534 | 0x0042c8b6 4156 push r14 535 | 0x0042c8b8 4157 push r15 536 | 0x0042c8ba 4883ec08 sub rsp, 8 537 | 0x0042c8be e81d000000 call 0x42c8e0 538 | 0x0042c8c3 488b6c2428 mov rbp, qword [rsp + 0x28] ; [0x28:8]=-1 ; '(' ; 40 539 | 0x0042c8c8 488b5c2430 mov rbx, qword [rsp + 0x30] ; [0x30:8]=-1 ; '0' ; 48 540 | ``` 541 | 542 | Here is our function. Now we only have to break there and print out the flag! \o/ 543 | 544 | ```sh 545 | [0x0042c8b0]> ps@rdi 546 | radare2con4ever!`\xdbC 547 | ``` 548 | 549 | The key is `radare2con4ever!`. 550 | 551 | 552 | # Write-up by Roberto Gutierrez 553 | 554 | 555 | Let's inspect a bit the binary: 556 | 557 | ```sh 558 | $ rabin2 -I antir2 559 | arch x86 560 | binsz 1200746 561 | bintype elf 562 | bits 64 563 | canary false 564 | class ELF64 565 | crypto false 566 | endian little 567 | havecode true 568 | lang c 569 | linenum false 570 | lsyms false 571 | machine AMD x86-64 architecture 572 | maxopsz 16 573 | minopsz 1 574 | nx true 575 | os linux 576 | pcalign 0 577 | pic false 578 | relocs false 579 | rpath NONE 580 | static true 581 | stripped true 582 | subsys linux 583 | va true 584 | ``` 585 | 586 | So we have a static ELF x64 with no symbols. if we execute it, we get the following output: 587 | 588 | ```sh 589 | $ ./antir2 590 | ******************************************* 591 | * Radare2con 2017 rhme3 pre-quals edition * 592 | ******************************************* 593 | 594 | r<< Can you r2 me? 595 | 596 | Plaintext : 47 65 74 20 74 68 65 20 61 65 73 6B 65 79 21 21 597 | Encrypted : FD DA 9B 78 FC F8 E9 BF 33 72 6E 0A 8A E5 F6 8C 598 | Decrypted : 47 65 74 20 74 68 65 20 61 65 73 6B 65 79 21 21 599 | ``` 600 | 601 | It seems that the objective is to find the encryption key. Let's open IDA Pro and load the bina....just kidding :) The first idea is to debug the binary and trace the key, but If you try, you will get this annoying message... 602 | 603 | ```sh 604 | $ r2 -d antir2 605 | [0x004008c0]> dc 606 | Selecting and continuing: 30699 607 | ******************************************* 608 | * Radare2con 2017 rhme3 pre-quals edition * 609 | ******************************************* 610 | 611 | r2 in debug mode won't help you much! ha ha ha...... :) 612 | PTRACE_EVENT_EXIT pid=30699, status=0x0 613 | [0x00484b88]> 614 | ``` 615 | It seems we will need to deal with some kind of anti-debugging trick. Let's start the analysis: 616 | 617 | ```sh 618 | $ r2 -Aw antir2 619 | ``` 620 | 621 | Taking a look into the code, immediately we realise that there's something wrong...the main function apparently is huge, there are a lot of conditional and unconditional jumps and radare stop responding when we open the graph mode. Interesting... 622 | 623 | Definitely the code is obfuscated or it contains some kind of anti-disassemble tricks. After some minutes reviewing the code, I found a string that confirms the obfuscator used: 624 | 625 | ```sh 626 | Obfuscator-LLVM clang version 4.0.1 (based on Obfuscator-LLVM 4.0.1) 627 | ``` 628 | 629 | With the obfuscation, the automatic analysis of radare fails to detect the boundaries of the functions. Let's fix that manually. 630 | 631 | In visual mode, let's go down in the main function until the start of the next function. Just after printing the strings "Plaintext", "Encrypted" and "Decrypted", it's easy to identify the preamble of the next function: 632 | 633 | ```sh 634 | [0x004009e0]> s 0x400cdb 635 | [0x00400cdb]> pd 4 @ $$-5 636 | │ ; JMP XREF from 0x00400cc0 (main) 637 | │ 0x00400cd6 e855830800 CALL fcn.00489030 638 | │ 0x00400cdb 0f1f440000 NOP DWORD [RAX + RAX] 639 | │ 640 | │ ; XREFS: CALL 0x00400a74 CALL 0x00400a86 CALL 0x00400bbe CALL 0x00400bca CALL 0x00400b2c CALL 0x00400b3b 641 | │ 0x00400ce0 55 PUSH RBP 642 | │ 0x00400ce1 4889e5 MOV RBP, RSP 643 | [0x00400cdb]> 644 | ``` 645 | 646 | Now we can define the end of the main function manually. Some useful commands in visual mode: 647 | 648 | ```sh 649 | de : set the end of the function 650 | df : analyze the next function 651 | dr : rename the function 652 | ``` 653 | Move the cursor to 0x00400cdb, press 'de' , then move the cursor one instruction down (0x00400ce0) and then press 'df'. 654 | 655 | ```sh 656 | # In visual mode 657 | o 0x00400cdb # NOP DWORD [RAX + RAX] 658 | de # sets the end of the main function 659 | j # seek next instruction 660 | df # defines the start of the next function 661 | ``` 662 | 663 | Probably this is not the best way to do it, but the trick works here and now it's possible to open the main function in the graph mode without problems. 664 | 665 | ```sh 666 | [0x00400cdb]> s main 667 | [0x004009e0]> VV 668 | ``` 669 | 670 | After that, I did the same with all the functions called in the main (that were not detected properly). The typical function prologue and the Xrefs can be used to clearly identify the boundaries of the functions. In less than one minute you can have all the necessary functions well defined. 671 | 672 | Now, it's time to get ride of the anti-debugging tricks. Let's locate the text message below: 673 | 674 | ```sh 675 | [0x004009e0]> iz~debug 676 | vaddr=0x004e1c67 paddr=0x000e1c67 ordinal=009 sz=57 len=56 section=.rodata type=ascii string=r2 in debug mode won't help you much! ha ha ha...... :)\n 677 | ``` 678 | Find where the anti-dbg string is used. 679 | 680 | ```sh 681 | [0x00400b88]> axt 0x004e1c67 682 | data 0x421208 movabs rdi, str.r2_in_debug_mode_won_t_help_you_much__ha_ha_ha......_:__n in fcn.00418db0 683 | data 0x41f9e4 movabs rdi, str.r2_in_debug_mode_won_t_help_you_much__ha_ha_ha......_:__n in fcn.00418db0 684 | ``` 685 | 686 | Let's patch the function fcn.00418db0 then: 687 | 688 | ```sh 689 | [0x00421208]> s fcn.00418db0 690 | [0x00418db0]> wa ret 691 | Written 1 bytes (ret) = wx c3 692 | ``` 693 | 694 | Now there won't be problems to run the debugger. The binary contains other anti-reversing tricks, but you can follow the same procedure to find and patch those functions using the below messages: 695 | 696 | ```sh 697 | vaddr=0x004e1ca0 paddr=0x000e1ca0 ordinal=010 sz=31 len=30 section=.rodata type=ascii string=Breakpoint detected @ %p+%x !\n 698 | vaddr=0x004e1cbf paddr=0x000e1cbf ordinal=011 sz=16 len=15 section=.rodata type=ascii string=/proc/self/maps 699 | vaddr=0x004e1ccf paddr=0x000e1ccf ordinal=012 sz=6 len=5 section=.rodata type=ascii string=frida 700 | vaddr=0x004e1cd5 paddr=0x000e1cd5 ordinal=013 sz=49 len=48 section=.rodata type=ascii string=Damn it! You're so shady! h00king isn't allowed\n 701 | ``` 702 | 703 | With the debugger ready, the objective is to identify the encryption function and stop the execution somewhere before to obtain the encryption key. My approach was pretty simple, locate where the program prints the encrypted string and trace it back until the encryption function. 704 | 705 | ```sh 706 | [0x00400c7e]> iz~Encrypted 707 | vaddr=0x004e1bbf paddr=0x000e1bbf ordinal=002 sz=10 len=9 section=.rodata type=ascii string=Encrypted 708 | 709 | [0x00400c7e]> axt 0x4e1bbf 710 | data 0x400c7e mov eax, str.Encrypted in main 711 | 712 | [0x00400c7e]> s 0x400c7e 713 | [0x00400c7e]> pd 5 714 | │ 0x00400c7e b8bf1b4e00 MOV EAX, str.Encrypted ; 0x4e1bbf ; "Encrypted" 715 | │ 0x00400c83 89c7 MOV EDI, EAX 716 | │ 0x00400c85 488bb568fdff. MOV RSI, QWORD [LOCAL_298H] 717 | │ 0x00400c8c 8b954cfdffff MOV EDX, DWORD [LOCAL_2B4H] 718 | │ 0x00400c92 e8a90a0100 CALL fcn.00411740 719 | ``` 720 | Checking the arguments of the function, we can confirm in the debugger that `local_298h` contains the encrypted string. Let's trace back this value until we find the encryption function. 721 | 722 | ```sh 723 | # Previously "local_298h" is used here: 724 | 0x00400c03 4889bd68fdff. MOV QWORD [LOCAL_298H], RDI 725 | 726 | # Tracing back the value of rdi. It's set here: 727 | 0x00400bf5 488d7dc0 LEA RDI, [LOCAL_40H] 728 | 729 | # Going back the value of local_40h. It is used as a param in the following call: 730 | 731 | │ │ 0x00400b6a 48bf901b4e00. MOVABS RDI, 0x4E1B90 ; 0x4e1b90 ; "Get the aeskey!!r<< Can you r2 me?\n\n" 732 | │ │ 0x00400b74 b810000000 MOV EAX, 0x10 733 | │ │ 0x00400b79 89c2 MOV EDX, EAX 734 | │ │ 0x00400b7b 488d8db8feff. LEA RCX, [LOCAL_148H] 735 | │ │ 0x00400b82 41b901000000 MOV R9D, 1 736 | │ │ 0x00400b88 4c8d45d0 LEA R8, [LOCAL_30H] 737 | │ │ 0x00400b8c 488d75c0 LEA RSI, [LOCAL_40H] 738 | │ │ 0x00400b90 e8fbc10200 CALL fcn.0042cd90 ;[3] 739 | ``` 740 | 741 | This function takes the following params: 742 | 743 | 1) Arg 1 (rdi): The plain text string (@ 0x4e1b90) "Get the aeskey!!r<< Can you r2 me?\n\n" 744 | 2) Arg 2 (rsi): Based on our trace, the encrypted string should be stored in this address (local_40h). 745 | 3) Arg 3 (rdx): The value 0x10 (the size of the encrypted string). 746 | 4) Arg 4 (rcx): Other variable local_148h. 747 | 5) Arg 5 (r8): Other variable local_30h. 748 | 6) Arg 6 (r9): The value 1. 749 | 750 | It looks like the encryption function we were looking for!! It receives as parameter the plaintext, the size and the address to store the encrypted string. So any of the other params (`rcx` or `r8`) should contain the encryption key. Let's confirm it with the debugger: 751 | 752 | ```sh 753 | 754 | [0x004008c0]> db 0x00400b90 # Breakpoint in the call 755 | [0x004008c0]> dc 756 | Selecting and continuing: 32381 757 | ******************************************* 758 | * Radare2con 2017 rhme3 pre-quals edition * 759 | ******************************************* 760 | 761 | hit breakpoint at: 400b90 762 | [0x00400b90]> 763 | [0x00400b2c]> drr 764 | rax 0x0000000000000010 (.comment) rdx 765 | rbx 0x00000000004002b8 (.init) (/home/ictsec/security/challenges/r2con-prequals-rhme3/re/antir2_mod) rbx program R X 'sub rsp, 8' 'antir2_mod' 766 | rcx 0x00007ffc92697768 rcx stack R W 0x6332657261646172 --> ascii 767 | rdx 0x0000000000000010 (.comment) rdx 768 | r8 0x00007ffc92697880 r8 stack R W 0x0 --> r15 769 | r9 0x0000000000000001 (.comment) r11 770 | r10 0x0000000000000000 r15 771 | r11 0x0000000000000001 (.comment) r11 772 | r12 0x000000000043db60 (.text) (/home/ictsec/security/challenges/r2con-prequals-rhme3/re/antir2_mod) r12 program R X 'push r14' 'antir2_mod' 773 | r13 0x000000000043dbf0 (.text) (/home/ictsec/security/challenges/r2con-prequals-rhme3/re/antir2_mod) r13 program R X 'push rbx' 'antir2_mod' 774 | r14 0x0000000000000000 r15 775 | r15 0x0000000000000000 r15 776 | rsi 0x00007ffc92697870 rsi stack R W 0x1 --> (.comment) r11 777 | rdi 0x00000000004e1b90 (.rodata) (/home/ictsec/security/challenges/r2con-prequals-rhme3/re/antir2_mod) rdi program R X 'je 0x4e1bb4' 'antir2_mod' (Get the aeskey!!r<< Can you r2 me? 778 | 779 | ) 780 | 781 | [0x00400b2c]> pxr @ rcx 782 | 0x7ffc92697768 0x6332657261646172 radare2c @rcx ascii 783 | 0x7ffc92697770 0x2172657665346e6f on4ever! ascii 784 | 0x7ffc92697778 0x3aab444c5999213e >!.YLD.: 785 | 0x7ffc92697780 0x7eed4f555f9f2a23 #*._UO.~ 786 | 0x7ffc92697788 0x9fc130f4a56a74b8 .tj..0.. 787 | ``` 788 | 789 | And here it is, the encryption key: `"radare2con4ever!"`. 790 | 791 | 792 | 793 | # Write-up by Irdeto team (Jonathan Beverley, Colin deWinter, Ben Gardiner) 794 | 795 | * Open challenge binary and analyze it 796 | 797 | ```sh 798 | r2 -d antir2 799 | [...] 800 | [0x004008c0]> aaa 801 | [ ] Analyze all flags starting with sym. and entry0 (aa) 802 | [...] 803 | ``` 804 | 805 | * When attempting to debug the program, there is a certain text string "r2 in debug.....hahahaha" printed before force-quitting you 806 | 807 | * I was able to cross reference the strings through a radare / search and then using axt find the point in which its mentioned. 808 | 809 | ```sh 810 | [0x004008c0]> s str.r2_in_debug_mode_won_t_help_you_much__ha_ha_ha......_:__n 811 | [0x004e1c67]> axt 812 | data 0x421208 movabs rdi, str.r2_in_debug_mode_won_t_help_you_much__ha_ha_ha......_:__n in main 813 | data 0x41f9e4 movabs rdi, str.r2_in_debug_mode_won_t_help_you_much__ha_ha_ha......_:__n in main 814 | [0x004e1c67]> s 0x421208 815 | ``` 816 | 817 | * It seems to be referenced from `main`; the anti-debug printout is called from a jump in `main` at `0x418fa6` 818 | 819 | ```sh 820 | [0x004e1c67]> axt 821 | data 0x421208 movabs rdi, str.r2_in_debug_mode_won_t_help_you_much__ha_ha_ha......_:__n in main 822 | data 0x41f9e4 movabs rdi, str.r2_in_debug_mode_won_t_help_you_much__ha_ha_ha......_:__n in main 823 | [0x00000000]> s 0x421208 824 | [0x00421208]> axt 825 | code 0x418fa6 je 0x421208 in main 826 | [0x00421208]> s 0x418fa6 827 | ``` 828 | 829 | * which looks like a million jump statements... they all seem to reference loading [`local_54h`] 830 | 831 | ```sh 832 | [0x00418fa6]> f antidebug_fromhere 833 | [0x00418fa6]> pD 128 @ $$-64~.. 834 | │ ; JMP XREF from 0x00418f61 (main) 835 | │ 0x00418f66 8b45ac mov eax, dword [local_54h] 836 | │ 0x00418f69 2dd355df15 sub eax, 0x15df55d3 837 | │ 0x00418f6e 898564ffffff mov dword [local_9ch], eax 838 | │ ┌─< 0x00418f74 0f8431150000 je 0x41a4ab 839 | │ ┌──< 0x00418f7a e900000000 jmp 0x418f7f 840 | │ ││ ; JMP XREF from 0x00418f7a (main) 841 | │ └──> 0x00418f7f 8b45ac mov eax, dword [local_54h] 842 | │ │ 0x00418f82 2d6f9f2b19 sub eax, 0x192b9f6f 843 | │ │ 0x00418f87 898560ffffff mov dword [local_a0h], eax 844 | │ ┌──< 0x00418f8d 0f843c7a0000 je 0x4209cf 845 | │ ┌───< 0x00418f93 e900000000 jmp 0x418f98 846 | │ │││ ; JMP XREF from 0x00418f93 (main) 847 | │ └───> 0x00418f98 8b45ac mov eax, dword [local_54h] 848 | │ ││ 0x00418f9b 2d2f99231a sub eax, 0x1a23992f 849 | │ ││ 0x00418fa0 89855cffffff mov dword [local_a4h], eax 850 | | ┌───< ;-- antidebug_fromhere: 851 | │ ┌───< 0x00418fa6 0f845c820000 je 0x421208 852 | │ ┌────< 0x00418fac e900000000 jmp 0x418fb1 853 | │ ││││ ; JMP XREF from 0x00418fac (main) 854 | │ └────> 0x00418fb1 8b45ac mov eax, dword [local_54h] 855 | │ │││ 0x00418fb4 2dfa6b571d sub eax, 0x1d576bfa 856 | │ │││ 0x00418fb9 898558ffffff mov dword [local_a8h], eax 857 | │ ┌────< 0x00418fbf 0f84cb140000 je 0x41a490 858 | │ ┌─────< 0x00418fc5 e900000000 jmp 0x418fca 859 | │ │││││ ; JMP XREF from 0x00418fc5 (main) 860 | │ └─────> 0x00418fca 8b45ac mov eax, dword [local_54h] 861 | │ ││││ 0x00418fcd 2df3fce31d sub eax, 0x1de3fcf3 862 | │ ││││ 0x00418fd2 898554ffffff mov dword [local_ach], eax 863 | │ ┌─────< 0x00418fd8 0f84b7810000 je 0x421195 864 | │ ┌──────< 0x00418fde e900000000 jmp 0x418fe3 865 | │ ││││││ ; JMP XREF from 0x00418fde (main) 866 | │ └──────> 0x00418fe3 8b45ac mov eax, dword [local_54h] 867 | [0x00418fa6]> afvW~local_54h 868 | local_54h 0x416625,0x418de3,0x418df4,0x418e0a,0x418e20,0x418e36,0x418e4c,0x418e62,0x418e78,0x418e8e,0x418ea4,0x418eba,0x418ed0,0x418ee9,0x418f02,0x418f1b,0x418f34,0x418f4d,0x418f66,0x418f7f,0x418f98,0x418fb1,0x418fca,0x418fe3,0x418ffc,0x419015,0x41902e,0x419047,0x419060,0x419079,0x419092,0x4190ab,0x4190c4,0x4190dd,0x4190f6,0x41910f,0x4231a2 869 | ``` 870 | 871 | * Inspecting around the function, all are reads, this local is only written to at `0x00418de3` with data from `[local_50h]`. 872 | 873 | ```sh 874 | [0x00418fa6]> pdb @ 0x00418dd8 875 | │ ; JMP XREF from 0x00421239 (main) 876 | │ 0x00418dd8 8b45b0 mov eax, dword [local_50h] 877 | │ 0x00418ddb 89c1 mov ecx, eax 878 | │ 0x00418ddd 81e905e77080 sub ecx, 0x8070e705 879 | │ 0x00418de3 8945ac mov dword [local_54h], eax 880 | │ 0x00418de6 894da8 mov dword [local_58h], ecx 881 | │ ┌─< 0x00418de9 0f84e74d0000 je 0x41dbd6 882 | ``` 883 | 884 | * Attempting to find the end, it looks like this whole function is cyclic. There are 33 jumpXREFs to the bottom. this might be some sort of.... loop? Switch statement? 885 | 886 | ```sh 887 | [0x00418fa6]> pdf 888 | [...] 889 | │ ────────> 0x0042bdb5 c78568ffffff. mov dword [local_98h], 0xd648722e 890 | │ │││││││ ; XREFS: JMP 0x0042bdb0 JMP 0x00427b28 JMP 0x00426458 JMP 0x0042a251 JMP 0x00428b2b JMP 0x0042bd62 JMP 0x0042aebc JMP 0x00427265 891 | │ │││││││ ; XREFS: JMP 0x00427b93 JMP 0x0042b9e0 JMP 0x0042bd92 JMP 0x004274e4 JMP 0x004276df JMP 0x0042bda1 JMP 0x0042bce3 JMP 0x00427b0a 892 | │ │││││││ ; XREFS: JMP 0x00427b51 JMP 0x0042aed9 JMP 0x0042bd48 JMP 0x0042bd19 JMP 0x0042b56b JMP 0x00427bb3 JMP 0x00428f6b JMP 0x0042b55c 893 | │ │││││││ ; XREFS: JMP 0x00427bcd JMP 0x00427b37 JMP 0x00429baa JMP 0x00428f2d JMP 0x00428f1e JMP 0x0042aead JMP 0x0042aef7 JMP 0x00427b6e 894 | │ │││││││ ; XREFS: JMP 0x0042a094 JMP 0x00428f4d JMP 0x0042bd7c JMP 0x0042751e JMP 0x0042a085 JMP 0x004274f3 JMP 0x00428f91 JMP 0x004260f0 895 | │ │││││││ ; XREFS: JMP 0x00426467 JMP 0x0042b2f7 JMP 0x0042549c 896 | │ └└└└└└└─< 0x0042bdbf e9e391ffff jmp 0x424fa7 897 | │ ; JMP XREF from 0x0042bcf8 (main) 898 | │ ────────> 0x0042bdc4 e867d20500 call fcn.00489030 899 | │ 0x0042bdc9 0f1f80000000. nop dword [rax] 900 | │ 0x0042bdd0 b8061d4e00 mov eax, str.aes_partial_ ; 0x4e1d06 ; "aes(partial)" 901 | └ 0x0042bdd5 c3 ret 902 | ``` 903 | 904 | * How does it know it is being debugged? The message only plays when the `local_54h` is equal to `1A23992F`. 905 | 906 | ```sh 907 | [0x00418fa6]> pdb @ 0x00418fa6 908 | │ ; JMP XREF from 0x00418f93 (main) 909 | │ 0x00418f98 8b45ac mov eax, dword [local_54h] 910 | │ 0x00418f9b 2d2f99231a sub eax, 0x1a23992f 911 | │ 0x00418fa0 89855cffffff mov dword [local_a4h], eax 912 | | ┌─< ;-- antidebug_fromhere: 913 | │ ┌─< 0x00418fa6 0f845c820000 je 0x421208 914 | ``` 915 | 916 | * The magic value `0x1a23992f` shows up only three times; the last is not in code, the first is the test against that value. Let's look at the second 917 | 918 | ```sh 919 | [0x00418fa6]> /v 0x1a23992f 920 | Searching 4 bytes in [0x400000-0x522000] 921 | hits: 3 922 | 0x00418f9c hit1_0 2f99231a 923 | 0x0041ebbd hit1_1 2f99231a 924 | 0x0041fa03 hit1_2 2f99231a 925 | [0x00418fa6]> pd @ hit1_1-5 926 | │ ┌─< 0x0041ebb8 7d26 jge 0x41ebe0 927 | │ │ 0x0041ebba 0000 add byte [rax], al 928 | │ │ ; JMP XREF from 0x00418f5b (main) 929 | │ │ 0x0041ebbc ~ b82f99231a mov eax, 0x1a23992f 930 | | │ ;-- hit1_1: 931 | │ │ 0x0041ebbd 2f invalid ; 0x1a23992f 932 | │ │ 0x0041ebbe 99 cdq 933 | │ │ 0x0041ebbf 231a and ebx, dword [rdx] 934 | [...] 935 | ``` 936 | 937 | NB: the flag on the immediate is breaking the disassembly there, we'll remove flags in the following to avoid this. 938 | 939 | * that value, once loaded into `eax` makes its way magically to `local_54h` (the EBB is very very long) 940 | 941 | * the above is only called is `local_54h` is equal to `0xae2d291` 942 | 943 | ```sh 944 | [0x004008c0]> s 0x00418f5b 945 | [0x00418f5b]> pd 946 | [0x00418f5b]> pdb 947 | │ ; JMP XREF from 0x00418f48 (main) 948 | │ 0x00418f4d 8b45ac mov eax, dword [local_54h] 949 | │ 0x00418f50 2d91d2e20a sub eax, 0xae2d291 950 | │ 0x00418f55 898568ffffff mov dword [local_98h], eax 951 | │ ┌─< 0x00418f5b 0f845b5c0000 je 0x41ebbc 952 | ``` 953 | 954 | * which is called only if `local_54h` is not `0x94524fa` 955 | 956 | ```sh 957 | │ │ ; JMP XREF from 0x00418f2f (main) 958 | │ └─> 0x00418f34 8b45ac mov eax, dword [local_54h] 959 | │ 0x00418f37 2dfa244509 sub eax, 0x94524fa 960 | │ 0x00418f3c 89856cffffff mov dword [local_94h], eax 961 | │ ┌─< 0x00418f42 0f84e2810000 je 0x42112a ;[2] 962 | │ ┌──< 0x00418f48 e900000000 jmp 0x418f4d ;[3] 963 | ``` 964 | 965 | * and so on, testing for various other values of `local_54h` that it is not. We'll focus on the test for the concrete value `0xae2d291` 966 | 967 | * the value `0xae2d291` which `local_54h` must attain is written to `local_50h` when `local_34h` is `6` 968 | 969 | ```sh 970 | [0x0041eba7]> /v 0xae2d291 971 | Searching 4 bytes in [0x400000-0x522000] 972 | hits: 2 973 | 0x00418f51 hit3_0 91d2e20a 974 | 0x0041eba7 hit3_1 91d2e20a 975 | [0x0041eba7]> s 0x0041eba7 976 | [0x0041eba7]> f-hit* 977 | [0x0041eba7]> pdb 978 | │ ; JMP XREF from 0x00418eaf (main) 979 | │ 0x0041eba1 b82f82de24 mov eax, 0x24de822f 980 | │ 0x0041eba6 b991d2e20a mov ecx, 0xae2d291 981 | │ 0x0041ebab 8b55cc mov edx, dword [local_34h] 982 | │ 0x0041ebae 83fa06 cmp edx, 6 ; 6 983 | │ 0x0041ebb1 0f45c1 cmovne eax, ecx 984 | │ 0x0041ebb4 8945b0 mov dword [local_50h], eax 985 | │ ┌─< 0x0041ebb7 e97d260000 jmp 0x421239 986 | ``` 987 | 988 | * it's not clear how that would also propagate to `local_54h` -- but let's just roll with it. Assuming it will, then if we prevent the magic value `0xae2d291` from getting into `local_50h`, we will prevent detection of debugging. So we can patch-out the conditional mov, `cmovne` at `0x41ebb1` 989 | 990 | ```sh 991 | [0x0041eba7]> s 0x0041ebb1 992 | [0x0041ebb1]> "wa nop;nop;nop" 993 | Written 3 bytes (nop;nop;nop) = wx 909090 994 | [0x0041ebb1]> pdb 995 | │ ; JMP XREF from 0x00418eaf (main) 996 | │ 0x0041eba1 b82f82de24 mov eax, 0x24de822f 997 | │ 0x0041eba6 b991d2e20a mov ecx, 0xae2d291 998 | │ 0x0041ebab 8b55cc mov edx, dword [local_34h] 999 | │ 0x0041ebae 83fa06 cmp edx, 6 ; 6 1000 | │ 0x0041ebb1 90 nop 1001 | │ 0x0041ebb2 90 nop 1002 | │ 0x0041ebb3 90 nop 1003 | │ 0x0041ebb4 8945b0 mov dword [local_50h], eax 1004 | │ ┌─< 0x0041ebb7 e97d260000 jmp 0x421239 1005 | [0x0041ebb1]> 1006 | ``` 1007 | * yay! we patched-out the anti-debug check! 1008 | 1009 | ```sh 1010 | [0x0041ebb1]> dc 1011 | child stopped with signal 28 1012 | [+] SIGNAL 28 errno=0 addr=0x00000000 code=128 ret=0 1013 | got signal... 1014 | [0x004008c0]> dc 1015 | ******************************************* 1016 | * Radare2con 2017 rhme3 pre-quals edition * 1017 | ******************************************* 1018 | 1019 | r<< Can you r2 me? 1020 | 1021 | Plaintext : 47 65 74 20 74 68 65 20 61 65 73 6B 65 79 21 21 1022 | Encrypted : FD DA 9B 78 FC F8 E9 BF 33 72 6E 0A 8A E5 F6 8C 1023 | Decrypted : 47 65 74 20 74 68 65 20 61 65 73 6B 65 79 21 21 1024 | [0x00484b88]> 1025 | ``` 1026 | 1027 | * Let's restart that debug session and plan a breakpoint. Working backwards from the "Decrypted" string 1028 | 1029 | ```sh 1030 | [0x0041ebb1]> s str.Decrypted 1031 | [0x004e1bc9]> axt 1032 | data 0x400c97 mov eax, str.Decrypted in main 1033 | [0x004e1bc9]> s 0x400c97 1034 | [0x00400c97]> pdb 1035 | [...] 1036 | │ 0x00400c34 e857c10200 call fcn.0042cd90 1037 | │ 0x00400c39 b8a01b4e00 mov eax, 0x4e1ba0 1038 | │ 0x00400c3e 89c7 mov edi, eax 1039 | │ 0x00400c40 8b8564fdffff mov eax, dword [local_29ch] 1040 | │ 0x00400c46 4188c3 mov r11b, al 1041 | │ 0x00400c49 4488d8 mov al, r11b 1042 | │ 0x00400c4c e8bfad0400 call fcn.0044ba10 1043 | │ 0x00400c51 41b9b51b4e00 mov r9d, str.Plaintext ; 0x4e1bb5 ; "Plaintext" 1044 | │ 0x00400c57 4489cf mov edi, r9d 1045 | │ 0x00400c5a 41b9901b4e00 mov r9d, 0x4e1b90 1046 | │ 0x00400c60 4489ce mov esi, r9d 1047 | │ 0x00400c63 41b910000000 mov r9d, 0x10 ; 16 1048 | │ 0x00400c69 4489ca mov edx, r9d 1049 | │ 0x00400c6c 898550fdffff mov dword [local_2b0h], eax 1050 | │ 0x00400c72 44898d4cfdff. mov dword [local_2b4h], r9d 1051 | │ 0x00400c79 e8c20a0100 call 0x411740 1052 | │ 0x00400c7e b8bf1b4e00 mov eax, str.Encrypted ; 0x4e1bbf ; "Encrypted" 1053 | │ 0x00400c83 89c7 mov edi, eax 1054 | │ 0x00400c85 488bb568fdff. mov rsi, qword [local_298h] 1055 | │ 0x00400c8c 8b954cfdffff mov edx, dword [local_2b4h] 1056 | │ 0x00400c92 e8a90a0100 call 0x411740 1057 | │ 0x00400c97 b8c91b4e00 mov eax, str.Decrypted ; 0x4e1bc9 ; "Decrypted" 1058 | │ 0x00400c9c 89c7 mov edi, eax 1059 | │ 0x00400c9e 488bb558fdff. mov rsi, qword [local_2a8h] 1060 | │ 0x00400ca5 8b954cfdffff mov edx, dword [local_2b4h] 1061 | │ 0x00400cab e8900a0100 call 0x411740 1062 | │ 0x00400cb0 64488b0c2528. mov rcx, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40 1063 | │ 0x00400cb9 488b75f8 mov rsi, qword [local_8h] 1064 | │ 0x00400cbd 4839f1 cmp rcx, rsi 1065 | │ ┌─< 0x00400cc0 0f8510000000 jne 0x400cd6 1066 | ``` 1067 | 1068 | * The first call to `0x411740` seems like a good spot. 1069 | 1070 | ```sh 1071 | [0x00400c97]> db 0x00400c79 1072 | [0x00400c97]> dc 1073 | child stopped with signal 28 1074 | [+] SIGNAL 28 errno=0 addr=0x00000000 code=128 ret=0 1075 | got signal... 1076 | [0x004008c0]> dc 1077 | ******************************************* 1078 | * Radare2con 2017 rhme3 pre-quals edition * 1079 | ******************************************* 1080 | 1081 | r<< Can you r2 me? 1082 | 1083 | hit breakpoint at: 400c79 1084 | [0x00400c79]> 1085 | ``` 1086 | 1087 | * Let's check the stack 1088 | 1089 | ```sh 1090 | [0x00400c79]> ad 10 @rsp 1091 | 0x7fff1ecc6bb0 0000000000000000 (null) 1092 | 0x7fff1ecc6bb8 0000000010000000 pointer 0x1000000000 1093 | `- 0x1000000000 ffffffffffffffff invalid 1094 | 0x7fff1ecc6bc0 1400000000000000 pointer 0x00000014 1095 | `- 0x00000014 ffffffffffffffff invalid 1096 | 0x7fff1ecc6bc8 206ecc1eff7f0000 pointer 0x7fff1ecc6e20 1097 | `- 0x7fff1ecc6e20 4765742074686520 pointer 0x2065687420746547 1098 | `- 0x7fff1ecc6bd0 0000000000000000 (null) 1099 | 0x7fff1ecc6bd8 306ecc1eff7f0000 pointer 0x7fff1ecc6e30 1100 | `- 0x7fff1ecc6e30 fdda9b78fcf8e9bf pointer 0xbfe9f8fc789bdafd 1101 | `- 0x7fff1ecc6be0 306ccc1eff7f0000 pointer 0x7fff1ecc6c30 1102 | `- 0x7fff1ecc6c30 7ccdaca5e94484fb pointer 0xfb8444e9a5accd7c 1103 | `- 0x7fff1ecc6be8 4500000000000000 pointer 0x00000045 1104 | `- 0x00000045 ffffffffffffffff invalid 1105 | 0x7fff1ecc6bf0 00000000a9085105 pointer 0x55108a900000000 1106 | `- 0x55108a900000000 ffffffffffffffff invalid 1107 | 0x7fff1ecc6bf8 0000000086aa782f pointer 0x2f78aa8600000000 1108 | `- 0x2f78aa8600000000 ffffffffffffffff invalid 1109 | ``` 1110 | 1111 | * we recognize `4765742074686520` as the plaintext. I wonder what is around there? 1112 | 1113 | ```sh 1114 | [0x00400c79]> px @ 0x7fff1ecc6e20 1115 | - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 1116 | 0x7fff1ecc6e20 4765 7420 7468 6520 6165 736b 6579 2121 Get the aeskey!! 1117 | 0x7fff1ecc6e30 fdda 9b78 fcf8 e9bf 3372 6e0a 8ae5 f68c ...x....3rn..... 1118 | 0x7fff1ecc6e40 fdda 9b78 fcf8 e9bf 3372 6e0a 8ae5 f68c ...x....3rn..... 1119 | 0x7fff1ecc6e50 7261 6461 7265 3263 6f6e 3465 7665 7221 radare2con4ever! 1120 | 0x7fff1ecc6e60 60db 4300 0000 0000 003f 3c3c d183 316e `.C......?<<..1n 1121 | 0x7fff1ecc6e70 1820 7200 0000 0000 46d2 4300 0000 0000 . r.....F.C..... 1122 | 0x7fff1ecc6e80 0000 0000 0000 0000 0000 0000 0100 0000 ................ 1123 | 0x7fff1ecc6e90 b86f cc1e ff7f 0000 e009 4000 0000 0000 .o........@..... 1124 | 0x7fff1ecc6ea0 b802 4000 0000 0000 4f84 4b96 1557 d8fe ..@.....O.K..W.. 1125 | 0x7fff1ecc6eb0 60db 4300 0000 0000 f0db 4300 0000 0000 `.C.......C..... 1126 | 0x7fff1ecc6ec0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 1127 | 0x7fff1ecc6ed0 4f84 7b0b 696a 2601 4f84 7972 7657 d8fe O.{.ij&.O.yrvW.. 1128 | 0x7fff1ecc6ee0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 1129 | 0x7fff1ecc6ef0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 1130 | 0x7fff1ecc6f00 0000 0000 0000 0000 0000 0000 0000 0000 ................ 1131 | 0x7fff1ecc6f10 bb07 0000 0000 0000 b86f cc1e ff7f 0000 .........o...... 1132 | ``` 1133 | 1134 | * EYYYYYYYYYYYYYYYYYYYYYYYY found it. 1135 | 1136 | ```sh 1137 | $echo -n 'Get the aeskey!!' | openssl aes-128-ecb -e -nopad -nosalt -K $(echo -n 'radare2con4ever!' | xxd -ps) | xxd -u -g1 1138 | 00000000: FD DA 9B 78 FC F8 E9 BF 33 72 6E 0A 8A E5 F6 8C ...x....3rn..... 1139 | ``` 1140 | 1141 | 1142 | # Write-up by Vegard Nossum without using radare2 1143 | 1144 | The very first thing I did was to run 'strings' on the binary, which after scrolling for a bit you start seeing the strings from the .rodata section, including the plaintext and some other hints at what the challenge actually is (finding the AES key used to encrypt/decrypt the plaintext): 1145 | ``` 1146 | .Get the aeskey!!r<< Can you r2 me? 1147 | Plaintext 1148 | Encrypted 1149 | Decrypted 1150 | ******************************************* 1151 | * Radare2con 2017 rhme3 pre-quals edition * 1152 | ******************************************* 1153 | %s : 1154 | %02X 1155 | r2 in debug mode won't help you much! ha ha ha...... :) 1156 | Breakpoint detected @ %p+%x ! 1157 | /proc/self/maps 1158 | frida 1159 | Damn it! You're so shady! h00king isn't allowed 1160 | aes(partial) 1161 | AES part of OpenSSL 1.0.2g 1 Mar 2016 1162 | cryptlib.c 1163 | dynamic 1164 | ``` 1165 | 1166 | From here, I tried gdb, strace, etc. without much luck because of the self-ptrace and debugger protection (on the other hand, I did notice that valgrind was fine). So the next step was to try to find a way to disable the debugger protection. 1167 | 1168 | I used readelf to find the address of the "debug mode" string: 1169 | ```sh 1170 | $ readelf -S antir2 | grep rodata 1171 | [ 9] .rodata PROGBITS 00000000004e1b40 000e1b40 1172 | $ readelf -p .rodata antir2 | grep 'debug mode' 1173 | [ 127] r2 in debug mode won't help you much! ha ha ha...... :)^J 1174 | ``` 1175 | 1176 | (so the address is 0x4e1b40 + 0x127 = 0x4e1c67) and then look where this address is used: 1177 | ``` 1178 | $ objdump -d antir2 | grep -A3 0x4e1c67 1179 | 41f9e4: 48 bf 67 1c 4e 00 00 movabs $0x4e1c67,%rdi 1180 | 41f9eb: 00 00 00 1181 | 41f9ee: b0 00 mov $0x0,%al 1182 | 41f9f0: e8 1b c0 02 00 callq 0x44ba10 1183 | -- 1184 | 421208: 48 bf 67 1c 4e 00 00 movabs $0x4e1c67,%rdi 1185 | 42120f: 00 00 00 1186 | 421212: b0 00 mov $0x0,%al 1187 | 421214: e8 f7 a7 02 00 callq 0x44ba10 1188 | ``` 1189 | 1190 | These calls are probably calls to printf() or puts() and so patching them out wouldn't do much beyond simply skipping the output, but I noticed that these two stubs were preceded by "jmp" instructions, so I figured they must themselves be jump targets, and that seemed correct: 1191 | ``` 1192 | 4190a0: 0f 84 3e 69 00 00 je 0x41f9e4 1193 | 418fa6: 0f 84 5c 82 00 00 je 0x421208 1194 | ``` 1195 | 1196 | Now, I tried patching out these calls (one by one) using a simple Python script: 1197 | ```python 1198 | with open('antir2', 'rb') as f: 1199 | antir2 = f.read() 1200 | 1201 | nop = '\x90' 1202 | 1203 | antir2 = antir2.replace('\x0f\x84\x3e\x69\x00\x00', nop * 6) 1204 | 1205 | with open('antir2.patched', 'wb') as f: 1206 | f.write(antir2) 1207 | ``` 1208 | 1209 | When I ran this one in particular under GDB, it would hang, BUT if I pressed Ctrl-C and then told it to return from the current function, it would allow me to continue past the debugging check: 1210 | ``` 1211 | $ gdb ./antir2.patched 1212 | [...] 1213 | Reading symbols from ./antir2.patched...(no debugging symbols found)...done. 1214 | (gdb) run 1215 | Starting program: antir2.patched 1216 | ******************************************* 1217 | * Radare2con 2017 rhme3 pre-quals edition * 1218 | ******************************************* 1219 | 1220 | ^C 1221 | Program received signal SIGINT, Interrupt. 1222 | 0x0000000000418dd8 in ?? () 1223 | (gdb) ret 1224 | Make selected stack frame return now? (y or n) y 1225 | #0 0x0000000000400a0c in ?? () 1226 | (gdb) c 1227 | Continuing. 1228 | r<< Can you r2 me? 1229 | 1230 | Plaintext : 47 65 74 20 74 68 65 20 61 65 73 6B 65 79 21 21 1231 | Encrypted : FD DA 9B 78 FC F8 E9 BF 33 72 6E 0A 8A E5 F6 8C 1232 | Decrypted : 47 65 74 20 74 68 65 20 61 65 73 6B 65 79 21 21 1233 | [Inferior 1 (process 2519) exited normally] 1234 | ``` 1235 | 1236 | I still don't know what the infinite loop is doing, and I always hit Ctrl-C in a slightly different spot, but it consistently allowed me to bypass the check, so I was satisfied to have this as a tool for later use. 1237 | 1238 | Now I got curious about other call instructions, so I started patching them out one by one (using a combination of bash and a Python script similar to the one above). For each one, I saved the output of the program and compared it with the output from the unpatched program. Of these, there were two call instructions that caught my eye in particular, the ~16th (0x400a4e) and ~21st (0x400aa8), not counting a few weird ones. 1239 | 1240 | For the ~21st, I got this diff: 1241 | ``` 1242 | $ diff -u <(./antir2) <(./antir2.patched) 1243 | --- /dev/fd/63 2017-09-15 00:21:54.208447723 +0200 1244 | +++ /dev/fd/62 2017-09-15 00:21:54.216447711 +0200 1245 | @@ -5,5 +5,5 @@ 1246 | r<< Can you r2 me? 1247 | 1248 | Plaintext : 47 65 74 20 74 68 65 20 61 65 73 6B 65 79 21 21 1249 | -Encrypted : FD DA 9B 78 FC F8 E9 BF 33 72 6E 0A 8A E5 F6 8C 1250 | +Encrypted : E2 4B C5 BA 63 67 89 32 7F CC F0 A9 E9 BD 22 2D 1251 | Decrypted : 47 65 74 20 74 68 65 20 61 65 73 6B 65 79 21 21 1252 | ``` 1253 | 1254 | This caught my eye because the encryption was different, but the decryption was still the same. This suggested that the key had been modified by this specific call instruction. If I could somehow look at what addresses this function was writing to, maybe I could find the location of the key in memory. 1255 | 1256 | However, before I got any further on that lead, I also ran valgrind and diffed its outputs (unpatched vs. patched) and then the ~16th call instruction really caught my eye: 1257 | ``` 1258 | $ valgrind ./antir2.patched 2>&1 | grep -A1 'Use of' 1259 | ==3318== Use of uninitialised value of size 8 1260 | ==3318== at 0x42C04F: ??? 1261 | -- 1262 | ==3318== Use of uninitialised value of size 8 1263 | ==3318== at 0x42C054: ??? 1264 | -- 1265 | ==3318== Use of uninitialised value of size 8 1266 | ==3318== at 0x42C059: ??? 1267 | -- 1268 | ==3318== Use of uninitialised value of size 8 1269 | ==3318== at 0x42C05E: ??? 1270 | -- 1271 | ==3318== Use of uninitialised value of size 8 1272 | ==3318== at 0x42C063: ??? 1273 | -- 1274 | ==3318== Use of uninitialised value of size 8 1275 | ==3318== at 0x42C06B: ??? 1276 | -- 1277 | ==3318== Use of uninitialised value of size 8 1278 | ==3318== at 0x42C073: ??? 1279 | -- 1280 | ==3318== Use of uninitialised value of size 8 1281 | ==3318== at 0x42C078: ??? 1282 | -- 1283 | ==3318== Use of uninitialised value of size 8 1284 | ==3318== at 0x42C09F: ??? 1285 | -- 1286 | ==3318== Use of uninitialised value of size 8 1287 | ==3318== at 0x42C0B3: ??? 1288 | -- 1289 | ==3318== Use of uninitialised value of size 8 1290 | ==3318== at 0x42C0BB: ??? 1291 | -- 1292 | ==3318== Use of uninitialised value of size 8 1293 | ==3318== at 0x42C0D7: ??? 1294 | -- 1295 | ==3318== Use of uninitialised value of size 8 1296 | ==3318== at 0x42C0DC: ??? 1297 | -- 1298 | ==3318== Use of uninitialised value of size 8 1299 | ==3318== at 0x42C0E1: ??? 1300 | -- 1301 | ==3318== Use of uninitialised value of size 8 1302 | ==3318== at 0x42C0E6: ??? 1303 | -- 1304 | ==3318== Use of uninitialised value of size 8 1305 | ==3318== at 0x42C0EB: ??? 1306 | ``` 1307 | 1308 | Exactly 16 reads of uninitialised memory, and 16 bytes is the key size. At first I actually thought the "size 8" gave a size in bits, and so I was excited to see 16 individual byte-sized reads, but these are actually 8-byte reads. 1309 | 1310 | Valgrind is helpful and tells us exactly where the reads are coming from. This is the disassembly (with annotations): 1311 | ``` 1312 | 42c04f: 47 0f b6 14 16 movzbl (%r14,%r10,1),%r10d 1313 | 42c054: 47 0f b6 1c 1e movzbl (%r14,%r11,1),%r11d 1314 | 42c059: 47 0f b6 24 26 movzbl (%r14,%r12,1),%r12d 1315 | 42c05e: 47 0f b6 04 06 movzbl (%r14,%r8,1),%r8d 1316 | 42c063: 45 0f b6 0c 36 movzbl (%r14,%rsi,1),%r9d 1317 | 42c068: 0f b6 f4 movzbl %ah,%esi 1318 | 42c06b: 45 0f b6 2c 3e movzbl (%r14,%rdi,1),%r13d 1319 | 42c070: 0f b6 f9 movzbl %cl,%edi 1320 | 42c073: 41 0f b6 2c 2e movzbl (%r14,%rbp,1),%ebp 1321 | 42c078: 41 0f b6 34 36 movzbl (%r14,%rsi,1),%esi 1322 | ``` 1323 | 1324 | I spent a while looking at this and then checking in gdb what the actual values of those registers were after the block had executed, but I didn't know if the reads were happening in the right order to be the key, and I also made the mistake of confusing valgrind's addresses as the addresses where the key was _read_ as opposed to where it was _used_. 1325 | 1326 | Eventually I realised that I had to see what the values were _before_ getting to this particular block of instructions, so I scrolled up just a tiny bit: 1327 | ``` 1328 | 42c016: eb 08 jmp 0x42c020 1329 | 42c018: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1) 1330 | 42c01f: 00 1331 | 42c020: 41 33 07 xor (%r15),%eax 1332 | 42c023: 41 33 5f 04 xor 0x4(%r15),%ebx 1333 | 42c027: 41 33 4f 08 xor 0x8(%r15),%ecx 1334 | 42c02b: 41 33 57 0c xor 0xc(%r15),%edx 1335 | 42c02f: 4d 8d 7f 10 lea 0x10(%r15),%r15 1336 | 42c033: 44 0f b6 d0 movzbl %al,%r10d 1337 | 42c037: 44 0f b6 db movzbl %bl,%r11d 1338 | 42c03b: 44 0f b6 e1 movzbl %cl,%r12d 1339 | 42c03f: 44 0f b6 c2 movzbl %dl,%r8d 1340 | 42c043: 0f b6 f7 movzbl %bh,%esi 1341 | 42c046: 0f b6 fd movzbl %ch,%edi 1342 | 42c049: c1 e9 10 shr $0x10,%ecx 1343 | 42c04c: 0f b6 ee movzbl %dh,%ebp 1344 | ``` 1345 | 1346 | The jump/nop instructions at 0x42c016 indicate that 0x42c020 must be the start of this function (as it cannot be reached otherwise), and by simply reading the code it becomes clear that %r15 at the start of this function is where all the values come from. We have 4x4-byte reads from it, which is exactly the 16 bytes of the key! So loading up gdb again, we get: 1347 | ``` 1348 | (gdb) run 1349 | Starting program: antir2.patched 1350 | ******************************************* 1351 | * Radare2con 2017 rhme3 pre-quals edition * 1352 | ******************************************* 1353 | 1354 | ^C 1355 | Program received signal SIGINT, Interrupt. 1356 | 0x0000000000418fb9 in ?? () 1357 | (gdb) break *0x42c020 1358 | Breakpoint 1 at 0x42c020 1359 | (gdb) ret 1360 | Make selected stack frame return now? (y or n) y 1361 | #0 0x0000000000400a0c in ?? () 1362 | (gdb) c 1363 | Continuing. 1364 | 1365 | Breakpoint 1, 0x000000000042c020 in ?? () 1366 | (gdb) info registers 1367 | rax 0x20746547 544499015 1368 | rbx 0x20656874 543516788 1369 | rcx 0x6b736561 1802724705 1370 | rdx 0x21217965 555841893 1371 | rsi 0x7fffffffde60 140737488346720 1372 | rdi 0xec130ccd 3960671437 1373 | rbp 0xa3a32e0 0xa3a32e0 1374 | rsp 0x7fffffffd8f8 0x7fffffffd8f8 1375 | r8 0x42dbc0 4381632 1376 | r9 0x7fffffffde60 140737488346720 1377 | r10 0x2e2578ba 774207674 1378 | r11 0x1198f8e1 295237857 1379 | r12 0x43db60 4447072 1380 | r13 0x43dbf0 4447216 1381 | r14 0x42db40 4381504 1382 | r15 0x7fffffffdd58 140737488346456 1383 | rip 0x42c020 0x42c020 1384 | eflags 0x206 [ PF IF ] 1385 | cs 0x33 51 1386 | ss 0x2b 43 1387 | ds 0x0 0 1388 | es 0x0 0 1389 | fs 0x63 99 1390 | gs 0x0 0 1391 | (gdb) x/16b 0x7fffffffdd58 1392 | 0x7fffffffdd58: 0x72 0x61 0x64 0x61 0x72 0x65 0x32 0x63 1393 | 0x7fffffffdd60: 0x6f 0x6e 0x34 0x65 0x76 0x65 0x72 0x21 1394 | (gdb) 1395 | ``` 1396 | 1397 | (You can tell I'm a gdb noob from the fact that I didn't use "x/s $r15"). 1398 | 1399 | These bytes at *%r15 are clearly ASCII (0x6*/0x7* is a dead giveaway) so it's not surprising that they are, in fact, the key. And again I used a short Python script to verify it: 1400 | ```python 1401 | from Crypto.Cipher import AES 1402 | import binascii 1403 | import os 1404 | 1405 | """ 1406 | >>> [chr(int(x, 16)) for x in "0x72 0x61 0x64 0x61 1407 | 0x72 0x65 0x32 0x63".split()] 1408 | ['r', 'a', 'd', 'a', 'r', 'e', '2', 'c'] 1409 | >>> [chr(int(x, 16)) for x in "0x6f 0x6e 0x34 0x65 1410 | 0x76 0x65 0x72 0x21".split()] 1411 | ['o', 'n', '4', 'e', 'v', 'e', 'r', '!'] 1412 | """ 1413 | 1414 | key = 'radare2con4ever!' 1415 | 1416 | print "key len =", len(key) 1417 | encryptor = AES.new(key, AES.MODE_ECB) 1418 | text = 'Get the aeskey!!' 1419 | ciphertext = encryptor.encrypt(text) 1420 | print "encrypted:", binascii.hexlify(ciphertext).upper() 1421 | ``` 1422 | 1423 | And this prints the same as the program itself: 1424 | ```sh 1425 | $ python aes.py 1426 | key len = 16 1427 | encrypted: FDDA9B78FCF8E9BF33726E0A8AE5F68C 1428 | ``` 1429 | 1430 | -------------------------------------------------------------------------------- /re/antir2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enovella/r2con-prequals-rhme3/3834565614eadeb5395de495ba913f744cd682ab/re/antir2 --------------------------------------------------------------------------------