CTF高手教你如何实现文件加解密破解 您所在的位置:网站首页 versioncode翻译 CTF高手教你如何实现文件加解密破解

CTF高手教你如何实现文件加解密破解

2023-03-01 21:35| 来源: 网络整理| 查看: 265

0x01

前几日,在公司对客户单位的网站与APP进行授权渗透,在使用用户提供账号登录APP端进行抓包时,发现APP传输包与返回包均进行了加密

bp抓包界面如下:

为破解密文获取请求与传输包中内容,于是便对APK文件进行逆向

0x02 APP逆向寻找破解点

使用jadx 打开 APK 文件, 搜索发送加密请求接口,寻找加密方式

跟踪到请求接口,发现请求接口加密使用注解@Encrypt,未找到直接的请求包加密方式,在jadx中搜索Request,找到 RequestDataBuilder类

该类中包含有请求包加密解密函数,并含有一个APP_KEY

在主管帮助下在github上获取到一个js文件

 

因该文件为js文件,且客户单位给的网站与APP同样进行了数据加密,猜测客户单位的网站应该是使用了该js文件进行传输数据加解密,于是根据该js文件尝试对客户单位网站数据加密进行破解

0x03 网站js加密破解

对该JS文件进行分析,发现使用该JS文件会发送两种类型请求,登录请求 和 登录后发送的普通请求 , 对应函数如下:

登录请求

登录后发送的普通请求

分析登录请求

登录请求会调用login,首先对登录函数login进行分析,该函数传值并调用generateKey函数

/**

* 登录函数

* @param username 会员号

* @param pass 密码

*/

export function login(username, pass, resultfunc) {

generateKey(username,

function(obj) {

dologin(obj, username, pass, resultfunc);

},

function(info) {

resultfunc(false, info);

});

}

/*

* 在这个login函数中传入username变量和两个function函数到generateKey函数中

* 因为在JavaScript中函数是第一公民,所以可以作为变量传递

*/

login函数传参到generateKey函数后,分析generateKey函数逻辑

/**

* 发送请求到服务端,进行秘钥的产生,产生的秘钥包括RSA密钥对,和进行秘钥交换通讯的会话秘钥

* @param tjid

* @param successfunc

* @param errorfunc

*/

function generateKey(tjid, successfunc, errorfunc) {

$.ajax({

type: "get",

url: nctUrl + "/gen/key",

data: {

"tjid": tjid

},

dataType: "json",

success: function(obj) {

successfunc(obj);

},

error: function(req, info) {

errorfunc(info);

}

});

}

/*

* generateKey函数通过get请求向接口 /gen/key 发起请求,传入参数为tjid

* tjid参数其实就是登录网站时输入的用户名

* 如果请求成功, 则调用第一个传入generateKey函数的函数,就是调用dologin函数

* 如果请求失败, 则调用第二个传入generateKey函数,就是resultfunc函数

*/

// resultfunc函数其实也是传入的一个函数变量,猜测传入的函数应该为failfunc函数

// 该函数作用其实就是alert一下错误信息,如下

function failfunc(info) {

alert(info);

}

// 总结一下, generateKey函数的作用就是拿用户输入的用户名去跟服务器请求会话秘钥

// 如果成功调用dologin函数, 如果失败alert弹出错误信息

调用 /gen/key 接口如下

如果tjid存在,也就是用户名存在,则请求成功,获取到服务器返回的 ss 和 ps两个值。

当generateKey函数发起请求成功后,则调用dologin函数,下面查看dologin函数

/***

* 执行登录

* @param obj

* @param username

* @param pass

*/

