火曜日, 1月 31, 2017

#OCJP-134: ダブル「sh」ELFのリバーシング (Linuxハッキング事件調査)

■はじめに

今回Linuxのハッキング事件のレポートを書かせて頂きます。

内容的には「Linux OS x86」、「ELFバイナリリバーシング」と「シェルコード」の絡みとなります。

この記事を読むだけでもOKですし、もし再現したい場合ASM、gccとLinuxリバーシングのノウハウが必要だと思います。環境的にLinuxのシェルですので解析は全てradare2でやりました。

取り合えず簡単に書きますので、リラックスしながら楽しくに読んで下さい。


■ハッキングされた情報

とあるLinuxマシンに怪しいプロセスが発見されました↓
28641 ?        S      0:00 [kworker/2:0]
30514 ?        S      0:00 [kworker/1:0]
30518 pts/1    S+     0:00 [sh]
31544 ?        S      0:00 [kworker/3:2]
31670 pts/1    S      0:00 netstat -tc <======ココ
プロセスツリーで確認したら↓
systemd-+-acpid
        |-agetty
        |-cron
        |-dbus-daemon
        |-irqbalance
        |-rsyslogd-+-{in:imklog}
        |          |-{in:imuxsock}
        |          `-{rs:main Q:Reg}
        |-sshd-+-sshd---bash ←私はここに居ますよw
        |      `-sshd---sshd---bash---sh---netstat <===========ココ
        |-systemd---(sd-pam)
「netstat -tc」のコマンドを使ったこと無いって聞いたいたので。
「netstat」の親プロセスを探すと「sh」コマンド、それと「sshd」がありますね。

と言う事で親プロセスはここで↓

28641 ?        S      0:00 [kworker/2:0]
30514 ?        S      0:00 [kworker/1:0]
30518 pts/1    S+     0:00 [sh] <==========親プロセス
31544 ?        S      0:00 [kworker/3:2]
31670 pts/1    S      0:00 netstat -tc <========子プロセス
不思議な「sshd」プロセスが見つかりませんでした。

探すと、historyで下記のコマンドが実行されたそうで分かりました↓

2097  2017-01-XX 14:30:54 rm -rf sshd
hddはext4のフォーマットなので、extundelete /○○ --restore-allで偽sshdを取り戻しました↓
-rwxr-xr-x  1 xxx xxx 3336 Jan XX 13:48 sshd
完全に怪しいと思って、「sshd」ファイルの形をチェックしました↓
sshd: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, xxx, stripped
ダイナミックタイプですね、次、リンクされたライブラリをチェック↓
$ ldd sshd
        linux-gate.so.1 (0xb7712000)
        libc.so.6 => /lib/i386-linux-gnu/i686/cmov/libc.so.6 (0xb7556000)
        /lib/ld-linux.so.2 (0xb7715000)
やはり偽者です。

もっと見たら(readelfで)ハッキングされたマシンでコンパイルされた物と分かりました↓

ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x8048357
  Start of program headers:          52 (bytes into file)
  Start of section headers:          2216 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         8
  Size of section headers:           40 (bytes)
  Number of section headers:         28
  Section header string table index: 27
   :
Symbol table '.dynsym' contains 6 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 00000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     2: 00000000     0 FUNC    GLOBAL DEFAULT  UND mmap@GLIBC_2.0 (2)
     3: 00000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.0 (2)
     4: 00000000     0 FUNC    GLOBAL DEFAULT  UND munmap@GLIBC_2.0 (2)
     5: 08048518     4 OBJECT  GLOBAL DEFAULT   15 _IO_stdin_used
ここ迄見たら、どうやってコンパイルしたのか分からないので、ここからsshdのバイナリを調査します。

因みに残したネットワークソケットの情報があるので、ハッカーの元IPが分かりました↓

180.97.220.28:8080
BGP情報↓
23650 | 180.97.220.0/24 | CHINANET-JS-AS | CN | chinatelecom.com.cn | ChinaNet Jiangsu Province Network
プロキシっぽいですね。。


■バイナリーのリバーシング

最初に偽sshdのストリングでチェックし、面白い物を見つけた↓

