记录一些2022年乱七八糟的比赛的WP和复现。
第五空间
wb
这其实是个密码题,比赛的时候我给dbt👴在这题打下手,就顺便写一下逆向的部分,方便密码👴们复现这道题。
输出:
1 | This is an highly obfuscated whitebox ECDSA implementation (on an certain standard elliptic curve)! |
main函数:
1 | __int64 __fastcall main(int a1, char **a2, char **a3) |
v5赋0,sub_404565向v4里写了一堆东西然后打印出来,v4不会影响结果,修改v5的值会得到不同的结果。
至于怎么修改v5?直接丢给队里的逆向手吧hhhhhhh
sub_404565里做了严重的混淆,无法逆向,只有两个函数能看。这两个函数分别在混淆的最初和最后调用,相关内容我已经写在注释里。
1 | __int64 __fastcall sub_409980(__int64 a1) |
1 | __int64 __fastcall sub_409A5F(__int64 a1) |
dbt让我修改v5的值,运行程序得到以下数据:
1 | v5 = 1 |
最终脚本(来自dbt👴):
1 | n =0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551 |
re2
mips,ghidra可冲
1 | undefined8 move(void) |
数据里找地图,注意是word类型的
1 | map = [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 3, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 3, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0] |
打印出来迷宫手动走即可。泪目,这么简单的签到题真的不多了
Universal
这题就是纯调试。。。没任何技巧。。。对着trace边看边调试,抽了3天的空终于调出来了,这对我这个调试菜鸡来说还是太艰难了😭😭😭
感觉这种东西还是要多练啊,做熟练了,比赛遇到的时候就不会紧张了
但是这个题莫名其妙的……好像没人写这个题的wp……(还是自己硬着头皮嗯啃汇编了…… 但还是想吐槽汇编真难看
现在面对着trace出来1w+行我终于也能说
区区1w行(((
这都小场面
泪目
ctrl s找data段,往下翻找到密文unk_415100
,找交叉引用,反编译到sub_402747
,主要的加密逻辑:
1 | if ( sub_40AE50() == 32 ) |
用sub_40553C
加密了4次,然后用sub_405432
加密,最后sub_40A530
和密文对比。
可以知道flag长度是32,调试选项Paramenters加上一个长度为32的字符串就可以正常运行。此后下断点去trace,看trace去分析。
trace看逻辑,调试看内存。还原出来的逻辑如下:
1 | from struct import * |
逆着去写解密脚本脚本即可。
注意decode2里面的what值是不可知的,所以只能爆破,可以通过限制what等于(input_byte[0] + input_byte[-1]) & 0xff
来减少解的情况。
1 | from struct import * |
5_crackme
还没复现,先占个位,等学弟浇浇我移动安全再来做(逃
CISCN 初赛
baby ast
算是拖了很久,对这题充满了不想复现的恐惧……其实比赛的时候早就装好swift了,也想过自己编译一段语法树出来对照代码看看,但是总觉得很麻烦,又耐不下心去看……
总觉得八百多行很多很恐惧,但是现在看也就还行……
测试代码tmp.swift
:(语法好像有点问题,我也没仔细看swift的语法,凑合看8
1 | func check(keyValue: String, encoded: String) -> Int { |
使用命令swiftc -dump-ast tmp.swift
来得到抽象语法树。
func check(keyValue: String, encoded: String) -> Int {
对应
1 | (func_decl range=[tmp.swift:1:1 - line:6:1] "check(keyValue:encoded:)" interface type='(String, String) -> Int' access=internal |
所以题目里的
1 | (func_decl range=[re.swift:1:1 - line:14:1] "check(_:_:)" interface type='(String, String) -> Bool' access=internal |
对应的应该就是check(_: String,_: String) -> Bool
,即定义一个check函数,返回值为布尔类型。
let a = 9
对应
1 | (pattern_binding_decl range=[tmp.swift:2:5 - line:2:13] |
let b = 10
同理。
return a ^ b
对应
1 | (return_stmt range=[tmp.swift:5:5 - line:5:16] |
其中运算符^
在extension.^
print(check("1234","dfgh"))
对应
1 | (top_level_code_decl range=[tmp.swift:8:1 - line:8:27] |
let n = [0,1,2,3]
对应
1 | (top_level_code_decl range=[tmp.swift:10:1 - line:10:17] |
for i in 0...3{}
对应
1 | (brace_stmt implicit range=[tmp.swift:11:1 - line:13:1] |
for_each_stmt
创建循环。
n[i] = 0
对应
1 | (assign_expr type='()' location=tmp.swift:12:7 range=[tmp.swift:12:2 - line:12:9] |
extension.subscript(_:)
表示切片操作。
题目中刚开始定义了一个check(_: String,_: String) -> Bool
,然后给两个变量b和k赋值,大概是类似于bytes的操作,即对字符串解码存为ASCII码,然后定义4个变量r0,r1,r2,r3。
定义for循环:
1 | (for_each_stmt range=[re.swift:5:5 - line:12:5] |
extension....
表示for i in x...y
的形式,x和y可以根据缩进来判断。
x定义在
1 | (argument |
即x=0。
y定义在
1 | (argument |
即y=b.count-4
,翻译成python就是y=len(b)-4
。
由于swift的for in循环含首也含尾,所以这句翻译成python的形式应该是for i in range(len(b)-4+1)
再往下,
1 | (tuple_expr type='(@lvalue UInt8, @lvalue UInt8, @lvalue UInt8, @lvalue UInt8)' location=re.swift:6:9 range=[re.swift:6:9 - line:6:24] names='','','','' |
即r0,r1,r2,r3 =
下一步切片操作,以第2个为例,即
1 | (subscript_expr type='@lvalue UInt8' location=re.swift:6:36 range=[re.swift:6:35 - line:6:40] decl=Swift.(file).Array extension.subscript(_:) [with (substitution_map generic_signature=<Element> (substitution Element -> UInt8))] |
其中操作对象是b(re.(file).check(_:_:).b@re.swift
)和i(re.(file).check(_:_:).i@re.swift
),运算符是+(extension.+
),值为1,即b[i+1]
整理一下就是r0,r1,r2,r3=b[i],b[i+1],b[i+2],b[i+3]
加密部分第一句
1 | (assign_expr type='()' location=re.swift:7:16 range=[re.swift:7:9 - line:7:49] |
这一句是由多个子句构成,从最内层的缩进看起,最内层的两个子句分别是
1 | (argument |
即k[0]
1 | (dot_syntax_call_expr implicit type='(UInt8, Int) -> UInt8' location=re.swift:7:36 range=[re.swift:7:36 - line:7:36] nothrow |
即r0>>4
处于同一缩进的子句是同级的关系,两子句的运算符要在次一级缩进处找。
即k[0]+(r0>>4)
此后过程大致相同,这一句翻译过来即b[i]^=(k[0]+(r0>>4))&0xff
,同理,下一段是b[i+1]^=(k[1]+(r1>>4))&0xff
完整加密过程:
1 | for i in range(len(b)-4+1): |
程序使用的密文可以在五百多行的地方获取到,密钥是"345y"
。据此写出解密脚本:
1 | b=[88,35,88,225,7,201,57,94,77,56,75,168,72,218,64,91,16,101,32,207,73,130,74,128,76,201,16,248,41,205,103,84,91,99,79,202,22,131,63,255,20,16] |
babycode
要装mruby,先装ruby,再装doxygen,注意把doxygen放到环境变量里,最后才能装mruby……
编译好之后在bin目录下可以反编译。
.\mruby -v -b .\babycode.mrb > ruby.txt
区区三百多行,看我手撕
屮……最开始输出的字节码少了四十多行,我就说为什么没有密文密钥,而且那个check方法看起来那么奇怪……心累
不想再看一遍了
又因为运算优先级出锅了……
1 | import struct |
关于本文
本文作者 云之君, 许可由 CC BY-NC 4.0.