CTF-WP-2
四川省赛2023
big number
main_main
主逻辑只是获取输入,并调用main.encyrpt
,最后进行比较:
main.encrypt
main.encrypt
逻辑:
两个大数相乘115792089237316195423570985008687907853269984665640564039457584007913129640233
和463168356949264781694283940034751631413079938662562256157830336031652518559817
,然后将输入由二进制数据转化为大数,并进行指数为131101
,模为两个大数的乘积的运算,即一个RSA加密。
这其中的大数的结构大概这样子:
这里将输入转化为大数,然后将输入作为底,num_131101
作为幂,大数乘积作为模,进行运算,结果放在rax
上。
然后调用main_encrypt2
。
main_encrypt2
这个函数其实就是一个换表的base58
。
表为:rUzoi1mGW6u8TFH3RCbeKYZSJ2DsAXwgfhdQj5cvLNMaxV97kq4ynEBPpt
将输入的大数不断除以58,将余数进表得到字符加到结果中。
最后倒转字符串:
exp
1 | import math |
SDK
数独游戏。
网上的脚本:
1 |
|
然后将输出按程序的规范提取一下:
1 | a = "010c000400000510000609000f070e0802030506010407080a0c0e0f000b0d1007080900020b0e0f01030d100400060c000e0f100609000d00000708010203000301020504060007090a0b0c000e100f00000700050100030d0f100e0a09000b09000b000d0f100003010200050008070d0f0000090c0a00000008060201000300020003000804060b090a0d0010000e060408070a02000c0e100f030b0d050000090e0b0f10000006000c010300070400100c0d0b0e030007080005060a01000805000000000902000e000a10000b0d0c00040208030b00100d0500000f0a000e0b0009100d0f0a0c00000708030005100d0a0f0e050604080b0302000c0900" |
HitCon2023
blade
程序逻辑
执行以下程序,是有以下逻辑的:
由师傅的wp关注程序的这个main函数:seccomp_shell::main::hef7e76ec97275895()
:
关注下seccomp_shell::cli::prompt::h56d4b6fe2f13f522(&v5)
,其中有switch
去判断是否server
还是shell
:
进入seccomp_shell::shell::prompt::h76cecfe7bd3bdf50(v33)
,其中有刚才打印出来的字符串:
再往下走,有switch
,应该是根据字符串长度再判别是啥命令:
其中有case
为flag
,其中有verify、fail、success
三个函数:
进入seccomp_shell::shell::verify::h898bf5fa26dafbab
有判别参数是否为0x64
,应该是字符串长度:
之后进入一个大循环,大循环中有几个小循环,是根据dest
的值打乱input
的顺序,一共有八个:
打乱input
之后,对input
进行如下的加密:
之后大循环这样子执行256轮,然后结束。
对最后的比较结果赋值,以及更改:
unk_556233FE7B2B
部分其实是字节码:
反编译可得到:
1 | .rodata:000055978D72FB2B loc_55978D72FB2B: ; DATA XREF: seccomp_shell::shell::verify::h898bf5fa26dafbab+44D↑o |
但要注意,程序运行过程中会对字节码进行更改:
因此逻辑应该是分别去dest
和input
的四个字符到字节码的红框中的位置,然后执行字节码进行比较。
dest
的部分:
exp
对于大循环中,对输入的顺序更改,可以使用输入输出表来解决;对输入的加密,由于是字节的加密,也可以使用输入输出表来解决。
更改顺序的输入输出表:可以输入base64的字符串,来获取转换一次后的字符串结果。
字节加密的输入输出表:可以将0x0-0xff
分为四组分别输入一次,得到结果。
比较的部分的操作基本都在图中,需要到r12、r13、r14三个寄存器的值,可以执行一遍字节码,得到结果(也可以弄懂字节码自己推结果):
最后脚本:
1 | import struct |
lessequalmore
👉 网上师傅的wp
程序逻辑
程序逻辑是虚拟机,输入然后验证。
用python实现虚拟机:
1 | import sys |
根据输出的信息,可以知道输入被放到了opcode[16:]
的地方,长度放在了opcode[4]
,4375, 4376, 4394, 4561, 4562, 4580
这几个地方放了最后一位输出的位置。
根据程序逻辑,输出之后会再次输出回车符,程序一共输出了四次回车符(eip = 5433
):
第三次回车符之后就是开始检查输入了,因此可以不用管前面的部分,上面的代码尝试当程序第三次执行完输出之后,即到eip=5436
时,将opcode
dump
出来。
dump
出来后,直接让虚拟机从5436
开始执行:
1 | import sys |
可以使用sympy
,让输入是符号,以便在过程的表达式中好看点,在执行之前,将输入放到opcode中。
在输出的信息中,跟踪x0
走,可以发现其逐渐变成了多项式:
原本存放x0
的地方opcode[16]
存放了多项式:
跟踪这个多项式的最后结果,其和一个大数进行了减法,猜测是比较:
最后,虚拟机结束后,输出opcode的结果,存放输入的地方都变成了多项式:
比较的结果可以在下面找到:
以上可以列成等式,但能看出部分等式,左右相差较大,观察其他式子,可以补充个16777216
。
exp
1 | from z3 import * |
CrazyArcade
根据这位师傅的思路来做👉hitcon2023/rev_crazyarcade
… it uses a driver to read and write memory in the driver via
DeviceIoControl
.
1
2
3
4
5
6 read ioctl handler bytes into handler_bytes
write magic 0x25 bytes into driver+0x3000
for i in range(0x1337):
(driver+0x3000)[i%0x25] ^= (i&0xff) ^ handler_bytes[i%0x584]
关注DeviceIoControl
的调用,来到sub_7FF6F6CD6070
,大概是初始化部分:
1 | __int64 sub_7FF6F6CD6070() |
DeviceIoControl
的另一处调用:可以发现WP中的加密逻辑
1 | int __fastcall sub_7FF6F6CD30A0(__int64 a1) |
CrazyArcade.sys
的部分逻辑,为CrazyArcade
的驱动操作,以下的case
的为根据控制代码来进行(只贴了两个用到了的)。
1 | __int64 __fastcall sub_11450(__int64 a1, IRP *a2){ |
根据CrazyArcade.sys
中的代码,加上GPT可以判断0x8000204C
是写数据,0x80002048
是读数据。
那么在sub_7FF6F6CD6070
中就是初始化数据了。
调试sub_7FF6F6CD30A0
发现当经过DeviceIoContorl
后,v9
发生了变化,猜测该数据就是读出来的数据。
v9
在OutBuffer的四个字节之后,而下方计算的值v13
在InBuffer
在四个字节之后,猜测v13
就是写回去的数据。
并且OutBuffer[1], InBuffer[1]
是读写驱动内存的数据的位置。
因此,在sub_7FF6F6CD6070
就是初始化数据赋值的部分:
以上就可以求flag了,中间的数据可以直接dump出来。
1 | import struct |