function dologin(obj, username, pass, resultfunc) {

if (obj.code == 1) {

var sessionkey = obj.data.ss;

$.ajax({

url: nctUrl + "/login",

type: "post",

data: {

"username": username,

"userpass": pass,

"publickey": obj.data.ps

},

dataType: "json",

beforeSend: function(xhr) {

addHeaders(xhr);

},

error: function(xhr, info) {

resultfunc(false, info);

},

success: function(obj) {

if (obj.code == 1) {

var secretkey = obj.data.secretkey;

var cryptedInfo = obj.data.info;

decryptSecretKey(username, sessionkey, secretkey, function(retObj) {

if (retObj.code == 1) {

sessionKey = decodeAndDecryptWith3Des(sessionkey, retObj.data);

var decoded = decode(cryptedInfo);

var plainInfo = decodeAndDecryptWith3Des(produceSessionKey(sessionKey), decoded);

var plainObj = JSON.parse(plainInfo);

sessionId = plainObj.sessionid;

$.cookie("sessionKey", sessionKey);

$.cookie("sessionId", sessionId);

// $("#content1").val("sessionid:" + sessionId + " sessionKey:" + sessionKey);

resultfunc(true, "登录成功!", plainInfo);

} else {

resultfunc(false, retObj.msg);

}

}, function(info) {

resultfunc(false, info);

});

} else {

resultfunc(false, obj.msg);

}

}

});

} else {

resultfunc(false, obj.msg);

}

}

/*

* dologin函数首先将之前请求获取到的ss值存储到sessionKey变量中,

* 之后向 /login 接口发送POST请求,请求中带有username、password以及之前获取到的ps值(作为publicKey参数)

* 如果请求成功,则从返回数据包中获取secretKey和info

* 之后调用decryptSecretKey函数对secretKey进行解密,解密成功后操作如下:

* 1. 对sessionKey值(注意是sessionKey,不是secretKey,sessionKey就是之前请求返回的ss)加密,处理后存储到浏览器cookie中

* 2. 对info(info值存储到变量cryptedInfo中,因此解密使用该变量名)进行解密,解密后获取sessionId存储到cookie中

* 3. 页面alert登录成功信息

* 另外如果dologin函数请求失败,也就是登录失败,则alert错误信息

*/

请求获得secretKey如下:

上面dologin函数解密用到的decryptSecretKey函数分析如下:

// 对服务端返回的secretKey进行解密的decryptSecretKey函数

/**

* 对服务端返回的secretKey进行解密,目前通过与服务端交互实现

* @param secretKey

*/

function decryptSecretKey(tjid, sessionkey, secretKey, succcessfunc, failfunc) {

var crypted = encryptWith3DesAndEncode(sessionkey, secretKey);

$.ajax({

type: "get",

url: nctUrl + "/gen/unc",

data: {

tjid: tjid,

crypted: crypted

},

dataType: "json",

success: function(obj) {

succcessfunc(obj);

},

error: function(xhr, info) {

failfunc(info);

}

});

}

/*

* 该函数首先使用加密函数encryptWith3DesAndEncode,将sessionKey(此时sessionKey还未进行处理,值还是之前的服务端返回的ss的值)和secreteKey进行加密,加密后的结果赋值给crypted变量

* 之后向 /gen/unc 接口发送POST请求,请求参数是tjid(用户输入的用户名)和上面加密得到的crypted

* 如果请求成功,服务端会返回一个data参数,并进行后续操作,也就是上面dologin函数分析的decryptSecretKey函数解密成功后的操作

* 如果请求失败,则alert失败信息

*/

请求解密secretKey如下:

其他加解密函数如下:

/**

* 用3des加密,结果采用编码返回

* @param key

* @param plainText

* @returns {*}

*/

function encryptWith3DesAndEncode(key, plainText) {

if (key == null || key == '' || plainText == null || plainText == '') {

return '';

}

var ckey = produce3DesKey(key);

var result = CryptoJS.TripleDES.encrypt(plainText, CryptoJS.enc.Utf8.parse(ckey), {

mode: CryptoJS.mode.ECB,

padding: CryptoJS.pad.Pkcs7

});

return result.toString();

}

