SekaiCTF 2025 Sekai Bank WriteUp

分析

打开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抓包看看请求体

image.png

发现有一个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字符串,是由getEndpointPathgetRequestBodyAsString拼接得到的

1
String str = request.method() + "/api".concat(getEndpointPath(request)) + getRequestBodyAsString(request); SekaiApplication instance = SekaiApplication.getInstance();

Hook

打开LuaHook Hook calculateHMAC方法看看第一个参数str

image.png

参数是POST/api/auth/login{"password":"114514","username":"114514"}

跟我们猜想的一样,说明他是通过这个计算X-Signature

那就简单了,直接HookgetEndpointPathgetRequestBodyAsString方法

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重写请求就行了

请求重写

image.png

image.png

image.png

image.png

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

谢谢大家


SekaiCTF 2025 Sekai Bank WriteUp
http://example.com/2025/08/28/SekaiCTF 2025 Sekai Bank WriteUp/
作者
帕帝天秀
发布于
2025年8月28日
许可协议