【2022春节红包】全部题目WriteUp
2022-2-24 08:40:0 Author: mp.weixin.qq.com(查看原文) 阅读量:20 收藏

作者坛账号:JemmyloveJenny

前期准备

先运行一下随便输入一点东西,看看错误提示


输出是 Error, please try again

用 DetectItEasy 查了一下,是32位程序无壳的,可以直接开始逆。

静态分析

拖到IDA里分析一下,主要逻辑都在_main函数里


从能看到有个循环,循环条件还很怪,地址小于字符串HappyNewYear的地址,跟过去看一下

给了一个提示,flag的长度是00010111,转成10进制就是23
在前面也能看到if语句判断了v21 != 23
运行程序输入一串长度为23的字符,可以看到输出文字有变化,更加确定了这一点

再来看一眼这个奇怪的循环,v3是一个指针,最开始指向unk_4140B0


跟过去发现,这个东西在字符串HappyNewYear的前面,而且四个字节一组的样子,数数总共是23个,可以猜测这边是经过加密的flag

由于解密函数不那么简单,也就不再继续静态分析了,上调试器跟着看看

动态分析

我用的是 x96dbg (x32dbg) 用 Ollydbg 或者 IDA直接调试 也一样吧,没啥区别
x32dbg要注意一点,它停下来的EntryPoint并不是_main函数,而是_start函数


要在这个地方下个断点,再F7单步跟过去
到了入口点,看一下0x004012F60x00401317这一段 便是那个循环了,直接在之后下个断点让他跑完

果然就出来一个字符串2022HappyNewYear52PoJie,长度刚好是23

用程序验证一下


成功~这就完事了

剧透一下

这道题和密码学有关哦~
仿射密码 还有 计算模反元素
在CTF中做了不少密码题,在这里看到我好激动啊啊啊啊!

前期准备

还是运行一下看看,这个是要输入UID和flag的,所以每个人flag还会不一样哦,我就不截图了

一看这个文件大小就不太对劲,根初级题比有点小了,查个壳看看


果不其然,是被UPX压缩过了,这个我不知道能不能用工具直接脱
看着是UPX [modified]大概不行吧,我就用ESP定律了

ESP定律脱壳

入口点经典pushad单步一下看到只有ESP寄存器是红色的


对ESP寄存器 右键-在内存窗口中转到,接着在内存窗口中对这个地址 右键-断点-硬件,访问 几字节都无所谓
接着F9运行,等着popad触发硬件断点

把硬件断点取消,然后在下面的jmp大跳转上下个断点
断下来之后F7单步,来到OEP

接下来就是要用Scylla插件了,从顶上的插件菜单打开Scylla


不解释了,按照图里说的做吧
最后会获得一个xxxxx_dump_SCY.exe这个就是脱壳之后的程序了

静态分析

关于C++的string

这道题使用C++写的
不得不说 我好讨厌C++ 源代码看着都费劲,逆向分析更恶心
C++的源代码中经常会用std::string这个类型,但是逆向的时候好像IDA分析不出来
我没找到std::string的正确结构,在这道题里,我根据代码反推了一个它的结构,也不一定对[捂脸],如果有大佬知道请一定告诉我

 复制代码 隐藏代码
struct string
{
    char field_0;
    char field_1[3];
    char *buf;
    int size;
};

可以先添加上这个结构以简化分析流程

看数据流向

会变化的只有 我们输入的UID和key
我们紧盯着它们啥时候被使用就行了


那个sub_402460其实就是std::string的构造函数,或者是字符串复制函数,并不重要……但这东西耗了我好长时间(从中反推出string的结构)

前几个调用的函数都很简单,简单分析就可以知道,分别返回uid %25,map[uid % 12],还有一个通过map[uid % 12]算出来的值,这个值之后再说(最开始我也没看出来)

看一下最终判断函数,IDA最开始反编译出来的不是这个样子,参数个数比现在多,这边是我修复了std::string这个类型之后才分析出来的
前4个参数实际是结构体的各个部分,合起来就是一个std::string
所以需要手动修改一下函数的定义

再来看参数:
v18其实就是复制后的key
v19的话,伪代码里看不清,但实际上就是v21(其实很明显,v21没被用过,但不应该是多余的嘛)这个v21其实很神奇,是乘法逆元,不知道也没关系
v20来源于v6,也就是uid % 25

现在我们就知道了,key是否正确,是和uid % 25,uid % 12有关的,我们根据最终判断函数来分析一下

分析判断逻辑

