Scrapyを使ったFossdroidのAPK-crawl

FossdroidというAndroidOSSのアプリを集めたサイトがある。ここにあるアプリの内HTMLハイブリッドアプリが欲しくて、集めようとした備忘録

Scrapyとshellscriptを使ったよ

Scrapyのインストー

$ sudo pip install --upgrade pip
$ sudo pip install pyopenssl
$ sudo pip install --upgrade pyopenssl
$ sudo pip install scrapy
$ sudo pip install --upgrade Scrapy

pipでいれればいいけど色々エラーが起きたのでその度調べて解決、最終的には私は上のコマンドを打ってインストール完了

Scrapyを使ったcrawl

プロジェクトの作成

まずはクロールを行うプロジェクトを作成する.scrapy startproject プロジェクト名で作成

$ scrapy startproject apkcrawl

設定ファイルの編集

今回対象とするfossdroid.comのrobots.txtに従うように、かつ負荷をかけないようにDELAYをつける

ROBOTSTXT_OBEYTrueになっていること(デフォルトでTrueなので問題ないはず)

DOWNLOAD_DELAYコメントアウトをはずし3と設定する

$ cd apkcrawl/apkcrawl
$ vim settings.py


# -*- coding: utf-8 -*-

# Scrapy settings for apkcrawl project
#
# For simplicity, this file contains only settings considered important or
# commonly used. You can find more settings consulting the documentation:
#
#     http://doc.scrapy.org/en/latest/topics/settings.html
#     http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
#     http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html

BOT_NAME = 'apkcrawl'

SPIDER_MODULES = ['apkcrawl.spiders']
NEWSPIDER_MODULE = 'apkcrawl.spiders'


# Crawl responsibly by identifying yourself (and your website) on the user-agent
#USER_AGENT = 'apkcrawl (+http://www.yourdomain.com)'

# Obey robots.txt rules
ROBOTSTXT_OBEY = True

# Configure maximum concurrent requests performed by Scrapy (default: 16)
#CONCURRENT_REQUESTS = 32

# Configure a delay for requests for the same website (default: 0)
# See http://scrapy.readthedocs.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
DOWNLOAD_DELAY = 3

(snipping)

保存するデータのクラスを作成

crawlした際に保存するデータのクラスを作成する.

今回はapkのタイトルとapkのurlを保存したいので、そういったクラスとする

$ vim items.py

# -*- coding: utf-8 -*-

# Define here the models for your scraped items
#
# See documentation in:
# http://doc.scrapy.org/en/latest/topics/items.html

import scrapy
    

class ApkcrawlItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    title = scrapy.Field() # name of application
    url = scrapy.Field() # url of APK

crawl本体を書く

scrapy genspider クロールファイル名 集めたいデータのurlでクロール本体のスクリプトファイルを作成

クロールファイル名は何でもいいので私はapkcrawl_spider,fossdroidからデータを集めるので集めたいデータのurlはfossdroid.comとなる

$ scrapy genspider apkcrawl_spider fossdroid.com
$ vim spiders/apkcrawl_spider.py

# -*- coding: utf-8 -*-
from ..items import ApkcrawlItem
import scrapy


class ApkcrawlSpiderSpider(scrapy.Spider):
    name = 'apkcrawl_spider'
    allowed_domains = ['fossdroid.com']
    start_urls = (
            'http://fossdroid.com/c/system/whats_new.html',
            'http://fossdroid.com/c/internet/whats_new.html',
            'http://fossdroid.com/c/multimedia/whats_new.html',
            'http://fossdroid.com/c/games/whats_new.html',
            'http://fossdroid.com/c/navigation/whats_new.html',
            'http://fossdroid.com/c/time/whats_new.html',
            'http://fossdroid.com/c/writing/whats_new.html',
            'http://fossdroid.com/c/science-education/whats_new.html',
            'http://fossdroid.com/c/reading/whats_new.html',
            'http://fossdroid.com/c/connectivity/whats_new.html',
            'http://fossdroid.com/c/theming/whats_new.html',
            'http://fossdroid.com/c/security/whats_new.html',
            'http://fossdroid.com/c/development/whats_new.html',
            'http://fossdroid.com/c/money/whats_new.html',
            'http://fossdroid.com/c/sports-health/whats_new.html',
            'http://fossdroid.com/c/phone-sms/whats_new.html',
            'http://fossdroid.com/c/graphics/whats_new.html'
            )


    def parse(self, response):
        for res in response.css("h5"):
            apk = ApkcrawlItem()
            apk['title'] = res.css("a::text").extract_first()
            print(apk['title'])
            nextpage = res.css("a::attr('href')").extract_first()
            print(nextpage)
            if nextpage:
                url = response.urljoin(nextpage)
                apk['url'] = url

            yield apk

