Skip to content

Commit eda5e2e

Browse files
new post
1 parent 070c52d commit eda5e2e

10 files changed

+272
-0
lines changed
Binary file not shown.
+272
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
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>
198 KB
Loading
Loading
Loading
Loading
Loading
Loading
106 KB
Loading
Loading

0 commit comments

Comments
 (0)