主要判断逻辑是在sub_401520里面了,其实逻辑很简单,但是被C++这么一弄就很恶心
再次感到修复类型十分重要,这个函数里用了好多std::string,分清楚就简单了


变量的定义大概是这样吧
这个函数前面一大串相似的函数调用,它的作用就是在初始化一大串字符串,后面的循环,便是将他们全部拼接到flag后面得到正确的flag内容,也就是flag{Happy_New_Year_52Pojie_2022}

真正的重点在于后面


我们可以直接在字符串比较函数上下个断点,看看是在比较什么东西,动态调试看一下

动态调试

我输入了自己的UID也就是520012,key随便写了个HappyNewYear
在调用字符串比较函数sub_403ED0的地方0x00401CCE下个断点


这就能看到比较的两个字符串了,根据调用约定,ecx寄存器里的是第一个参数,栈上的是第二个

对ecx 右键-在内存窗口中转到 就能找到std::string的结构了,根据定义可以看到char* buf指向的地址是0x871199(x86架构 Little Endian顺序读取)


我们跟过去就可以看到字符串内容是LqjjkDceKcqp,那么久可以认为这是HappyNewYear经过变换后的结果了
第二个参数如法炮制,它的内容就是前面拼接出来的flag{Happy_New_Year_52Pojie_2022}

分析变换方式

我们再仔细捋一捋哦HappyNewYear变成了LqjjkDceKcqp
注意观察,这是一种替换,比如a -> q,yY -> kK,能看出来是替换这一点就行了

更进一步的话,再联系一下,前面求出来2个参数对吧,uid % 25和 通过map[uid % 12]算出来的值
我在CTF里做了不少密码题,一下就想到这是古典密码学的仿射密码了,想不出来也无所谓了

查表逆变换

假如没看出来是仿射密码,只知道是替换的话,这个嘛…也很简单的
输入key的时候直接输入abcdefghijklmnopqrstuvwxyz
继续在这个位置断下来,看看变换完是什么结果呗

 复制代码 隐藏代码
abcdefghijklmnopqrstuvwxyz 变换前
qtwzcfiloruxadgjmpsvybehkn 变换后

然后倒着查呗,这多简单呢

仿射密码加密

什么是仿射密码呢?
有两个参数AB,a-z分别编号0-25
然后加密字符c就是计算c*A + B mod 26再转换成字符
那么解密字符c就是计算(c - B)/A mod 26再转换成字符

这些运算都是在模26的意义下进行的,加减B都好办,那除以A如果不能整除怎么办呢?
这就要引入一个概念,乘法逆元
举个例子吧,3和9在模26下互为乘法逆元,这是什么意思呢?
3 * 9 = 27 = 1 (mod 26),同理还有5 * 21 = 105 = 1 (mod 26)7 * 15 = 105 = 1 (mod 26)等等,在有限域运算下,两者的乘积是1,也就是互为倒数,在有限域中就称作互为乘法逆元。
当我们乘上一个乘法逆元,那么就相当于在有限域中除以了原来的数字,比如:11乘以3就是11 * 3 = 33 = 7 (mod 26),再除以3,可以变为乘以乘法逆元,也就是9,7 * 9 = 63 = 11 (mod 26)又变回来了

我们再实战一下,比如说我的参数是A = 9, B = 12,9的乘法逆元是3
对字母'b'进行加密'b'*9 + 12 = 1*9 + 12 = 21 (mod 26) = 'v',加密完就是'v'
对字母'b'进行解密('b' - 12) / 9 = (1 - 12) * 3 = -33 = 19 (mod 26) = 't',解密完就是't'

我们再对照一下之前断点得到的码表,验证了是完全正确的~

还想再说一下,论坛这道题没有出错,真的很好~密码学这些知识用不好是会出错的
要知道,乘法逆元不是一定存在,只有与26互质的这euler_phi(26) = 12个数字才存在乘法逆元,这道题处理得很好,查了个表,还贴心地帮我们求好了乘法逆元(v21就是求了乘法逆元的结果)

编写注册机

理解了 仿射密码加密 的原理,这个就很简单了,我用C语言写一个

 复制代码 隐藏代码
#include <stdio.h>
#include <string.h>

char const flag[] = "flag{Happy_New_Year_52Pojie_2022}";
int map[] = {1, 3, 5, 7, 9, 11, 15, 17, 19, 21, 23, 25};

