从2022 CISCN 生发的对Swift Ast逆向类题目的一点思路

来蹭一波刚打完的CISCN 2022热度。分享一下个人对re第一题出现的新题型的理解与复现。

0x00 前言

来蹭一波刚打完的CISCN 2022热度。对于逆向手来说确实有点措手不及,三道题竟然只有一道能用ida打开的(乐)

不过还是很开心的,hack just for fun。未知意味着能够有机会学到新的知识点。

再一次致敬ciscn2022 逆向手的阅读理解大赛

(开场swift ast阅读题 中午mruby bytecode阅读题 下午rabbit xtime 爆破题,1k+行的main函数......)

0x01 swift ast产生原理

image-20220530045703338

可以看到,AST文件是swift源程序到可执行文件.o编译过程中的一个中间性文件。

某国外大佬对 swift生成ast过程的介绍

First, the compiler parses the source code and build the Abstract syntax tree (AST). We could see the AST by the option -dump-ast:

xcrun swiftc -dump-ast main.swift

Semantic analysis could be performed when the AST is constructed.

解析。首先,编译器解析源代码并构建抽象语法树(AST)。我们可以通过选项看到 AST -dump-ast

xcrun swiftc -dump-ast main.swift

可以在构建 AST 时执行语义分析。

0x02 从swift AST 到源码

暂时还未找到从swift AST编译回Swift的方法,那么最好的办法就变成了手撕,不过手撕不能生拉硬撕,我们应该寻找规律。接下来我们借国赛的这道题目来看看swift AST有什么样的规律(如何优雅的手撕)。

函数初始化

 (func_decl range=[re.swift:1:1 - line:14:1] "check(_:_:)" interface type='(String, String) -> Bool' access=internal//函数声明
    (parameter_list range=[re.swift:1:11 - line:1:49]
      (parameter "encoded" type='String' interface type='String')//定义局部变量
      (parameter "keyValue" type='String' interface type='String'))
    (result
      (type_ident
        (component id='Bool' bind=Swift.(file).Bool)))//返值回类型

swift AST的结构看似复杂,实际上还是比较妙的,毕竟这种文件其中一个作用就是用来检查代码的正确与否,所以细节和严谨是必须的,加上注释,可以说是一目了然

0x03 变量赋值

  (argument
        (integer_literal_expr type='Int' location=re.swift:6:55 range=[re.swift:6:55 - line:6:55] value=3 builtin_initializer=Swift.(file).Int.init(_builtinIntegerLiteral:) initializer=**NULL**))
                        )))

按上述格式,swift变量赋值大概有如下四个关键参数

type:变量类型

range:范围(没弄明白什么意思)

value:变量值(这个好理解)

initializer:初始值设定项 一般为null

list转换为字符数组

(argument_list
            (argument
              (member_ref_expr type='String.UTF8View' location=re.swift:2:29 range=[re.swift:2:21 - line:2:29] decl=Swift.(file).String extension.utf8
                (declref_expr type='String' location=re.swift:2:21 range=[re.swift:2:21 - line:2:21] decl=re.(file).check(_:_:).encoded@re.swift:1:14 function_ref=unapplied)))//@的方式给函数传参
          ))

上述代码可以翻译成

encoded.encode('utf8')

encode函数在这种奇奇怪怪的表达形式中是如何被调用的呢,仔细观察了一波发现好像是因为encoded@re.swift

循环结构