var secretSeed = "123456789012345678901234";

function produce3DesKey(key) {

if (key == undefined || key == null || key == '')

return secretSeed;

if (key.length >= 24) {

return key.substr(0, 24);

}

return key + secretSeed.substr(key.length);

}

/**

* 进行解码

* @param bs64

* @returns {*}

*/

function decode(bs64) {

var decoded = CryptoJS.enc.Utf8.stringify(CryptoJS.enc..parse(bs64));

return decoded;

}

/**

* 进行编码

* @param

* @returns {*}

*/

function encode(str) {

var encoded = CryptoJS.enc..stringify(CryptoJS.enc.Utf8.parse(str));

return encoded;

}

/**

* 进行3des解密,解密之前,会将密文进行解码

* @param key

* @param cryptedBs64

* @returns {*}

*/

function decodeAndDecryptWith3Des(key, cryptedBs64) {

if (key == null || key == '' || cryptedBs64 == null || cryptedBs64 == '') {

return '';

}

var ckey = produce3DesKey(key);

var result = CryptoJS.TripleDES.decrypt(

cryptedBs64, CryptoJS.enc.Utf8.parse(ckey), {

mode: CryptoJS.mode.ECB,

padding: CryptoJS.pad.Pkcs7

});

return result.toString(CryptoJS.enc.Utf8)

}

至此登录请求过程已经分析完成,流程如下:

用户输入账号和密码,之后浏览器携带用户输入的账号向服务端/gen/key接口发起请求,如果用户输入的账号存在,则服务器返回 ss 和 ps参数浏览器携带用户输入的账号、密码以及上面请求返回的ps向服务端 /login 接口发起请求,如果信息正确,服务端返回secretKey和info参数浏览器对ss和secretKey合并加密,获取到新内容crypted,浏览器携带用户输入的账号和crypted向服务端/gen/unc接口发起请求对secretKey进行解密,解密成功后返回data参数,之后对ss进行处理,处理后的值作为sessionKey存储到浏览器cookie中,同时浏览器对之前返回的info进行解密,解密后获取到sessionId,sessionId也存储到浏览器中 下面分析第二种请求,登录后发送的普通请求

登录进web系统后浏览器向服务端请求数据都是使用该请求,该请求本质是调用函数sendRequest,如下:

/**

* 发送请求到服务端

* @param url

* @param params

* @param successfunc

* @param failfunc

*/

export function sendRequest(url, params, successfunc, failfunc, $this) {

var crypted = "";

var pstr = JSON.stringify(params);

var sessionKey = $.cookie("sessionKey");

var sessionId = $.cookie("sessionId");

if (pstr != "{}") {

crypted = encode(encryptWith3DesAndEncode(produceSessionKey(sessionKey), pstr));

}

$.ajax({

url: url,

type: "post",

data: crypted,

dataType: "json",

contentType: "application/json",

xhrFields: {

withCredentials: true

},

crossDomain: true,

beforeSend: function(xhr) {

addHeaders(xhr);

//发送业务请求时,需要进行签名

var timestamp = parseInt(new Date().getTime() / 1000);

var random = randomString(8);

var sign = produceSign(sessionId, timestamp, random, crypted);

xhr.setRequestHeader("sessionid", sessionId);

xhr.setRequestHeader("timestamp", timestamp);

xhr.setRequestHeader("random", random);

xhr.setRequestHeader("sign", sign);

},

error: function(xhr, info) {

failfunc(info);

$this.$Message.error({

content: info,

duration: 3,

closable: true

})

},

success: function(obj) {

if (obj.code == 1) {

var data = obj.data;

if (data) {

var decoded = decode(data);

var plainText = decodeAndDecryptWith3Des(produceSessionKey(sessionKey), decoded);

var plainObj = JSON.parse(plainText);

successfunc(plainObj);

} else {

successfunc(obj);

}

} else {

failfunc(obj);

$this.$Message.error({

content: obj.msg,

duration: 3,

closable: true

})

}

}

});

}

