Hacker101 CTF Android challenges write up(一)
hjsz 发表于 山东 移动安全 1020浏览 · 2024-01-27 08:39

本文是关于Hacker101 CTF中Android题的wp以及自己的一些思路

  • Hacker101是一个免费的网络安全学习网站,由HackerOne负责。HackerOne相当于国外有名的src平台,有能力的师傅可以去看看。

H1 Thermostat

  • 下载下来是个挺小的apk,看题目名好像是个温度计,提示有两处flag。先安装一下,由下图可以看出有个温度显示面板,下面可以用加减或者拖动来调节温度。通过这看不出来什么,拖入Jadx查看代码逻辑。
  • 从amf.xml图可以看出,只有ThermostatActivity一个Activity被定义,这里就只看这一个Activity即可。
  • 首先,分析ThermostatActivity,并没有发现什么和flag有关的代码。接着搜索flag,看看能不能从关键词中找出点线索。由于flag关键词还是比较多的,尤其是其他包中,这里需要检索一下,主要找com.hacker101.level11目录下的,结果找到下图中的两个线索。
  • 跟入PayloadRequest函数,接收一个 JSON 对象作为参数,生成有效载荷,并设置请求的参数和头部信息。请求的 URL 是固定的,错误监听器会在请求失败时返回特定的错误信息。
    public PayloadRequest(JSONObject jSONObject, final Response.Listener<String> listener) throws Exception {
          super(1, "https://d7299d*****4704859cc2cc8382.ctf.hacker101.com/", new Response.ErrorListener() { // from class: com.hacker101.level11.PayloadRequest.1
              @Override // com.android.volley.Response.ErrorListener
              public void onErrorResponse(VolleyError volleyError) {
                  Response.Listener.this.onResponse("Connection failed");
              }
          });
          this.mListener = listener;
          this.mParams = new HashMap<>();
          String buildPayload = buildPayload(jSONObject);
          this.mParams.put("d", buildPayload);
          this.mHeaders = new HashMap<>();
          MessageDigest messageDigest = MessageDigest.getInstance("MD5");
          messageDigest.update("^FLAG^911074676b0d1ed58ba09ca5755930daf7266886bbcf32ca8f785f6e5c631eb9$FLAG$".getBytes());
          messageDigest.update(buildPayload.getBytes());
          this.mHeaders.put("X-MAC", Base64.encodeToString(messageDigest.digest(), 0));
          this.mHeaders.put("X-Flag", "^FLAG^ab34de7822af3f9f75b311e0dcca2d1db34b5e63aa4fbb7386696a547405405a$FLAG$");}
    
  • 然后看一下该函数的引用,在中有对此函数的引用,那关键点应该就在这了。
    • setDefaults函数,这个方法用于设置默认值。它首先将目标温度和当前温度设置为特定的值,然后创建一个包含 "cmd" 键值对的 JSON 对象。接下来,它创建一个 PayloadRequest 对象,并将其添加到请求队列中。当请求成功返回时,通过回调函数更新目标温度和当前温度。
      private void setDefaults(final ThermostatModel thermostatModel) throws Exception {
        thermostatModel.setTargetTemperature(77);
        thermostatModel.setCurrentTemperature(76);
        JSONObject jSONObject = new JSONObject();
        jSONObject.put("cmd", "getTemp");
        volleyQueue.add(new PayloadRequest(jSONObject, new Response.Listener<String>() { // from class: com.hacker101.level11.ThermostatActivity.2
            @Override // com.android.volley.Response.Listener
            public void onResponse(String str) {
                thermostatModel.setTargetTemperature(70);
                thermostatModel.setCurrentTemperature(73);
            }
        }));
      }
      
    • 从图一也可以看出,当前温度是73,证明已经从76改为73,则onResponse是正常进行的,请求顺利完成。
    • 那我们就抓包看一下具体的网络请求。从PayloadRequest也可以看出一个flag经过MD5和Base64,然后作为值添加到X-MAC中。另一直接作为明文添加到X-Flag中。从下面的抓包也可以看出,当当然只抓包只能获取一个明文flag,另一个md5编码后难以解码,只有通过查看真实代码才可以得出两个flag。

  • 综上,flag为:911074676b0d1ed58ba09ca5755930daf7266886bbcf32ca8f785f6e5c631eb9和ab34de7822af3f9f75b311e0dcca2d1db34b5e63aa4fbb7386696a547405405a。经过验证flag正确。
  • 可以看出,每此不同的环境得到的app是不同的,因为修改了app请求中的flag值,

