SekaiCTF 2025 Sekai Bank WriteUp

612 阅读1分钟

分析

打开NP管理器反编译APK

    @POST("flag")
    Call<String> getFlag(@Body FlagRequest flagRequest)

找到获取Flag的方法com.sekai.bank.network.ApiService.getFlag

进到FlagRequest里面

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字符串

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方法,定位进去看看

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拼接得到的

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方法

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?}

谢谢大家