【2022春节】解题领红包之二、三、番外,新手单步解密过程
by dongye
写在最前面,这篇帖子是纯新手破解、可以算上是手动单步调试的暴力破解,稍复杂一点的加密工作量就太大了,不过解密一些玩具类的题目应该也够用
技术实在有限, 安卓题环境搭起来了就花了不少时间, 最后动态调试虚拟机下没能启动成功, 静态调试不会找入口也不太看得懂, 有一点小遗憾, 看了官方解析,感觉还是太复杂了
以下正文:
解题领红包之二
解题思路1:
盲猜,高端叫法可以叫做社工
打开解题文件熟悉的命令行窗口, 上次用OD还是2021春节时候, 偶尔逛论坛消遣一下, 已经忘了怎么用了, 但这个界面很熟悉, 和2021年是一样的
题目的注1:【 由于年前时间较紧,所以题目比往年少了一些,望大家海涵。】
那答案会不会也是一样的?
打开2021年的记事本,去年答案 【2021HappyNewYear52PoJie】,直接修改年份,输入 【2022HappyNewYear52PoJie】,成功
解题之后还发现了文件名还是
解题思路2:
脱壳破解
开始是用OD打开程序的,但里面非常多函数调用,看不太懂,猜测可能是有壳,查一下
PEiD 扫描显示没有壳
Exeinfo扫描,显示 Armadillo 和 PseudoSigner
搜索许久未找到是什么,可能就没有壳,还是用OD打开
执行一遍发现非常多的函数调用,并不明白是什么逻辑
四处下断点发现堆栈调用就有明文密码,应该是完全没有加密了,大概在输入口令后进行的加载
最后验证出一个方法:
- 首先运行程序等待用户输入时暂停
- 随便输入数字 123 后通过 【执行到返回】六次, 一直执行回主函数 也就是 40xxxx 的地址
- 在堆栈记录里可以看到明文口令
这里应该是输入口令后程序调用了读取或者比较的方法留下的痕迹
今年的第一题确实够简单的
2022解题领红包之三
第三题难度大了一些、由于工具用的很不熟,可以算得上是暴力破解了,相较去年题目应该是简单了一些
首先还是查壳
UPX,老演员了,上OD,esp定律,论坛里有很多教程了,不再赘述
跟踪到一个循环判断,不知道是什么,直接修改值跳过循环
F8 跳转, 顺利转到程序入口
使用 OllyDump
直接脱壳, 脱壳顺利完成
再次用OD打开脱壳后程序
输入 id
和 key
之后暂停, F8跟踪到判断处
追踪判断前的函数 401520
进入后F8跟踪观察
发现一大段循环,并观察到特殊字符串 flag{Happy_New_Year_52Pojie_2022} 猜测可能是解密口令
PS:开始以为目标key是 Happy_New_Year_52Pojie_2022
后面发现是 flag{Happy_New_Year_52Pojie_2022}
再往后跟踪,很快到return,观察return就是把比较结果返回,主函数判断后结束
那判断部分函数就在这之间了,对之前调用的函数加断点,优先查看循环次数最多的方法,最终确定加密函数在 4011B0
进入后跟踪发现这个函数是将输入的口令进行变换,观察发现此处执行时每次循环会改变一个字符,此处变换代码很长,代码相似度高,看不懂,从外部入手
此处应该是将UID作为密钥将口令进行加密那么尝试输入不同UID观察变换规律
UID输入1-5,口令 Happy_New_Year_52Pojie_2022,得到结果如下
1: Crwwz_Ebh_Zbro_52Wnulb_2022
2: Bknnu_Xqe_Uqkd_52Nsrwq_2022
3: Ihyyd_Upz_Dphc_52Yjmxp_2022
4: Johhi_Bac_Iaon_52Hepma_2022
5: Mjiix_Whl_Xhju_52Ipyfh_2022
此时发现变换有一定规律, 相同字母在不同位置变换结果相同, 猜测变换为一一对应关系, 尝试输入:
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789{}_
作为key, 观察变换后值,得到:
RAJSBKTCLUDMVENWFOXGPYHQZIrajsbktcludmvenwfoxgpyhqzi0123456789{}_
对比可以看出加密函数对字母进行了替换,相当于彩虹表,要想得到目标口令,通过加密后密文反查应该使用的明文得到以下对应关系:
flag{Happy_New_Year_52Pojie_2022}
qibt{Wbuuv_Onp_Vnba_52Urczn_2022}
经验证口令正确
中途由于猜测目标口令是Happy_New_Year_52Pojie_2022还走了一些弯路,基本都是通过F8看堆栈观察数据变化找到的关键代码段进行分析
有了这些信息应该可以写注册机了,但是不会做注入,无法实施
有一个思路,可以在加密时输入UID,使用全字符字典作为口令输入,在加密结果拿到后再使用目标口令进行反查,可以得到彩虹表和解密口令,这样可以在不解析加密算法的情况下做解密
【2022解题领红包之番外篇】保姆级解析
准备工作
此题是 Fiddler 和 Web Archive 保存的日志文件, 我选择用 fiddler 文件 首先下载题目文件, 安装 fiddler everywhere 软件
官网地址https://www.telerik.com/fiddler
导出文件
使用 fiddler
打开 52tube.saz
文件
看了一下应该是一个视频的网络请求记录, 其中视频是 live.m3u8
和一系列 ts文件
这是常用的视频加密播放方式, 整个请求包含了所有文件, 那就可以在本地运行这个视频站了
在 [ Session ] 列表的 [ 52tube ] 上右键导出, 选择 raw files
导出
运行网站
将文件夹放入 nginx
搭建出本地网站, 将文件中的 xx.html
改成 index.html
以便访问
在浏览器中进行访问网站 http://localhost/52tube.mmxxii/
, F12
打开控制台, 查看出错情况
发现ping访问路径为 /api/ping/
而静态资源路径为 /52tube.mmxxii/*
,将/52tube.mmxxii/api
移动到根目录下
修改后刷新网页, 出现新的错误, /api/drm
的 POST 请求出现 405
, 这是因为网站只是放在了 nginx 下, 是只支持 GET 请求的, 自然会出现 405
因为前面在fiddler中已经将 /api/drm
的 response 保存成文件, 因此这里就是要让前端页面请求到文件的内容, 有三种处理办法
- 第一种是修改 nginx 配置, 将 POST 请求转发成 GET
- 第二种是修改 前端的请求参数将 POST 改为 GET
- 第三种是起一个本地服务, 做一个 POST 接口, 返回 drm 数据
第二种方法最直观, 也比较顺手, 所以直接在代码中找请求出错的位置,
在出错请求的 Initiator 列中直接点击第一行 script.bundle.js:17199
, 这是js代码格式化后的位置, 正是发起请求的位置
由于现在没有服务器相应请求, 直接修改参数把 POST 改成 GET 就可以
参数的代码如下
var a = {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: r
};
在本地文件中修改为
var a = {
method: "GET",
params: r
};
保存后再次刷新网页, drm 和 ts 视频文件顺利加载, 但视频无法播放, 应该遇到正题了
解密
来到 drm 请求的位置, 整个网站只有在这里有POST请求, 应该是向服务器请求密钥
看一下请求参数的构成, 是 h 和 id
var r = new URLSearchParams;
r.append("h", n(e.buffer)),
r.append("id", t);
var a = {
method: "GET",
params: r
};
其中 id 是固定的 "live",那么发送的密钥应该就是 h 了, 再看 h 的生成,由 e
经过 n()
处理生成
n()
是将传入的 t/e
每位的数字转换成长度为 2 的十六进制字符再拼接
function n(t) {
return [...new Uint8Array(t)].map((t=>t.toString(16).padStart(2, "0"))).join("")
}
简单测试一下
x = 27
x.toString(16).padStart(2, "0")
输出 '1b'
而 e
是由这段代码生成的
let e = await async function() {
let t = new Uint8Array(16);
crypto.getRandomValues(t);
let e = n(t.buffer) + Date.now() + Math.random();
return new Uint8Array((await async function(t) {
const e = (new TextEncoder).encode(t);
return await crypto.subtle.digest("SHA-256", e)
}(e)).slice(0, 16))
}();
e
的生成是由随机数和时间戳生成的, 那么从正向是不可能得到密钥的
到这里可以分析出视频加密的原理, 应该是生成的 n(e)
作为密码向服务器请求 一个 使用这个 密码e 对解密 ts 的密钥进行加密 的结果, 再用密码e对服务器返回结果进行解码得到ts的密钥, 流程大致如下
sequenceDiagram
客户端(e)->>服务端(key): 密码e 经过n(e) 得到h
服务端(key)->>客户端(e): 用 h 对 key 进行编码得到 drm
客户端(e)->>客户端(e): 对 drm 进行解码得到 key
客户端(e)->>视频: 使用 key 对视频进行解码
这里经过分析可以得到结论, 服务端返回的结果与发送的数据为解密关键, 现在我们有的是服务端返回的 drm 和对应的请求参数, 这个参数在 fiddler 中可以查看
h='7b10311e6e310f0df068d9ede10475a8'
这个发送的参数是 n(e)
的结果, 解密时需要的是 e
, 需要对 n(e)
进行解码得到一个 e
, 上面分析过 n()
的编码规则比较简单
只需要简单做一下反向编码, 替换掉随机密钥, 使之变成和保存下来的 drm
相对应的数据即可, 解码规则是每两位字符作为十六进制数转成十进制, 代码如下
// let e = await async function() {
// let t = new Uint8Array(16);
// crypto.getRandomValues(t);
// let e = n(t.buffer) + Date.now() + Math.random();
// return new Uint8Array((await async function(t) {
// const e = (new TextEncoder).encode(t);
// return await crypto.subtle.digest("SHA-256", e)
// }(e)).slice(0, 16))
// }();
let e = new Uint8Array(16);
let m = '0123456789abcdef';
let h = '7b10311e6e310f0df068d9ede10475a8'
for(let i=0;i<32; i+=2){
e[i/2] = m.indexOf(h[i]) * 16 + m.indexOf(h[i+1] )
}
这是发送的参数 n(e)
就变成了 固定的 h='7b10311e6e310f0df068d9ede10475a8'
, 保存代码后再次刷新页面视频出现!!
答案口令
题目口令从现在视频第 12 秒处
完结撒花