Before all
最近剛好推坑學員來打,自己上來補一下以前沒打的安卓題XD
整體而言很舒服,挺適合新手的(含 Writeup 大概一節自習弄完ㄅ)
H1 Thermostat
用 decompiler.com 拆包,在 /sources/com/hacker101/level11/PayloadRequest.java
就可以在請求的 requests headers 中找到兩把 flag 了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public PayloadRequest(JSONObject jSONObject, final Response.Listener<String> listener) throws Exception { super(1, "https://8902e68f31e537242087dcf6b1b9ce7b.ctf.hacker101.com/", new Response.ErrorListener() { public void onErrorResponse(VolleyError volleyError) { Response.Listener.this.onResponse("Connection failed"); } }); this.mListener = listener; String buildPayload = buildPayload(jSONObject); this.mParams.put("d", buildPayload); this.mHeaders = new HashMap<>(); MessageDigest instance = MessageDigest.getInstance("MD5"); instance.update("^FLAG^1cc123c2258b6b617ec31b25d4e9f91ec564809f31dee35e2b3051e1724bc9dc$FLAG$".getBytes()); instance.update(buildPayload.getBytes()); this.mHeaders.put("X-MAC", Base64.encodeToString(instance.digest(), 0)); this.mHeaders.put("X-Flag", "^FLAG^cb1bbd86372b3da20e3d3be856526a5367b0ec75d4f699bc82aff2a6f59c9cfd$FLAG$"); }
|
Intentional Exercise
拆 /sources/com/hacker101/level13/MainActivity.java
,找到這一段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public void onCreate(Bundle bundle) { super.onCreate(bundle); setContentView((int) R.layout.activity_main); WebView webView = (WebView) findViewById(R.id.webview); webView.setWebViewClient(new WebViewClient()); Uri data = getIntent().getData(); String str = "https://46191d6bd16422b230e5f462400be91e.ctf.hacker101.com/appRoot"; String str2 = BuildConfig.FLAVOR; if (data != null) { str2 = data.toString().substring(28); str = str + str2; } if (!str.contains("?")) { str = str + "?"; } try { MessageDigest instance = MessageDigest.getInstance("SHA-256"); instance.update("s00p3rs3cr3tk3y".getBytes(StandardCharsets.UTF_8)); instance.update(str2.getBytes(StandardCharsets.UTF_8)); webView.loadUrl(str + "&hash=" + String.format("%064x", new Object[]{new BigInteger(1, instance.digest())})); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } }
|
在 Android 中,getIntent 的方法會在點下圖標/連結或某些系統變化發生時取值,在這邊感覺是拼接一個 url 所以先假設它想取的是一個連結。
再來注意到 /resources/AndroidManifest.xml
裡的 hardcode 值:
1 2 3 4 5 6
| <intent-filter> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> <data android:scheme="http" android:host="level13.hacker101.com"/> </intent-filter>
|
可以看到 http://level13.hacker101.com
的長度正好為 28,大概就是你了…
最後又因為打開 https://46191d6bd16422b230e5f462400be91e.ctf.hacker101.com/appRoot/
後獲得的連結
1 2
| <h1>Welcome to Level13</h1><a href="appRoot/flagBearer">Flag</a>
|
嘗試拿 hash 為 SHA-256('s00p3rs3cr3tk3y/flagBearer')
去存取成功拿到 flag ><
Final Payload: https://46191d6bd16422b230e5f462400be91e.ctf.hacker101.com/appRoot/flagBearer?&hash=8743a18df6861ced0b7d472b34278dc29abba81b3fa4cf836013426d6256bd5e
P.S. 我一開始沒有 ?&
被卡好久…
Oauthbreaker
Flag 1
首先注意到 /sources/com/hacker101/oauth/WebAppInterface.java
當中定義了一個 getFlagPath 的函數:
1 2 3 4 5
| public String getFlagPath() { int[] iArr = {174, 95, ......}; ...... 略 return str + ".html"; }
|
把它包進去另一個 Java,然後 println
出來就行 XD
solve script: https://www.programiz.com/online-compiler/8vC5V0QcMaODh
Flag 2
分析 /sources/com/hacker101/oauth/MainActivity.java
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
| package com.hacker101.oauth;
import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.view.View; import android.widget.Button; import androidx.annotation.RequiresApi; import androidx.appcompat.app.AppCompatActivity; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets;
public class MainActivity extends AppCompatActivity implements View.OnClickListener { String authRedirectUri; Button button;
@RequiresApi(api = 19) public void onCreate(Bundle bundle) { super.onCreate(bundle); setContentView((int) R.layout.activity_main); this.authRedirectUri = "oauth://final/"; try { Uri data = getIntent().getData(); if (!(data == null || data.getQueryParameter("redirect_uri") == null)) { this.authRedirectUri = data.getQueryParameter("redirect_uri"); } } catch (Exception unused) { } this.button = (Button) findViewById(R.id.button); this.button.setOnClickListener(this); }
public void onClick(View view) { if (view.getId() == R.id.button) { String str = null; try { str = "https://022f85b6d943e20227ea7920941a21e0.ctf.hacker101.com/oauth?redirect_url=" + URLEncoder.encode(this.authRedirectUri, StandardCharsets.UTF_8.toString()) + "login&response_type=token&scope=all"; } catch (UnsupportedEncodingException e) { e.printStackTrace(); } Intent intent = new Intent("android.intent.action.VIEW"); intent.setData(Uri.parse(str)); startActivity(intent); } } }
|
觀察一下,就是一個 Oauth URL 請求器,走的是自己的 oauth://
協議
一開始根本沒有 Oauth URL 可用,所以會是 oauth://final/
首先請求 https://022f85b6d943e20227ea7920941a21e0.ctf.hacker101.com/oauth?redirect_url=oauth://final/login&response_type=token&scope=all
獲得含 token url oauth://final/login?token=afbe631ca8889b2000be4b6c820b6f09
再次請求,就可以在回傳的 oauth url 中拿到 flag
https://022f85b6d943e20227ea7920941a21e0.ctf.hacker101.com/oauth?redirect_url=oauth://final/login?token=afbe631ca8889b2000be4b6c820b6f09&response_type=token&scope=all
Mobile Webdev
拆開,在 /sources/com/hacker101/webdev/MainActivity.java
拿到幾個 endpoint 和 hmac key
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 63 64 65 66 67 68 69 70 71 72 73 74 75
| package com.hacker101.webdev;
import android.net.http.SslError; import android.os.Bundle; import android.view.View; import android.webkit.SslErrorHandler; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.Button; import androidx.annotation.RequiresApi; import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity implements View.OnClickListener { protected String HmacKey = "8c34bac50d9b096d41cafb53683b315690acf65a11b5f63250c61f7718fa1d1d"; Button editButton; Boolean editing = false; Button refreshButton; WebView webView;
private class SSLTolerantWebViewClient extends WebViewClient { WebView webView;
SSLTolerantWebViewClient(WebView webView2) { this.webView = webView2; }
public boolean shouldOverrideUrlLoading(WebView webView2, String str) { this.webView.loadUrl(str); return true; }
public void onReceivedSslError(WebView webView2, SslErrorHandler sslErrorHandler, SslError sslError) { super.onReceivedSslError(webView2, sslErrorHandler, sslError); sslErrorHandler.proceed(); } }
public String Hmac(byte[] bArr) throws Exception { throw new Exception("TODO: Implement this and expose to JS"); }
@RequiresApi(api = 19) public void onCreate(Bundle bundle) { super.onCreate(bundle); setContentView((int) R.layout.activity_main); this.editButton = (Button) findViewById(R.id.edit); this.editButton.setOnClickListener(this); this.refreshButton = (Button) findViewById(R.id.refresh); this.refreshButton.setOnClickListener(this); this.webView = (WebView) findViewById(R.id.webview); WebView webView2 = this.webView; webView2.setWebViewClient(new SSLTolerantWebViewClient(webView2)); this.webView.getSettings().setJavaScriptEnabled(true); this.webView.loadUrl("https://477e4c09ef978f693ce0ea1dac93d31e.ctf.hacker101.com/content/"); }
public void onClick(View view) { int id = view.getId(); if (id != R.id.edit) { if (id == R.id.refresh) { this.webView.reload(); } } else if (this.editing.booleanValue()) { this.editButton.setText("Edit"); this.webView.loadUrl("https://477e4c09ef978f693ce0ea1dac93d31e.ctf.hacker101.com/content/"); this.editing = false; } else { this.editButton.setText("View"); this.webView.loadUrl("https://477e4c09ef978f693ce0ea1dac93d31e.ctf.hacker101.com/edit.php"); this.editing = true; } } }
|
大致上是一個文件上傳/編輯網站,直接連進去 edit.php 找到一個 upload.php

進去之後發現只能上傳 zip,上傳後會顯示 Incorrect Hmac,接著會說大小為 16 bytes, 盲猜用 HMAC.MD5 簽
1 2 3 4 5 6 7 8 9 10
| import hmac import hashlib
file_content = open('l.zip', 'rb').read() hmac_key = "8c34bac50d9b096d41cafb53683b315690acf65a11b5f63250c61f7718fa1d1d" hmac_key_bytes = bytes.fromhex(hmac_key)
hmac_digest = hmac.new(hmac_key_bytes, file_content, hashlib.md5).hexdigest() print(hmac_digest)
|
這是 Signer,最後送回去上傳就好

結果本來只是手癢開 hexedit 上 zip-slip 攻擊不小心就多一個 Flag 惹