题目

一个ELF和流量包,先看看流量包

是一个客户端的tcp通信流量,看样子是加密文件内容

ELF则是linux版本的PyInstaller引导程序,直接pyinstxtractor解包


得到了一堆pyc和相关环境文件,大部分都是依赖,client.pyc应该是我们要分析的文件
反编译
尝试通过pycdc反编译获取源码

反编译失败,看样子是做了混淆,直接反编译字节码分析

可以看到_1667常量是一段加密,同时看到了b85decode函数名,应该就是base85,解码
1 | _j0 = lambda: (30 ^ 126) + (520 % 26) |
依然是混淆后的代码,其中大部分是干扰分析的代码
1 | _opaque_true() |
还有一些控制流的混淆,排除之后手动分析一下,其中所有关键字符串都经过了_oe函数加密
_oe函数
从字节码中可以看到很多
第一阶段

首先将密文字符串_d进行base85解码,得到一个字节数组_b

随后将传入的三个整数组成一个元组密钥_k1, _k2, _rn

遍历字节数组_b,用当前字节的索引对3取模(_i % 3),从密钥元组中取出对应的整数,然后与当前字节进行异或 (XOR) 操作,最后把字节数组解码为普通的UTF-8字符串
第二阶段

首先对第一阶段得到的字符串_s进行逐字符遍历,利用参数_rn作为偏移量进行位移还原
判断大小写和数字进行相应的处理,其他字符原样处理,这一步其实就是凯撒密码
由此我们可以手动构造一个_oe函数用于解密
1 | import base64 |

crypt_core模块
在client的最上面还引入了一个crypt_core模块,从解压文件中发现了so文件

ida分析一下

看的出来是Cython编译的,Cython编译有个特性就是在打包过程中会将Python可调用的函数名以“模块名.函数名”的格式嵌入二进制,我们直接提取字符串

这几个都是SM4的典型实现函数
解混淆client.py
通过之前的_oe函数对所有加密的关键字符进行解密
1 | 自定义base64字母表:QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890!@ |
由此我们可以还原client.py源码
1 | #!/usr/bin/env python3 |
总结一下,就是通过自定义的base64字母表解码密钥本身,然后取前16位作为通信密钥,对文件内容进行加密后发送到目标服务器,加密方式上面分析了,看函数大概率就是SM4,没有传入IV,要么是函数内置的IV,要么就是ECB模式
回到最开始的流量包,内容如下:
1 | {"filename": "readme.txt", "ciphertext": "4fe09336577aa52de2d0de1489784d0f6306686ceeae6b28e578c70f5f74fa2a9e48d441c78c3633e2f6335ff00722818ab4c977d77d6dd7d2595640ebf6abc4229230cbb0a238bd1f151f69026e5d8d45e47c89898cbc2875d62933b7cef22379f97a499e4716b92ba8c6eb687d56e385ef94071d2ccd18fc72f4d670d680d801216d06e1a1214041fb7f66fef6c55b8d9b94a9231f60504ed77231c0db127a8647fdb8ed958c259c56d7638f4bf3e1"} |
然而用标准SM4/ECB解密失败了,怀疑出题人魔改了标准SM4流程,只能回头继续看crypt_core模块
魔改SM4分析
这里有几种思路,我们直接找字符串交叉引用

主要加密函数是encode_data

跳转到data段

查阅官方文档我们可知,Cython中PyMethodDef结构是这样的:
1 | struct PyMethodDef { |
那么下个字节地址的sub_9820函数就是encode_data函数的入口,双击跟进
前面大部分都是cython的内存管理逻辑,检查完成后的调用就是我们的目标函数,双击跟进,是一个非常大的函数,我们分开来看
PKCS#7填充 (16字节分组)

计算离16字节的长度然后填充
密钥扩展流程
来到508行,是一个很大的while 1循环,就是一个不知道是不是手搓的密钥扩展流程


先取前三个密钥与 CK 异或
S盒字节替换

这里把一个32位的整数v45切成了4个独立的字节,然后去xmmword_DBA0数组里查表,这个xmmword_DBA0就是S盒的位置
左移13位

S盒及其他参数
在上面这整个过程中,我们首先去排查S盒,也就是xmmword_DBA0这个数组,双击跟进

发现来到了bss段,说明S盒并不是常量,是动态生成的,同样搜索交叉引用

找到一个标记为写入的段,跟进

这里应该还是拷贝内存的操作,但是我们注意到一共是0xad90-0xac10+16=400个字节
在SM4中S盒(256)+FK系统参数(16)+CK轮常数(128)正好是16字节
1 | # S盒,逐字节读取,所以是大端序 |
拿到了之后继续往下看看还有没有其他魔改

来到循环体末尾结束的地方,这个v18是每次循环增加4的计数器,每个轮密钥是4个字节,循环在v18 == 96时就强行跳出到了LABEL_114结束密钥拓展流程的循环
标准SM4一共是32轮循环,96/4=24,这里只进行了24层循环
魔改S盒+魔改常量+24轮加密
到此我们可以编写脚本尝试解密,把数据包中的三个文件密文放进来:
1 | # S盒/大端序/256 |
FLAG

得到flag:dart{f4b547fc-b3d0-44c3-bf21-8f3fb5ad3220}