int main()
{
    int uid;

    while (scanf("%d", &uid) != EOF)
    {
        int A = map[uid % 12];
        int B = uid % 25;
        char buf[sizeof(flag)];

        strcpy(buf, flag);
        char *p = buf;

        while (*p)
        {
            if ('a' <= *p && *p <= 'z')
            {
                *p = ((*p - 'a') * A + B) % 26 + 'a';
            }
            else if ('A' <= *p && *p <= 'Z')
            {
                *p = ((*p - 'A') * A + B) % 26 + 'A';
            }

            ++p;
        }
        printf("%s\n", buf);
    }

    return 0;
}

输入自己的UID按回车就好了

分析Java代码

安卓题,想用什么用什么吧,GDA, JD-GUI, JADX 这些都可以啊
我是用了GDA看了一下,找到了验证输入的函数


核心在于MainActivitycheckSn函数
这是一个Native函数,也就是说在.so动态链接库里面

Java这边还提供了一个线索,flag长度是16
除此以外就没啥了,分析so去吧

定位checkSn函数位置

前期分析

用压缩软件打开apk,发现lib文件夹里只有一个lib52pojie.so文件,我们要分析的就是它了
这个是arm64-v8a架构的库,所以要用 IDA x64 打开

Native函数有两种定义方法,一种是按照类名函数名静态声明并导出,一种是在JNI_OnLoad里调用vm->GetEnv->RegisterNatives动态声明
看一眼它的导出表,发现并没有导出checkSn函数,那么就说明是JNI_OnLoad里动态注册的

那么就是要找到,通过RegisterNatives注册的checkSn函数是哪个呗
我原本还想用IDA静态分析出来的,可是一看那函数指针乱飞的伪代码就放弃了,这东西只能动态调试
我有个root过的Android手机,可以当作调试用的真机,调试比模拟器省事多了
把这个apk安装好,IDA的android_server64也传上去运行,准备调试~

最开始的想法是用IDA的动态调试功能,直接下断点追着分析
但这IDA真机调试好像只能用attach附加到已有进程上,不能直接启动一个app(也可能是我不会,有会的教教我)
我当时绕了不少弯路,整了JDB,DDMS这些乱七八糟的,用他们调试启动app,在JNI_OnLoad前就用IDA附加,好不容易弄完了,但是还有问题,IDA没能把文件里的地址和运行时地址对应起来,也就是说IDA不知道把断点下在哪个地址,根本断不下来,这都白费功夫

用Frida来Hook注册函数

既然动态调试也跟踪不了 那我就在你必然会调用的地方守着嘛~
想法就是,知道会调用RegisterNatives,那么就在那Hook一下传入的参数呗

在网上搜Android Hook相关内容的时候,就看到了有用Frida来Hook那个RegisterNatives的脚本
这个Frida我也早就听说过它的大名,这次就试试看怎么样
安装Frida的过程我就不说了,要有Python环境什么的
然后那个Hook的脚本在这里
,把脚本内容存到文件里,然后命令行输入frida -Uf com.wuaipojie.crackme2022 --no-pause -l script.js就能Hook到真实地址了


得到了这样一个结果,确实是通过RegisterNatives注册了checkSn函数,那个0x70c7f7ff74是运行时的地址,而不是IDA里的地址
Frida也是能获取运行时地址的

 复制代码 隐藏代码
var base = Module.getBaseAddress("lib52pojie.so")
console.log(base)

这样就能输出lib52pojie.so加载的基址
减一下就知道,验证函数是sub_6F74,同时还可以利用基址计算其他函数的偏移量

动态调试

这个so文件基本上没办法直接静态分析,在汇编中能看到很多的BLR X8这种指令,也就是说调用X8寄存器中地址的这个函数,而X8的计算乱七八糟,IDA的F5直接报废
那么我们接下来要做的就是去除这些干扰,把代码变回正常的样子

我还是用Frida启动这个app,Frida可以获得到lib52pojie.so的基址
然后再用IDA附加到这个进程上,手动计算一下运行时地址下断点

每到一个BLR X8就可以在IDA的寄存器窗口中看到真正的地址,减掉lib52pojie.so的基址之后,就能获得调用的函数在文件中的偏移了,然后把BLR X8改成BL $(address)即可,顺便把计算X8所用到的指令改成nop

修改的话,我是给IDA装了一个插件叫做KeyPatch (KeyStone),修改效果大概如下:


修改完之后,应该就没啥难点了,IDA的F5又支棱起来了!!!

静态分析

之后就是一般的逆向过程,修复类型,修复参数,重命名函数等等
其中有个用到的字符串类型,可能是C++编译器弄出来的吧(C++真讨厌)
我大概逆向了一下他的结构,不一定对

 复制代码 隐藏代码
struct basic_string
{

