分析
打开NP管理器反编译APK
1 2
| @POST("flag") Call<String> getFlag(@Body FlagRequest flagRequest)
|
找到获取Flag的方法com.sekai.bank.network.ApiService.getFlag
进到FlagRequest里面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class FlagRequest { private boolean unmask_flag;
public FlagRequest(boolean z) { this.unmask_flag = z; }
public boolean getUnmaskFlag() { return this.unmask_flag; }
public void setUnmaskFlag(boolean z) { this.unmask_flag = z; } }
|
从这里分析的值,我们可以伪造一个POST请求来得到Flag
抓包
先登录,通过ProxyPin抓包看看请求体

发现有一个X-Signature
继续打开NP管理器搜索X-Signature字符串
1 2 3 4 5 6 7 8 9 10
| public Response intercept(Chain chain) throws IOException { Request request = chain.request(); try { chain = chain.proceed(request.newBuilder().header("X-Signature", generateSignature(request)).build()); return chain; } catch (Exception e) { Log.e("SekaiBank-API", "Failed to generate signature: " + e.getMessage()); return chain.proceed(request); } }
|
可以看到一个generateSignature方法,定位进去看看
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
| private String generateSignature(Request request) throws IOException, GeneralSecurityException { Throwable e; String str = request.method() + "/api".concat(getEndpointPath(request)) + getRequestBodyAsString(request); SekaiApplication instance = SekaiApplication.getInstance(); PackageManager packageManager = instance.getPackageManager(); String packageName = instance.getPackageName(); try { Signature[] apkContentsSigners; if (VERSION.SDK_INT >= 28) { PackageInfo packageInfo = packageManager.getPackageInfo(packageName, 134217728); SigningInfo signingInfo = packageInfo.signingInfo; apkContentsSigners = signingInfo != null ? signingInfo.hasMultipleSigners() ? signingInfo.getApkContentsSigners() : signingInfo.getSigningCertificateHistory() : packageInfo.signatures; } else { apkContentsSigners = packageManager.getPackageInfo(packageName, 64).signatures; } if (apkContentsSigners == null || apkContentsSigners.length <= 0) { throw new GeneralSecurityException("No app signature found"); } MessageDigest instance2 = MessageDigest.getInstance("SHA-256"); for (Signature toByteArray : apkContentsSigners) { instance2.update(toByteArray.toByteArray()); } return calculateHMAC(str, instance2.digest()); } catch (NameNotFoundException e2) { e = e2; throw new GeneralSecurityException("Unable to extract app signature", e); } catch (NoSuchAlgorithmException e3) { e = e3; throw new GeneralSecurityException("Unable to extract app signature", e); } }
|
可以看到str字符串,是由getEndpointPath和getRequestBodyAsString拼接得到的
1
| String str = request.method() + "/api".concat(getEndpointPath(request)) + getRequestBodyAsString(request); SekaiApplication instance = SekaiApplication.getInstance();
|
Hook
打开LuaHook Hook calculateHMAC方法看看第一个参数str

参数是POST/api/auth/login{"password":"114514","username":"114514"}
跟我们猜想的一样,说明他是通过这个计算X-Signature的
那就简单了,直接HookgetEndpointPath和getRequestBodyAsString方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| hook("com.sekai.bank.network.ApiClient$SignatureInterceptor", lpparam.classLoader, "getEndpointPath", "okhttp3.Request", function(it)
end, function(it) it.result="/flag" end)
hook("com.sekai.bank.network.ApiClient$SignatureInterceptor", lpparam.classLoader, "getRequestBodyAsString", "okhttp3.Request", function(it) end, function(it) it.result=[[{"unmask_flag":true}]] end)
|
接着我们直接登录,然后他就会自动计算X-Signature,然后拿ProxyPin重写请求就行了
请求重写




SEKAI{are-you-ready-for-the-real-challenge?}
谢谢大家