Intentional Exercise

  • 首先还是安装看一下大致是个什么app。首先该app也是只有一个界面,然后有个可点击的Flag。

    点击之后出现Invalid request。
  • 好的,拖入Jadx查看源代码。这段代码根据传入的Intent中的数据URI构建一个URL,并根据特定规则添加查询参数。然后,它使用SHA-256算法计算URL及其他数据的哈希值,并将带有哈希值的URL加载到WebView中进行显示。
    Uri data = getIntent().getData();
          String str = "https://09f0e1735ea21fef3ae3c1578d677f09.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 messageDigest = MessageDigest.getInstance("SHA-256");
              messageDigest.update("s00p3rs3cr3tk3y".getBytes(StandardCharsets.UTF_8));
              messageDigest.update(str2.getBytes(StandardCharsets.UTF_8));
              webView.loadUrl(str + "&hash=" + String.format("%064x", new BigInteger(1, messageDigest.digest())));
          } catch (NoSuchAlgorithmException e) {
              e.printStackTrace();
          }
    
  • 考虑到也是关于请求的,先抓包看看结果。因为是在Android设备上使用Webview,所以我们可以直接抓包拿到url在电脑上进行操作,还更方便。
  • 直接F12查看。首先可以看出,在点击Flag之前,url是ctf.hacker101.com/appRoot?&hash=**,点击Flag请求的URL是ctf.hacker101.com/appRoot/flagBearer。
  • 当讲第一个url的hash加到flag请求的URL后,响应不再是Invalid request而且Invalid hash,我们可以猜测本题的关键就是代码中的hash值。

  • 继续分析代码

    • 从下面的代码可以看出s00p3rs3cr3tk3y和str2的组合经过sha256的编码放入URL参数hash后面。所以下一步要继续找str2的值。
      essageDigest messageDigest = MessageDigest.getInstance("SHA-256");
            messageDigest.update("s00p3rs3cr3tk3y".getBytes(StandardCharsets.UTF_8));
            messageDigest.update(str2.getBytes(StandardCharsets.UTF_8));
            webView.loadUrl(str + "&hash=" + String.format("%064x", new BigInteger(1, messageDigest.digest())));
      
    • 首先往前看,str与str2进行相加,而对str(ctf.hacker101.com/appRoot)是知道的,而且之前抓包也知道二者相加后的值(ctf.hacker101.com/appRoot/flagBearer),就可以反推出来str为/flagBearer
    • 找个在线工具对s00p3rs3cr3tk3y/flagBearer进行Sha256编码。
    • 将编码结果作为hash的值进行请求。成功获取Flag。
    • 验证Flag。正确
  • 方法2
    • 如下图,由intent过滤器指定的url是:http://level13.hacker101.com ,不包含字符串/flagBearer。而且str2 = data.toString().substring(28);的结果导致str2为空,所以就不能得到s00p3rs3cr3tk3y/flagBearer再进行sha256编码。
    • 而本app没有对url进行验证,这就说明我们可以设置启动的Intent的URL数据。这里直接使用ADB的命令即可。开始使用下面的命令,发现app的web跳转到起始页,还是没有Flag。
      adb shell am start -W -a "android.intent.action.VIEW" -d "http://level13.hacker101.com" com.hacker101.level13
      
    • 结合之前的经验,是没有加上/flagBearer这一获取flag的路径。修改adb命令,发现appweb界面加载到flag。成功。参考资料
      adb shell am start -W -a "android.intent.action.VIEW" -d "http://level13.hacker101.com/flagBearer" com.hacker101.level13
      

Oauthbreaker

