all repos — site @ 2e62d976dc907a96e45f0f644e3f8f9fd3299b02

source for my site, found at icyphox.sh

pages/txt/rop-on-arm.txt (view raw)

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