目标:得到”Encrypt”的算法
逆向的步骤
使用查壳工具发现没有壳,抓包发现有一个加密字段:
"Encrypt": "NIszaqFPos1vd0pFqKlB42Np5itPxaNH\/\/FDsRnlBfgL4lcVxjXii\/UNcdXYMk0EQMz15fWd0AZ3\n9d4qc0ZgtMCbKF+l+FsXvqFFi4HhQ8Z56cxaZpXIUEhJ5R9IBRCtI97HMUH8l2PmJT4N+mA\/k2lT\nUTN+8LjHbtYGerEukUyVhjQHzeMCIEFbLVavcACyOG7MZVeYVISwA3hNO+DU7\/YXdymd3vpf\n"
|
FridaHook辅助算法分析
找到一些疑似关键函数,可以通过hook来确认app执行某个操作的时候,是否调用了它们
如果没有触发这些函数,可以考虑以下问题
- app在执行这个操作的时候,真的没有调用这个函数,换一个思路找关键函数
- 代码写错了,导致hook函数没执行
- 一般可以通过主动调用上层函数,来触发这些hook函数
如果触发了这些函数,可以通过hook来打印执行过程中传入函数的参数和返回值
利用以后得到的信息,来进行算法复现
完整分析步骤
抓包

发现有一个Encrypt字段
{ "Encrypt": "NIszaqFPos1vd0pFqKlB42Np5itPxaNH\/\/FDsRnlBfgL4lcVxjXii\/UNcdXYMk0EbKdY0NLSwwFL\nLJfVtM8N6zAdGdzs2bHOVQO\/4ArQGwKPMxbQ\/tOgKvxvHpbTLSTjEt7U0l9Ejuc3Yt1kW3s3tjk8\nn40vRguUqAZ1ygd+5+uuFtuqJfUg0hD6vG4IGR8w6\/ZVWkq2yZSM3KcKxIQurlc98XMQ+ete\n" }
|
\代表着转义字符
下面我们便来对生成Encrypt的算法进行复现
Hook分析Encrypt算法
首先直接人肉搜索以下Encrypt字段

搜索到了两个与包名相关的方法,一般与包名相关的我们都需要重点关注,查看这两个函数

hook这两个函数,看看app运行时走了哪一个
hook脚本:
Java.perform(function () { var JsonRequest = Java.use("com.dodonew.online.http.JsonRequest"); JsonRequest.addRequestMap.overload('java.util.Map', 'int').implementation = function (a, b){ console.log("addRequestMap is called"); this.addRequestMap(a, b); } JsonRequest.paraMap.implementation = function(a){ console.log("paraMap is called"); this.paraMap(a); }
});
|

跑一下脚本发现走了addRequest函数
那么接下来我们自然要进一步分析这个函数
分析addRequest函数
可以看到
String encrypt = RequestUtil.encodeDesMap(code, this.desKey, this.desIV);
|
说明encrypt是由RequestUtil类的encodeDesMap产生的
hook以下这个函数看看它的参数与结果
Java.perform(function () { var JsonRequest = Java.use("com.dodonew.online.http.JsonRequest"); JsonRequest.addRequestMap.overload('java.util.Map', 'int').implementation = function (a, b){ console.log("addRequestMap is called"); this.addRequestMap(a, b); console.log("============================================================================================"); } var RequestUtil = Java.use("com.dodonew.online.http.RequestUtil"); RequestUtil.encodeDesMap.overload('java.lang.String', 'java.lang.String', 'java.lang.String').implementation = function(a, b, c){ console.log("encodeDesMap is called"); console.log("data: ", a); console.log("desKey: ", b); console.log("desIV: ", c); let result = this.encodeDesMap(a, b, c); console.log("result: ", result); console.log("============================================================================================"); return result; } });
|

可以看到这个函数的返回值就是我们的encrypt
要进行算法复现发现还有一个sign是未知的值,看到它是在paraMap函数中经过md5加密产生的

自然要hook这个函数一探究竟
Java.perform(function () { var JsonRequest = Java.use("com.dodonew.online.http.JsonRequest"); JsonRequest.addRequestMap.overload('java.util.Map', 'int').implementation = function (a, b){ console.log("addRequestMap is called"); this.addRequestMap(a, b); console.log("============================================================================================"); } var RequestUtil = Java.use("com.dodonew.online.http.RequestUtil"); RequestUtil.encodeDesMap.overload('java.lang.String', 'java.lang.String', 'java.lang.String').implementation = function(a, b, c){ console.log("encodeDesMap is called"); console.log("data: ", a); console.log("desKey: ", b); console.log("desIV: ", c); let result = this.encodeDesMap(a, b, c); console.log("result: ", result); console.log("============================================================================================"); return result; } var Utils = Java.use("com.dodonew.online.util.Utils"); Utils.md5.implementation = function (a){ console.log("md5 is called"); console.log("string: ", a); let result = this.md5(a); console.log("result: ", result); console.log("============================================================================================"); return result; } });
|

