###### tags: `MWSCup`
# MWSCUP2022_静的解析まとめ
ELF形式のマルウェア
swiはListing windowを見る
https://faculty.nps.edu/cseagle/assembly/sys_call.html
<!-- ## 参考webページ -->
## 反省点
### 篠崎
- 最初にすべての問題を見ておくべき
- ヒントをちゃんと聞く.重要なことに言及している
- 復号は暗号化と逆手順で行うことに留意する
- 暗号化の際「加算->XOR」なら,復号の際は「XOR->減算」
- **答えを大きな声で喋らない!**(答えを共有するとき)
### 池澤
- 回答回数を意識してとくべき
- 最初は回数制限の記述がなかったのでわからなかったが...
- 例えば回数の制限がなければ適当にぽいものを入力しまくれば良い
- 回数の制限が1回であれば、全員で回答を合わせる
- 必要最小限の労力で回答に届くようにすべき
- (今回は丁寧に中身を見すぎた)
- 解けなかった問題は積極的に別の人が読んでチャレンジすべき
- コピペミスで余計な時間を費やしてしまった場合があった
- 回答側の設定が間違っていたこともあったが...
- ファイルの全体を把握するのはとても良かった
- 命名規則を統一しておくと良いかもしれない
- system_
## 当日の解き方
### 問1
#### 1-1
このマルウェアは複数のIPアドレスとポートをコンフィグとして持つことができる。1つのコンフィグのサイズを10進数で答えよ。
- FUN_08048515
- 最初のforが怪しい
- DAT_080d4b00でcharの配列らしきものを見ている
- 掛け算 + 定数でcharの比較
- 0x3c = 60 妥当そうな文字数
- より確実に回答するためには↑(DAT_080d4b00)の初期化部を探すとよさげ
- ただいま探索中
- ↑訂正:DAT_080d4b00はintの比較なので初期化部はない、paramの方にIPアドレスの情報が載っている
- param自体はハードコーディングされている
- connect系のmessage stringが散見
- 篠崎
- FUN_08048515にIPアドレス(172.16.123.133)が渡されている
- FUN_0805f1a0に5.0.0.0が渡されている
- 08048cecにCRC32が見つかった
- 嶋田
- FUN_08048515にアドレス渡し
- FUN_08064ba0
- 池澤2
- 0x3eが回答
- 37行目とかを見るとchar[60]とshortから構成されたCONFIGであることがわかる
- 0x3cが文字列長, shortで2byte追加されて0x3e
- 0x3e = 62
#### 1-2
このマルウェアが保有できるIPアドレスとポートの組の最大数を10進数で答えよ。
- 1-1と同じ場所
- for(DAT_080d4b00 = 0;DAT_080d4b00 < 10;DAT_080d4b00++)
- なので10個では?
- これは大丈夫そう
| 池澤:一度次の問題に移ります
#### 1-3
本検体の通信先(C2サーバ)を"IPアドレス:ポート番号"の形式で答えよ。 複数存在する場合はカンマ(,)区切りで連続させて、IPアドレスの第4オクテットが小さい順に並べること。
- パット見1個だけに見えるが...
- 複数存在することに言及しているのでちょい待ち
- TODO
- 1-1の関数でどのようにparamsが使われているか見る
- DONE
- strings search
- 080d40a0しか生のIPはなさそう?
- ただポート番号がない...
- ポート番号はあったので、1つで問題なさそう
- 0x080d40dcがport番号
- 0xbb,0x01 shortで解釈すると47873?
- エンディアンは大丈夫か怪しい
- 逆で解釈すると443
- こっちのほうが正しそう
- 
- なので443です
172.16.123.133:443
- 間違いでしたまだ回答できてよかった
- わからん...
- 後回しにします
- 嶋田
- 0x656e6f4e という怪しめな値がFUN_0805f1a0に与えられている
### 問2
#### 2-1
FUN_0804b79e はC2サーバから受信したコマンドをもとに処理を決定する関数である。コマンド数を答えよ。
- 篠崎
1. 137
1. 132
1. 130
1. 131(130より大きく133未満で132でない)
1. 21
1. 134
1. 133(134未満)
1. 135
1. 198
1. 139
1. 138(138以上で139未満)
2. 143
3. 247
4. 248
5. 246
- 以上,15個
- 嶋田
- 13?
- 池澤
- 0x89
- 0x84
- 0x82
- 0x83~0x84
- 0x15
- 0x86
- 0x85(<0x86)
- 0x87
- 0xc6
- 0x8b
- ~0x8a
- 0x8f
- 0xf7
- 0xf8
- 0xf6
- 以上15個
### 問3
コマンド0x85の処理はどれか。以下から選択せよ。
ファイル読み込み
ファイル書き込み
ファイル削除
ファイルの名前変更
- 篠崎
- 0x85は133
- FUN_08049626が呼ばれる
- local_cにopenの関数が呼ばれてる? 0x05
- open権限は504
- sprintfが呼ばれる
- FUN_0804ec5aでwriteコール
- 私も書き込みだと思います
- FUN_08048a08
- 嶋田
- local_cにopenのコールが呼ばれてる? 0x05
- open権限は504
- FUN_0804ef90でlseekコールが呼ばれている? 19
- lseed lseek() を使用すると、現行のファイルの終わりを超えて新しい ファイル・オフセットを指定することができます。データがその位置に書き込まれる場合、このデータと旧ファイルの終わりとの間 のギャップでの読み取り操作は、複数のゼロを含むバイトを戻します(つまり、ギャップはゼロで埋められると仮定されます)。
- FUN_0804ec50でWrite callの呼び出し
- FUN_0804ed10でclose
- さすがに書き込み? 最後の処理 FUN_08048a08だけ長くて見ていないからここが怖い
- 池澤
- FUN_08048a08はRC4で暗号化して、sendしている気がする
書き込み&書き込んだBytes数をアップロード
### 問4
コマンドID 0xF6 の動作を説明せよ。
- 嶋田
- 0x86 = 246
- FUN_0804ad79の実行
- FUN_0804b003内で"Exit"と"cd"という文字の登場
- pipeを作っている
- dupをしている
- execぽいことをしている
- enc_sendをしている
- fdはグローパル変数で管理されている
- バックドアとしてC2サーバへShell機能を提供する。コンテキストに応じてshellへのpipeを開いたり、すでにあるshellに対して情報を送信したり、現在いるディレクトリなどの装飾をともなうshellのプロンプトを提供したりする。
### 問5
ファイル名の変更をするコマンドIDを16進数で答えよ。
例えば、コマンドIDが 0x1234ABCD だと判明した場合、0x1234ABCD の形式で回答せよ。
- 篠崎
- 132(0x84)
- FUN_080493e5
- "`/`"や"`\\`"が登場している(パスで使われる文字)
- DAT_080d8780を見る
- FUN_08049626でも呼ばれている(コマンド0x85)
- DAT_080d8780にはファイルパスが入る?
- 143(0x8f)
- renameコールが呼ばれていた
- 嶋田
- 暫定の答え 143 (0x8f)
- FUN_0804a456
- 中でrenameコールが呼ばれていた? (0x26)
- 132(0x84)
- openとcloseのコールが呼ばれていた
- renameのこーるは恐らくなかった
- renameならファイル変える必要ない?
### 問6
#### 6-1
FUN_080482c5 はマルウェアが通信の暗号化のために使用する関数である。
暗号のベースとなっているアルゴリズムを以下から選択せよ。
- 篠崎
- 回答:RC4
- 最初に256個の要素を持つ初期テーブル作成
- 配列のスワップをしている
- KSAとPRGAを確認
- 鍵は5行目で宣言されている配列?
- 池澤
- 少なくともAESではない
- roundの形は見えない
- XXTEAでもない
- シフトを多用する演算がない
- XORとRC4の差別化ができない
- swapはありそう
- 確かにRC4で間違いなさそう
- https://kashiwaba-yuki.com/windows-windbg-011-rc4#ksa%E3%81%AE%E3%83%87%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%AB
これ何?RC4で足し算ある?
```
*(char *)(i + data) = *(char *)(i + data) + (char)l_param5;
```
#### 6-2
マルウェアが暗号化で使用する鍵を16進数で答えよ。
例えば、鍵が文字列 “ABCD” だった場合、\x41\x42\x43\x44 の形式で回答せよ。
- 篠崎
- keyにはFUN_080482c5のparam_3が入っている(鍵長はparam_4=0x10)
- FUN_08048294のparam_1
- おそらく以下が鍵
```
local_4c = 0xa3;
local_4b = 0x78;
local_4a = 0x26;
local_49 = 0x35;
local_48 = 0x57;
local_47 = 0x32;
local_46 = 0x2d;
local_45 = 0x60;
local_44 = 0xb4;
local_43 = 0x3c;
local_42 = 0x2a;
local_41 = 0x5e;
local_40 = 0x33;
local_3f = 0x34;
local_3e = 0x72;
local_3d = 0;
```
- 回答:
- `\xa3\x78\x26\x35\x57\x32\x2d\x60\xb4\x3c\x2a\x5e\x33\x34\x72\x00`
- N回試せるので↑まず投げてみたら正解でした!!
#### 6-3
FUN_080482c5 のアルゴリズムで暗号化されたファイルを encrypted.bin として取得した。
解析結果をもとにファイルを復号し、得られた平文を答えよ。
なお、ベースとなっている暗号アルゴリズムのコードは encrypt_hint.py として実装されている。必要なチームは本コードをベースに復号コードを書いてもよい。
```
void q234_enc(int data,int size,int key,int mayKeySize_0x10,byte param_5_0x16)
{
byte mayKey [256];
byte S [256];
int i;
uint j;
uint l_param5;
byte tmp_Key;
byte tmp_b;
byte S_i_tmp;
for (i = 0; i < 0x100; i = i + 1) {
S[i] = (byte)i;
}
for (i = 0; i < 0x100; i = i + mayKeySize_0x10) {
for (j = 0; ((int)j < mayKeySize_0x10 && ((int)(j + i) < 0x100)); j = j + 1) {
mayKey[i + j] = *(byte *)(j + key);
}
}
j = 0;
for (i = 0; i < 0x100; i = i + 1) {
S_i_tmp = S[i];
tmp_b = mayKey[i];
j = S_i_tmp + j + (uint)mayKey[i] & 0xff;
tmp_b = S[j];
S[i] = S[j];
S[j] = S_i_tmp;
}
l_param5 = (uint)param_5_0x16;
j = 0;
for (i = 0; i < size; i = i + 1) {
tmp_Key = S[i + 1U & 0xff];
j = S[i + 1U & 0xff] + j & 0xff;
S[i + 1U & 0xff] = S[j];
S[j] = tmp_Key;
tmp_b = S[i + 1U & 0xff] + tmp_Key;
tmp_Key = S[(byte)(S[i + 1U & 0xff] + tmp_Key)];
if ((l_param5 & 0x80) == 0) {
*(char *)(i + data) = *(char *)(i + data) + (char)l_param5;
*(byte *)(i + data) = *(byte *)(i + data) ^ tmp_Key;
}
else {
tmp_Key = tmp_Key ^ *(byte *)(i + data);
*(byte *)(i + data) = (char)l_param5 + tmp_Key;
}
}
return;
}
```
```
KEY = b'\xa3\x78\x26\x35\x57\x32\x2d\x60\xb4\x3c\x2a\x5e\x33\x34\x72'
def encrypt(data: bytes, key: bytes) -> bytearray:
result: list[int] = []
S: list[int] = [0] * 0x100
for i in range(0, 0x100, 1):
S[i] = i
chKey= ([0 for i in range(int(1e4))])
for i in range(0,0x100,0x10):
for j in range(0,0x10-1):
if i+j >= 0x100:
break
chKey[i+j] = key[j]
j: int = 0
for i in range(0, 0x100):
S_i_tmp: int = S[i]
k: int = chKey[i % len(key)]
j = (S_i_tmp + j + k) & 0xFF
S[i] = S[j]
S[j] = S_i_tmp
j: int = 0
for i in range(0, len(data), 1):
S_i_tmp: int = S[(i + 1) & 0xFF]
j = (S[(i + 1) & 0xFF] + j) & 0xFF
S[(i + 1) & 0xFF] = S[j]
S[j] = S_i_tmp
index: int = (S[(i + 1) & 0xFF] + S[j]) & 0xFF
xor_key: int = S[index]
calc: int = (data[i]+0x16) ^ xor_key
result.append(calc)
return bytearray(result)
```
- 篠崎
```
import sys
# Please replace the "KEY" value
KEY = [0xa3,0x78,0x26,0x35,0x57,0x32,0x2d,0x60,0xb4,0x3c,0x2a,0x5e,0x33,0x34,0x72,0x00]
KEY_LEN = 0x10
key_ = [0 for i in range(0x100)]
def encrypt(data: bytes, key) -> bytearray:
result: list[int] = []
S: list[int] = [0] * 0x100
for i in range(0, 0x100, 1):
S[i] = i
for i in range(0, 0x100, KEY_LEN):
for j in range(0, KEY_LEN):
if i+j >= 0x100:
break
key_[i+j] = key[j]
# print(key_[i+j])
j: int = 0
for i in range(0, 0x100, 1):
S_i_tmp: int = S[i]
k: int = key_[i]
j = (S_i_tmp + j + k) & 0xFF
S[i] = S[j]
S[j] = S_i_tmp
j: int = 0
for i in range(0, len(data), 1):
S_i_tmp: int = S[(i + 1) & 0xFF]
j = (S[(i + 1) & 0xFF] + j) & 0xFF
S[(i + 1) & 0xFF] = S[j]
S[j] = S_i_tmp
index: int = (S[(i + 1) & 0xFF] + S[j]) & 0xFF
xor_key: int = S[index]
calc: int = ((data[i]+0x16)&0xff) ^ xor_key
result.append(calc)
return bytearray(result)
if __name__ == "__main__":
if len(sys.argv) != 2:
print(f"[*] usage: {sys.argv[0]} filepath")
sys.exit(0)
try:
with open(sys.argv[1], "rb") as f:
data = f.read()
result = encrypt(data, KEY)
print(result)
except FileNotFoundError:
print(f"[!] {sys.argv[1]} was not found ;(")
except Exception as e:
print(e)
```
## 解説の内容
- MWSCUPで初のELFマルウェア
- RAT
### 問1
- 実際のマルウェアの特徴を調べる目的
- Errorメッセージを辿ることが、理解を進める上での1つの手
### 問2
- コマンド数は数える
- システムコールを見ればわかる(swiのeaxを参照)
### 問3
- 文字列からshellを開くことを推定
### 問5
- renameのシステムコール番号が分かれば、検索をかければ良い。
### 問6
- 慣れているとぱっと見で分かる(RC4を見た瞬間分かるかのページを参照)
- dataの型が分かったらさっさと変えると分かりやすい
- 複合処理は暗号化と手順が逆