如下代码声明了一个for循环,定义了循环变量

 (for_each_stmt range=[re.swift:5:5 - line:12:5]
        (pattern_named type='Int' 'i')
        (pattern_named type='Int' 'i')
        (binary_expr type='ClosedRange<Int>' location=re.swift:5:15 range=[re.swift:5:14 - line:5:26] nothrow
          (dot_syntax_call_expr implicit type='(Int, Int) -> ClosedRange<Int>' location=re.swift:5:15 range=[re.swift:5:15 - line:5:15] nothrow
            (declref_expr type='(Int.Type) -> (Int, Int) -> ClosedRange<Int>' location=re.swift:5:15 range=[re.swift:5:15 - line:5:15] decl=Swift.(file).Comparable extension.... [with (substitution_map generic_signature=<Self where Self : Comparable> (substitution Self -> Int))] function_ref=double)
            (argument_list implicit
              (argument
                (type_expr implicit type='Int.Type' location=re.swift:5:15 range=[re.swift:5:15 - line:5:15] typerepr='Int'))
            ))

计算

swift ast中,计算是怎么进行的呢。了解如下几个概念,方能拨云见日

.extersion

我们要注意程序中的.extersion后缀 后面存储的是运算符号,比如此处,存储了我们的加号

image-20220530062154140

本题目中出现的.extersion

.extersion +
.extersion -
.extersion &
.extersion >>
.extersion ^
.extersion count # 计算数组长度。相当于len()
.extersion subscript # subscript b = b[] 原谅笔者语文水平不佳,不知道如何形容了
extension. ... # 不知道是啥,和循环有关

运算顺序:先符号后数据

什么意思呢,我们看如下一段实例(去除了非关键代码)

  (subscript_expr type='@lvalue UInt8' location=re.swift:6:44 range=[re.swift:6:43 - line:6:48] decl=Swift.(file).Array ##extension.subscript(_:)## [with (substitution_map generic_signature=<Element> (substitution Element -> UInt8))]
  (declref_expr type='@lvalue [UInt8]' location=re.swift:6:43 range=[re.swift:6:43 - line:6:43] decl=re.(file).check(_:_:).##b@re.swift##:2:9 function_ref=unapplied))
  // 加## 的地方是标出要关注的重点 
  //

subscript 英文意思:下标的;写在下方的;脚注的。这里作为一种运算,subscript b = b[](初始化了一个数组 b)

(declref_expr type='(Int.Type) -> (Int, Int) -> Int' location=re.swift:6:46 range=[re.swift:6:46 - line:6:46] decl=Swift.(file).Int## extension.+ ##function_ref=double)
    (declref_expr type='Int' location=re.swift:6:45 range=[re.swift:6:45 - line:6:45] decl=re.(file).check(_:_:).##i##@re.swift:5:9 function_ref=unapplied))
       (integer_literal_expr type='Int' location=re.swift:6:47 range=[re.swift:6:47 - line:6:47] ##value=2 ##builtin_initializer=Swift.(file).Int.init(_builtinIntegerLiteral:) initializer=**NULL**))

第一句是说把两个int变量做加法运算。第二三句分别给加法运算所需的变量赋值 及i+2;

以上结合起来则翻译成:

b[i+2]

未解决的问题:range是什么?

例:

range=[re.swift:5:15 - line:5:15]

re.swift:-line:这个代表什么,没搞明白,希望有明白的大佬能给弟弟传授一二(猜测和地址有关)。

0x04 题目回顾:baby_tree

题目给出了一个 swift ast文件。

p.s:原来从来没见过这种类型的逆向。

非常有趣,一开始蒙蔽了

思路类似于python 给出opcode字节码。鄙人不才采用手撕的办法得到源码

image-20220529134135336

这里定义了check函数。定义了两个变量 encode keyvalue

函数内定义了两个字符数组,b k 分别为 encode,keyvalue的值

image-20220529134521297

image-20220529134531104

数据来源在下方代码中有定义,这样就能推出如下部分源码

def check(encoded,keyValue):
    b= bytearray(encoded.encode('utf8'))
    k= bytearray(encoded.encode('utf8'))

image-20220529141356512

image-20220529141501662

结合着两处可以推出

 b[i + 1] = r3 ^ ((k[1] + (r0 >> 2)) & 0xff)

同理,我们恢复到

 for i in range(len(b)-4+1):
        r0,r1,r2,r3=b[i],b[i+1],b[i+2],b[i+3]
        b[i+0]=r2^((k[0]+(r0>>4))&0xff)
        b[i + 1] = r3 ^ ((k[1] + (r0 >> 2)) & 0xff)
        b[i + 2] = r0 ^ k[2]
        b[i + 3] = r1 ^ k[3]

根据下述两张图片,反写出

 k[0],k[1],k[2],k[3]=k[1],k[2],k[3],k[0]

image-20220529141724265

image-20220529141733405

encode数据

image-20220529134826403

keyvalue

image-20220529134855986

最终得到大致加密源码如下

def check(encoded, keyValue):
    b = bytearray(encoded.encode('utf8'))
    k = bytearray(keyValue.encode('utf8'))
    for i in range(len(b)-4+1):
        r0,r1,r2,r3=b[i],b[i+1],b[i+2],b[i+3]
        b[i+0]=r2^((k[0]+(r0>>4))&0xff)
        b[i + 1] = r3 ^ ((k[1] + (r0 >> 2)) & 0xff)
        b[i + 2] = r0 ^ k[2]
        b[i + 3] = r1 ^ k[3]
        k[0],k[1],k[2],k[3]=k[1],k[2],k[3],k[0]
    return b ==bytes[flag加密后数据]
check(flag,'345y')

根据加密源码写脚本解密即可得到flag

0x05 参考文章

https://cloud.tencent.com/developer/article/1858023

  • 发表于 2022-05-31 09:29:46
  • 阅读 ( 6489 )
  • 分类:其他

2 条评论

打印式雏形许愿机
range 就是在源码中的位置范围吧,例如 `range=[re.swift:6:43 - line:6:48]` 代表第 6 行 43 个字节开始到第 48 个字节
感谢大佬解惑
请先 登录 后评论
请先 登录 后评论
绿冰壶
绿冰壶

学生

18 篇文章

站长统计