可以看到我们的sign值得产生方法,就是将传入得参数经过md5加密得到的
接下来我们仔细看看encodeMap的加密函数


可以看到在这个des中将我们传入的密钥进行了一个md5加密,至此已经分析明了
算法总结
encrypt字段的生成方法就是,先将用户输入的账号密码,时间戳以及一些固定值经过md5加密生成sign值;然后再将生成的sign与用户输入的账号密码,时间戳以及一些固定的值经过des加密得到最终答案,在des加密中又将我们传入的密钥进行了一次md5加密作为des的密钥。
算法复现
使用CryptoJS库
var CryptoJS = module.exports; function getSign(user, pass, time){ var signStr = "equtype=ANDROID&loginImei=Androidnull&timeStamp=" + time + "&userPwd=" + pass +"&username="+ user + "&key=sdlkjsdljf0j2fsjk"; return CryptoJS.MD5(signStr).toString().toUpperCase(); } function encrypt(user, pass){ var time = new Date().getTime(); var sign = getSign(user, pass, time); var plainText = '{"equtype":"ANDROID","loginImei":"Androidnull","sign":"' + sign'","timeStamp":"' + time + '","userPwd":"'+ pass +'","username":"'+ time +'"}'; var key = CryptoJS.enc.Hex.parse(CryptoJS.MD5("65102933").toString()); var iv = CryptoJS.enc.UTF8.parse("32028092"); return CryptoJS.DES.encrypt(plainText, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }).toString(); }
|
协议复现(java)
HttpHelper.java
package org.example;
import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.Proxy; import java.net.URL;
public class HttpHelper {
public static String HttpURLConnection(String method, String url, String outputStr) { try { URL u = new URL(url); HttpURLConnection conn = (HttpURLConnection) u.openConnection(Proxy.NO_PROXY); conn.setRequestMethod("GET"); conn.setDoInput(true); conn.setUseCaches(false); conn.setConnectTimeout(30000);
if(method.equals("POST")){ conn.setRequestMethod("POST"); conn.setDoOutput(true); conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); } conn.setRequestProperty("User-Agent", "Dalvik/2.1.0 (Linux; U; Android 10; Pixel XL Build/QP1A.191005.007.A3)");
if (null != outputStr) { OutputStream outputStream = conn.getOutputStream(); outputStream.write(outputStr.getBytes("UTF-8")); outputStream.close(); }
conn.connect();
InputStream inputStream = conn.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8"); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String str = null; StringBuffer buffer = new StringBuffer(); while ((str = bufferedReader.readLine()) != null) { buffer.append(str); } bufferedReader.close(); inputStreamReader.close(); inputStream.close(); conn.disconnect(); return buffer.toString(); } catch (Exception e) { e.printStackTrace(); } return null; } }
|
dodonew.java
package org.example;
import javax.script.Invocable; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import java.io.BufferedReader; import java.io.File; import java.io.FileReader;
public class dodonew { public static void main(String[] args) throws Exception {
ScriptEngine engine = new ScriptEngineManager().getEngineByName("javascript"); engine.eval(readJSFile()); Invocable inv = (Invocable) engine; String res = (String) inv.invokeFunction("encrypt", "13905020820", "a12345678"); System.out.println("res:" + res);
String str = HttpHelper.HttpURLConnection( "POST", "http://api.dodovip.com/api/user/login", "{\"Encrypt\":\"" + res + "\"}"); System.out.println(str); String plainText = (String) inv.invokeFunction("decrypt", str); System.out.println(plainText);
}
private static String readJSFile() throws Exception { StringBuffer script = new StringBuffer(); String projectPath = System.getProperty("user.dir"); File file = new File(projectPath + "\\JSDir\\demo.js"); FileReader filereader = new FileReader(file); BufferedReader bufferreader = new BufferedReader(filereader); String tempString = null; while ((tempString = bufferreader.readLine()) != null) { script.append(tempString).append("\n"); } bufferreader.close(); filereader.close(); return script.toString(); } }
|