安装

  • 打开是个简单的界面,只有一个认证的按钮,然后点击后会跳转到手机浏览器,web界面有个验证app的button,点击后就会再次跳转到app,显示成功验证。
  • 发现hacker 101的安卓题怎么都和web关系这么多。难道是因为以前的app应用都是web改的?

源码分析

  • 照旧拖入Jadx查看源码。主要代码是下面的onClick代码。这是个点击事件的回调函数。在点击按钮时,构建一个特定的URL并通过隐式Intent启动一个新的Activity来打开该URL。
    public void onClick(View view) {
          if (view.getId() != R.id.button) {
              return;
          }
          String str = null;
          try {
              str = "https://0b6**ded40f29af68fb3b108c.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);
      }
    }
    
  • 接下来的关键还是找到这个url,然后用web直接访问,查看网络包的具体内容。这次直接省略抓包的过程,因为click后会跳转到手机浏览器,这里直接从浏览器复制url。https://0b65a**f29af68fb3b108c.ctf.hacker101.com/oauth?redirect_url=oauth%3A%2F%2Ffinal%2Flogin&response_type=token&scope=all
  • 使用Chrome直接进入上面那个网址,当F12的时候看到响应中除了显示出来的还有其他内容,比如这个token,而且看样子符合hack101的flag样式,就尝试提交一下,发现不对.
  • 接着看MainActivity代码,从上面的onCLick函数可以看到,最终的url中位置的值是this.authRedirectUri,而在onCreate函数中有对这个值的相关处理,代码如下。从之前获取的真实url看this.authRedirectUri的值依旧没变,是"oauth://final/", 原因是当前Intent是MainActivity,其定义中的data没有redirect_uri参数,所以在if判断语句中data.getQueryParameter("redirect_uri")返回的是空。因为两个url只有final和login的不同,那就先简单尝试一下login的结果。结果如下图,出现了flag的样式,提交一下,成功。证明找到了第一个flag。
    public void onCreate(Bundle bundle) {
          super.onCreate(bundle);
          setContentView(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);
      }
    

flag2

  • MainActivity的代码Intent intent = new Intent("android.intent.action.VIEW");会启动Browser这个Activity(amf.xml文件中定义了Browser的Intent的过滤器)。
  • 来看一下BrowserActivity。主要代码如下
    public void onCreate(Bundle bundle) {
          super.onCreate(bundle);
          setContentView(R.layout.activity_browser);
          String str = "https://0b65afadf874ded40f29af68fb3b108c.ctf.hacker101.com/authed";
          try {
              Uri data = getIntent().getData();
              if (data != null && data.getQueryParameter("uri") != null) {
                  str = data.getQueryParameter("uri");
              }
          } catch (Exception unused) {
          }
          WebView webView = (WebView) findViewById(R.id.webview);
          webView.setWebViewClient(new SSLTolerentWebViewClient(webView));
          webView.getSettings().setJavaScriptEnabled(true);
          webView.addJavascriptInterface(new WebAppInterface(getApplicationContext()), "iface");
          webView.loadUrl(str);
      }
    
  • 该函数的作用是在Browser Activity中创建一个WebView并加载指定的URL。其中关键的是 webView.getSettings().setJavaScriptEnabled(true);。这行代码允许web执行任意的js代码,这是比较危险的。webView.addJavascriptInterface(new WebAppInterface(getApplicationContext()), "iface"):在WebView中添加一个JavaScript接口,即 WebAppInterface,用于通过接口与应用程序的Java代码进行交互。
  • 接着查看一下WebAppInterface的具体定义。分析一下下面这个函数的具体功能,从函数名可以看出这是获取flag的path,而且返回值是str.html,更像是一个包含flag的html文件的地址。
  • 将其转换为python代码(chatgpt就可以完成,这里就不放代码了。太长了),直接运行输出结果。最终得到一个html的地址:48ce217fea4529a070a9d3e3c87db512b1596d413e580f7b2e1eab65f3948ab8.html
  • 直接将html拼接到原有的URL后就可以得到第二个flag。
  • 提交,结果正确。
0 条评论
某人
表情
可输入 255