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 |