/*

* 该请求主要分为两部分,请求前数据加密和请求后数据解密

* 请求前数据加密:

* 1. 从cookie中取出sessionKey,进行处理

* 2. 将处理后的seesionKey同要加密的数据一起通过encryptWith3DesAndEncode函数进行加密,加密后* 在进行一次编码,编码后的结果发送到服务器

* 请求后数据解密:

* 1. 从cookie中取出sessionKey,进行处理

* 2. 对服务端返回的结果进行解码,之后将处理后的sessionKey和解码后的数据一同通过调用

* decodeAndDecryptWith3Des函数进行解密

*/

// 对sessionKey进行处理的函数如下

function produceSessionKey(key) {

return CryptoJS.MD5(APP_KEY_SEED + key).toString().toUpperCase();

}

至此登录后发送的普通请求过程分析完成,通过对以上两种类型请求过程分析,并通过使用该js文件中提供的加解密函数写出请求的加解密脚本(脚本为js脚本,可通过node调用运行),如下:

获取SeesionKey脚本, 该脚本获取的sessionKey是放置到cookie中的sessionKey

const CryptoJS = require('crypto-js')

var secretSeed = "123456789012345678901234";

function decodeAndDecryptWith3Des(key, cryptedBs64) {

if (key == null || key == '' || cryptedBs64 == null || cryptedBs64 == '') {

return '';

}

var ckey = produce3DesKey(key);

var result = CryptoJS.TripleDES.decrypt(

cryptedBs64, CryptoJS.enc.Utf8.parse(ckey), {

mode: CryptoJS.mode.ECB,

padding: CryptoJS.pad.Pkcs7

});

return result.toString(CryptoJS.enc.Utf8)

}

function produce3DesKey(key) {

if (key == undefined || key == null || key == '')

return secretSeed;

if (key.length >= 24) {

return key.substr(0, 24);

}

return key + secretSeed.substr(key.length);

}

var ss = ""; // 请求/gen/key接口返回的ss

var data = ""; // 请求 /gen/unc 接口返回的data

console.log(decodeAndDecryptWith3Des(ss, data));

加密数据包脚本

const CryptoJS = require('crypto-js')

var secretSeed = "123456789012345678901234";

var APP_KEY_SEED = "terjoycht2014!@#";

/**

* 用3des加密,结果采用编码返回

* @param key

* @param plainText

* @returns {*}

*/

function encryptWith3DesAndEncode(key, plainText) {

if (key == null || key == '' || plainText == null || plainText == '') {

return '';

}

var ckey = produce3DesKey(key);

var result = CryptoJS.TripleDES.encrypt(plainText, CryptoJS.enc.Utf8.parse(ckey), {

mode: CryptoJS.mode.ECB,

padding: CryptoJS.pad.Pkcs7

});

return result.toString();

}

/**

* 进行编码

* @param

* @returns {*}

*/

function encode(str) {

var encoded = CryptoJS.enc..stringify(CryptoJS.enc.Utf8.parse(str));

return encoded;

}

function produce3DesKey(key) {

if (key == undefined || key == null || key == '')

return secretSeed;

if (key.length >= 24) {

return key.substr(0, 24);

}

return key + secretSeed.substr(key.length);

}

function produceSessionKey(key) {

return CryptoJS.MD5(APP_KEY_SEED + key).toString().toUpperCase();

}

var sessionKey = ''; // 浏览器cookie中的sessionKey

pstr = ''; // 浏览器发送的需要加密的数据

crypted = encode(encryptWith3DesAndEncode(produceSessionKey(sessionKey), pstr));

console.log(crypted)

解密数据包脚本

const CryptoJS = require('crypto-js')

var APP_KEY_SEED = "terjoycht2014!@#";

var secretSeed = "123456789012345678901234";

