JemmyloveJenny 发表于 2022-2-16 20:28

【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的大括号
第一天的三次机会就这么全错光了

wsad012348 发表于 2022-2-16 21:12

看不懂哇

JemmyloveJenny 发表于 2022-2-16 22:41

三滑稽甲苯 发表于 2022-2-16 22:17
我分析web时试过直接把所有文件放到服务器上然后浏览器访问,但是怎么调都会报错,这是为什么呢

因为HTTP请求头中的那个h是随机生成的,你请求的时候h和抓包的h不同
再说了…drm那个请求是POST,一般服务器不允许POST静态文件的
Content-Type也不一定对

mmmll1234 发表于 2023-3-10 19:47

本帖最后由 mmmll1234 于 2023-3-10 22:30 编辑

时间戳那个能不能深度请教一下,楼主看到了回我一下谢谢
https://www.52pojie.cn/thread-908684-1-1.html

夜泉 发表于 2022-2-16 20:55

不错,除了最后web还能看懂,上两个盲区了

amoxuan 发表于 2022-2-16 20:43

膜拜大佬们

云在天 发表于 2022-2-16 21:17

我尽量下次用Cstring不用C++ string了

fjqisba 发表于 2022-2-16 21:46

C++ string不是能直接看到源码吗{:301_1008:}

三滑稽甲苯 发表于 2022-2-16 22:17

我分析web时试过直接把所有文件放到服务器上然后浏览器访问,但是怎么调都会报错,这是为什么呢{:301_998:}

h07799486 发表于 2022-2-16 22:55

高手~看不懂~

hzcdong 发表于 2022-2-16 23:06

卡在安卓了
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: 【2022春节红包】全部题目WriteUp