-
-
[原创]某黑盒某请求参数分析
-
2022-10-28 10:35 19759
-
前言
业余时间写的,纯兴趣更新! 做个记录。
测试环境
雷电模拟器 Android 7.1.2/pixexl2 Android 8.1
抓包测试
首先在apk中进行登录账户的时候进行抓包,获取登录凭证。
请求报文如下
1 2 3 4 5 6 7 | GET / store / has_unfinished_game / ?heybox_id = ???&imei = ???&os_type = Android&os_version = 7.1 . 2 &version = 1.1 . 50 &_time = 1666165262 &hkey = 5656c95245635812219460113c5a14eb HTTP / 1.1 Referer: http: / / api.maxjia.com / User - Agent: Mozilla / 5.0 AppleWebKit / 537.36 (KHTML, like Gecko) Chrome / 41.0 . 2272.118 Safari / 537.36 ApiMaxJia / 1.0 Cookie: pkey = ??? Host: api.xiaoheihe.cn Connection: Keep - Alive Accept - Encoding: gzip |
在get请求中发现有传递以下参数
1 2 3 4 5 6 7 | heybox_id 固定且唯一(对于每一个用户来说) imei 移动通信国际识别码(每个移动设备具有唯一性) os_type 系统类型 os_version 系统版本 version apk的版本 _time 时间 hkey 时间key |
以及cookie中的pkey
1 | Cookie: pkey = ??? 固定且唯一(对于每一个用户登录后) |
以上参数我们需要知道hkey是怎么来的
因为是请求报文,所以算法肯定在apk内部实现的。要么在应用层要么在so层
定位算法(v1.1.50)
将apk拖入到GDA中进字符串搜索"hkey"
发现在intercept方法中有存在这个字符串。双击进入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | package com. max .xiaoheihe.network.b$ 1 ; import okhttp3.w; import com. max .xiaoheihe.network.b; import java.lang. Object ; import okhttp3.w$a; import okhttp3.ad; import okhttp3.ab; import com. max .xiaoheihe.app.HeyBoxApplication; import com. max .xiaoheihe.bean.account.User; import okhttp3.v; import okhttp3.v$a; import java.lang.StringBuilder; import java.lang.System; import java.lang.String; import com. max .xiaoheihe.b.d; import com. max .xiaoheihe.b.af; import com. max .xiaoheihe.b.c; import com. max .xiaoheihe.bean.account.AccountDetailObj; import android.os.Build$VERSION; import okhttp3.ab$a; class b$ 1 implements w / / class @ 000e5c { final b a; void b$ 1 (b p0){ this.a = p0; super (); } public ad intercept(w$a p0){ ab uoab = p0.request(); User user = HeyBoxApplication.b(); v$a uoa = uoab.a().v(); StringBuilder str = (System.currentTimeMillis() / 1000 ) + ""; / / 获取当前时间 / 1000 String str1 = d.h(((d.h( "xiaoheihe/_time=" + str )).replaceAll( "a" , "app" )).replaceAll( "0" , "app" )); / / 这里是关键代码 if (c.b(af.b(uoab.a().toString(), "heybox_id" ))) { String str2 = "heybox_id" ; String userid = (user.isLoginFlag())? user.getAccount_detail().getUserid(): "-1" ; uoa.b(str2, userid); } uoa.b( "imei" , d.g()).b( "os_type" , "Android" ).b( "os_version" , (Build$VERSION.RELEASE).trim()).b( "version" , d.h()).b( "_time" , str ).b( "hkey" , str1); if (!c.b("")) { uoa.b( "game_type" , ""); } v ov = uoa.c(); str = ""; String str3 = (c.b(user.getPkey()))? " ": " pkey = " + user.getPkey(); str = str + str3; str3 = (c.b(uoab.a( "Cookie" )))? " ": " ; "+uoab.a(" Cookie"); return p0.proceed(uoab.f().b( "Referer" , "http://api.maxjia.com/" ).b( "User-Agent" , "Mozilla/5.0 AppleWebKit/537.36 \(KHTML, like Gecko\) Chrome/41.0.2272.118 Safari/537.36 ApiMaxJia/1.0" ).a( "Cookie" , str + str3).a(ov).d()); } } |
str是当前时间/1000
分析下str1是怎么赋值的
1 | String str1 = d.h(((d.h( "xiaoheihe/_time=" + str )).replaceAll( "a" , "app" )).replaceAll( "0" , "app" )); |
首先调用类d的h方法,参数为特定字符串"xiaoheihe/_time="和当前时间str进行拼接的结果
然后再次调用类d的h方法,参数为 将第一次调用的的返回值中的“a”和"0"给替换为"app"的结果
最后就是对str1进行赋值了,将第二次调用d的h方法的返回值.
后面其实就是一个拼接请求头的操作,不需要关注。
所以这里我们看下类d的h方法的怎么实现的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public static String h(String p0){ if (TextUtils.isEmpty(p0)) { return ""; } try { byte[] uobyteArray = MessageDigest.getInstance( "MD5" ).digest(p0.getBytes()); String str = ""; int len = uobyteArray.length; for ( int i = 0 ; i < len ; i = i + 1 ) { int i1 = uobyteArray[i] & 0x00ff ; String str1 = Integer.toHexString(i1); if (str1.length() = = 1 ) { str1 = "0" + str1; } str = str + str1; } return str ; }catch(java.security.NoSuchAlgorithmException e6){ e6.printStackTrace(); return ""; } } |
发现就是个md5.
我们可以得知请求报文中的_time和hkey字段是关联的。
1 2 3 4 5 6 7 | GET / store / has_unfinished_game / ?heybox_id = 46447531 &imei = 863342023667829 &os_type = Android&os_version = 7.1 . 2 &version = 1.1 . 50 &_time = 1666165262 &hkey = 5656c95245635812219460113c5a14eb HTTP / 1.1 Referer: http: / / api.maxjia.com / User - Agent: Mozilla / 5.0 AppleWebKit / 537.36 (KHTML, like Gecko) Chrome / 41.0 . 2272.118 Safari / 537.36 ApiMaxJia / 1.0 Cookie: pkey = MTY2NjE2NTIyNC45M180NjQ0NzUzMWhyd2JubG9ud2NndHFubmI__ Host: api.xiaoheihe.cn Connection: Keep - Alive Accept - Encoding: gzip |
模拟算法(v1.1.50)
用脚本去验证下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | #coding:utf8 import hashlib import datetime def md5(test_str: str , key = None ): print ( 'MD5加密前为 :' + test_str) if key is None : m = hashlib.md5() else : m = hashlib.md5(key.encode( 'utf-8' )) m.update(test_str.encode()) res = m.hexdigest() print ( 'MD5加密后为 :' + res) return res def gethkey(): _time = "1666165262" str = md5(_time) str1 = md5( str .replace( "a" , "app" ).replace( "0" , "app" )) if __name__ = = '__main__' : gethkey() ''' MD5加密前为 :xiaoheihe/_time=1666165262 MD5加密后为 :49d640ad07515bc4c9ec4a9f893c24a7 MD5加密前为 :49d64appappdapp7515bc4c9ec4app9f893c24app7 MD5加密后为 :5656c95245635812219460113c5a14eb 和请求报文中的hkey是一致的 ''' |
定位算法(v.1.3.92)
下载了小黑盒 v.1.3.92版本。
同样在登录的时候进行抓包。
1 2 3 4 5 6 7 | GET / bbs / app / api / user / permission?heybox_id = ???&imei = ???&os_type = Android&os_version = 8.1 . 0 &version = 1.3 . 92 &_time = 1666259837 &hkey = 0cf9be6f88 HTTP / 1.1 Referer: http: / / api.maxjia.com / User - Agent: Mozilla / 5.0 AppleWebKit / 537.36 (KHTML, like Gecko) Chrome / 41.0 . 2272.118 Safari / 537.36 ApiMaxJia / 1.0 Cookie: pkey = ??? Host: api.xiaoheihe.cn Connection: Keep - Alive Accept - Encoding: gzip |
这里imei变化了是因为换了台手机。
我们先不逆向
用上面的gethkey.py跑一下看看hkey和请求报文中的是否一致。如果不一致就说明,加密算法变化了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | #coding:utf8 import hashlib import datetime def md5(test_str: str , key = None ): print ( 'MD5加密前为 :' + test_str) if key is None : m = hashlib.md5() else : m = hashlib.md5(key.encode( 'utf-8' )) m.update(test_str.encode()) res = m.hexdigest() print ( 'MD5加密后为 :' + res) return res def gethkey(): _time = "xiaoheihe/_time=" + "1666165262" #v1.1.50 _time = "xiaoheihe/_time=" + "1666259837" #v.1.3.92 str = md5(_time) str1 = md5( str .replace( "a" , "app" ).replace( "0" , "app" )) if __name__ = = '__main__' : gethkey() ''' MD5加密前为 :xiaoheihe/_time=1666259837 MD5加密后为 :5ff217a9966ad4c64a17fd4df122f03b MD5加密前为 :5ff217app9966appd4c64app17fd4df122fapp3b MD5加密后为 :2ba491715784e9606d289bde1f7890b0 ''' |
发现不一致,所以这个版本的getkey的算法发生了变化。
额,脑子坏掉了,请求报文中的hkey的长度只有10位的长度,肯定加密算法变了。
同样的操作定位到hkey的位置
发现新版本有三处地址含有该字符串。
先看第一个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | private void i(){ L l = new L$a().c( 0 , TimeUnit.MILLISECONDS).b( 0 , TimeUnit.MILLISECONDS).a(); String str = this.h(); T.a( "zzzzconntest" , "url==" + str ); HashMap hashMap = new HashMap( 16 ); User user = HeyBoxApplication.k(); String userid = (user.isLoginFlag())? user.getAccount_detail().getUserid(): "-1" ; hashMap.put( "userid" , userid); hashMap.put( "appid" , "heybox" ); hashMap.put( "pkey" , user.getPkey()); hashMap.put( "imei" , Q.d()); hashMap.put( "os_type" , "Android" ); hashMap.put( "os_version" , (Build$VERSION.RELEASE).trim()); hashMap.put( "version" , Q.g()); hashMap.put( "hkey" , Q.b(((Q.b( "xiaoheihe/_time=" + (System.currentTimeMillis() / 1000 ) + " ")).replaceAll(" a ", " app ")).replaceAll(" 0 ", " app"))); / / 这里其实就是v1. 1.50 版本gethkey是一样的 l.a(new N$a().b(Ab.a( str , hashMap)).a(), new l$c(this)); l.h().b().shutdown(); return ; } |
类Q下的b方法,就是md5加密
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public static String b(String p0){ if (TextUtils.isEmpty(p0)) { return ""; } try { byte[] uobyteArray = MessageDigest.getInstance( "MD5" ).digest(p0.getBytes()); int len = uobyteArray.length; String str = ""; for ( int i = 0 ; i < len ; i = i + 1 ) { int i1 = uobyteArray[i] & 0x00ff ; String str1 = Integer.toHexString(i1); if (str1.length() = = 1 ) { str1 = "0" + str1; } str = str + str1; } return str ; }catch(java.security.NoSuchAlgorithmException e7){ e7.printStackTrace(); return ""; } } |
第二个是就是这个版本使用的接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | package com. max .xiaoheihe.network.c; import okhttp3.H; import com. max .xiaoheihe.network.d; import java.lang. Object ; import okhttp3.H$a; import okhttp3.T; import okhttp3.N; import com. max .xiaoheihe.app.HeyBoxApplication; import com. max .xiaoheihe.bean.account.User; import okhttp3.G; import okhttp3.G$a; import java.lang.StringBuilder; import java.lang.System; import java.lang.String; import com. max .xiaoheihe.utils.NDKTools; import com. max .xiaoheihe.utils.Q; import com. max .xiaoheihe.utils.Ab; import com. max .xiaoheihe.utils.M; import com. max .xiaoheihe.bean.account.AccountDetailObj; import android.os.Build$VERSION; import okhttp3.N$a; class c implements H / / class @ 00135d { final d a; void c(d p0){ this.a = p0; super (); } public T intercept(H$a p0){ N n = p0.request(); User user = HeyBoxApplication.k(); G$a uoa = n.h().j(); String str = ""; StringBuilder str1 = (System.currentTimeMillis() / 1000 ) + str ; String str2 = n.h().c(); int i = 0 ; if (str2.endsWith( "/" )) { str2 = str2.substring(i, (str2.length() - 1 )); } String str3 = "app" ; str2 = (Q.b(((NDKTools.encode(HeyBoxApplication.g(), str2, str1)).replaceAll( "a" , str3)).replaceAll( "0" , str3))).substring(i, 10 ); String str4 = "heybox_id" ; if (M.d(Ab.b(n.h().toString(), str4))) { String userid = (user.isLoginFlag())? user.getAccount_detail().getUserid(): "-1" ; uoa.a(str4, userid); } uoa.a( "imei" , Q.d()).a( "os_type" , "Android" ).a( "os_version" , (Build$VERSION.RELEASE).trim()).a( "version" , Q.g()).a( "_time" , str1).a( "hkey" , str2); if (Q.h()) { uoa.a(str3, "concept" ); } G g = uoa.a(); str1 = ""; String str5 = (M.d(user.getPkey()))? str : "pkey=" + user.getPkey(); str1 = str1 + str5; if (!M.d(n.a( "Cookie" ))) { str = ";" + n.a( "Cookie" ); } return p0.proceed(n.f().a( "Referer" , "http://api.maxjia.com/" ).a( "User-Agent" , "Mozilla/5.0 AppleWebKit/537.36 \(KHTML, like Gecko\) Chrome/41.0.2272.118 Safari/537.36 ApiMaxJia/1.0" ).b( "Cookie" , str1 + str ).a(g).a()); } } |
关键代码
1 | uoa.a( "imei" , Q.d()).a( "os_type" , "Android" ).a( "os_version" , (Build$VERSION.RELEASE).trim()).a( "version" , Q.g()).a( "_time" , str1).a( "hkey" , str2); |
看下str2是怎么获得的
1 2 3 4 5 6 7 8 9 | String str = ""; StringBuilder str1 = (System.currentTimeMillis() / 1000 ) + str ; String str2 = n.h().c(); int i = 0 ; if (str2.endsWith( "/" )) { str2 = str2.substring(i, (str2.length() - 1 )); } String str3 = "app" ; str2 = (Q.b(((NDKTools.encode(HeyBoxApplication.g(), str2, str1)).replaceAll( "a" , str3)).replaceAll( "0" , str3))).substring(i, 10 ); |
发现调用了类NDKTools下的encode方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | package com. max .xiaoheihe.utils.NDKTools; import java.lang.System; import java.lang.String; import java.lang. Object ; public class NDKTools / / class @ 00138f { static { System.loadLibrary( "native-lib" ); } public void NDKTools(){ super (); } public static native int checkSignature( Object p0); public static native synchronized String encode( Object p0,String p1,String p2); public static native String getrsakey( Object p0,String p1); } |
encode方法在native-lib的so中进行实现的。
hook&疑问
本地环境
1 2 | frida - - version 15.2 . 2 |
手机端运行frida的服务端
1 | . / frida - server - 15.2 . 2 - android - arm64 & |
电脑端查看进程
1 | Frida - ps - U |
run.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | #coding:utf8 import sys import frida process_name = 'com.max.xiaoheihe' # 发送信息回调函数 def on_message(message, data): if message[ 'type' ] = = 'send' : print (f "[*] {message['payload']}" ) else : print (message) if __name__ = = '__main__' : try : device = frida.get_usb_device(timeout = 1000 ) print ( "* get usb device成功" ) except : device = frida.get_remote_device(timeout = 1000 ) print ( "* get remote device成功" ) if not device: print ( "* 连接到Frida Server失败" ) else : process = device.attach(process_name) # 加载JS脚本 js = open ( 'hook.js' , encoding = 'utf-8' ).read() print (js) script = process.create_script(js) script.on( 'message' , on_message) script.load() # 读取返回输入 input () script.unload() |
hook.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | console.log( "脚本载入成功" ); Java.perform(function () { var encodeAddr = Module.findExportByName( "libnative-lib.so" , "encode" ); console.log(encodeAddr); if (encodeAddr ! = null) { Interceptor.attach(encodeAddr, { onEnter: function (args) { / / args参数数组 console.log( 'encode-Enter' ) console.log(args[ 0 ], Memory.readCString(args[ 0 ])); console.log(args[ 1 ], Memory.readCString(args[ 1 ])); console.log(args[ 2 ], Memory.readCString(args[ 2 ])); console.log(args[ 3 ], Memory.readCString(args[ 3 ])); console.log(args[ 4 ], Memory.readCString(args[ 4 ])); }, onLeave: function (retval) { / / retval函数返回值 console.log( 'encode-Leave' ); console.log(retval.toString()); console.log( '======' ); } }); } }) |
发现可以成功运行起来
0xc9e90b01是encode函数的地址,当打开app后,这个地址是不变的。
这里我运行这个脚本只输出这些内容,但并没有打印出来参数和返回地址。
先静态分析吧。解包获得libnative-lib.so并拖入ida中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | int __fastcall encode(_JNIEnv * env, int a2, int a3, int can_str2, int can_str1) { const char * v5; / / r10 _JNIEnv * v6; / / r4 int v7; / / r6 int v8; / / r8 const char * str2; / / r11 int str1; / / r0 bool v11; / / zf size_t v12; / / r5 char * v13; / / r5 char * v14; / / r0 size_t v15; / / r0 char * v16; / / r0 int v17; / / r5 int result; / / r0 int v19; / / [sp + 0h ] [bp - 30h ] int * v20; / / [sp + 4h ] [bp - 2Ch ] int v21; / / [sp + 8h ] [bp - 28h ] int v22; / / [sp + Ch] [bp - 24h ] v19 = can_str2; v6 = env; v7 = can_str2; if ( j_check_signature(env) = = 1 ) { v8 = 0 ; str2 = (const char * )(( int (__fastcall * )(_JNIEnv * , int , _DWORD))v6 - >functions - >GetStringUTFChars)(v6, v7, 0 ); str1 = (( int (__fastcall * )(_JNIEnv * , int , _DWORD))v6 - >functions - >GetStringUTFChars)(v6, can_str1, 0 ); / / 时间戳 v11 = str2 = = 0 ; if ( str2 ) { v5 = (const char * )str1; v11 = str1 = = 0 ; } if ( !v11 ) / / str2不为空进入 if { v21 = can_str1; v12 = strlen(str2); v20 = &v19; v13 = (char * )&v19 - ((strlen(v5) + v12 + 21 ) & 0xFFFFFFF8 ); v14 = strcpy(v13, str2); v15 = strlen(v14); _aeabi_memcpy(&v13[v15], "/bfhdkud_time=" , 15 ); / / v13为str2后面拼接上 "/bfhdkud_time=" v16 = strcat(v13, v5); / / v16为v13后面拼接str1 即时间戳 v17 = j_MDString(v16); / / 对v16进行md5加密赋值给v17 ((void (__fastcall * )(_JNIEnv * , int , const char * ))v6 - >functions - >ReleaseStringUTFChars)(v6, v7, str2); ((void (__fastcall * )(_JNIEnv * , int , const char * ))v6 - >functions - >ReleaseStringUTFChars)(v6, v21, v5); v8 = j_charToJstring(v6, v17); / / md5加密的结果赋值给v8 } if ( _stack_chk_guard = = v22 ) return v8; / / 返回 } result = _stack_chk_guard - v22; if ( _stack_chk_guard = = v22 ) result = j_j_charToJstring(v6, UNSIGNATURE[ 0 ]); return result; } |
分析得知encode函数是将(str2+"/bfhdkud_time="+str1)进行md5加密,将加密的结果吗作为返回值
回到接口类中
1 2 | String str3 = "app" ; str2 = (Q.b(((NDKTools.encode(HeyBoxApplication.g(), str2, str1)).replaceAll( "a" , str3)).replaceAll( "0" , str3))).substring(i, 10 ); |
发现只取md5返回值然后将其中的"a"和"0"都替换成str3即"app"
然后又调用了Q类下的b(String)方法,发现同样是md5算法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public static String b(String p0){ if (TextUtils.isEmpty(p0)) { return ""; } try { byte[] uobyteArray = MessageDigest.getInstance( "MD5" ).digest(p0.getBytes()); int len = uobyteArray.length; String str = ""; for ( int i = 0 ; i < len ; i = i + 1 ) { int i1 = uobyteArray[i] & 0x00ff ; String str1 = Integer.toHexString(i1); if (str1.length() = = 1 ) { str1 = "0" + str1; } str = str + str1; } return str ; }catch(java.security.NoSuchAlgorithmException e7){ e7.printStackTrace(); return ""; } } |
然后再将其返回值取前10位赋值给str2,即后面的hkey
模拟算法(v.1.3.92)
写成python实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | #coding:utf8 import hashlib import datetime def md5(test_str: str , key = None ): print ( 'MD5加密前为 :' + test_str) if key is None : m = hashlib.md5() else : m = hashlib.md5(key.encode( 'utf-8' )) m.update(test_str.encode()) res = m.hexdigest() print ( 'MD5加密后为 :' + res) return res def gethkey(): str1 = "1666285158" str1 = "1666259837" str2 = "/bbs/app/api/user/permission/" #str2="/account/get_ads_info/" if str2[ - 1 ] = = "/" : str2 = str2[: - 1 ] zuhe_str = str2 + "/bfhdkud_time=" + str1 #print(zuhe_str) ret = md5(zuhe_str).replace( "a" , "app" ).replace( "0" , "app" ) hkey = md5(ret)[ 0 : 10 ] print (hkey) if __name__ = = '__main__' : gethkey() ''' MD5加密前为 :/bbs/app/api/user/permission/bfhdkud_time=1666259837 MD5加密后为 :5e3fa112b55e9b8a087a5a1196f4645f MD5加密前为 :5e3fapp112b55e9b8appapp87app5app1196f4645f MD5加密后为 :0cf9be6f884cbf25ffd97e9fa61778d4 0cf9be6f88 ''' |
对比下请求报文,发现key值是一样的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | GET / bbs / app / api / user / permission?heybox_id = ???&imei = ???&os_type = Android&os_version = 8.1 . 0 &version = 1.3 . 92 &_time = 1666259837 &hkey = 0cf9be6f88 HTTP / 1.1 Referer: http: / / api.maxjia.com / User - Agent: Mozilla / 5.0 AppleWebKit / 537.36 (KHTML, like Gecko) Chrome / 41.0 . 2272.118 Safari / 537.36 ApiMaxJia / 1.0 Cookie: ??? Host: api.xiaoheihe.cn Connection: Keep - Alive Accept - Encoding: gzip ##################################################### GET / account / get_ads_info / ?heybox_id = ???&imei = ???&os_type = Android&os_version = 8.1 . 0 &version = 1.3 . 92 &_time = 1666285158 &hkey = d630c904c2 HTTP / 1.1 Referer: http: / / api.maxjia.com / User - Agent: Mozilla / 5.0 AppleWebKit / 537.36 (KHTML, like Gecko) Chrome / 41.0 . 2272.118 Safari / 537.36 ApiMaxJia / 1.0 Cookie: ???? Host: api.xiaoheihe.cn Connection: Keep - Alive Accept - Encoding: gzip |
参考
小黑盒逆向分析笔记(二) - Chr_小屋 (chrxw.com)
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。