function decodeAndDecryptWith3Des(key, cryptedBs64) {

if (key == null || key == '' || cryptedBs64 == null || cryptedBs64 == '') {

return '';

}

var ckey = produce3DesKey(key);

var result = CryptoJS.TripleDES.decrypt(

cryptedBs64, CryptoJS.enc.Utf8.parse(ckey), {

mode: CryptoJS.mode.ECB,

padding: CryptoJS.pad.Pkcs7

});

return result.toString(CryptoJS.enc.Utf8)

}

/**

* 进行解码

* @param bs64

* @returns {*}

*/

function decode(bs64) {

var decoded = CryptoJS.enc.Utf8.stringify(CryptoJS.enc..parse(bs64));

return decoded;

}

function produceSessionKey(key) {

return CryptoJS.MD5(APP_KEY_SEED + key).toString().toUpperCase();

}

function produce3DesKey(key) {

if (key == undefined || key == null || key == '')

return secretSeed;

if (key.length >= 24) {

return key.substr(0, 24);

}

return key + secretSeed.substr(key.length);

}

sessionKey = ""; // 存储在浏览器Cookie中的sessionKey

let data = ""; // 要解密的数据,可以是请求包,也可以是服务端返回包的数据

let crypted = decodeAndDecryptWith3Des(produceSessionKey(sessionKey), decode(data));

console.log(crypted);

脚本使用对数据进行加解密如下: 获取sessionKey

数据解密

 

 

数据加密(对上面解密出的请求包进行加密,确认加密效果一致)

至此已完成数据包的加解密,当准备进行渗透测试时,发现只要修改数据包,服务端就会返回401错误

然后问了主管,发现请求包中存在签名sign,还需要修改数据包签名,于是查看js中的签名函数,编写生成签名脚本

签名函数如下

/***

* 根据给出的数据产生签名

* @param sessionid

* @param timestamp

* @param random

* @param crypted 加密之后的请求参数

* @returns {string}

*/

function produceSign(sessionid, timestamp, random, crypted) {

var arr = new Array();

arr.push("sessionid=" + sessionid);

arr.push("apptype=" + APP_TYPE);

arr.push("iversion=" + APP_IVERSION);

arr.push("versioncode=" + APP_VERSION);

if (crypted != "")

arr.push("data=" + crypted);

arr.push("timestamp=" + timestamp);

arr.push("random=" + random);

arr.push("secretkey=" + produceSessionKey($.cookie("sessionKey")));

arr.sort();

var signString = "[" + arr.join() + "]";

return CryptoJS.MD5(signString).toString().toUpperCase();

}

签名脚本如下

const CryptoJS = require('crypto-js')

var APP_KEY_SEED = "terjoycht2014!@#";

var APP_TYPE = "20";

var APP_VERSION = "388";

var APP_IVERSION = "2";

/***

* 根据给出的数据产生签名

* @param sessionid

* @param timestamp

* @param random

* @param crypted 加密之后的请求参数

* @returns {string}

*/

function produceSign(sessionid, timestamp, random, crypted) {

var arr = new Array();

arr.push("sessionid=" + sessionid);

arr.push("apptype=" + APP_TYPE);

arr.push("iversion=" + APP_IVERSION);

arr.push("versioncode=" + APP_VERSION);

if (crypted != "")

arr.push("data=" + crypted);

arr.push("timestamp=" + timestamp);

arr.push("random=" + random);

arr.push("secretkey=" + produceSessionKey(sessionKey));

arr.sort();

var signString = "[" + arr.join() + "]";

return CryptoJS.MD5(signString).toString().toUpperCase();

}

function produceSessionKey(key) {

return CryptoJS.MD5(APP_KEY_SEED + key).toString().toUpperCase();

}

var sessionId = ""; // 请求头中的sessionId

var sessionKey = ""; // 浏览器Cookie中的sessionKey

var timestamp = ""; // 请求包中的 timestamp

