|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: More angr - Defeating 5 ELF Crackmes |
| 4 | +tags: [angr, cutter, qiling, emulation, symbolic-execution, crackme, reverse-engineering, ELF, Linux] |
| 5 | +author-id: julian |
| 6 | +--- |
| 7 | + |
| 8 | +The purpose of this post is to demonstrate how emulation can be used to find solutions to a few keygenme-style crackme programs. |
| 9 | +It is not always necessary or efficient to rely on just a disassembler or debugger when emulation can be used to assist with the analysis. |
| 10 | +In fact, by using tools like angr and Cutter one can save a significant amount of time when solving challenges like these. |
| 11 | +Rather than post each write-up seperately, the solutions to 5 challenges are posted together here. |
| 12 | +They are of rather low difficulty, so they should be accessible to beginners. |
| 13 | + |
| 14 | + |
| 15 | +### Contents: |
| 16 | + |
| 17 | + 1. Bkamp's `glow wine` - keygenme |
| 18 | + 2. Exxtra12's `xordemo` - password |
| 19 | + 3. kawaii-flesh's `key and keygen` - 1 password per username |
| 20 | + 4. m3hd1's `half-twins` - keygenme |
| 21 | + 5. paypain's `de_tcrack1` - keygenme |
| 22 | + |
| 23 | +### Arsenal |
| 24 | + |
| 25 | + - [angr](https://angr.io/) |
| 26 | + - [Qiling](https://github.com/qilingframework/qiling) (used to solve #3) |
| 27 | + - [Cutter](https://cutter.re) |
| 28 | + |
| 29 | +<hr> |
| 30 | + |
| 31 | +# 1) Bkamp's glow wine |
| 32 | +## a Webkinz-level keygenme |
| 33 | + |
| 34 | +- Emulator: angr |
| 35 | +- Solve time: ~1 second. |
| 36 | + |
| 37 | + - [Challenge page](https://crackmes.one/crackme/5df26b4033c5d419aa013362) |
| 38 | + - [Challenge download](https://crackmes.one/static/crackme/5df26b4033c5d419aa013362.zip) |
| 39 | + - Password to unzip: crackmes.one |
| 40 | + |
| 41 | +The decompilation (courtesy of the Ghidra decompiler integrated with Cutter) illustrates the constraints a solution must conform to: |
| 42 | + |
| 43 | +```java |
| 44 | +undefined8 main(undefined8 argc, char **argv) |
| 45 | +{ |
| 46 | + int64_t iVar1; |
| 47 | + char **s; |
| 48 | + undefined8 var_4h; |
| 49 | + |
| 50 | + if ((int32_t)argc < 2) { |
| 51 | + sym.imp.puts(0x848); |
| 52 | + } else { |
| 53 | + iVar1 = sym.imp.strlen(argv[1]); |
| 54 | + if (iVar1 != 5) { |
| 55 | + sym.sorrybro(); |
| 56 | + } |
| 57 | + if (argv[1][1] != '@') { |
| 58 | + sym.sorrybro(); |
| 59 | + } |
| 60 | + if ((int32_t)argv[1][4] + (int32_t)argv[1][2] + (int32_t)argv[1][3] != 300) { |
| 61 | + sym.sorrybro(); |
| 62 | + } |
| 63 | + sym.imp.puts(0x860); |
| 64 | + } |
| 65 | + return 0; |
| 66 | +} |
| 67 | +``` |
| 68 | + |
| 69 | +Demo: |
| 70 | + |
| 71 | +<script id="asciicast-kv1tpqeYmVKG3LTvhc5xAdAp2" src="https://asciinema.org/a/kv1tpqeYmVKG3LTvhc5xAdAp2.js" async data-rows="35" data-cols="150" data-speed="2"></script> |
| 72 | + |
| 73 | +Here is a script using angr to compute keys: |
| 74 | +<script src="https://gist.github.com/BinaryResearch/5576f56e5673f7f9e860cf1c5729b764.js"></script> |
| 75 | + |
| 76 | + |
| 77 | + |
| 78 | +# 2) Exxtra12's xordemo |
| 79 | +## single solution, input XORed |
| 80 | + |
| 81 | + - Emulator: angr |
| 82 | + - Solve time: Less than 1 second. |
| 83 | + |
| 84 | + - [Challenge page](https://crackmes.one/crackme/5dfd77a833c5d419aa013406) |
| 85 | + - [Crackme download](https://crackmes.one/static/crackme/5dfd77a833c5d419aa013406.zip) |
| 86 | + - Password to unzip: crackmes.one |
| 87 | + |
| 88 | +The input is XORed with some hardcoded data to determine if it is correct. The correct input results in `Jackpot` being output to stdout. |
| 89 | +This program is very simple - there are only 2 functions that concern us and very few branches in flow-of-control. In addition, the symbol |
| 90 | +table has not been removed from the binary. |
| 91 | + |
| 92 | +### main |
| 93 | + |
| 94 | + - if `argc` == 2, `argv[1]` is passed as an argument to `checkPassword` |
| 95 | + - after `checkPassword` returns, if the value is 0, print "fail". Else, print "Jackpot". |
| 96 | + |
| 97 | +<img src="{{site.baseurl}}/assets/img/2020-1-22-shooting-gallery/xordemo_graph_main.png"> |
| 98 | + |
| 99 | +### checkPassword |
| 100 | + |
| 101 | + - loop over bytes in input string, XORing them with bytes in hardcoded string `badbeef1` |
| 102 | + - upon success, return 1, else return 0 |
| 103 | + |
| 104 | +<img src="{{site.baseurl}}/assets/img/2020-1-22-shooting-gallery/xordemo_graph_sym_checkPassword.png"> |
| 105 | + |
| 106 | +### Demo |
| 107 | + |
| 108 | +<script id="asciicast-NU5fbnG4c6ZtaiLVobUdxHwgz" src="https://asciinema.org/a/NU5fbnG4c6ZtaiLVobUdxHwgz.js" async data-rows="25" data-cols="150" data-speed="2"></script> |
| 109 | + |
| 110 | +### Script |
| 111 | + |
| 112 | +<script src="https://gist.github.com/BinaryResearch/0aa4c544ac5a45989e86e9daac9dc3ef.js"></script> |
| 113 | + |
| 114 | +# 3) Kawaii-flesh's key and keygen |
| 115 | +## input correct username-password pairs via stdin |
| 116 | + |
| 117 | + - Emulator: Qiling |
| 118 | + - Solve time: N/A |
| 119 | + - [Challenge page](https://crackmes.one/crackme/5d17962b33c5d41c6d56e1f2) |
| 120 | + - [Crackme download](https://crackmes.one/static/crackme/5d17962b33c5d41c6d56e1f2.zip) |
| 121 | + - password to unzip: crackmes.one |
| 122 | + |
| 123 | +The task of writing a program to generate solutions to this crackme was interesting due to the design of the program. The crackme |
| 124 | +takes a username and password as inputs from `stdin` and then computes a password based on the byte values in the username. This computed password |
| 125 | +is then checked against the password input from stdin; if they match, the challenge is solved. |
| 126 | + |
| 127 | +In sum: |
| 128 | + - for every username, there is only 1 correct password |
| 129 | + - the correct password is calculated by the crackme at runtime |
| 130 | + |
| 131 | +All one has to do to find a single solution is set a breakpoint after the password is calculated and inspect the buffer it is written to. |
| 132 | +The real goal is to automate this. |
| 133 | + |
| 134 | +### main |
| 135 | + |
| 136 | +After `scanf` reads the username and password from stdin, a buffer of length 10 is allocated on the heap; a pointer to this buffer is passed to the |
| 137 | +`encr` function along with a pointer to the buffer holding the username. |
| 138 | + |
| 139 | +When `encr` returns after calculating the password, the buffer on the heap holds this password. That password is then compared with the one that was read by `scanf`. |
| 140 | + |
| 141 | +<img src="{{site.baseurl}}/assets/img/2020-1-22-shooting-gallery/graph_kawaii_keygen1_main.png"> |
| 142 | + |
| 143 | +### encr |
| 144 | + |
| 145 | +It is actually not necessay to analyze the `encr` function to understand how the password is calculated if the password can be retrieved automatically. |
| 146 | +It has a single exit point, which is returning to `main`. |
| 147 | + |
| 148 | +<img src="{{site.baseurl}}/assets/img/2020-1-22-shooting-gallery/graph_kawaii_keygen1_encr.png"> |
| 149 | + |
| 150 | +There are several approaches to accomplishing the task of automatically generating a list of acceptable username-password pairs. |
| 151 | +The most traditional would be to reverse engineer and study the algorithm responsible for creating the key and then writing a program in a higher-level language |
| 152 | +that implements this algorithm. In light of the fact that the program computes the password for us already, this sounds quite a bit of unnecessary work; |
| 153 | +something that can be done |
| 154 | +instead is to execute the crackme for each new username and then retrieve the password for that username from memory. For example, since the crackme is a |
| 155 | +dynamically-linked binary |
| 156 | +and the `strcmp` function is used to compare the password from stdin to the one computed by the crackme, one option for retrieving the password is hooking |
| 157 | +`strcmp` and injecting code from a custom shared library using LD_PRELOAD. The injected code can simply output the arguments passed to `strcmp`. |
| 158 | + |
| 159 | +The approach chosen here however is emulating the crackme binary with Qiling and hooking an address at which the password has already been calculated and |
| 160 | +the memory address of the password buffer is in a register, making its retrieval straightforward. This can be done for each new username that that we want |
| 161 | +to input to the crackme. |
| 162 | + |
| 163 | +### Demo: |
| 164 | + |
| 165 | +<script id="asciicast-YuAqisKMjEyflfkF4E1XxhEyj" src="https://asciinema.org/a/YuAqisKMjEyflfkF4E1XxhEyj.js" async data-rows="30" data-cols="200" data-speed="2"></script> |
| 166 | + |
| 167 | +The solution demonstrated above involves 2 programs: |
| 168 | + |
| 169 | + - `emulate_keygenme.py` emulates the crackme such that for a given username, the password calculated in `encr` is printed to stdout: |
| 170 | + |
| 171 | +<script id="asciicast-aTMEaiIgWC1F4JygGw85fcM0c" src="https://asciinema.org/a/aTMEaiIgWC1F4JygGw85fcM0c.js" async data-rows="27" data-cols="170" data-speed="2"></script> |
| 172 | + |
| 173 | +As we can see, input entered at the "key" prompt has no bearing on the actual key/password. Only the username string matters. |
| 174 | + |
| 175 | +Here is the code for `emulate_keygenme.py` |
| 176 | + |
| 177 | +<script src="https://gist.github.com/BinaryResearch/b1b2df950a168525253e01ae589c80e9.js"></script> |
| 178 | + |
| 179 | + - `kawaii_keygen.py` below performs the following: |
| 180 | + 1. generates a list of usernames |
| 181 | + 2. for each username in the list, executes `emulate_keygenme.py` |
| 182 | + - the output of the emulation contains the password generated for that username |
| 183 | + 3. executes the crackme, inputting the username and the password to confirm that they are correct |
| 184 | + 4. prints the output |
| 185 | + |
| 186 | +<script src="https://gist.github.com/BinaryResearch/e31296ab6112f21ed5bffbed74aa59db.js"></script> |
| 187 | + |
| 188 | +# 4) m3hd1's half-twins |
| 189 | +## input 2 valid command-line arguments |
| 190 | + |
| 191 | + - Emulator: angr |
| 192 | + - Solve time: ~3 seconds per solution |
| 193 | + - [Challenge page](https://crackmes.one/crackme/5dce805c33c5d419aa0131ae) |
| 194 | + - [Crackme download](https://crackmes.one/static/crackme/5dce805c33c5d419aa0131ae.zip) |
| 195 | + - password to unzip: crackmes.one |
| 196 | + |
| 197 | +The order of characters in the arguments to the program plus the relationship between the arguments together is what really matters in this case, |
| 198 | +so it is essential here to limit the range of possible byte values in solutions to printable ASCII characters in order for the program to accept |
| 199 | +them as valid. Here are some example solutions: |
| 200 | + |
| 201 | + - `hbbbpfpb`, `hbbbxbrp` |
| 202 | + - `BHAPPHAP`, `BHAPBBHB` |
| 203 | + |
| 204 | +As long as the first 4 (out of 8) characters of the 2 strings match, the strings are accepted as solutions. This means the set of solutions |
| 205 | +is extremely large. |
| 206 | + |
| 207 | +### main |
| 208 | + |
| 209 | +The entirety of the crackme code is in the `main` function. The code printing the message indicating success is at `0x0000134e`. The program |
| 210 | +expects 2 arguments of length 8: |
| 211 | + |
| 212 | +<img src="{{site.baseurl}}/assets/img/2020-1-22-shooting-gallery/half-twins_graph_main.png"> |
| 213 | + |
| 214 | +### Demo |
| 215 | + |
| 216 | +<script id="asciicast-3TRyICGXzbazxuaZ130Gz3OH5" src="https://asciinema.org/a/3TRyICGXzbazxuaZ130Gz3OH5.js" async data-rows="30" data-speed="10"></script> |
| 217 | + |
| 218 | +### Script |
| 219 | + |
| 220 | +<script src="https://gist.github.com/BinaryResearch/4aacd891019354ed552c5d2de5df0955.js"></script> |
| 221 | + |
| 222 | +# 5) paypain's de_tcrack1 |
| 223 | +## input valid arg longer than 10 chars |
| 224 | + |
| 225 | + - Emulator: angr |
| 226 | + - Solve time: ~3 seconds total |
| 227 | + - [Challenge page](https://crackmes.one/crackme/5c9d9eea33c5d4419da55641) |
| 228 | + - [Crackme download](https://crackmes.one/static/crackme/5c9d9eea33c5d4419da55641.zip) |
| 229 | + - password to unzip: crackmes.one |
| 230 | + |
| 231 | +To solve this crackme, 2 requirements must be met: |
| 232 | + - the input string must pass all the checks in basic blocks |
| 233 | +0x00001080, 0x0000109c, 0x000010b0, and 0x000010c6 |
| 234 | + - the string must be the correct length. This information is contained in the |
| 235 | + logic of the larger basic block at 0x00001107 |
| 236 | + |
| 237 | +### main |
| 238 | + |
| 239 | +`angr` can be used to efficiently compute strings that pass the various checks |
| 240 | +preceding basic block 0x00001107. If the correct length is specified, setting the target address for exploration to 0x00001107 |
| 241 | +is sufficient to compute valid keys. I was not able to successfully use angr to emulate the code at 0x0000111b and beyond, but solutions can |
| 242 | +be generated without exploring that far. |
| 243 | + |
| 244 | +<img src="{{site.baseurl}}/assets/img/2020-1-22-shooting-gallery/paypain-de_tcrack1-graph_main.png"> |
| 245 | + |
| 246 | +It turns out that the computations in basic block 0x00001107 append the input |
| 247 | +string together with `FL4GiNyOUrMinD` and `WiNAll`, 2 strings hardcoded in the `.rodata` section. |
| 248 | +For example, if the input string is `bb??@???b?A?`, then in memory this composite string looks like this: `FL4GiNyOUrMinDbb??@???b?A?WiNAll`. |
| 249 | + |
| 250 | +<img src="{{site.baseurl}}/assets/img/2020-1-22-shooting-gallery/paypain-de_tcrack1-bb_0x00001107-highlighted.png"> |
| 251 | + |
| 252 | +When the input string is 10 characters long, the value in RDX at 0x00001162 is `0x1e`. If the input string is longer than this, |
| 253 | +the crackme is solved and the license is printed to stdout. |
| 254 | + |
| 255 | +### Demo |
| 256 | + |
| 257 | +<script id="asciicast-fPhgezaXGM5M8Abm5tiQIJcgd" src="https://asciinema.org/a/fPhgezaXGM5M8Abm5tiQIJcgd.js" async data-rows="30" data-cols="170"></script> |
| 258 | + |
| 259 | +When typed in the terminal, the output looks like this: |
| 260 | + |
| 261 | +``` |
| 262 | +$ ./de_tcrack1 "????@??bbb+?" |
| 263 | +
|
| 264 | +[+] Login Complete |
| 265 | +[+] License->FL4GiNyOUrMinD????@??bbb+?WiNAll |
| 266 | +``` |
| 267 | + |
| 268 | +The "Login Complete" line is excluded from the script output. |
| 269 | + |
| 270 | +### Script |
| 271 | + |
| 272 | +<script src="https://gist.github.com/BinaryResearch/4c071d95d3867033382df080c6496f3b.js"></script> |
0 commit comments