    int64_t field_0;
    int64_t length;
    int32_t field_10;
    char buf[];
};

修复完的代码大概如下


可以看到,最后的结果v5是字符串比较的结果,下断点看了一下,这些都是无意义的数据,猜测是一种加密算法的密文比较,之后也证实了,就是变形的sm4

这个cipher变量是怎么看出来的呢…?就是在sub_9C68中用同样的方法修复,可以发现使用了0x357C0那个地方的数据,跳过去一看是sm4的vtable


这很明显是C++的类的结构,那么sub_9E90,sub_9F50,sub_A048,sub_A080,sub_A4F0这些肯定都是sm4的类函数了

解密密文

用openssl解密(失败)

盲猜sub_9E90,sub_9F50是设置key和iv的,然后sub_A080是加密函数
然后我就从里面下断点得到了keyiv,比较函数获得了正确的密文

 复制代码 隐藏代码
key = 0xc099403db00550812ea00fd803dc0e7c
iv = 0x022b26284a337015f04de065e05fc094
cipher = 6e6649305baf80c49b1b063c0500c80346ccfd42b3063ae7312b52a21cd334d8

用openssl解密试了一下,换任何模式都无法解密,加密的密文也都不同
那么可以断定,这道题修改过sm4的默认参数,这个方法行不通

猜测sm4类函数的作用

sub_A048,sub_A080,sub_A4F0的功能还不太确定
其中sub_A048调用的时候传入参数是1,有可能是1代表加密,0代表解密
也有可能是sub_A4F0是解密函数,sub_A080设置加密模式(ECB/CBC)

想想这个sm4的代码大概率不是出题人自己写的,肯定是网上开源的代码
所以直接上Github搜索sm4的C++代码,果然就有
https://github.com/tonyonce2017/SM4
看了一眼,确定解密是单独的函数,那么我们只要拿密文调用解密函数即可

用调试器执行解密函数

我们看一下这一段的汇编


可以很轻松地 理解这段的含义
那么我们在调用sub_A080加密函数时,稍稍修改一下寄存器的值
把原来的输入内容地址,改成正确的密文的地址,然后修改PC寄存器的值,改成解密函数的地址
处理器会执行PC寄存器的位置,那么就会调用解密函数,还原出正确的明文
看一下解密结果即可
正确的flag就是[WwW.52P0Ji3.cN]

hls加密嘛…肯定是有个key的
我们把里面的文件都提取出来,有好几个加密的.ts,一个script.bundle.js,还有一个drm的请求
尝试直接用返回的drm解密失败了,说明还是得分析javascript

重点在这里


图里解释的很清楚了,drm请求的前后16字节异或,再与请求头中的h异或,即可获得正确的key
我是用cyberchef实现的

 复制代码 隐藏代码
https://icyberchef.com/#recipe=From_Hex('Auto')XOR(%7B'option':'Hex','string':'7b10311e6e310f0df068d9ede10475a8'%7D,'Standard',true)XOR(%7B'option':'Hex','string':'DA4E5CEAE16FED46EB6F498C9B63D53B'%7D,'Standard',false)To_Hex('Space')&input=MDhBNUU2QzJDMjYxQThBQ0I0RDc5QzQ5QUYxNjBBM0E")

得到正确的key就是a9fb8b364d3f4ae7afd00c28d571aaa9,其实iv也是这个

然后解密就好了嘛 随便怎么弄了,我是用了openssl

 复制代码 隐藏代码
openssl aes-128-cbc -d -in live_00003.ts -out live_00003.decrypt.ts -K a9fb8b364d3f4ae7afd00c28d571aaa9 -iv a9fb8b364d3f4ae7afd00c28d571aaa9


然后在 live_00003.decrypt.ts 中就能看到flag了
flag{like_sub_52tube}
这个flag也有点坑…我把Sub的s看成大写了,然后提交又不知道要不要加flag的大括号
第一天的三次机会就这么全错光了

活动已结束,题目打包放到爱盘供大家下载学习:
https://down.52pojie.cn/Challenge/Happy_New_Year_2022_Challenge.rar
欢迎发帖讨论分享分析过程和结果。

--

www.52pojie.cn

--

pojie_52


文章来源: http://mp.weixin.qq.com/s?__biz=MjM5Mjc3MDM2Mw==&mid=2651137296&idx=1&sn=5b80516e32e21b9a0a1fbc67127cef51&chksm=bd50b5448a273c52c362ea0404f0143e17a6841b828a974df834d7f13de64fe5bf2cae98fd58#rd
如有侵权请联系:admin#unsafe.sh