Radare2 メモ
控え目に言ってコマンドが分かりにくいandリファレンスが足りてないので自分用にメモ。
afvn old_name new_name @ func
func内のold_nameをnew_nameに変える。 デフォルトだとlocal_4hとかarg_4hとかになっているのでよほど短いコードか抜群の記憶力がないと動作を把握できないので解明は積極的にした方がいいと思う。
afn new_name old_name
関数の名前を変える。 何故か変数とは古い名前と新しい名前の順番が入れかわっているので混乱しがち。 その上変更に失敗してもエラーメッセージが表示されない(表示する機能ないんだろうか?)ので気づきにくい。 同じような機能で違うパターンを使うのは良くないUIだと思うんだけど、理由があるんだろうか?
f name = addr
アドレスにラベルを付ける。 調べてもさっぱりやり方がさっぱり分からなかったけど、ツイッターで凄い人達が言ってたのを見てようやく知った機能。 nameをobj.nameのようにピリオド区切りにしないと、そのアドレスを参照している箇所でラベルが反映されないので注意。 引数の値によって動作変えるのいくない。
Ps name
今解析しているプログラムの解析結果を保存する。 ラベルとか関数に付けた名前とかが保存される。 長すぎる関数は保存してロードしたときに壊れる可能性がある。 極力使わない方がいいかもしれないが代替案もない。
ロードしたときに関数の境界がおかしくなったときの解決策
afu end_addr @ function_addr
で関数の終端を指定できる。
pdコマンドで終端を目で探してから、end_addrでretの次のアドレスを指定しないとend_addr自体は含まれない。
Po name
保存した解析結果をロードする。 r2コマンドの引数にnameを指定するとロードして起動するので、こっちの方を使うことが多いかもしれない。
r2 -p name
関数として解析したいとき
afr func_name address
これでaddress
の位置から関数として解析できる。
afva
で自動的に引数の解析をしてくれる。
afv
系のコマンドで操作できるらしいが、思い通りに動かない。
pwn基礎 バッファオーバーフロー1
自分自身の理解を深めるために、また後でまぬけにも分からなくなったときに参照できるようにするために、基本事項についてまとめておこう。 もしかしたら間違っているところ教えてもらえるかもしれない。
pwn バッファオーバフローを使った攻撃
x86を想定する。 他のアーキテクチャだと話が変わってくるかもしれない。 pwnでもっとも基本的な攻撃だと思われるバッファオーバーフローについてのメモ。
必要な知識
バッファオーバーフローがどのようにして発生し、どのようにして攻撃に利用するかを理解するために必要な知識。
関数呼び出しによるスタックの変化
関数呼び出しが行なわれると様々なお決まりの処理が行なわれる。 その中でも値がどのようにスタックに積まれるかは、様々なexploitで使える知識である。 スタックは下位の方向に積まれることに注意。 例えば、push命令が実行されるとespの値は減る、スタックの位置を戻すときはespに加算されることになる。
参考までに関数呼び出しされた直後に実行される命令は以下のような感じになる。 この例ではcanaryはなし。
80484c4: e8 92 ff ff ff call 804845b <vuln> ; 1 0804845b <vuln>: 804845b: 55 push ebp ; 2 804845c: 89 e5 mov ebp,esp ; set current stack top to ebp. 804845e: 81 ec 18 01 00 00 sub esp,0x118 ; 4 ...
以下の順番で値が積まれる。
- call命令の次の命令があるアドレスをスタックに積む、いわゆるリターンアドレス
- その時点でのebpをスタックに積む
- canaryを設定する、これがある場合バッファオーバフローが検出される
- ローカル変数があるなら、ここにそのための領域を確保する
スタックはこのような状態になる。
stack |
---|
4 (local var) |
3 (canary) |
2 ebp |
1 return addr |
()付きのものは場合によっては無いときもあるもの。
ebpを積むのは、関数を抜けるときに値を復元するためである。 どの時点で関数が呼ばれても引数やローカル変数に対して同じ命令、レジスタでアクセスできるように、ベースポインタを現在のスタックトップに設定する必要があるのでebpは変更される。
canaryを積むのは、バッファオーバーフローによる不正なスタックの書き換えを検出するためである。 大抵の場合乱数が使われ、先頭がNULLバイトになるようになっている。 canaryを書き換えてしまうと、バッファオーバーフローが検出され書き換えたリターンアドレスを実行させることができなくなる。 そのためcanaryがある場合にバッファオーバーフローさせるには、canaryの値を上書きしないように同じ値を書き込むようにしなければならない。 forkしたりしてもcanaryの値は同じままなので、そのような場合には一度canaryをリークさせてから再びその値を用いてオーバーフローさせることもできる。
関数からのreturn
関数からリターンするときの処理について。
大抵一番最後にleave; ret
の命令が出現する。
参考までに実際の命令。
80484b1: c9 leave 80484b2: c3 ret
leave命令はespをebpに設定し、popしてebpに設定する。
つまりmov esp, ebp; pop ebp
と等価である。
ebpは一番最初に設定したcall時のespになっている。
そしてそれは元のebpが格納されている位置を指している。
前の図でいうと2 ebp
を指していることになる。
つまり、ebpを復元していることになる。またpopしたことでespはひとつ戻り、リターンアドレスの位置を指すようになる。
ret命令は細かい仕様が色々あるけど、通常はpop eip
と同じだと思っていい。
つまり、現在のスタックトップに格納されている値の位置に命令カウンタを動かすという理解で十分である。
正しく実行されたならば、この時点でespはリターンアドレスを指しているため、関数呼び出しの次の命令がeipにセットされる。
そしてpopが実行されたことでespも復元される。
ちなみに返り値はたいていの場合eaxにセットされている。 ただし、これはコンパイラの生成するコードに依存するため、場合によっては違うレジスタを介して返り値を返したりしているバイナリもあるかもしれない。
バッファへの書き込み
C言語などでは、入力を受けとるときにfgetsを使うことができる。 宣言はman見た限りこんな感じ。
fgets(char* s, int size, FILE* stream);
動作としてはsにsizeバイトだけstreamから読み込むといった感じになる。 ここで一つ大きな問題がある。fgetsはsの大きさがsizeより小さいかどうかをチェックしないのである。(検出するツールなどは存在するらしい) では、sより大きなsizeを引数に渡すとどうなるのか? 答えは、単純に大きさを無視してメモリ上に書き込んでいくのである。
これで必要な知識は全て揃っているはず。
バッファオーバーフロー
書き換えの範囲
これで、fgetsなどでバッファのサイズをきちんと管理してないとバッファの大きさを無視してメモリに書き込んでしまうということが分かった。 このバッファがローカル変数の固定配列として宣言されていると考えてみよう。 このとき、スタックの4の位置にバッファが存在していることになる。 バッファに対する書き込みは低位から高位に向かって行なわれるため、図でいうところの下の方向に向かって書き込まれていくことになる。 これらのことを統合して考えると、バッファのサイズより書き込めるサイズが十分に大きいとき3、2、1全ての位置に任意の値を書き込めるということが分かる。 ただし、canaryは書き換えて違う値にしてしまうとバッファオーバーフローとして検知される。
また、単にバッファより先に確保された変数についても書き換えが可能である。
書き換えの範囲にある値
さらにここで、1が書き換え可能であるということを掘り下げる。 といっても単純なことで、リターンアドレスは関数から返った後に実行される命令の位置なので、リターンアドレスを書き換えられると任意の位置の命令を実行させられるよね?ということである。
これがいわゆる典型的なバッファオーバーフロー攻撃である。
デモ
簡単なオーバフローによる書き換えのデモ。 ここでは、オーバーフローによって関数内で定義されたローカル変数を変更することができるを試してみる。 bofの脆弱性を含ませたコードは以下。
#include <stdio.h> void vuln() { int flag = 0; char buf[256]; fgets(buf, 300, stdin); // vulnerability here, you can write 300 chars to 256 len buffer. if (flag == 0) { printf("Failed!\n"); } else { printf("You won FLAG!\n"); } } int main(int argc, char** argv){ vuln(); return 0; }
コンパイルするにはgcc -m32 -fno-stack-protector overflow.c -o overflow
これで、x86のcanaryなしのバイナリがコンパイルされる。
main関数から一度呼び出しを挟んでいるのは、main関数だけ変なバリデーションのようなコードが生成されて上手くいかないときがあるので、それを避けるためで意味はないです。
vuln関数では、最初にflag = 0
されているので、"Failed!"が表示されるはず。
しかし、バッファオーバーフローさせることでflagの値を書き換えることができるので、if文の分岐を変更させられる。
300文字のAとかを入力すれば、"You won FLAG!”が表示され、成功するはず。
これで、実際にバッファ配列とは無関係な値を変更し、プログラムの挙動を意図していないものに変更させられることが確認できると思う。
CBCTF2017の供養
参加したけど1問しか解けてない。 writeup書いても仕方ないような問題(Common modulus1)なので他の人に任せよう。 なので解くのに使った自分のスクリプト紹介をする。 lispで書いているのでroswellとemacsとslimeがあれば基本動く。 sbclしか想定していないけど、それ以外でも動くかもしれない。
ここにある。
基本的には暗号系の問題を解くのに使っている。 lispで書いていて、いくつかはquicklispのlocal-projectsに入れるなりリンク張るなりすれば簡単にロードできるようになる。 今回はrsaの中のhackrsaを使った。 いまのところ、common modulus attackとwiener attackはまともに動くはず。 今回の問題では計算に時間がかかり過ぎて解けなかったので、改善してあるのでそこそこ使えるはずだと思う。
基本的にはあちこちにpythonスクリプトがあるのでわざわざlispを憶えてまでこれを使う必要はない気がするが...
利点としては、組み込みで分数があり多倍長整数を扱えるため整数の演算では精度は落ちないこと、REPLでの評価がインデント崩れると死ぬpythonよりやりやすい気がすること、sbclならデフォルトでGMP、MPFRを使えたこと(終わってから気付いた)だろうか。 あと、コンパイルしたり型を指定してチェックしないようにしたりもできるので、pythonより高速に動かせる(とはいえ、速度が必要な場面でpythonは使わない気もするが)。
ここで紹介したし、もう少し使いやすくしようかな。READMEとか書いてテストも書いて。
Dockerまとめ
環境はCentOS7のはず。
インストール方法
curl -fsSL get.docker.com -o get-docker.sh sh ./get-docker.sh
あるいは単にcurl -fsSL get.docker.com | sh
でもいいかもしれない。
Dockerの起動
systemctl start docker
Dockerの状態を確認するにはsystemctl status docker
サーバ起動時にDockerも起動する場合は
systemctl enable docker
使い方
基本的にはgitやlinuxのコマンドを踏襲した名前のサブコマンドを使って管理する。
例えば、imageの一覧はdocker image ls
のような感じになる。
コンテナの動かし方
イメージとコンテナがある。 イメージはコンテナの設計図のようなもので、何をインストールしてどのコマンドを実行するべきかなどの情報を持っているらしい。 一方コンテナは実際に稼動しているサービスのファイルシステムのようである。 イメージに従いコンテナを作りあげ、Dockerはそのコンテナの中でサービスを動かすといったような感じだろうか? 実際のコマンドは以下のような感じになる。
docker container run hello-world
hello-worldというのはリポジトリにある、Helloと表示するだけのDockerイメージである。 これを使ってDockerが使えるかどうか試せる。 他のイメージでもだいたい同じようにして動かせる。
Dockerではコンテナの実行が終了するとそのコンテナを破棄するらしい(実際はしばらくの間/var以下にあるっぽい)。 なので、データを保存したり次の実行時に使う場合には-vを使って共有する領域に書き出すようにするなどの工夫が必要になる。
イメージのダウンロード
どこかが用意したリポジトリから既に設定されたイメージを使うことができる。 基本的に動かそうとしたときにローカルにないなら自動的にダウンロードされることになる。
Dockerイメージの作り方
- docker container commit
- コンテナから作る方法、知らない
- docker image build
- Dockerfileから作る方法、教わった
Dockerfileはこんな感じになった。 FROMには元となるイメージ、このイメージの上に新しい皮を被せていく感じで構成していくことになる。 RUNはコンテナを構成するときに実行するコマンド、ソフトウェアのインストールとか。 CMDはコンテナとして実行されたときにデフォルトで実行するコマンド、hello-worldと自動で表示されたのはこれによるものだと思われる。
FROM base image RUN running-command(like apt install something) CMD defalut-running-command
ビルドするには以下のようにする。
docker image bulid -t image-name path"
dockerhubへのログイン
githubのようなサービスとして、イメージを共有できるdockerhubがある。 ログインしておくとイメージのアップロード、ダウンロードができるようになる。 ログインからアップロードまでの流れ。
docker login docker image tag image-name:tag <user-name>/image-name:latest docker image push <user-name>/image-name docker logout
まとめ
後でもっと詳しく書く。 ほかにもいくつかのイメージをまとめたものをまとめて扱うといったようなこともできる。
Dockerまとめ
環境はCentOS7のはず。
インストール方法
# curl -fsSL get.docker.com -o get-docker.sh # sh ./get-docker.sh
あるいは単にcurl -fsSL get.docker.com | sh
でもいいかもしれない。
Dockerの起動
# systemctl start docker
Dockerの状態を確認するには# systemctl status docker
サーバ起動時にDockerも起動する場合は
systemctl enable docker
使い方
基本的にはgitやlinuxのコマンドを踏襲した名前のサブコマンドを使って管理する。
例えば、imageの一覧はdocker image ls
のような感じになる。
コンテナの動かし方
イメージとコンテナがある。 イメージはコンテナの設計図のようなもので、何をインストールしてどのコマンドを実行するべきかなどの情報を持っているらしい。 一方コンテナは実際に稼動しているサービスのファイルシステムのようである。 イメージに従いコンテナを作りあげ、Dockerはそのコンテナの中でサービスを動かすといったような感じだろうか? 実際のコマンドは以下のような感じになる。
# docker container run hello-world
hello-worldというのはリポジトリにある、Helloと表示するだけのDockerイメージである。 これを使ってDockerが使えるかどうか試せる。 他のイメージでもだいたい同じようにして動かせる。
Dockerではコンテナの実行が終了するとそのコンテナを破棄するらしい(実際はしばらくの間/var以下にあるっぽい)。 なので、データを保存したり次の実行時に使う場合には-vを使って共有する領域に書き出すようにするなどの工夫が必要になる。
イメージのダウンロード
どこかが用意したリポジトリから既に設定されたイメージを使うことができる。 基本的に動かそうとしたときにローカルにないなら自動的にダウンロードされることになる。
Dockerイメージの作り方
+ docker container commit
- docker image build
FROM base image RUN running-command(like apt install something) CMD defalut-running-command
docker image bulid -t image-name path"
dockerhub login
docker login
docker image tag image-name:tag <user-name>/mywhale:latest
make space for container
docker image push <user-name>/mywhale
docker logout
nginx
docker image pull nginx:alpine
docker contianer run -d nginx:alpine
-d is detach-mode
docker container run -d -P nginx:alpine
-P is port mapping
docker container run -d -p 8080:80 nginx:alpine
host 8080 -> docker 80
docker network inspect bridge | less
network setting
docker network create --driver bridge --subnet 192.168.10.0/24 --gateway 192.168.10.1 mynet
run
docker run -it alpine /bin/sh
-i accept stdin
-t pseudo-tty
--net network-name
make volume
docker volume create myvolune
docker run -it -v myvolume:/data alpine /bin/sh
-v volume, this can mount local-directory
swarm
ps auxfw [0] tkgsy@tkgsy-HP-Pr0B00k ~/m/s/docker ➞
Othlotechさん主催のDocker勉強会に参加した
参加することに決めたきっかけ
そもそも去年あたりに一度勧誘を受けていてOthlotechの存在を知っていたし参加したいとも思っていたけど、なかなか興味のあるテーマがなかったり院試があったりして伸び伸びになっていた。 院試が終わりFEも合格したので、余裕ができたので色々なことをやってみたくなった。 なので何かしらに参加しようと思っていたところふとこの勉強会が目に留まり、Docker勉強するいい機会だと思って参加を決めた。 最初のうちは人数が一杯だったのでキャンセル待ちだったが、台風のおかげかキャンセルが出て参加することができた。
参加してみて
見事に遅刻した、まあ滅多に出歩かないのでしかたない。 内容自体はとても面白かったし勉強になった、参加して良かった。 講義はDockerがどのようなものでどのような使い方をされているのか、どのようにして実現されているのかといった基本的な背景の解説から始まった。 そして休憩を挟んでハンズオンが始まった、実際にさくらインターネットのクラウドサービスを借りてそこでいちからDockerの環境を作った。 そしてDockerhubにあるイメージを利用して、Dockerを使ってアプリケーションを稼動させた。 Dockerが生まれることになった背景から、実際にどれほどの効果があったのかを実際にやってみて体験することで、その有効性を体感できた。 やっぱり新しい技術なんかを勉強するときは何故それが必要なのかを理解していると、すんなりと解説を飲み込むことができる。
感想
勉強会というものに始めて参加したけど、やっぱりひとりで勉強するより知ってる人に教わる方が確実に素早く学べるし、積極的に参加した方がいいと思った。 できれば同じことをやれる友達が欲しいんだけど、できなかった、残念。
libnet
libnet
RDNSS付きのルータ広告を投げる必要があったのでlibnetについて調べてみたが情報がなかったため残しておく。
やり方
初期化
libnetにはIPv6に対応したパケットを作成する機能があるため、普通のパケットならば問題なく作成できる。 今回は近隣探索のパケットを投げるため、ICMPv6のパケットの作成もしている。 libnetを使うときは、最初にinit関数を呼ぶ必要がある。 この返り値に作成するパケットの情報が保持されているようなので最後まで引き回すことになる。
// interfaceはパケットを投げる先のインターフェースを指定する。 exp. "enp4s0" libnet_t* l = libnet_init(LIBNET_RAW6, interface, errbuf);
パケット作成
buildと付いた関数がたくさんあるので、その中からそれっぽいのを探してそれを使う。 ICMPv6などのパケットならば、連続してbuild関数を呼び出すとそれっぽく処理してくれる。 今回はICMPv6, IPv6のパケットのbuild関数を呼び出している。 checksumなどの値も自動で計算されるので楽。もちろん間違った値をわざと入れることもできる。
今回使おうとしたRDNSSはうまくオプションを設定できなかったため、直接値を入れるようにしている。 多分もっと普通の方法があると思うけど、なぜか近隣探索のオプション設定がうまくできなかったので力技でやっている。
追記 libnet_ptag_tでそれぞれのプロトコルのブロックを管理しているらしいので試してみる。
送信
最後のlibnet_write
で送信している。
送信先アドレスや送信元アドレスによって失敗することもある。
あまり適当すぎるアドレスだと不正になってしまうようだ。
code
#include <stdio.h> #include <libnet.h> #define ND_RA_MANAGED_CONFIG_FLAG 0x0800000 #define ND_RA_OTHER_CONFIG_FLAG 0x0400000 #define ND_RA_HOP_LIMIT 0x1000000 #define ND_OPT_RDNSS 0x19 #define LIFETIME_INF 0xffffffff typedef struct libnet_in6_addr libnet_in6_addr; void build_icmpv6_rdnss_opt(libnet_t* l, libnet_in6_addr *header, uint8_t *payload, uint32_t lifetime, const char* dns_addr); int main(int argc, char** argv){ if (argc != 5) { fprintf(stderr, "%s <interface> <src addr> <dist addr> <dns addr>\n", argv[0]); exit(1); } // set argv char *interface = argv[1]; char *dist_addr = argv[2]; char *src_addr = argv[3]; char *dns_addr = argv[4]; libnet_t *l; libnet_in6_addr sip, dip, trg; char errbuf[LIBNET_ERRBUF_SIZE]; /*************************************************************** initialize libnet, this must be called before other functionsn ***************************************************************/ l = libnet_init(LIBNET_RAW6, interface, errbuf); if(l == NULL) { printf("libnet_init: %s\n", errbuf); exit(1); } // get ipv6-addr struct sip = libnet_name2addr6(l, src_addr, LIBNET_DONT_RESOLVE); dip = libnet_name2addr6(l, dist_addr, LIBNET_DONT_RESOLVE); /********************************* * build router advertisement * *********************************/ uint32_t lt = LIFETIME_INF; uint8_t payload[16]; build_icmpv6_rdnss_opt(l, &trg, payload, lt, dns_addr); libnet_build_icmpv6_ndp_nadv( ND_ROUTER_ADVERT, // uint8_t type 0, // uint8_t code 0, // uint16_t check_sum 64 * ND_RA_HOP_LIMIT + ND_RA_OTHER_CONFIG_FLAG, // uint32_t flags trg, // libnet_in6_addr target payload, // uint8_t* payload 16, // uint32_t payload size l, // libnet_t* context 0 // libnet_ptag_t ptag, 0 means create new one ); // build ipv6 packet libnet_build_ipv6( 0, // uint8_t traffic class 0, // uint32_t flow label LIBNET_IPV6_H + LIBNET_ICMPV6_NDP_NADV_H, //uint16_t len IPPROTO_ICMP6, //uint8_t nh -> next header 64, //uint8_t hl -> hop limit sip, //libnet_in6_addr src dip, //libnet_in6_addr dst NULL, //uint8_t* payload 0, //uint32_t payload_s l, //libnet_t* l 0 //libnet_ptag_t ptag ); if(libnet_write(l) == -1) { printf("libnet_write: %s\n", libnet_geterror(l)); exit(1); } libnet_destroy(l); return 0; } /** * set config value to header and payload. * @param l libnet context * @param header header of RDNSS, set some value into this * @param payload dns address is set here * @param lifetime lifetime of dns server * @param dns_addr address of dns server, like "2001:db8::1" */ void build_icmpv6_rdnss_opt(libnet_t* l, libnet_in6_addr *header, uint8_t *payload, uint32_t lifetime, const char* dns_addr){ // copy address, builder funciton accepts only uint8_t* for (int i = 0; i < 16; i++) payload[i] = libnet_name2addr6(l, dns_addr, LIBNET_DONT_RESOLVE).__u6_addr.__u6_addr8[i]; header->__u6_addr.__u6_addr8[8] = 0x19; // type num RDNSS header->__u6_addr.__u6_addr8[9] = 0x2 + 0x1; // 0x2 + number_of_dns_addr header->__u6_addr.__u6_addr32[3] = lifetime; return; }