all repos — site @ ae34231b69976d54d3e88c58ea2a9badcce75263

source for my site, found at icyphox.sh

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