pages/blog/rop-on-arm.md (view raw)
1---
2template: text.html
3title: Return Oriented Programming on ARM (32-bit)
4subtitle: Making stack-based exploitation great again!
5date: 2019-06-06
6slug: rop-on-arm
7---
8
9Before we start _anything_, you’re expected to know the basics of ARM
10assembly to follow along. I highly recommend
11[Azeria’s](https://twitter.com/fox0x01) series on [ARM Assembly
12Basics](https://azeria-labs.com/writing-arm-assembly-part-1/). Once you’re
13comfortable with it, proceed with the next bit -- environment setup.
14
15## Setup
16
17Since we’re working with the ARM architecture, there are two options to go
18forth with:
19
201. Emulate -- head over to [qemu.org/download](https://www.qemu.org/download/) and install QEMU.
21And then download and extract the ARMv6 Debian Stretch image from one of the links [here](https://blahcat.github.io/qemu/).
22The scripts found inside should be self-explanatory.
232. Use actual ARM hardware, like an RPi.
24
25For debugging and disassembling, we’ll be using plain old `gdb`, but you
26may use `radare2`, IDA or anything else, really. All of which can be
27trivially installed.
28
29And for the sake of simplicity, disable ASLR:
30
31```shell
32$ echo 0 > /proc/sys/kernel/randomize_va_space
33```
34
35Finally, the binary we’ll be using in this exercise is [Billy Ellis’](https://twitter.com/bellis1000)
36[roplevel2](/static/files/roplevel2.c).
37
38Compile it:
39```sh
40$ gcc roplevel2.c -o rop2
41```
42
43With that out of the way, here’s a quick run down of what ROP actually is.
44
45## A primer on ROP
46
47ROP or Return Oriented Programming is a modern exploitation technique that’s
48used to bypass protections like the **NX bit** (no-execute bit) and **code sigining**.
49In essence, no code in the binary is actually modified and the entire exploit
50is crafted out of pre-existing artifacts within the binary, known as **gadgets**.
51
52A gadget is essentially a small sequence of code (instructions), ending with
53a `ret`, or a return instruction. In our case, since we’re dealing with ARM
54code, there is no `ret` instruction but rather a `pop {pc}` or a `bx lr`.
55These gadgets are _chained_ together by jumping (returning) from one onto the other
56to form what’s called as a **ropchain**. At the end of a ropchain,
57there’s generally a call to `system()`, to acheive code execution.
58
59In practice, the process of executing a ropchain is something like this:
60
61- confirm the existence of a stack-based buffer overflow
62- identify the offset at which the instruction pointer gets overwritten
63- locate the addresses of the gadgets you wish to use
64- craft your input keeping in mind the stack’s layout, and chain the addresses
65of your gadgets
66
67[LiveOverflow](https://twitter.com/LiveOverflow) has a [beautiful video](https://www.youtube.com/watch?v=zaQVNM3or7k&list=PLhixgUqwRTjxglIswKp9mpkfPNfHkzyeN&index=46&t=0s) where he explains ROP using “weird machines”.
68Check it out, it might be just what you needed for that “aha!” moment :)
69
70Still don’t get it? Don’t fret, we’ll look at _actual_ exploit code in a bit and hopefully
71that should put things into perspective.
72
73## Exploring our binary
74
75Start by running it, and entering any arbitrary string. On entering a fairly
76large string, say, “A” × 20, we
77see a segmentation fault occur.
78
79![](https://cdn.icyphox.sh/qrN69.png)
80
81Now, open it up in `gdb` and look at the functions inside it.
82
83![](https://cdn.icyphox.sh/3j-MJ.png)
84
85There are three functions that are of importance here, `main`, `winner` and
86`gadget`. Disassembling the `main` function:
87
88![](https://cdn.icyphox.sh/p2iFF.png)
89
90We see a buffer of 16 bytes being created (`sub sp, sp, #16`), and some calls
91to `puts()`/`printf()` and `scanf()`. Looks like `winner` and `gadget` are
92never actually called.
93
94Disassembling the `gadget` function:
95
96![](https://cdn.icyphox.sh/1T8XT.png)
97
98This is fairly simple, the stack is being initialized by `push`ing `{r11}`,
99which is also the frame pointer (`fp`). What’s interesting is the `pop {r0, pc}`
100instruction in the middle. This is a **gadget**.
101
102We can use this to control what goes into `r0` and `pc`. Unlike in x86 where
103arguments to functions are passed on the stack, in ARM the registers `r0` to `r3`
104are used for this. So this gadget effectively allows us to pass arguments to
105functions using `r0`, and subsequently jumping to them by passing its address
106in `pc`. Neat.
107
108Moving on to the disassembly of the `winner` function:
109
110![](https://cdn.icyphox.sh/BDtJr.png)
111
112Here, we see a calls to `puts()`, `system()` and finally, `exit()`.
113So our end goal here is to, quite obviously, execute code via the `system()`
114function.
115
116Now that we have an overview of what’s in the binary, let’s formulate a method
117of exploitation by messing around with inputs.
118
119## Messing around with inputs :^)
120
121Back to `gdb`, hit `r` to run and pass in a patterned input, like in the
122screenshot.
123
124![](https://cdn.icyphox.sh/7IDsI.png)
125
126We hit a segfault because of invalid memory at address `0x46464646`. Notice
127the `pc` has been overwritten with our input.
128So we smashed the stack alright, but more importantly, it’s at the letter ‘F’.
129
130Since we know the offset at which the `pc` gets overwritten, we can now
131control program execution flow. Let’s try jumping to the `winner` function.
132
133Disassemble `winner` again using `disas winner` and note down the offset
134of the second instruction -- `add r11, sp, #4`.
135For this, we’ll use Python to print our input string replacing `FFFF` with
136the address of `winner`. Note the endianness.
137
138```shell
139$ python -c 'print("AAAABBBBCCCCDDDDEEEE\x28\x05\x01\x00")' | ./rop2
140```
141
142![](https://cdn.icyphox.sh/A~RaT.png)
143
144The reason we don’t jump to the first instruction is because we want to control the stack
145ourselves. If we allow `push {rll, lr}` (first instruction) to occur, the program will `pop`
146those out after `winner` is done executing and we will no longer control
147where it jumps to.
148
149So that didn’t do much, just prints out a string “Nothing much here...”.
150But it _does_ however, contain `system()`. Which somehow needs to be populated with an argument
151to do what we want (run a command, execute a shell, etc.).
152
153To do that, we’ll follow a multi-step process:
154
1551. Jump to the address of `gadget`, again the 2nd instruction. This will `pop` `r0` and `pc`.
1562. Push our command to be executed, say “`/bin/sh`” onto the stack. This will go into
157`r0`.
1583. Then, push the address of `system()`. And this will go into `pc`.
159
160The pseudo-code is something like this:
161```
162string = AAAABBBBCCCCDDDDEEEE
163gadget = # addr of gadget
164binsh = # addr of /bin/sh
165system = # addr of system()
166
167print(string + gadget + binsh + system)
168```
169Clean and mean.
170
171
172## The exploit
173
174To write the exploit, we’ll use Python and the absolute godsend of a library -- `struct`.
175It allows us to pack the bytes of addresses to the endianness of our choice.
176It probably does a lot more, but who cares.
177
178Let’s start by fetching the address of `/bin/sh`. In `gdb`, set a breakpoint
179at `main`, hit `r` to run, and search the entire address space for the string “`/bin/sh`”:
180
181
182```
183(gdb) find &system, +9999999, "/bin/sh"
184```
185![](https://cdn.icyphox.sh/SiNzl.png)
186
187One hit at `0xb6f85588`. The addresses of `gadget` and `system()` can be
188found from the disassmblies from earlier. Here’s the final exploit code:
189```python
190import struct
191
192binsh = struct.pack("I", 0xb6f85588)
193string = "AAAABBBBCCCCDDDDEEEE"
194gadget = struct.pack("I", 0x00010550)
195system = struct.pack("I", 0x00010538)
196
197print(string + gadget + binsh + system)
198
199```
200Honestly, not too far off from our pseudo-code :)
201
202Let’s see it in action:
203
204![](https://cdn.icyphox.sh/9ob4r.png)
205
206Notice that it doesn’t work the first time, and this is because `/bin/sh` terminates
207when the pipe closes, since there’s no input coming in from STDIN.
208To get around this, we use `cat(1)` which allows us to relay input through it
209to the shell. Nifty trick.
210
211## Conclusion
212
213This was a fairly basic challenge, with everything laid out conveniently.
214Actual ropchaining is a little more involved, with a lot more gadgets to be chained
215to acheive code execution.
216
217Hopefully, I’ll get around to writing about heap exploitation on ARM too. That’s all for now.