协议分析实战

VSole2022-08-18 16:56:24

协议分析是逆向技术中的一个重要技能,本篇文章先分享3个app。

第一个app

找到登录界面,我输入的手机号是13905376666,密码是:666666666。

先抓包:

这个是请求包:

http://m.xxxx.com.cn/v2/member?modules=cloudlogin%3A1&password=666666666&siteid=10001&sign=d01f5e4445af0a30d148d4dc451b41cf&clientid=1&system_name=android&type=android&time=1659366738640&ip=10.0.2.15&device_id=2a%3Aa5%3A33%3A2d%3Ade%3Ac9&account=13905376666 HTTP/1.1

第一步,也是最重要的一步,得先定位到关键代码的位置:

(这里插一句,分析协议字段的时候一般都是最后分析sign

字段,这个sign字段经过我这几天的学习总结出来的一般是这样生成的:

先将其他字段加密之后,然后有的还可能带一些盐,最后将各个字段排序之后,然后加密,MD5一般是)

回到分析过程中,这里发现,只有sign字段看着像加密的,其他的基本都是明文,然后找到两个方法,分别下断点,然后看看他走哪个就完事了。

这里我打算搜索post请求中的v2/member,和"system_name"。

最终定位到这个两个方法中比较像:

然后动态调试的时候我怕段不下来,就遇见sign都下了断点(这也是一种方法吧哈哈,但是他会在一些奇奇怪怪的方法中段下来,但是只要不是你想要的断点处,直接让程序继续运行就好了),然后发现我的判断没有错:(当点击登录按钮的时候程序从这里断了下来)

这个协议分析我也是刚刚开始自己动手,然后就打算把步骤写的详细一点:

首先看这个函数的两个参数:

第一个参数表示一个集合,在这个集合中保存发往服务器的数据包中的各个参数,然后第二个参数的翻译是上下文,所以这个参数应该就是连接上下函数的一个变量。

然后看这个device_id就是表示手机的串号:

是这样声明的:

就是返回手机的型号,没有什么好说的。

然后解释完第一个,其他的就不用过多解释啦:

这个clientid就是定值1:ClientID(客户端标识号)用于标识连接到API服务的客户端。

然后下一个就是本机IP地址,还有时间timestamp,siteid站点标识符定值10001,系统名称system_name,型号type是Android的。

然后重点就是这个sign了,这个是个签名校验,这个校验对于我们来说就是我们自己分析算出来的sign值和发往服务器的sign值一样,就说明分析正确了,一般的校验都是看这个发往服务器的sign值和服务器那边计算出来的sign值是否一样:

所以接下来就是要分析这个函数了 m8098a(params.getURLHashMap(), timestamp + ""));

他是这样声明的:

然后就要分析这个关键函数了:

  public static String m8098a(HashMap<String, String> paramsMap, String time) {//传入两个参数,一个是map集合,一个是刚刚获取的time        LinkedHashMap<String, String> sortParams = new LinkedHashMap<>();//声名一个新的集合        Object[] key_arr = paramsMap.keySet().toArray();//将map中的属性取出来,存放在key_arr数组里面        Arrays.sort(key_arr);//进行排序        for (Object key : key_arr) {            try {                sortParams.put(key.toString(), URLEncoder.encode(paramsMap.get(key).toString(), "UTF-8"));            } catch (UnsupportedEncodingException e) {                e.printStackTrace();            }        }//将刚刚key_arr数组里面的值转化成string类型,然后将map中的数据进行url编码,然后将这两个数组组合成一个键值对放在新声名的sortParams中        StringBuilder result = new StringBuilder();        for (Map.Entry<String, String> entry : sortParams.entrySet()) {//把刚刚处理完的sortParams中的值利用迭代器取出来放在entry变量中            if (result.length() > 0) {                result.append("&");            }//在每一个键值对后面用 & 连接            result.append(entry.getKey());            result.append("=");            result.append(entry.getValue());//将键和值之间用 = 连接        }//这样就像发往服务器的包的结构了        String replace = result.toString().replace("*", "%2A").replace("%7E", "~").replace("+", "%20");//将处理完的结果result在to string之后进行字符替换为replace        String resultMD5 = MD5.md5(replace);//将replace进行md5加密之后为resultMD5        String str = resultMD5 + "1fa50ba25ed527f3fd1eb9467686f2bb" + time;//进行字符串拼接之后(加盐)转换为str        String md5Result = MD5.md5(str);//将str在进行md5加密之后作为函数的返回值md5Result        return md5Result;//返回的md5Result即为sign值    }

这样的话第一个app的协议字段到此就分析完了。

第二个app:

我输入的用户名是kanxue,密码是kanxue123。

data值明文传输:

POST http://xxxx.xx8xx88xx.com/v2_2/user/login HTTP/1.1nonce=b104cc74ade1441a9c61759fe330883c&codeSign=2086B76A137CBA8B84DD1CBCAC3F7B45&timestamp=1659843481869&data=%7B%22params%22%3A%7B%22username%22%3A%22kanxue%22%2C%22password%22%3A%22kanxue123%22%7D%7D&version=2.2.1&product_version=220&platform=HD1910&network=1&device=864282012982996&access_token=62cb31ab6f2ffdaef382236aba9b98f4&screen_width=1280&screen_height=720&bbsnopic=0&system=2&system_version=19&theme=4

根据上一个app的分析,这回需要重点分析的应该是codeSign,nonce,access_token(这个介绍一下吧):

accesstoken一个访问令牌包含了此登陆会话的安全信息。登录一次,服务器生成一个token返回给你,你只需每次请求附带这个token就能对网站标识自己的身份。换句话说,就是一个电子版的令牌。(这个字段可以通过解码,变成人可以看的字段)

然后剩下的字段看着直接random就好了。

然后就该定位了,还是把看着像的地方都下上断点,然后看看从哪里段下来就好了:(这个挺好的,就三处断点,上一个app我下了9个,不要嫌麻烦,静下心来就好):

然后发现程序断在了这里:

这里我插一句,之前我说过JEB谁用谁说好...今天出bug了哈哈,这个JEB他有的时候不显示寄存器的值:

然后用readvar v0 string还报错,这就得上Android stdio。

定位到这个关键函数:

直接上代码分析过程了:

   public void mo17350a(String str, JSONObject jSONObject, AbstractC5417d<T> dVar) {        String replaceAll = UUID.randomUUID().toString().replaceAll("-", "");//这里就是随机一个UUID,然后赋值给nonce字段        long currentTimeMillis = System.currentTimeMillis();//获取系统时间,在计算codesign字段时会用到的·1        JSONObject a = mo17349a(jSONObject);//从函数的传入的参数中取出的JSON_object中应该就是那个data字段的值        if (MyApplication.getInstance().isLogin()) {//如果点击登录按钮,就走下面这个分支,就是进行键值对的拼接            C5445g.m20674a(str, new C5445g.C5461f[]{new C5445g.C5461f(ReportActivity.USER_ID, "" + MyApplication.getInstance().getUserDataEntity().getUid()), new C5445g.C5461f("login_token", "" + MyApplication.getInstance().getUserDataEntity().getLogin_token()), new C5445g.C5461f("nonce", replaceAll), new C5445g.C5461f("codeSign", C6485v.m23485a(replaceAll, a, MyApplication.getInstance().getUserDataEntity().getUid() + "", currentTimeMillis)), new C5445g.C5461f("timestamp", currentTimeMillis + ""), new C5445g.C5461f("data", a.toString()), new C5445g.C5461f("version", C5414a.f16227f + ""), new C5445g.C5461f("product_version", "220"), new C5445g.C5461f(C1380c.PLATFORM, Build.PRODUCT + ""), new C5445g.C5461f(CandidatePacketExtension.NETWORK_ATTR_NAME, MyApplication.getNetworkType() + ""), new C5445g.C5461f("device", "" + MyApplication.getDeviceId()), new C5445g.C5461f("access_token", "" + C5414a.f16229h), new C5445g.C5461f("screen_width", "" + C5414a.f16230i), new C5445g.C5461f("screen_height", "" + C5414a.f16231j), new C5445g.C5461f("bbsnopic", MyApplication.isForumNoIMG() + ""), new C5445g.C5461f(C7748d.C7755c.f24519a, MessageService.MSG_DB_NOTIFY_CLICK), new C5445g.C5461f("system_version", Build.VERSION.SDK_INT + ""), new C5445g.C5461f("theme", C5414a.f16223b + "")}, (ResultCallback) dVar);            return;        }        C5445g.m20674a(str, new C5445g.C5461f[]{new C5445g.C5461f("nonce", replaceAll), new C5445g.C5461f("codeSign", C6485v.m23484a(replaceAll, a, currentTimeMillis)), new C5445g.C5461f("timestamp", currentTimeMillis + ""), new C5445g.C5461f("data", a.toString()), new C5445g.C5461f("version", C5414a.f16227f + ""), new C5445g.C5461f("product_version", "220"), new C5445g.C5461f(C1380c.PLATFORM, Build.PRODUCT + ""), new C5445g.C5461f(CandidatePacketExtension.NETWORK_ATTR_NAME, MyApplication.getNetworkType() + ""), new C5445g.C5461f("device", "" + MyApplication.getDeviceId()), new C5445g.C5461f("access_token", "" + C5414a.f16229h), new C5445g.C5461f("screen_width", "" + C5414a.f16230i), new C5445g.C5461f("screen_height", "" + C5414a.f16231j), new C5445g.C5461f("bbsnopic", MyApplication.isForumNoIMG() + ""), new C5445g.C5461f(C7748d.C7755c.f24519a, MessageService.MSG_DB_NOTIFY_CLICK), new C5445g.C5461f("system_version", Build.VERSION.SDK_INT + ""), new C5445g.C5461f("theme", C5414a.f16223b + "")}, (ResultCallback) dVar);    }

接下来这个函数new C5445g.C5461f()就是键值对的拼接了:

然后就要一个个看看各个字段对应的值是怎么生成的了。

nonce:先生成一个随机数,然后进行字符的替换。

然后这几个就是明文,没有加密过程:

其他的几个也是直接明文传输,就不一一展示了。

然后来看看这个加密的字段access_token:

然后就要分析这个东西了 C5414a.f16229h 跳过去看看:

接着跟进去分析,终于找到了定义他的地方了:

static {        String str = f16227f + Build.PRODUCT + MyApplication.getNetworkType() + MyApplication.getDeviceId();        crack.log(str);        f16229h = C6448r.m23455a(str);    }

这个字段f16227f是获取versionname:

后面的几个是deviceid,还有网络信息:

最后再看看这个函数C6448r.m23455a()干了什么:

就是个md5加密就完了。

最后重点看这个codesign字段:

第一个参数relaceall是随机生成的数字,然后进行了字符串的替换。

第二个参数a是之前传入的

jsonobject中的data中的数据,就是明文的用户名和密码。

第三个参数是当前的时间。

然后就要跟进分析这个函数了:

这个函数的三个参数都分析完了,只有这个参数我们还不知道是什么。m23483a():

public static final int forum_key = 2131230990;

然后就要看这个函数了C6420af.m23337b()。

这个是获取资源文件的函数。

所以要想得到这个字段的值就要从资源文件中看看了:

<string name="forum_key">94ac5cfb69e87bd7</string>

这样所有字段的值都得到了,最后直接看看这个函数就好了。

C6448r.m23455a()还是刚刚那个md5加密的函数:

这样这款app就分析完了。

分析完上面两款app大家也可以发现,这些工具没有谁最好,只是都得使用,各有个的好处,上次说那个JEB yyds就打脸了哈哈,这个jadx也挺好的,一些代码直接翻译成人能看懂的了。

第三个app

这款app我用的真机,模拟器出了点问题,先登录:

然后抓包,登录包中只有这两个数据:

这里如果只在key上面下断点是段不下来程序的(分析到后面就知道了,这个传输的字段叫key_id),所以这里我打算从post字段上下手。

POST http://passport.xxxx.com.cn/login_jsonp_active.do HTTP/1.1Content-Type: application/x-www-form-urlencoded; charset=utf-8Content-Length: 302Host: passport.xxxx.com.cnConnection: Keep-AliveAccept-Encoding: gzipCookie: JSESSIONID=aaapcYHe7KtGb7NDScYhyUser-Agent: okhttp/3.1.2 key=dTdlMmtGQjZIQk9CNmdudi95QURUbUNrZ2xKWFRNc0t0Z3g4NnpKRkZDYjRGc25RU05CL0wzSjQ2ZFYrMmxqd1ZaU2JtTVJvaURudWJhVnpFZGRsRmZGQldGQzBxbE0xVFVNVER5TDRpNkFpc1E4eVJVK0VnZWxBUUdaR0lvZ0Y5NTdFKzJKRVFtNlR0SDN5SGtCY1FOZnFIYnpNdmZqa3FPVnc2SkNGUzJ1SWUrb2xBby9wbGUrSUh1bU1wK2pTbW1XYkhzajNsYmM9P2tleUlkPTE

这里我搜索的是login_jsonp_active这个字段,然后定位到这里:

然后看那个API_LOGIN的交叉引用定位到了这里:

然后向下找找看看有没有突破点:这个函数中有解密字段,还有用户名和结果,看着比较像:

然后继续看交叉引用,这里有解密的标志,所以这里看着比较像:

最终定位到了这里,发现了是key_id字段,不是key字段.....难怪下这么多断点都找不到它:

然后还发现了这个:发现他是des加密:

sb.append(DESedeCoder.encode(json, KEYS.get("1"))).append("?keyId=").append("1");

然后就要分析这个函数是怎么加密key_id字段了:

public String encrypt(Map params) {

这个传入的参数中的map数据,在fiddler中得数据包中可以发现。

然后通过搜索sign字段也可以定位到在app中的位置:

public static Map<String, String> m264a(Map<String, String> map) {      map.put("t", String.valueOf(System.currentTimeMillis()));      StringBuilder sb = new StringBuilder(128);      sb.append(m265b(map.get("appkey"))).append("&").append(m265b(map.get("domain"))).append("&").append(m265b(map.get("appName"))).append("&").append(m265b(map.get(SdkConstants.APP_VERSION))).append("&").append(m265b(map.get("bssid"))).append("&").append(m265b(map.get("channel"))).append("&").append(m265b(map.get(LeService.KEY_DEVICE_ID))).append("&").append(m265b(map.get("lat"))).append("&").append(m265b(map.get("lng"))).append("&").append(m265b(map.get("machine"))).append("&").append(m265b(map.get("netType"))).append("&").append(m265b(map.get("lng"))).append("&").append(m265b(map.get("platform"))).append("&").append(m265b(map.get("platformVersion"))).append("&").append(m265b(map.get("preIp"))).append("&").append(m265b(map.get("sid"))).append("&").append(m265b(map.get("t"))).append("&").append(m265b(map.get("v")));      map.put("sign", m263a(sb.toString()));      return map;  }

分析完参数是啥,就要看看函数的具体操作了:

先声名一个stringbuffer变量,在将map中的数据转化为string类型。

然后就是对key字段进行的加密了:那个 KEYS.get("1")在这个可以找到了值:应该是密钥。

然后就要分析这个函数了:DESedeCoder.encode(json, KEYS.get("1"))

第一个参数是map中的数据,第二个参数是密钥。

先将传入的字符串转化为byte类型,然后传入encrypt函数:继续跟进分析:

这款app的加密字段就分析完了。总结一下:这个协议分析和找flag基本上是一样的,就是定位到关键字段和函数,然后分析加密过程,这次就先分享三个java层的吧,下次再分析so的。


stringsign
本作品采用《CC 协议》,转载必须注明作者和本文链接
得益于Unicorn的强大的指令trace能力,可以很容易实现对cpu执行的每一条汇编指令的跟踪,进而对ollvm保护的函数进行剪枝,去掉虚假块,大大提高逆向分析效率。
介绍实战中由于各种情况,可能会对反序列化Payload的长度有所限制,因此研究反序列化Payload缩小技术是有意义且必要的本文以CommonsBeanutils1链为示例,
本篇主要思路及方法来自于bit4woo和CC11001100两位巨佬的成果。学习链接https://github.com/bit4woo/burp-api-drops
JSP Webshell的检测工具
2021-12-13 12:04:53
在11月初,我做了一些JSP Webshell的免杀研究,主要参考了三梦师傅开源的代码。然后加入了一些代码混淆手段,编写了一个免杀马生成器JSPHorse,没想到在Github上已收获500+的Star
它们基于JSON格式,并包含一个令牌签名以确保令牌的完整性。本文主要讨论使用JSON Web令牌的安全隐患,以及攻击者如何利用它们绕过访问控制。有效载荷和签名。
frida复现某app算法
2023-01-11 10:44:51
获取数据某app的登录界面抓包,发现参数都加密了查验一下壳,未加壳,可以进行反编译使用jadx进行反编译,尝试搜索一下参数名”Encrypt”,发现两个JsonRequest类的方法存在运用frida及其下面的代码在java层进行hook,发现只调用了addRequestMap方法Java.perform(function (){. 跟进addRequestMap方法,了解其使用des加密,hook该方法可获取明文及加密结果hook代码Java.perform(function () {
首先使用jadx对apk进行逆向。?搜索关键字 QDSign,可以直接找到对应的类,可以看到参数经过加密得到。??进一步跟踪,发现了c类中有如下三个so方法,还有3个loadlibrary,分别进行了hook,发现c-lib动态注册了sign,sos动态注册了s,没有发现crypto有动态注册。
买车报价APP sign分析
2023-01-12 11:14:54
数字企业壳懂得都懂不可能全部脱下来的只能用哪些类就脱哪些了,略微麻烦点。想着通过url的地址去找对应的发请求的位置是不是可以,于是通过搜索url路径找到了一个类。这是具体的加密方法,没啥难度了。
在火线zone看到利用app历史版本来进行app的逆向分析,可以降低一下分析难度,比较老版本的安全防护做的不是很到位。正好最近有一个app,尝试利用一下老版本进行分析登录签名,没想到效果非常好。
FastJson结合二次反序列化绕过黑名单
VSole
网络安全专家