start_urlsにはクロールするurlを書いておく。start_urlsに書いてあるurl毎にparse関数を実行していくことになる.

あとはfossdroidのソースコードを確認していく。urlから更にurlを入手してcrawlしていく方法がわからなかったため、ここではアプリケーションページのurlを入手することにした.

scrapyの実行

$ scrapy crawl apkcrawl_spider -o apklist.json

-oで書き出すjsonファイルを指定する.

入手したいapkのファイルは入手したアプリケーション名.htmlのhtmlをapkに変えるだけで良いとわかったのでここからはスクリプトを書く

apkのダウンロード

文字列を置換してひたすらwgetするだけ.保存用のディレクトリapkを作成しておく

import json
import os

f = open("apklist.json","r")
apklist = []
json_data = json.load(f)
for i in range(len(json_data)):
    url = json_data[i]['url'].replace("html","apk")
    os.system("wget "+url+" -P apk")

HTMLハイブリッドアプリの判別

最後に集めたapkの中からHTMLハイブリッドアプリを判別する.apkの実体はzipファイルなのでunzipしてhtmlファイルがあるかないかで判別を行う

事前に作業ディレクトリであるtmpディレクトリとhtmlハイブリッドアプリのapkファイルを保存するhtmlapkディレクトリを作成しておく

