【2022春节红包】全部题目WriteUp
本帖最后由 JemmyloveJenny 于 2022-2-16 23:11 编辑# Windows初级题
## 前期准备
先运行一下随便输入一点东西,看看错误提示
输出是 `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单步跟过去
到了入口点,看一下`0x004012F6`到`0x00401317`这一段 便是那个循环了,直接在之后下个断点让他跑完
果然就出来一个字符串`2022HappyNewYear52PoJie`,长度刚好是23
用程序验证一下
成功~这就完事了
# Windows中级题
## 剧透一下
这道题和密码学有关哦~
仿射密码 还有 计算模反元素
在CTF中做了不少密码题,在这里看到我好激动啊啊啊啊!
## 前期准备
还是运行一下看看,这个是要输入UID和flag的,所以每个人flag还会不一样哦,我就不截图了
一看这个文件大小就不太对劲,根初级题比有点小了,查个壳看看
果不其然,是被UPX压缩过了,这个我不知道能不能用工具直接脱
看着是`UPX `大概不行吧,我就用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;
char *buf;
int size;
};
```
可以先添加上这个结构以简化分析流程
### 看数据流向
会变化的只有 我们输入的UID和key
我们紧盯着它们啥时候被使用就行了
那个`sub_402460`其实就是`std::string`的构造函数,或者是字符串复制函数,并不重要……但这东西耗了我好长时间(从中反推出string的结构)
前几个调用的函数都很简单,简单分析就可以知道,分别返回`uid %25`,`map`,还有一个通过`map`算出来的值,这个值之后再说(最开始我也没看出来)
看一下最终判断函数,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`算出来的值
我在CTF里做了不少密码题,一下就想到这是古典密码学的仿射密码了,想不出来也无所谓了
### 查表逆变换
假如没看出来是仿射密码,只知道是替换的话,这个嘛…也很简单的
输入`key`的时候直接输入`abcdefghijklmnopqrstuvwxyz`
继续在这个位置断下来,看看变换完是什么结果呗
```
abcdefghijklmnopqrstuvwxyz 变换前
qtwzcfiloruxadgjmpsvybehkn 变换后
```
然后倒着查呗,这多简单呢
### 仿射密码加密
什么是仿射密码呢?
有两个参数`A`和`B`,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;
int B = uid % 25;
char buf;
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按回车就好了
# Android中级题
## 分析Java代码
安卓题,想用什么用什么吧,GDA, JD-GUI, JADX 这些都可以啊
我是用了GDA看了一下,找到了验证输入的函数
核心在于`MainActivity`的`checkSn`函数
这是一个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的[脚本在这里](https://github.com/iddoeldor/frida-snippets/#reveal-native-methods)
,把脚本内容存到文件里,然后命令行输入`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`是加密函数
然后我就从里面下断点得到了`key`和`iv`,比较函数获得了正确的密文
```
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就是``
# Web中级题
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的大括号
第一天的三次机会就这么全错光了 看不懂哇 三滑稽甲苯 发表于 2022-2-16 22:17
我分析web时试过直接把所有文件放到服务器上然后浏览器访问,但是怎么调都会报错,这是为什么呢
因为HTTP请求头中的那个h是随机生成的,你请求的时候h和抓包的h不同
再说了…drm那个请求是POST,一般服务器不允许POST静态文件的
Content-Type也不一定对 本帖最后由 mmmll1234 于 2023-3-10 22:30 编辑
时间戳那个能不能深度请教一下,楼主看到了回我一下谢谢
https://www.52pojie.cn/thread-908684-1-1.html 不错,除了最后web还能看懂,上两个盲区了 膜拜大佬们 我尽量下次用Cstring不用C++ string了 C++ string不是能直接看到源码吗{:301_1008:} 我分析web时试过直接把所有文件放到服务器上然后浏览器访问,但是怎么调都会报错,这是为什么呢{:301_998:} 高手~看不懂~ 卡在安卓了