var random = ""; // 请求包中random

var crypted = ""; // 加密后的请求数据

let sign = produceSign(sessionId,

timestamp,

random,

crypted)

console.log(sign)

但是用编写的签名脚本生成签名后,服务端依旧返回401

感觉哪里不对,于是对最初的请求进行计算签名,查看是否和请求中的签名一致

生成的签名为B1576578C9D96654E7FBF961907DD6B6, 原始请求签名为B467D7093028A1B4E9BB97492B76EF9F,两者并不一致,猜测该网站开发人员修改了签名中的部分配置参数,为了找寻修改后的配置参数,于是进行chrome debug

0x04 chrome debug 进行签名破解

首先进入后台,之后打开chrome开发人员工具,在Sources菜单处,找到页面js源代码位置,因为该网站对源代码进行了打包,因此js文件分为app , mainfest 和 vendor这三类,只用查看app的js文件即可,app的js文件为该网站开发人员的开发代码打包后的文件,另外需要点击pretty-print,方便调试

之后找到一处可控的查询位置处,点击查询

抓包,获取请求接口

在js源代码中搜索该接口,因查询到该接口有4处,于是在该4处全打上断点

打上断点后,再一次点击查询,chrome debug开始

调试跟踪找到签名函数调用位置后,在签名函数的调用位置打上断点,然后点击Resume script excution

 

取消之前接口处的4个断点,仅留该签名函数处的断点,重新点击查询,chrome debug开始

点击箭头处,深入到函数执行内部

两个箭头搭配使用

寻找到全局参数的不同点,即开发人员将APP_TYPE 这个参数从之前的js文件中的20修改为了42

之后修改签名脚本中的该参数,调用签名脚本获取签名,并和之前请求包中的签名进行比对,结果一致,成功获取签名,改包之后也可生成新的签名,成功发包

修改后签名脚本如下:

const CryptoJS = require('crypto-js')

var APP_KEY_SEED = "terjoycht2014!@#";

var APP_TYPE = "42";

var APP_VERSION = "388";

var APP_IVERSION = "2";

/***

* 根据给出的数据产生签名

* @param sessionid

* @param timestamp

* @param random

* @param crypted 加密之后的请求参数

* @returns {string}

*/

function produceSign(sessionid, timestamp, random, crypted) {

var arr = new Array();

arr.push("sessionid=" + sessionid);

arr.push("apptype=" + APP_TYPE);

arr.push("iversion=" + APP_IVERSION);

arr.push("versioncode=" + APP_VERSION);

if (crypted != "")

arr.push("data=" + crypted);

arr.push("timestamp=" + timestamp);

arr.push("random=" + random);

arr.push("secretkey=" + produceSessionKey(sessionKey));

arr.sort();

var signString = "[" + arr.join() + "]";

return CryptoJS.MD5(signString).toString().toUpperCase();

}

function produceSessionKey(key) {

return CryptoJS.MD5(APP_KEY_SEED + key).toString().toUpperCase();

}

var sessionId = ""; // 请求头中的sessionId

var sessionKey = ""; // 浏览器Cookie中的sessionKey

var timestamp = ""; // 请求包中的 timestamp

var random = ""; // 请求包中random

var crypted = ""; // 加密后的请求数据

let sign = produceSign(sessionId,

timestamp,

random,

crypted)

console.log(sign)

0x05 后记

这里主要记录写这篇文章复盘时的一些新发现

因为sessionKey是存储在cookie中的,因此不用特意写脚本获取,可以直接在浏览器的cookie中找到sessionKey

在复盘的时候抓包,突然发现apptype这个值在某些请求包中自带了,不需要特意通过chrome debug获取

近期渗透测试基础课程有公开课,跟着大佬学挖漏洞!可留言发课程哦!

*本文章仅供技术交流分享,请勿做未授权违法攻击,雨笋教育不负任何责任。具体请参考《网络安全法》。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有