#!/bin/bash
for file in `\find ./apk -maxdepth 1 -type f`; do
    unzip -d ./tmp $file
    htmlfile=`find ./tmp/ -type f -name "*.html"`
    for a in $htmlfile; do
        cp $file ./htmlapk/
        break
    done
    rm -r ./tmp/*
done

参考

brokenpasscode Writeup

AngstromCTF2017のAndroid問題brokenpasscodeのwriteup

Writup

いつも通りJavaのコードをみる

MainActivity.java

 public void addListenerOnButton()
    {
        ((Button)findViewById(0x7f0b0055)).setOnClickListener(new android.view.View.OnClickListener() {

            public void onClick(View view)
            {
                int i = Integer.parseInt(((EditText)findViewById(0x7f0b0054)).getText().toString());
                if(i == r)
                {
                    Log.i("debug", (new StringBuilder()).append("If you think you've successfully recovered my passcode...enter actf{").append(i).append("} as the flag!").toString());
                    return;
                } else
                {
                    Log.i("debug", "lmao you messed up");
                    return;
                }
            }

            final MainActivity this$0;

            
            {
                this$0 = MainActivity.this;
                super();
            }
        }
);
    }
protected void onCreate(Bundle bundle)
    {
        super.onCreate(bundle);
        setContentView(0x7f04001a);
        try
        {
            bundle = getPackageManager().getApplicationInfo(getPackageName(), 128);
            if(((ApplicationInfo) (bundle)).metaData != null)
            {
                System.out.println(((ApplicationInfo) (bundle)).metaData.getInt("com.example.guest1.passcode_actf.key"));
                r = ((ApplicationInfo) (bundle)).metaData.getInt("com.example.guest1.passcode_actf.key");
            }
        }
        // Misplaced declaration of an exception variable
        catch(Bundle bundle)
        {
            bundle.printStackTrace();
        }
        addListenerOnButton();
    }

rがAndroidManifest.xmlにある数字だとわかる、apktoolでデコードしてみてAndroidManifest.xmlを見てみると9999999であるとわかる。だがflag送信してみると答えじゃなかった。当日はそこで諦めた。

ここからWriteupを見てみたがMETA-INFの署名情報と比較すると答えと違うことが分かる。

$ cat tmp/META-INF/MANIFEST.MF| grep Android -A 2
Created-By: Android Gradle 1.5.0

Name: res/drawable-xhdpi-v4/abc_ic_star_half_black_48dp.png
--
Name: AndroidManifest.xml
SHA1-Digest: F0T3vG9oImHgTmMPeAu0dfJ0sVk=

$ python 
>>> f = open("AndroidManifest.xml","rb")
>>> data = f.read()
>>> import hashlib
>>> import base64
>>> tmp = hashlib.sha1(data).digest()
>>> tmp
'\x07\x98R\xdaT\xd4O`g\xa5L8\xac\xa6\x0f)\x82\xa3L\x86'
>>> base64.b64encode(tmp)
'B5hS2lTUT2BnpUw4rKYPKYKjTIY='

9999999を変更していけばSHA1-Digestが一緒になるであろうことを予想する

それでbase64の感じからして9999999より小さいと思われる。後はブルートフォース

import hashlib
import base64
import struct

key = "F0T3vG9oImHgTmMPeAu0dfJ0sVk="
encode = base64.b64decode(key)

f = open("./tmp/AndroidManifest.xml","rb")
content = f.read()

for i in range(1000000,9999999):
    tmp = content.replace(struct.pack("i",9999999),struct.pack("i",i))
    if hashlib.sha1(tmp).digest() == encode:
        print(i)
$ python solve.py
8195472

flag:RCTF{8195472}となる

Dai Lai Lake Writeup

WhiteHat Contest13で解けなかったDai Lai LakeのWriteup。

正直ggr力が足りなかっただけの問題(正直知るかって感じがある)

Dai Lai Lake

問題文

Can you find my sensitive infomation?

Dai Lai has acquired a reputation for the land of graceful mountains and debonair water

Download file:

http://material.wargame.whitehat.vn/contests/13/passcode.zip

Writeup

apkが配られるので、いつものようにMainActivity.jadを見てみる

public void onClick(View view)
            {
                if(!keyPadLockedFlag)
                {
                    view = (Button)view;
                    if(userEntered.length() < 4)
                    {
                        userEntered = (new StringBuilder()).append(userEntered).append(view.getText()).toString();
                        Log.v("PinView", (new StringBuilder()).append("User entered=").append(userEntered).toString());
                        passwordInput.setText((new StringBuilder()).append(passwordInput.getText().toString()).append("*").toString());
                        if(userEntered.length() == 4)
                        {
                            view = new DataBase(MainActivity.this);
                            try
                            {
                                view.createDataBase();
                            }
                            // Misplaced declaration of an exception variable
                            catch(View view)
                            {
                                throw new Error("Unable to create database");
                            }
                            try
                            {
                                view.openDataBase();
                            }
                            // Misplaced declaration of an exception variable
                            catch(View view)
                            {
                                throw view;
                            }
                            if(view.getAllNotes().contains(userEntered))
                            {
                                statusView.setTextColor(0xff00ff00);
                                statusView.setText("Correct");
                                error = 0;
                                return;
                            }
                            MainActivity mainactivity = MainActivity.this;
                            mainactivity.error = mainactivity.error + 1;
                            if(error == 5)
                            {
                                final Dialog commentDialog = new Dialog(MainActivity.this);
                                commentDialog.setContentView(0x7f04002f);
                                ((Button)commentDialog.findViewById(0x7f0b008a)).setOnClickListener(view. new android.view.View.OnClickListener() {

                                    public void onClick(View view)
                                    {
                                        view = ((EditText)commentDialog.findViewById(0x7f0b0089)).getText().toString();
                                        String s = String.format("%04d", new Object[] {
                                            Integer.valueOf((new Random()).nextInt(9999))
                                        });
                                        db.Add(view, s);
                                        (new SendMail(_fld0, view, "PASSCODE", (new StringBuilder()).append("Your new PASSCODE is: ").append(s).toString())).execute(new Void[0]);
                                        commentDialog.dismiss();
                                    }

ここだけ見てもおそらくデータベースから照合していることを察した。で詳しく見ていくとassets/内にsqliteがあるので中を見てみる

$ sqlite3 passcode.sqlite
sqlite> .tables
user     zadminz
sqlite> select * from user;
1|xxx@gmail.com|1234
2|aaa@gmail.com|3333
sqlite> select * from zadminz;
1|admin_contest_05@spamdecoy.net|7777
sqlite> 

情報通り1234と3333でOKとかでる。zadminzの内容がflagなのかと試したけどハズレ。ここで止まってた

Writeupを見てみたらspamdecoy.netという囮のメールのものがあるらしい

そこでメールアドレスさえわかっていればメールが読める。なのでadmin_contest_05@spamdecoy.netでログインしてみると

f:id:kataware8136:20170601223138p:plain

f:id:kataware8136:20170601223159p:plain

Your new PASSCODE is: check_your_db_before_building_appがflagとなるあとはsha1にして

WhiteHat{254eb81a7b439405a5d006eb7cfdf0cd841c6d28}