cowrieのセットアップメモ
cowrieはkippoを改良したSSHハニーポットである.
ログをMySQL(mariaDB)に保存するまでのメモ.
基本的にINSTALL.mdを読んでいけばいい.
cowrieはroot権限を持たないユーザーで動かすことを推奨されている.
まずはユーザーの作成から.
$ sudo adduser --disabled-password cowrie $ sudo su - cowrie $ git clone http://github.com/micheloosterhof/cowrie $ cd cowrie $ cp cowrie.cfg.dist cowrie.cfg
とりあえずこれでstart.shを実行すれば2222/tcpでcowrieが待ち受ける.
待ち受けるポートを変更したい場合はcowrie.cfgの該当箇所を編集すればいい.
また, 22/tcpで待ち受けたい場合root権限が必要とされるが, root権限でハニーポットを動かすことは推奨されていない行為である.
そのため, ipatblesなどでポートフォワーディングしてあげるといい.
$ sudo iptables -t nat -A PREROUTING -p tcp --dport 22 -j REDIRECT --to-port 2222
続いて, ログをMySQLに吐き出すように設定を行う.
まずは, ユーザーの権限などの設定.
$ mysql -u root -p $ CREATE USER 'cowrie'@'localhost' IDENTIFIED BY 'PASSWORD'; $ GRANT ALL PRIVILEGES ON *.* TO cowrie@localhost IDENTIFIED BY 'PASSWORD'; $ create database cowrie;
続いて, cowrie.cfgの編集.
[database_mysql] host = localhost database = cowrie username = cowrie password = PASSWORD port = 3306
最後にテーブルを作成する.
$ cd doc/sql/ $ cat mysql.sql | mysql -u cowrie -p -D cowrie
また, cowrie.cfgのデフォルト設定, 特にhostnameあたりは変更しておいた方がいい.
ログなどはlog/以下に保存され, log/tty/以下に侵入者が入力したコマンドを再現できるファイルがある.
このあたりはkippoと同じなので, utils/playlog.py log/tty/hoge.log
で再生できる.
アセンブリメモ -スタック-
関数呼び出し時のスタックの動きなどのメモ.
#include <stdio.h> int func(int a, int b, int c, int d){ int y = 20; char buf[10] = "ABCD"; return a+b+c+d; } int main(){ int x = 10; func(1, 2, 3, 4); }
main()の逆アセンブル結果
0x08048400 <+0>: push %ebp 0x08048401 <+1>: mov %esp,%ebp 0x08048403 <+3>: sub $0x10,%esp 0x08048406 <+6>: movl $0xa,-0x4(%ebp) 0x0804840d <+13>: push $0x4 0x0804840f <+15>: push $0x3 0x08048411 <+17>: push $0x2 0x08048413 <+19>: push $0x1 0x08048415 <+21>: call 0x80483cb <func> 0x0804841a <+26>: add $0x10,%esp 0x0804841d <+29>: leave 0x0804841e <+30>: ret
func()の逆アセンブル結果
0x080483cb <+0>: push %ebp 0x080483cc <+1>: mov %esp,%ebp 0x080483ce <+3>: sub $0x10,%esp 0x080483d1 <+6>: movl $0x14,-0x4(%ebp) 0x080483d8 <+13>: movl $0x44434241,-0xe(%ebp) 0x080483df <+20>: movl $0x0,-0xa(%ebp) 0x080483e6 <+27>: movw $0x0,-0x6(%ebp) 0x080483ec <+33>: mov 0x8(%ebp),%edx 0x080483ef <+36>: mov 0xc(%ebp),%eax 0x080483f2 <+39>: add %eax,%edx 0x080483f4 <+41>: mov 0x10(%ebp),%eax 0x080483f7 <+44>: add %eax,%edx 0x080483f9 <+46>: mov 0x14(%ebp),%eax 0x080483fc <+49>: add %edx,%eax 0x080483fe <+51>: leave
先頭のpush, mov, subといった一連の命令が共通していることが分かる.
これは"関数プロローグ"などと呼ばれ, ebpをスタック上に退避させ, その関数で使用する局所変数のメモリをスタック上に確保する処理である.
main()から順に処理を見ていく.
関数プロローグの処理はfunc()で詳しく追いかけるとし, 0x0804840dからのpush命令から見ていく.
callで関数を呼び出す際はスタックに引数をpushしていく.
0x0804840d <+13>: push $0x4 0x0804840f <+15>: push $0x3 0x08048411 <+17>: push $0x2 0x08048413 <+19>: push $0x1 0x08048415 <+21>: call 0x80483cb <func>
func()呼び出し前に引数が逆順(FILO)でスタック上に確保pushされていることが確認できる.
続いてcallでfuncが呼ばれると, 処理は0x080483cbに移る.
この時, callは自分が呼ばれたアドレスを覚えておかないといけないため, 戻るべきアドレス(リターンアドレス)をスタックにプッシュする.
ここでのリターンアドレスは0x0804841aである.
ここまでのスタックを整理すると以下のようになる.
| | 0xbffff444 --> 0x804841a (Return Addr) | | 0xbffff448 --> 0x1 | | 0xbffff44c --> 0x2 | | 0xbffff450 --> 0x3 |______| 0xbffff454 --> 0x4
続いてfunc()の処理を見ていく.
(gdb) b *0x80483cb Breakpoint 1, 0x080483cb in func () (gdb) x/4i $pc => 0x80483cb <func>: push ebp 0x80483cc <func+1>: mov ebp,esp 0x80483ce <func+3>: sub esp,0x10 0x80483d1 <func+6>: mov DWORD PTR [ebp-0x4],0x14 (gdb) i r ebp ebp 0xbffff468 0xbffff468
func()の関数プロローグである.
ebpの値は0xbffff468で, スタックにpushされる.
これは退避されたフレームポインタ(SFP)と呼ばれ, main()に処理を戻す際にebpを元に戻すために使用される.
結果, スタックは以下のようになる.
| | 0xbffff440 --> 0xbffff468 --> 0x0 (SFP) | | 0xbffff444 --> 0x804841a (Return Addr) | | 0xbffff448 --> 0x1 | | 0xbffff44c --> 0x2 | | 0xbffff450 --> 0x3 |______| 0xbffff454 --> 0x4
続くmov命令でebpの値がespに, つまり, スタックの底とスタックの頂点が同じになる.
さらにsub命令でespが0x10減算される.
これによって0x10バイト分のメモリが確保された.
ここまでが関数プロローグである.
続いてfunc()では局所変数をyとbufの2種類を確保している.
変数確保後(0x080483df)でのスタックは以下のようになる.
(gdb) x/12 $esp 0xbffff430: 0x4241f666 0x00004443 0x080496ac 0x00000014 0xbffff440: 0xbffff468 0x0804841a 0x00000001 0x00000002 0xbffff450: 0x00000003 0x00000004 0x0804842b 0xb7fb6000 (gdb) x/s 0xbffff432 0xbffff432: "ABCD" (gdb) x/u 0xbffff43c 0xbffff43c: 20
| | 0xbffff430 --> 0x4241f666 | | 0xbffff434 --> 0x4443 ('CD') | | 0xbffff43c --> 0x14 | | 0xbffff440 --> 0xbffff468 --> 0x0 (SFP) | | 0xbffff444 --> 0x804841a (Return Addr) | | 0xbffff448 --> 0x1 | | 0xbffff44c --> 0x2 | | 0xbffff450 --> 0x3 |______| 0xbffff454 --> 0x4
このようにスタックはメモリの低位へ成長していく.
続くreturn a+b+c+dは続くmov, addの部分.
0x080483ec <+33>: mov edx,DWORD PTR [ebp+0x8] 0x080483ef <+36>: mov eax,DWORD PTR [ebp+0xc] 0x080483f2 <+39>: add edx,eax 0x080483f4 <+41>: mov eax,DWORD PTR [ebp+0x10] 0x080483f7 <+44>: add edx,eax 0x080483f9 <+46>: mov eax,DWORD PTR [ebp+0x14] 0x080483fc <+49>: add eax,edx
edxにebp+0x8(0xbffff448)の値である0x1を, eaxにebp+0xc(0xbffff44c)の値である0x2を代入し, edxにeaxを加算.
さらにeaxに0x3を代入しedxに加算...のようになり, 最終的にleave命令でeaxが戻り値として返る.
最後のret命令でリターンアドレスであるmain()の0x804841aに処理が戻る.
アセンブリメモ -ループ-
前回に続いてループ処理を見ていく.
#include <stdio.h> int main(){ int i; for (i=0; i<10; i++){ printf("%d\n", i); } return 0; }
0x080483fb <+0>: lea 0x4(%esp),%ecx 0x080483ff <+4>: and $0xfffffff0,%esp 0x08048402 <+7>: pushl -0x4(%ecx) 0x08048405 <+10>: push %ebp 0x08048406 <+11>: mov %esp,%ebp 0x08048408 <+13>: push %ecx 0x08048409 <+14>: sub $0x14,%esp 0x0804840c <+17>: movl $0x0,-0xc(%ebp) 0x08048413 <+24>: jmp 0x804842c <main+49> 0x08048415 <+26>: sub $0x8,%esp 0x08048418 <+29>: pushl -0xc(%ebp) 0x0804841b <+32>: push $0x80484d0 0x08048420 <+37>: call 0x80482d0 <printf@plt> 0x08048425 <+42>: add $0x10,%esp 0x08048428 <+45>: addl $0x1,-0xc(%ebp) 0x0804842c <+49>: cmpl $0x9,-0xc(%ebp) 0x08048430 <+53>: jle 0x8048415 <main+26> 0x08048432 <+55>: mov $0x0,%eax 0x08048437 <+60>: mov -0x4(%ebp),%ecx 0x0804843a <+63>: leave 0x0804843b <+64>: lea -0x4(%ecx),%esp 0x0804843e <+67>: ret
0x08048413にてjmp命令で0x804842cに飛ばされる.
0x804842cではcmp命令で0x9と変数iを比較され, jle命令で9より小さい場合, printfの処理へと移る.
ではbreakが入った場合どうなるか.
#include <stdio.h> int main(){ int i; for (i=0; i<10; i++){ if (i == 5){ break; } printf("%d\n", i); } return 0; }
0x080483fb <+0>: lea 0x4(%esp),%ecx 0x080483ff <+4>: and $0xfffffff0,%esp 0x08048402 <+7>: pushl -0x4(%ecx) 0x08048405 <+10>: push %ebp 0x08048406 <+11>: mov %esp,%ebp 0x08048408 <+13>: push %ecx 0x08048409 <+14>: sub $0x14,%esp 0x0804840c <+17>: movl $0x0,-0xc(%ebp) 0x08048413 <+24>: jmp 0x8048434 <main+57> 0x08048415 <+26>: cmpl $0x5,-0xc(%ebp) 0x08048419 <+30>: jne 0x804841d <main+34> 0x0804841b <+32>: jmp 0x804843a <main+63> 0x0804841d <+34>: sub $0x8,%esp 0x08048420 <+37>: pushl -0xc(%ebp) 0x08048423 <+40>: push $0x80484e0 0x08048428 <+45>: call 0x80482d0 <printf@plt> 0x0804842d <+50>: add $0x10,%esp 0x08048430 <+53>: addl $0x1,-0xc(%ebp) 0x08048434 <+57>: cmpl $0x9,-0xc(%ebp) 0x08048438 <+61>: jle 0x8048415 <main+26> 0x0804843a <+63>: mov $0x0,%eax 0x0804843f <+68>: mov -0x4(%ebp),%ecx 0x08048442 <+71>: leave 0x08048443 <+72>: lea -0x4(%ecx),%esp 0x08048446 <+75>: ret
breakのif文が0x08048415にあることが分かる.
条件式を満たさなければjne命令で0x804841dへと飛び, 満たしていればjmpで0x804843aへと飛ぶ.
ちなみにwhile文もfor文と同じようなアセンブラコードになる.
#include <stdio.h> int main(){ int i=0; while(i<=10){ printf("%d\n", i); i++; } return 0; }
0x080483fb <+0>: lea 0x4(%esp),%ecx 0x080483ff <+4>: and $0xfffffff0,%esp 0x08048402 <+7>: pushl -0x4(%ecx) 0x08048405 <+10>: push %ebp 0x08048406 <+11>: mov %esp,%ebp 0x08048408 <+13>: push %ecx 0x08048409 <+14>: sub $0x14,%esp 0x0804840c <+17>: movl $0x0,-0xc(%ebp) 0x08048413 <+24>: jmp 0x804842c <main+49> 0x08048415 <+26>: sub $0x8,%esp 0x08048418 <+29>: pushl -0xc(%ebp) 0x0804841b <+32>: push $0x80484d0 0x08048420 <+37>: call 0x80482d0 <printf@plt> 0x08048425 <+42>: add $0x10,%esp 0x08048428 <+45>: addl $0x1,-0xc(%ebp) 0x0804842c <+49>: cmpl $0xa,-0xc(%ebp) 0x08048430 <+53>: jle 0x8048415 <main+26> 0x08048432 <+55>: mov $0x0,%eax 0x08048437 <+60>: mov -0x4(%ebp),%ecx 0x0804843a <+63>: leave 0x0804843b <+64>: lea -0x4(%ecx),%esp 0x0804843e <+67>: ret
アセンブリメモ -if文-
Cを逆アセンブルした結果どのようになるかのメモ.
まずはif文
if文
#include <stdio.h> int main(){ int x = 1; if(x > 5){ printf("x > 5\n"); } return 0; }
0x080483fb <+0>: lea 0x4(%esp),%ecx 0x080483ff <+4>: and $0xfffffff0,%esp 0x08048402 <+7>: pushl -0x4(%ecx) 0x08048405 <+10>: push %ebp 0x08048406 <+11>: mov %esp,%ebp 0x08048408 <+13>: push %ecx 0x08048409 <+14>: sub $0x14,%esp 0x0804840c <+17>: movl $0x1,-0xc(%ebp) 0x08048413 <+24>: cmpl $0x5,-0xc(%ebp) 0x08048417 <+28>: jle 0x8048429 <main+46> 0x08048419 <+30>: sub $0xc,%esp 0x0804841c <+33>: push $0x80484d0 0x08048421 <+38>: call 0x80482d0 <puts@plt> 0x08048426 <+43>: add $0x10,%esp 0x08048429 <+46>: mov $0x0,%eax 0x0804842e <+51>: mov -0x4(%ebp),%ecx 0x08048431 <+54>: leave 0x08048432 <+55>: lea -0x4(%ecx),%esp 0x08048435 <+58>: ret
0x0804840cにて変数x(%ebp-0xc)に1を代入している.
0x08048413にて変数xと0x5を比較している.
0x08048417にてjle命令で0x8048429へと処理が移る.
jle命令は"より小さいか等しい"という条件である.
つまり, 変数xが5より小さい場合に0x8048429へと処理が移るという意味である.
そのため, if文の条件によってcmpの次の命令が変わる.
if文の条件をx < 5とすると次のようになる.
0x0804840c <+17>: movl $0x1,-0xc(%ebp) 0x08048413 <+24>: cmpl $0x4,-0xc(%ebp) 0x08048417 <+28>: jg 0x8048429 <main+46>
jg命令は"より大きい"である.
つまり, xが4より大きいならば0x8048429へ処理が移る.
また, x == 5とすると次のようになる.
0x0804840c <+17>: movl $0x1,-0xc(%ebp) 0x08048413 <+24>: cmpl $0x5,-0xc(%ebp) 0x08048417 <+28>: jne 0x8048429 <main+46>
jneは"等しくない時に"という意味である.
つまりxと5が等しくない時に0x8048429に処理が移る.
次にif-elseの場合.
#include <stdio.h> int main(){ int x = 1; if(x == 5){ printf("x == 5\n"); } else { printf("x != 5\n"); } return 0; }
0x080483fb <+0>: lea 0x4(%esp),%ecx 0x080483ff <+4>: and $0xfffffff0,%esp 0x08048402 <+7>: pushl -0x4(%ecx) 0x08048405 <+10>: push %ebp 0x08048406 <+11>: mov %esp,%ebp 0x08048408 <+13>: push %ecx 0x08048409 <+14>: sub $0x14,%esp 0x0804840c <+17>: movl $0x1,-0xc(%ebp) 0x08048413 <+24>: cmpl $0x5,-0xc(%ebp) 0x08048417 <+28>: jne 0x804842b <main+48> 0x08048419 <+30>: sub $0xc,%esp 0x0804841c <+33>: push $0x80484e0 0x08048421 <+38>: call 0x80482d0 <puts@plt> 0x08048426 <+43>: add $0x10,%esp 0x08048429 <+46>: jmp 0x804843b <main+64> 0x0804842b <+48>: sub $0xc,%esp 0x0804842e <+51>: push $0x80484e7 0x08048433 <+56>: call 0x80482d0 <puts@plt> 0x08048438 <+61>: add $0x10,%esp 0x0804843b <+64>: mov $0x0,%eax 0x08048440 <+69>: mov -0x4(%ebp),%ecx 0x08048443 <+72>: leave 0x08048444 <+73>: lea -0x4(%ecx),%esp 0x08048447 <+76>: ret
続いて, else ifを追加した場合.
#include <stdio.h> int main(){ int x = 5; if(x > 5){ printf("x > 5\n"); } else if( x < 5){ printf("x < 5\n"); } else { printf("x == 5\n"); } return 0; }
0x080483fb <+0>: lea 0x4(%esp),%ecx 0x080483ff <+4>: and $0xfffffff0,%esp 0x08048402 <+7>: pushl -0x4(%ecx) 0x08048405 <+10>: push %ebp 0x08048406 <+11>: mov %esp,%ebp 0x08048408 <+13>: push %ecx 0x08048409 <+14>: sub $0x14,%esp 0x0804840c <+17>: movl $0x5,-0xc(%ebp) 0x08048413 <+24>: cmpl $0x5,-0xc(%ebp) 0x08048417 <+28>: jle 0x804842b <main+48> 0x08048419 <+30>: sub $0xc,%esp 0x0804841c <+33>: push $0x80484f0 0x08048421 <+38>: call 0x80482d0 <puts@plt> 0x08048426 <+43>: add $0x10,%esp 0x08048429 <+46>: jmp 0x8048453 <main+88> 0x0804842b <+48>: cmpl $0x4,-0xc(%ebp) 0x0804842f <+52>: jg 0x8048443 <main+72> 0x08048431 <+54>: sub $0xc,%esp 0x08048434 <+57>: push $0x80484f6 0x08048439 <+62>: call 0x80482d0 <puts@plt> 0x0804843e <+67>: add $0x10,%esp 0x08048441 <+70>: jmp 0x8048453 <main+88> 0x08048443 <+72>: sub $0xc,%esp 0x08048446 <+75>: push $0x80484fc 0x0804844b <+80>: call 0x80482d0 <puts@plt> 0x08048450 <+85>: add $0x10,%esp 0x08048453 <+88>: mov $0x0,%eax 0x08048458 <+93>: mov -0x4(%ebp),%ecx 0x0804845b <+96>: leave 0x0804845c <+97>: lea -0x4(%ecx),%esp 0x0804845f <+100>: ret
次にAND, OR演算子を使用した場合にどういったアセンブリコードになるかを確認する.
まずはAND演算子を用いたコードから.
#include <stdio.h> int main(){ int x = 1; if(x > 0 && x < 10){ printf("x > 0 && x < 10\n"); } else { printf("x > 10 || x < 0\n"); } return 0; }
0x080483fb <+0>: lea 0x4(%esp),%ecx 0x080483ff <+4>: and $0xfffffff0,%esp 0x08048402 <+7>: pushl -0x4(%ecx) 0x08048405 <+10>: push %ebp 0x08048406 <+11>: mov %esp,%ebp 0x08048408 <+13>: push %ecx 0x08048409 <+14>: sub $0x14,%esp 0x0804840c <+17>: movl $0x1,-0xc(%ebp) 0x08048413 <+24>: cmpl $0x0,-0xc(%ebp) 0x08048417 <+28>: jle 0x8048431 <main+54> 0x08048419 <+30>: cmpl $0x9,-0xc(%ebp) 0x0804841d <+34>: jg 0x8048431 <main+54> 0x0804841f <+36>: sub $0xc,%esp 0x08048422 <+39>: push $0x80484e0 0x08048427 <+44>: call 0x80482d0 <puts@plt> 0x0804842c <+49>: add $0x10,%esp 0x0804842f <+52>: jmp 0x8048441 <main+70> 0x08048431 <+54>: sub $0xc,%esp 0x08048434 <+57>: push $0x80484f0 0x08048439 <+62>: call 0x80482d0 <puts@plt> 0x0804843e <+67>: add $0x10,%esp 0x08048441 <+70>: mov $0x0,%eax 0x08048446 <+75>: mov -0x4(%ebp),%ecx 0x08048449 <+78>: leave 0x0804844a <+79>: lea -0x4(%ecx),%esp 0x0804844d <+82>: ret
注目すべき点はcmp命令の部分.
0x08048413 <+24>: cmpl $0x0,-0xc(%ebp) 0x08048417 <+28>: jle 0x8048431 <main+54> 0x08048419 <+30>: cmpl $0x9,-0xc(%ebp) 0x0804841d <+34>: jg 0x8048431 <main+54>
条件が1つだったときは
cmp X, X jle 0xXXXXXXX call <puts> ... cmp X, X jle 0xXXXXXXX cal <puts> ...
のように条件分岐後にifステートメント中の処理コード(printfなど)が来ていた.
しかし, AND演算子を用いた結果,
cmp X, X jle 0xXXXXXXXX cmp X, X jle 0xXXXXXXXX
のようにcmp+jxxのコードが連続して登場するようになる.
OR演算子を使った場合も同様である.
0x08048413 <+24>: cmpl $0xa,-0xc(%ebp) 0x08048417 <+28>: jg 0x804841f <main+36> 0x08048419 <+30>: cmpl $0x0,-0xc(%ebp) 0x0804841d <+34>: jns 0x8048431 <main+54>