火曜日, 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)を実行されます。自分はこれを初めて見ました。


■再現

解析の結果が正しいかどうかの確認が必要です。自分ですと必ず再現をします。
偽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 件のコメント:

コメントを投稿