[^_]
[^_]
;*2$"(
RRRT[S_
/bin  <====  (ノ゚ο゚)ノ オオオオォォォォォォ...
//sh@u <======   ..ノオオオォ…(ノ゚ο゚)ノミ(ノ _ _)ノコケッ!!
.shstrtab
.interp
「bin」と「sh」の単語を見たら気分が悪くなりますね..

sshdバイナリのDATAセクションを見たら「bin」と「sh」の単語が近い所にあります....

さくっとradare2をインストールし、asmにリバースしました↓

0x0804974c      31f6           xor esi, esi <==== DATA XREF 0x08048343
0x0804974d      f6f7           div bh
0x0804974f  ~   e652           out 0x52, al
0x08049750     .string "RRRT[S_" ; len=8
0x08049758     .string "\\a/bin" ; len=6
0x0804975e      47             inc edi
0x0804975f  ~   042f           add al, 0x2f
0x08049760     .string "//sh@u" ; len=7
0x08049767      b03b           mov al, 0x3b
0x08049769      0f05           syscall <===== (ノ゚ω゚)ノ*...ウオオォォォォォォォー!!
こんど「syscall」の単語が出て来ました(--;;)

0x0804974cが0x08048343からxrefされたので、0x08048343を見ようと↓

0x08048330      8d4c2404       lea ecx, [esp + local_4h_2]
0x08048332      2404           and al, 4
0x08048334      83e4f0         and esp, 0xfffffff0
0x08048337      ff71fc         push dword [ecx - 4]
0x0804833a      55             push ebp
0x0804833b      89e5           mov ebp, esp
0x0804833d      51             push ecx
0x0804833e      83ec0c         sub esp, 0xc
0x08048341      6a25           push 0x25  
0x08048343      684c970408     push 0x804974c <====(1) => "1...RRRT[S_../bin.G.//sh@u..;..1....." @ 0x804974c
0x08048348      e8fe000000     call fcn.0804844b <======(2)
0x0804834d      8b4dfc         mov ecx, dword [ebp - local_4h]
0x08048350      31c0           xor eax, eax
0x08048352      c9             leave
0x08048353      8d61fc         lea esp, [ecx - 4]
0x08048356      c3             ret
恐らく0x08048330はmain()の関数ですね。コードに書いた(1)と(2)の説明は下記となります↓

(1)はxrefの元のアドレス、そこに0x0804974cからのデータをスタック(stack)にプッシュされた。
(2)その後fcn.0804844bの関数をコールされます。

fcn.0804844bはこの辺にありますね↓

↑関数の元名前がstripされたそうですね。

fcn.0804844bをradareで分析をすると↓

0x0804844b      55             push ebp
0x0804844c      89e5           mov ebp, esp
0x0804844e      57             push edi
0x0804844f      56             push esi
0x08048450      53             push ebx
0x08048451      83ec24         sub esp, 0x24
0x08048454      8b5d0c         mov ebx, dword [ebp + arg_ch]
0x08048457      8b7508         mov esi, dword [ebp + arg_8h]
0x0804845a      6a00           push 0 
0x0804845c      6aff           push -1 
0x0804845e      6a22           push 0x22
0x08048460      6a07           push 7
0x08048462      53             push ebx 
0x08048463      6a00           push 0 
0x08048465      e896feffff     call sym.imp.mmap <=====mmap()
0x0804846a      89d9           mov ecx, ebx
0x0804846c      89c7           mov edi, eax
0x0804846e      8945e4         mov dword [ebp - local_1ch], eax
0x08048471      f3a4           rep movsb byte es:[edi], byte ptr [esi]
0x08048473      83c420         add esp, 0x20
0x08048476      ffd0           call eax
0x08048478      8b45e4         mov eax, dword [ebp - local_1ch] //memcpyなはずですが....
0x0804847b      895d0c         mov dword [ebp + arg_ch], ebx
0x0804847e      894508         mov dword [ebp + arg_8h], eax
0x08048481      8d65f4         lea esp, [ebp - local_ch]
0x08048484      5b             pop ebx
0x08048485      5e             pop esi
0x08048486      5f             pop edi
0x08048487      5d             pop ebp
0x08048488      e993feffff     jmp sym.imp.munmap <===munmap()
↑このASMの意味はこんな感じです↓

サイズ0x24とPROT_EXEC+PROT_WRITE+PROT_READフラグのmmap(バーチャルメモリのマッングlinux syscall機能)がコールされ、そしてメモリ(stack)上でにデータを書き込んで(およそmemcpyの動きで)、PROT_EXECのフラグなので書き込み終わったらそのままで実行されるように見えます。

この偽sshdがどうやってコンパイルしたか分からないけど、上記のASMが複雑し過ぎて、恐らくコンパイルの時にstripだけじゃなくて、関数とデータが別れるように設定されたそうなので「memcpy」があるはずだが見えない状況です。

その分析ASMデータをもっとシンプルで書くと(少しパッチして、radare2のESILでアナライズをすると)memcpyが見えるようにしました↓

分かり易く上記のASMをCコードに書きましょう!これはデコードの基本ですね、結果は大体こんな感じです↓

void fnc.0804844b(char *_BLOB_DATA, int __size)
  { 
    void *JunkToExec; // argument passed -> int __size=0x24;
    JunkToExec=mmap(0,__size,PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
    memcpy (JunkToExec, *_BLOB_DATA, __size);
    munmap (JunkToExec, __size);
  }
よーし!ここ迄0x0804844b関数の意味が分かったでしょ。実行のローダー機能ですね、英語だと「executable loader」かな? こんな感じのC言語で書いたシェルコードに多いですね。

所で、どうやってfnc.0804844bが起動されでしょ?
ここで上記に書いたmain関数(0x08048330)の流れに繋がりますが、
C言語でmain()関数を書けば大体はこんな感じですね↓

#define __ExecData_SIZE 0x24 // global variable is needed for the "size"..
char __ExecData[] = {"fugahoge.."}; // 実行の為のデータはこんな感じ..
int main(int argc, char *argv[])
{
  fnc.0804844b (__ExecData, __ExecData_SIZE); //ここで↑2件のバリューを0x0804844bに送ります
  return 0;  
}

という意味では"fugahoge"って→「bin」や「sh」単語があるデータ(0x0804974c!0x24)ですね。シェルコードですか?もしシェルコードなら、どんな動きでしょうか?

詳しく確認しましょう。

■シェルコードのリバーシング

一応、偽sshdのリバーシングの時に一応最初からシェルコードの所に行ってしまいましたが、当時未だ色々分かりませんでした。 今、シェルコードがある事、そしてサイズとローダーがある事も分かりますので、明確にシェルコードの部分が見えるようになりました。

この辺ですね↓

それで、radareで最初に偽sshdの「0x0804974c」を開いたけど、 その時にr2は0x0804974cからバイナリのデータとして分析されていたので、ちゃんとx86実行バイナリ「opcode」に分析されなかった。

それをやり直しましょう。

Linux x86のopcode読む方法やツールがあるけど、このシェルコードのサイズが小さいので遣りやすい方法でやりましょう。
私が秀丸で調べ/計算しながら解析したので、その分析した結果はこんな感じです↓

↑まるでシェルコードの形ですね。ASMで結構です(C言語迄に書く必要がありません)。

『重要な調査のヒント』面白いなのは「jnz 0x1f」のコードですね。
意味合い的にこのシェルコードで2回syscall execve("/bin//sh",0,0)を実行されます。自分はこれを初めて見ました。

追加情報ですが、沢山の質問頂き有難う御座います。この場で色々質問の回答をさせて頂きます↓

(1)一番聞かれたポイントはどうやってシェルコードが実行されたのか?
分かり易いように図を作りました↓

読み方は①→②→③→④の方向で、周りの説明を見ながらです。

(2)シェルコードの実行前に、mmap()がコールされた時に、メモリ上で検知が出来る方法がありますでしょうか?

ありません、下記の詳細デバッグ画像を見たら分かるように、mmap()がコールされた時、不具合点が全然見えないので、stack上にフォレンジックをしたいなら、memcpy()からcall eaxの間にある動きのRAMスナップショットを取った方がお勧めです。

(3)デバッギングの時に、シェルコードを確認する方法はありませんか? 

あります。オリジナルのバイナリをパッチしたら『call eax又は((void(*)()) *JunkToExec)』のコールが見えますので、もしシェルコードをデバッギングでみたいならcall eax所で実行をしない、デバッグを一時停止にして、シェルコードが必ず*DWORD[ebp - local_ch]にありますので、[ebp - local_ch]の中身を見たら確実にシェルコードが見えますね。下記は私のFreeBSDでのr2のスクリーンショット↓

記事の通り私は最初シェルコードを読む時にツールを使わなかった、上記の画像はradare2で読み込んだシェルコードの分析結果ですね。そして、このシェルコードから解析などが出来ますね^-^)v 因みに私的に「407504」のdissasは実は「jnz 0x01f」です。


■再現

解析の結果が正しいかどうかの確認が必要です。
偽sshdバイナリを再現したらシェルコードが起動された時にトレースで確認が出来ます。

ちょっとだけミスがありますが結果が大体合っています。

ダブル「/bin//sh」についての再現は↓

※)もし色々試したいならバイナリーをここに保存しました。


■結論

ここ迄の分かった事まとめ↓

1. 中国IPアドレス(180.97.220.28)からのプロキシでハッカーがこのボックスに入りました。
2. ハッキングの方法は恐らくssh経由の攻撃です。
3. ラッピングされたシェルコードのCファイルを偽sshdバイナリとしてコンパイルしたようです。
4. 実行された後にその偽sshdを削除し、開いたshell(sh)を残し、ハッカーが最後に実行したコマンドもそのshell(sh)で発見されました。
5. ハッカーが使ったシェルコードの特徴があり、2回「sh」を実行します。
※)その他の情報が申し訳御座いませんがここで公開が出来ません。

Linuxのリバーシングは楽しいですね!(^-^v #MalwareMustDie!

@unixfreaxjp/0day.jp Tue Jan 31 01:45:34 JST 2017 - AVTokyo ELF Workshop

0 件のコメント:

コメントを投稿