こんにちは。セキュリティ対策室のn01e0です。11/14~11/15にかけて開催された国際的な情報セキュリティカンファレンスであるCODE BLUE 2024に昨年に続き参加してきました。
CODE BLUEとは、世界トップクラスの情報セキュリティ専門家による最先端の講演と、国や言語の垣根を越えた情報交換・交流の機会を提供する国際会議です。
欧米の著名な研究者を招へいし、最新の成果を共有するとともに、日本をはじめとするアジア各国の優れた研究者を発掘し、その研究成果を世界へと発信していきます。
医療の世界で使われるCODE BLUEという言葉は、「緊急事態発生」や「関係者招集」を意味します。
インターネットの世界においても、IoT(Internet of Things)の時代を迎えるなど、セキュリティ対策の重要性が高まっており、世界各国の研究者を招集し、事態への対処や解決策を共に考える場が必要とされています。
CODE BLUEは国際的なコミュニティ形成の場となることを目的にするとともに、CODE(技術)によってBLUE(海)を超えて人と人をつなぎ、よりよいインターネットの世界作りに貢献していきます。 https://codeblue.jp/
この記事では、特に印象に残っている発表やワークショップ、コンテストをいくつかピックアップして紹介します。
ワークショップ
BioHacking Village
Biohacking Villageは、常に世界中の専門家を結集し、医療、技術、サイバーセキュリティの専門家が生物医学セキュリティの最新の革新を探求する場を提供することに尽力しています。 今回のCODEBLUE では Device Lab と Capture the Flag の2つを体験いただけます。 これらは、医療機器業界の専門家、医療従事者、およびセキュリティ研究者が、セキュリティに関する知識を共有し、新しい技術を探求するための安全な環境を提供します。 私たちは、相互尊重の上での意見交換が、革新を推進し、医療を前進させる鍵であると信じています。 https://codeblue.jp/program/contests-workshops/biohackingvillage/
BioHacking Villageは医療技術とサイバーセキュリティの橋渡しを目的として開催されており、現地では実際に使用されている医療機器を触ることができます。
分解してみるとデバッグ用のパッドが露出していたり、意外とシンプルな作りになっていて面白かったです。
また、CTFも併設されていました。
コンテスト
Capture the Flag "Code D.A.R.K.: Biohacking Village CTF Challenge":
医療とサイバーセキュリティの世界を橋渡しする CTF です。 各チャレンジは、デジタル資産を保護するのと同様に重要な、生物データを保護する内容になっています。 また CTF に参加するのが初めての方には、CTFを始めるためのヒント、トリック、およびツールを用いたガイダンスを提供します。 https://codeblue.jp/program/contests-workshops/biohackingvillage/
BunkyoWesternsとして参加し、優勝しました。
問題数が非常に多く、解答すると問題が増えるスタイルはSANSのNetWarsを思い出しました。
こういったスタイルのCTFでは、「解きやすく、配点が高い」問題を優先して解くと良いです。
いくつか問題をピックアップして解説します
insulin pump malware
「医療機器を対象としたマルウェアを解析する」というコンセプトの問題のようです。
問題のバイナリはarm64のMach-Oです。CTFでMach-Oのバイナリを解析するのは初めてなので少し楽しかったです。
[0x100003de4]> iI
arch arm
baddr 0x100000000
binsz 33768
bintype mach0
bits 64
canary true
injprot false
class MACH064
compiler clang
crypto false
endian little
havecode true
intrp /usr/lib/dyld
laddr 0x0
lang c
linenum false
lsyms false
machine all
nx false
os macos
pic true
relocs false
sanitize false
static false
stripped false
subsys darwin
va true
[0x100003de4]> afl
0x100003ec0 1 12 sym.imp.__stack_chk_fail
0x100003ecc 1 12 sym.imp.printf
0x100003ed8 1 12 sym.imp.strcmp
0x100003ee4 1 12 sym.imp.strlen
0x100003de4 12 220 sub.main_100003de4
0x100003d4c 4 152 sub._check_killswitch_100003d4c
0x100003ce0 6 108 sub._decode_100003ce0
こんな感じのバイナリです。
問題はいくつかのパートに分かれており、最終的にはKillSwitchを特定するのが目標となります
arm64かつMach-Oという、普段使っているもののあまり読まないタイプのバイナリではありますが、基本は同じです。
特に難読化もされていないので、main
を読んでみましょう
[0x100003de4]> pdf @main
;-- main:
;-- entry0:
;-- _main:
;-- func.100003de4:
;-- pc:
┌ 220: sub.main_100003de4 (int argc, char **argv, int64_t envp);
│ ; arg int argc @ x0
│ ; arg char **argv @ x1
│ ; arg int64_t envp @ sp+0x60
│ ; var int64_t var_0h_3 @ sp+0x0
│ ; var char **var_10h @ sp+0x10
│ ; var int64_t var_0h_2 @ sp+0x18
│ ; var int64_t var_0h @ sp+0x1c
│ ; var int64_t var_20h @ sp+0x20
│ ; var int64_t var_20h_2 @ sp+0x28
│ 0x100003de4 ffc300d1 sub sp, sp, 0x30
│ 0x100003de8 fd7b02a9 stp x29, x30, [var_20h]
│ 0x100003dec fd830091 add x29, sp, 0x20
│ 0x100003df0 bfc31fb8 stur wzr, [x29, -4] ; argc
│ 0x100003df4 a0831fb8 stur w0, [x29, -8] ; argc
│ 0x100003df8 e10b00f9 str x1, [var_10h] ; argv
│ 0x100003dfc a8835fb8 ldur w8, [x29, -8]
│ 0x100003e00 08090071 subs w8, w8, 2
│ 0x100003e04 e8179f1a cset w8, eq
│ ┌─< 0x100003e08 88010037 tbnz w8, 0, 0x100003e38
│ ┌──< 0x100003e0c 01000014 b 0x100003e10
│ ││ ; CODE XREF from sub.main_100003de4 @ 0x100003e0c(x)
│ └──> 0x100003e10 e80b40f9 ldr x8, [var_10h]
│ │ 0x100003e14 080140f9 ldr x8, [x8]
│ │ 0x100003e18 e9030091 mov x9, sp
│ │ 0x100003e1c 280100f9 str x8, [x9]
│ │ 0x100003e20 00000090 adrp x0, 0x100003000
│ │ 0x100003e24 00e03b91 add x0, x0, 0xef8 ; 0x100003ef8 ; "Usage: %s <killswitch>\n" ; const char *format
│ │ 0x100003e28 29000094 bl sym.imp.printf ; int printf(const char *format)
│ │ 0x100003e2c 28008052 mov w8, 1
│ │ 0x100003e30 a8c31fb8 stur w8, [x29, -4]
│ ┌──< 0x100003e34 1f000014 b 0x100003eb0
│ ││ ; CODE XREF from sub.main_100003de4 @ 0x100003e08(x)
│ │└─> 0x100003e38 e80b40f9 ldr x8, [var_10h]
│ │ 0x100003e3c 000540f9 ldr x0, [x8, 8] ; const char *s
│ │ 0x100003e40 29000094 bl sym.imp.strlen ; size_t strlen(const char *s)
│ │ 0x100003e44 082000f1 subs x8, x0, 8
│ │ 0x100003e48 e8179f1a cset w8, eq
│ │┌─< 0x100003e4c 08010037 tbnz w8, 0, 0x100003e6c
│ ┌───< 0x100003e50 01000014 b 0x100003e54
│ │││ ; CODE XREF from sub.main_100003de4 @ 0x100003e50(x)
│ └───> 0x100003e54 00000090 adrp x0, 0x100003000
│ ││ 0x100003e58 00403c91 add x0, x0, 0xf10 ; 0x100003f10 ; "Invalid killswitch. Malware still active.\n" ; const char *format
│ ││ 0x100003e5c 1c000094 bl sym.imp.printf ; int printf(const char *format)
│ ││ 0x100003e60 28008052 mov w8, 1
│ ││ 0x100003e64 a8c31fb8 stur w8, [x29, -4]
│ ┌───< 0x100003e68 12000014 b 0x100003eb0
│ │││ ; CODE XREF from sub.main_100003de4 @ 0x100003e4c(x)
│ ││└─> 0x100003e6c e80b40f9 ldr x8, [var_10h]
│ ││ 0x100003e70 000540f9 ldr x0, [x8, 8] ; int64_t arg1
│ ││ 0x100003e74 b6ffff97 bl sub._check_killswitch_100003d4c
│ ││ 0x100003e78 08000071 subs w8, w0, 0
│ ││ 0x100003e7c e8179f1a cset w8, eq
│ ││┌─< 0x100003e80 c8000037 tbnz w8, 0, 0x100003e98
│ ┌────< 0x100003e84 01000014 b 0x100003e88
│ ││││ ; CODE XREF from sub.main_100003de4 @ 0x100003e84(x)
│ └────> 0x100003e88 00000090 adrp x0, 0x100003000
│ │││ 0x100003e8c 00ec3c91 add x0, x0, 0xf3b ; 0x100003f3b ; "Killswitch activated. Malware deactivated: Achievement unlocked: Cyber Guardian of the Pancreas!\n" ; const char *format
│ │││ 0x100003e90 0f000094 bl sym.imp.printf ; int printf(const char *format)
│ ┌────< 0x100003e94 05000014 b 0x100003ea8
│ ││││ ; CODE XREF from sub.main_100003de4 @ 0x100003e80(x)
│ │││└─> 0x100003e98 00000090 adrp x0, 0x100003000
│ │││ 0x100003e9c 00403c91 add x0, x0, 0xf10 ; 0x100003f10 ; "Invalid killswitch. Malware still active.\n" ; const char *format
│ │││ 0x100003ea0 0b000094 bl sym.imp.printf ; int printf(const char *format)
│ │││┌─< 0x100003ea4 01000014 b 0x100003ea8
│ ││││ ; CODE XREFS from sub.main_100003de4 @ 0x100003e94(x), 0x100003ea4(x)
│ └──└─> 0x100003ea8 bfc31fb8 stur wzr, [x29, -4]
│ ││┌─< 0x100003eac 01000014 b 0x100003eb0
│ │││ ; CODE XREFS from sub.main_100003de4 @ 0x100003e34(x), 0x100003e68(x), 0x100003eac(x)
│ └└└─> 0x100003eb0 a0c35fb8 ldur w0, [x29, -4]
│ 0x100003eb4 fd7b42a9 ldp x29, x30, [var_20h]
│ 0x100003eb8 ffc30091 add sp, sp, 0x30
└ 0x100003ebc c0035fd6 ret
雰囲気で読めます。
check_killswitch
が怪しいですね。
中でstrcmp
も呼んでいます
[0x100003de4]> axt @sym.imp.strcmp
sub._check_killswitch_100003d4c 0x100003d9c [CALL:--x] bl sym.imp.strcmp
おそらく入力値とKillswitchを比較しているのでしょう。
check_killswitch
の中身はこうなっています
[0x100003de4]> pdf @0x100003d9c
;-- _check_killswitch:
;-- func.100003d4c:
; CALL XREF from sub.main_100003de4 @ 0x100003e74(x)
┌ 152: sub._check_killswitch_100003d4c (int64_t arg1, char *s2, char *s1, int64_t arg_50h);
│ ; arg int64_t arg1 @ x0
│ ; arg char *s2 @ sp+0x58
│ ; arg char *s1 @ sp+0x68
│ ; arg int64_t arg_50h @ sp+0xa0
│ ; var int64_t var_0h_3 @ sp+0x8
│ ; var int64_t var_14h @ sp+0x14
│ ; var int64_t var_0h_2 @ sp+0x18
│ ; var int64_t var_19h @ sp+0x27
│ ; var int64_t var_0h_4 @ sp+0x2f
│ ; var int64_t var_10h @ sp+0x30
│ ; var int64_t var_0h @ sp+0x38
│ ; var int64_t var_40h @ sp+0x40
│ ; var int64_t var_40h_2 @ sp+0x48
│ 0x100003d4c ff4301d1 sub sp, sp, 0x50
│ 0x100003d50 fd7b04a9 stp x29, x30, [var_40h]
│ 0x100003d54 fd030191 add x29, sp, 0x40
│ 0x100003d58 080000b0 adrp x8, reloc.__stack_chk_fail ; 0x100004000
│ 0x100003d5c 080540f9 ldr x8, [x8, 8] ; 0x100004008
│ ; reloc.__stack_chk_guard
│ 0x100003d60 080140f9 ldr x8, [x8]
│ 0x100003d64 a8831ff8 stur x8, [x29, -8]
│ 0x100003d68 e00f00f9 str x0, [arg_50hx18] ; arg1
│ 0x100003d6c 08000090 adrp x8, 0x100003000
│ 0x100003d70 08c13b91 add x8, x8, 0xef0 ; 0x100003ef0
│ ; section.2.__TEXT.__const
│ 0x100003d74 080140f9 ldr x8, [x8] ; 0x100003ef0
│ ; section.2.__TEXT.__const
│ 0x100003d78 a04300d1 sub x0, x29, 0x10 ; int64_t arg1
│ 0x100003d7c a8031ff8 stur x8, [x29, -0x10]
│ 0x100003d80 a16700d1 sub x1, x29, 0x19 ; int64_t arg2
│ 0x100003d84 e10700f9 str x1, [var_0h_3]
│ 0x100003d88 02018052 mov w2, 8
│ 0x100003d8c d5ffff97 bl sub._decode_100003ce0
│ 0x100003d90 e10740f9 ldr x1, [var_0h_3] ; const char *s2
│ 0x100003d94 bff31e38 sturb wzr, [x29, -0x11]
│ 0x100003d98 e00f40f9 ldr x0, [arg_50hx18] ; const char *s1
│ 0x100003d9c 4f000094 bl sym.imp.strcmp ; int strcmp(const char *s1, const char *s2)
│ 0x100003da0 08000071 subs w8, w0, 0
│ 0x100003da4 e8179f1a cset w8, eq
│ 0x100003da8 e81700b9 str w8, [var_14h]
│ 0x100003dac a9835ff8 ldur x9, [x29, -8]
│ 0x100003db0 080000b0 adrp x8, reloc.__stack_chk_fail ; 0x100004000
│ 0x100003db4 080540f9 ldr x8, [x8, 8] ; 0x100004008
│ ; reloc.__stack_chk_guard
│ 0x100003db8 080140f9 ldr x8, [x8]
│ 0x100003dbc 080109eb subs x8, x8, x9
│ 0x100003dc0 e8179f1a cset w8, eq
│ ┌─< 0x100003dc4 68000037 tbnz w8, 0, 0x100003dd0
│ ┌──< 0x100003dc8 01000014 b 0x100003dcc
│ ││ ; CODE XREF from sub._check_killswitch_100003d4c @ 0x100003dc8(x)
│ └──> 0x100003dcc 3d000094 bl sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
│ │ ; CODE XREF from sub._check_killswitch_100003d4c @ 0x100003dc4(x)
│ └─> 0x100003dd0 e81740b9 ldr w8, [var_14h]
│ 0x100003dd4 00010012 and w0, w8, 1
│ 0x100003dd8 fd7b44a9 ldp x29, x30, [var_40h]
│ 0x100003ddc ff430191 add sp, sp, 0x50
└ 0x100003de0 c0035fd6 ret
0x100003ef0
の内容は
[0x100003de4]> px 8 @0x100003ef0
- offset - F0F1 F2F3 F4F5 F6F7 F8F9 FAFB FCFD FEFF 0123456789ABCDEF
0x100003ef0 e8e3 e5e9 f3e8 eff8 ........
で、これがdecode
に渡されるようです
[0x100003de4]> pdf @sub._decode_100003ce0
;-- section.0.__TEXT.__text:
;-- _decode:
;-- func.100003ce0:
; NULL XREF from aav.0x100000020 @ +0xb0(r)
; CALL XREF from sub._check_killswitch_100003d4c @ 0x100003d8c(r)
┌ 108: sub._decode_100003ce0 (int64_t arg1, int64_t arg2, int64_t arg3, int64_t arg_20h);
│ ; arg int64_t arg1 @ x0
│ ; arg int64_t arg2 @ x1
│ ; arg int64_t arg3 @ x2
│ ; arg int64_t arg_20h @ sp+0x40
│ ; var signed int64_t var_8h @ sp+0x8
│ ; var int64_t var_ch @ sp+0xc
│ ; var int64_t var_10h @ sp+0x10
│ ; var int64_t var_18h @ sp+0x18
│ 0x100003ce0 ff8300d1 sub sp, sp, 0x20 ; [00] -r-x section size 480 named 0.__TEXT.__text
│ 0x100003ce4 e00f00f9 str x0, [arg_20hx18] ; arg1
│ 0x100003ce8 e10b00f9 str x1, [arg_20hx10] ; arg2
│ 0x100003cec e20f00b9 str w2, [var_ch] ; arg3
│ 0x100003cf0 ff0b00b9 str wzr, [var_8h] ; arg1
│ ┌─< 0x100003cf4 01000014 b 0x100003cf8
│ │ ; CODE XREFS from sub._decode_100003ce0 @ 0x100003cf4(x), 0x100003d40(x)
│ ┌└─> 0x100003cf8 e80b40b9 ldr w8, [var_8h]
│ ╎ 0x100003cfc e90f40b9 ldr w9, [var_ch]
│ ╎ 0x100003d00 0801096b subs w8, w8, w9
│ ╎ 0x100003d04 e8b79f1a cset w8, ge
│ ╎┌─< 0x100003d08 e8010037 tbnz w8, 0, 0x100003d44
│ ┌───< 0x100003d0c 01000014 b 0x100003d10
│ │╎│ ; CODE XREF from sub._decode_100003ce0 @ 0x100003d0c(x)
│ └───> 0x100003d10 e80f40f9 ldr x8, [arg_20hx18]
│ ╎│ 0x100003d14 e90b80b9 ldrsw x9, [var_8h]
│ ╎│ 0x100003d18 08696938 ldrb w8, [x8, x9]
│ ╎│ 0x100003d1c 49158052 mov w9, 0xaa
│ ╎│ 0x100003d20 0801094a eor w8, w8, w9
│ ╎│ 0x100003d24 e90b40f9 ldr x9, [arg_20hx10]
│ ╎│ 0x100003d28 ea0b80b9 ldrsw x10, [var_8h]
│ ╎│ 0x100003d2c 28692a38 strb w8, [x9, x10]
│ ┌───< 0x100003d30 01000014 b 0x100003d34
│ │╎│ ; CODE XREF from sub._decode_100003ce0 @ 0x100003d30(x)
│ └───> 0x100003d34 e80b40b9 ldr w8, [var_8h]
│ ╎│ 0x100003d38 08050011 add w8, w8, 1
│ ╎│ 0x100003d3c e80b00b9 str w8, [var_8h]
│ └──< 0x100003d40 eeffff17 b 0x100003cf8
│ │ ; CODE XREF from sub._decode_100003ce0 @ 0x100003d08(x)
│ └─> 0x100003d44 ff830091 add sp, sp, 0x20
└ 0x100003d48 c0035fd6 ret
decode
ではその値が0xaa
とXORされているので、実際に計算してみましょう
radare2
で対象のバイト列を配列のフォーマットで取得したいときはpc
を使います
[0x100003de4]> pc 8 @0x100003ef0
#define _BUFFER_SIZE 8
const uint8_t buffer[_BUFFER_SIZE] = {
0xe8, 0xe3, 0xe5, 0xe9, 0xf3, 0xe8, 0xef, 0xf8
};
''.join([chr(x ^ 0xaa) for x in [0xe8, 0xe3, 0xe5, 0xe9, 0xf3, 0xe8, 0xef, 0xf8]])
# 'BIOCYBER'
復号できました
$ ./malware_insulin_pump BIOCYBER
Killswitch activated. Malware deactivated: Achievement unlocked: Cyber Guardian of the Pancreas!
実際に試してみると正しいことがわかります
insulin pump malware 2
今度はELFのバイナリです。
複数の難読化手法が用いられており、「どういった難読化が施されているか」という問題もありました。
[0x004010a0]> iI
arch x86
baddr 0x400000
binsz 12625
bintype elf
bits 64
canary true
injprot false
class ELF64
compiler GCC: (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0 clang version 10.0.0-4ubuntu1
crypto false
endian little
havecode true
intrp /lib64/ld-linux-x86-64.so.2
laddr 0x0
lang c
linenum false
lsyms false
machine AMD x86-64 architecture
nx true
os linux
pic false
relocs false
relro full
rpath NONE
sanitize false
static false
stripped true
subsys linux
va true
[0x004010a0]> afl
0x00401030 1 6 sym.imp.getenv
0x00401040 1 6 sym.imp.strlen
0x00401050 1 6 sym.imp.__stack_chk_fail
0x00401060 1 6 sym.imp.printf
0x00401070 1 6 sym.imp.strcmp
0x00401080 1 6 sym.imp.memcpy
0x00401090 1 6 sym.imp.exit
0x004010a0 1 46 sub.entry0_4010a0
0x00401740 4 90 sub.main_401740
0x00401180 5 55 sub.entry.init0_401180
0x00401150 3 32 sub.entry.fini0_401150
0x004010e0 4 31 fcn.004010e0
0x00401190 4 187 fcn.00401190
0x004014b0 6 137 sub.n_4014b0
0x00401460 3 68 sub.DEBUG_401460
0x00401540 1 53 sub.sub.n_4014b0_401540
0x004013f0 1 109 fcn.004013f0
0x00401580 18 441 sub.sub.DEBUG_401460_401580
0x00401000 3 27 sub.init_401000
0x00401250 4 78 fcn.00401250
0x004012a0 4 78 fcn.004012a0
0x004012f0 4 81 fcn.004012f0
0x00401350 4 78 fcn.00401350
0x004013a0 4 78 fcn.004013a0
0x004010d0 1 5 fcn.004010d0
0x004017a0 4 101 sub.sub.init_401000_4017a0
0x00401810 1 5 fcn.00401810
0x00401818 1 13 sub.fini_401818
[0x004010a0]> izq
0x402004 6 5 DEBUG
0x40200a 32 31 Debugging detected! Exiting...\n
0x40202a 25 24 Processing sector %d...\n
0x402043 30 29 Sector %d analysis complete.\n
0x402061 41 40 Opaque predicate failed: Access denied.\n
0x40208a 37 36 Killswitch matched: Access granted.\n
0x4020af 37 36 Killswitch mismatch: Access denied.\n
0x4020d4 24 23 Usage: %s <killswitch>\n
シンボルはstripされています。
まずはmain
から読んでみましょう。
[0x004010a0]> pdg @main
uint sub.main_401740(int32_t param_1, ulong *param_2)
{
ulong uVar1;
ulong *apuStack_20 [2];
int32_t iStack_10;
uint uStack_c;
uStack_c = 0;
apuStack_20[1] = param_2;
iStack_10 = param_1;
if (param_1 == 2) {
uVar1 = param_2[1];
*(*0x20 + -0x20) = 0x40178e;
uStack_c = sub.sub.DEBUG_401460_401580(uVar1);
}
else {
uVar1 = *param_2;
*(*0x20 + -0x20) = 0x401775;
sym.imp.printf("Usage: %s <killswitch>\n", uVar1);
uStack_c = 1;
}
return uStack_c;
}
実行時の引数をsub.sub.DEBUG_401460_401580
に渡しているようです。関数名が分かりづらいので変えておきましょう。
[0x004010a0]> afn check_killswitch sub.sub.DEBUG_401460_401580
[0x004010a0]> pdf @check_killswitch
; CALL XREF from sub.main_401740 @ 0x401789(x)
┌ 441: check_killswitch (int64_t arg1);
│ ; arg int64_t arg1 @ rdi
│ ; var int64_t var_8h @ rbp-0x8
│ ; var int64_t var_10h @ rbp-0x10
│ ; var int64_t var_11h @ rbp-0x11
│ ; var char *s2 @ rbp-0x19
│ ; var int64_t var_20h @ rbp-0x20
│ ; var char *s1 @ rbp-0x28
│ ; var signed int64_t var_2ch @ rbp-0x2c
│ ; var int64_t var_30h @ rbp-0x30
│ ; var int64_t var_34h @ rbp-0x34
│ ; var size_t var_38h @ rbp-0x38
│ ; var int64_t var_3ch @ rbp-0x3c
│ ; var uint32_t var_40h @ rbp-0x40
│ ; var int64_t var_44h @ rbp-0x44
│ 0x00401580 55 push rbp
│ 0x00401581 4889e5 mov rbp, rsp
│ 0x00401584 4883ec50 sub rsp, 0x50
│ 0x00401588 64488b0425.. mov rax, qword fs:[0x28]
│ 0x00401591 488945f8 mov qword [var_8h], rax
│ 0x00401595 48897dd8 mov qword [s1], rdi ; arg1
│ 0x00401599 e8c2feffff call sub.DEBUG_401460
│ 0x0040159e 488d75e7 lea rsi, [s2]
│ 0x004015a2 488d7df0 lea rdi, [var_10h]
│ 0x004015a6 488b053f0b.. mov rax, qword [0x004020ec] ; [0x4020ec:8]=0xe3a2a3c4d3e0aebe
│ 0x004015ad 488945f0 mov qword [var_10h], rax
│ 0x004015b1 ba08000000 mov edx, 8
│ 0x004015b6 e885ffffff call sub.sub.n_4014b0_401540
│ 0x004015bb c645ef00 mov byte [var_11h], 0
│ 0x004015bf c745d40000.. mov dword [var_2ch], 0
│ ; CODE XREF from check_killswitch @ 0x40161b(x)
│ ┌─> 0x004015c6 837dd40a cmp dword [var_2ch], 0xa
│ ┌──< 0x004015ca 0f8d50000000 jge 0x401620
│ │╎ 0x004015d0 8b45d4 mov eax, dword [var_2ch]
│ │╎ 0x004015d3 0faf45d4 imul eax, dword [var_2ch]
│ │╎ 0x004015d7 8945d0 mov dword [var_30h], eax
│ │╎ 0x004015da 8b45d0 mov eax, dword [var_30h]
│ │╎ 0x004015dd 83f864 cmp eax, 0x64 ; 'd' ; 100
│ ┌───< 0x004015e0 0f8516000000 jne 0x4015fc
│ ││╎ 0x004015e6 8b75d4 mov esi, dword [var_2ch]
│ ││╎ 0x004015e9 488d3d3a0a.. lea rdi, str.Processing_sector__d..._n ; 0x40202a ; "Processing sector %d...\n" ; const char *format
│ ││╎ 0x004015f0 b000 mov al, 0
│ ││╎ 0x004015f2 e869faffff call sym.imp.printf ; int printf(const char *format)
│ ┌────< 0x004015f7 e911000000 jmp 0x40160d
│ │││╎ ; CODE XREF from check_killswitch @ 0x4015e0(x)
│ │└───> 0x004015fc 8b75d4 mov esi, dword [var_2ch]
│ │ │╎ 0x004015ff 488d3d3d0a.. lea rdi, str.Sector__d_analysis_complete._n ; 0x402043 ; "Sector %d analysis complete.\n" ; const char *format
│ │ │╎ 0x00401606 b000 mov al, 0
│ │ │╎ 0x00401608 e853faffff call sym.imp.printf ; int printf(const char *format)
│ │ │╎ ; CODE XREF from check_killswitch @ 0x4015f7(x)
│ └┌───< 0x0040160d e900000000 jmp 0x401612
│ ││╎ ; CODE XREF from check_killswitch @ 0x40160d(x)
│ └───> 0x00401612 8b45d4 mov eax, dword [var_2ch]
│ │╎ 0x00401615 83c001 add eax, 1
│ │╎ 0x00401618 8945d4 mov dword [var_2ch], eax
│ │└─< 0x0040161b e9a6ffffff jmp 0x4015c6
│ │ ; CODE XREF from check_killswitch @ 0x4015ca(x)
│ └──> 0x00401620 c745cc0000.. mov dword [var_34h], 0
│ 0x00401627 488b7dd8 mov rdi, qword [s1] ; const char *s
│ 0x0040162b e810faffff call sym.imp.strlen ; size_t strlen(const char *s)
│ 0x00401630 8945c8 mov dword [var_38h], eax
│ 0x00401633 c745c40000.. mov dword [var_3ch], 0
│ ; CODE XREF from check_killswitch @ 0x401683(x)
│ ┌─> 0x0040163a 8b45c4 mov eax, dword [var_3ch]
│ ╎ 0x0040163d 3b45c8 cmp eax, dword [var_38h]
│ ┌──< 0x00401640 0f8d42000000 jge 0x401688
│ │╎ 0x00401646 8b45c4 mov eax, dword [var_3ch]
│ │╎ 0x00401649 83f001 xor eax, 1
│ │╎ 0x0040164c 83e006 and eax, 6
│ │╎ 0x0040164f 8b4dc8 mov ecx, dword [var_38h]
│ │╎ 0x00401652 81f1a5a5a5a5 xor ecx, 0xa5a5a5a5
│ │╎ 0x00401658 01c8 add eax, ecx
│ │╎ 0x0040165a 0345cc add eax, dword [var_34h]
│ │╎ 0x0040165d 8945cc mov dword [var_34h], eax
│ │╎ 0x00401660 8b45cc mov eax, dword [var_34h]
│ │╎ 0x00401663 c1e005 shl eax, 5
│ │╎ 0x00401666 8b4dcc mov ecx, dword [var_34h]
│ │╎ 0x00401669 c1e91b shr ecx, 0x1b
│ │╎ 0x0040166c 09c8 or eax, ecx
│ │╎ 0x0040166e 3345cc xor eax, dword [var_34h]
│ │╎ 0x00401671 8945cc mov dword [var_34h], eax
│ │╎ 0x00401674 8b45cc mov eax, dword [var_34h]
│ │╎ 0x00401677 8945cc mov dword [var_34h], eax
│ │╎ 0x0040167a 8b45c4 mov eax, dword [var_3ch]
│ │╎ 0x0040167d 83c001 add eax, 1
│ │╎ 0x00401680 8945c4 mov dword [var_3ch], eax
│ │└─< 0x00401683 e9b2ffffff jmp 0x40163a
│ │ ; CODE XREF from check_killswitch @ 0x401640(x)
│ └──> 0x00401688 6945c85a5a.. imul eax, dword [var_38h], 0x5a5a5a5a
│ 0x0040168f 3345cc xor eax, dword [var_34h]
│ 0x00401692 8945cc mov dword [var_34h], eax
│ 0x00401695 8b45cc mov eax, dword [var_34h]
│ 0x00401698 8945cc mov dword [var_34h], eax
│ 0x0040169b 8b7dcc mov edi, dword [var_34h]
│ 0x0040169e e84dfdffff call fcn.004013f0
│ 0x004016a3 83f800 cmp eax, 0
│ ┌─< 0x004016a6 0f851a000000 jne 0x4016c6
│ │ 0x004016ac 488d3dae09.. lea rdi, str.Opaque_predicate_failed:_Access_denied._n ; 0x402061 ; "Opaque predicate failed: Access denied.\n" ; const char *format
│ │ 0x004016b3 b000 mov al, 0
│ │ 0x004016b5 e8a6f9ffff call sym.imp.printf ; int printf(const char *format)
│ │ 0x004016ba c745e00100.. mov dword [var_20h], 1
│ ┌──< 0x004016c1 e949000000 jmp 0x40170f
│ ││ ; CODE XREF from check_killswitch @ 0x4016a6(x)
│ │└─> 0x004016c6 488d75e7 lea rsi, [s2] ; const char *s2
│ │ 0x004016ca 488b7dd8 mov rdi, qword [s1] ; const char *s1
│ │ 0x004016ce e89df9ffff call sym.imp.strcmp ; int strcmp(const char *s1, const char *s2)
│ │ 0x004016d3 8945c0 mov dword [var_40h], eax
│ │ 0x004016d6 837dc000 cmp dword [var_40h], 0
│ │┌─< 0x004016da 0f851a000000 jne 0x4016fa
│ ││ 0x004016e0 488d3da309.. lea rdi, str.Killswitch_matched:_Access_granted._n ; 0x40208a ; "Killswitch matched: Access granted.\n" ; const char *format
│ ││ 0x004016e7 b000 mov al, 0
│ ││ 0x004016e9 e872f9ffff call sym.imp.printf ; int printf(const char *format)
│ ││ 0x004016ee c745e00000.. mov dword [var_20h], 0
│ ┌───< 0x004016f5 e915000000 jmp 0x40170f
│ │││ ; CODE XREF from check_killswitch @ 0x4016da(x)
│ ││└─> 0x004016fa 488d3dae09.. lea rdi, str.Killswitch_mismatch:_Access_denied._n ; 0x4020af ; "Killswitch mismatch: Access denied.\n" ; const char *format
│ ││ 0x00401701 b000 mov al, 0
│ ││ 0x00401703 e858f9ffff call sym.imp.printf ; int printf(const char *format)
│ ││ 0x00401708 c745e00100.. mov dword [var_20h], 1
│ ││ ; CODE XREFS from check_killswitch @ 0x4016c1(x), 0x4016f5(x)
│ └└──> 0x0040170f 8b45e0 mov eax, dword [var_20h]
│ 0x00401712 64488b0c25.. mov rcx, qword fs:[0x28]
│ 0x0040171b 488b55f8 mov rdx, qword [var_8h]
│ 0x0040171f 4839d1 cmp rcx, rdx
│ 0x00401722 8945bc mov dword [var_44h], eax
│ ┌─< 0x00401725 0f8509000000 jne 0x401734
│ │ 0x0040172b 8b45bc mov eax, dword [var_44h]
│ │ 0x0040172e 4883c450 add rsp, 0x50
│ │ 0x00401732 5d pop rbp
│ │ 0x00401733 c3 ret
│ │ ; CODE XREF from check_killswitch @ 0x401725(x)
└ └─> 0x00401734 e817f9ffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
最初に呼ばれているsub.DEBUG_401460
ですが、これはDEBUG
という環境変数を確認しているだけのようです。
[0x004010a0]> pdg @sub.DEBUG_401460
void sub.DEBUG_401460(void)
{
ulong uStack_20;
uchar auStack_18 [4];
uint uStack_14;
int64_t iStack_10;
*(*0x20 + -0x20) = 0x401474;
iStack_10 = sym.imp.getenv("DEBUG");
if (iStack_10 != 0) {
*(*0x20 + -0x18 + -8) = 0x401491;
uStack_14 = sym.imp.printf("Debugging detected! Exiting...\n");
// WARNING: Subroutine does not return
*(*0x20 + -0x18 + -8) = 0x40149e;
sym.imp.exit(1);
}
return;
}
デバッガの検知かと思いましたが、杞憂でしたね。
というわけでgdbを使用した動的解析に切り替えます。
こういったバイナリは入力とFlagを比較していることが多いので、それらしきアドレスを特定してbreakpointをおいておきましょう。
│ │└─> 0x004016c6 488d75e7 lea rsi, [s2] ; const char *s2
│ │ 0x004016ca 488b7dd8 mov rdi, qword [s1] ; const char *s1
│ │ 0x004016ce e89df9ffff call sym.imp.strcmp ; int strcmp(const char *s1, const char *s2)
│ │ 0x004016d3 8945c0 mov dword [var_40h], eax
│ │ 0x004016d6 837dc000 cmp dword [var_40h], 0
│ │┌─< 0x004016da 0f851a000000 jne 0x4016fa
│ ││ 0x004016e0 488d3da309.. lea rdi, str.Killswitch_matched:_Access_granted._n ; 0x40208a ; "Killswitch matched: Access granted.\n" ; const char *format
│ ││ 0x004016e7 b000 mov al, 0
│ ││ 0x004016e9 e872f9ffff call sym.imp.printf ; int printf(const char *format)
│ ││ 0x004016ee c745e00000.. mov dword [var_20h], 0
│ ┌───< 0x004016f5 e915000000 jmp 0x40170f
│ │││ ; CODE XREF from check_killswitch @ 0x4016da(x)
│ ││└─> 0x004016fa 488d3dae09.. lea rdi, str.Killswitch_mismatch:_Access_denied._n ; 0x4020af ; "Killswitch mismatch: Access denied.\n" ; const char *format
│ ││ 0x00401701 b000 mov al, 0
│ ││ 0x00401703 e858f9ffff call sym.imp.printf ; int printf(const char *format)
ここでは0x004016ce
のstrcmp
が怪しいので、ここにbreakpointを設定して引数を見てみます。
gdb-peda$ b *0x004016ce
Breakpoint 1 at 0x4016ce
gdb-peda$ r foobarbaz
Starting program: /home/ctf/BioHack/insulin_pump_malware2/malware_insulin_pump_adv foobarbaz
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Sector 0 analysis complete.
Sector 1 analysis complete.
Sector 2 analysis complete.
Sector 3 analysis complete.
Sector 4 analysis complete.
Sector 5 analysis complete.
Sector 6 analysis complete.
Sector 7 analysis complete.
Sector 8 analysis complete.
Sector 9 analysis complete.
Opaque predicate failed: Access denied.
[Inferior 1 (process 2016993) exited with code 01]
Warning: not running
残念ながら呼ばれませんでした。他にも検証があるようです。
こういうときは大体文字列の長さで先に弾かれています。
怪しいのは
│ 0x004015a6 488b053f0b.. mov rax, qword [0x004020ec] ; [0x4020ec:8]=0xe3a2a3c4d3e0aebe
│ 0x004015ad 488945f0 mov qword [var_10h], rax
│ 0x004015b1 ba08000000 mov edx, 8
│ 0x004015b6 e885ffffff call sub.sub.n_4014b0_401540
│ 0x004015bb c645ef00 mov byte [var_11h], 0
│ 0x004015bf c745d40000.. mov dword [var_2ch], 0
│ ; CODE XREF from check_killswitch @ 0x40161b(x)
│ ┌─> 0x004015c6 837dd40a cmp dword [var_2ch], 0xa
│ ┌──< 0x004015ca 0f8d50000000 jge 0x401620
│ │╎ 0x004015d0 8b45d4 mov eax, dword [var_2ch]
│ │╎ 0x004015d3 0faf45d4 imul eax, dword [var_2ch]
│ │╎ 0x004015d7 8945d0 mov dword [var_30h], eax
│ │╎ 0x004015da 8b45d0 mov eax, dword [var_30h]
│ │╎ 0x004015dd 83f864 cmp eax, 0x64 ; 'd' ; 100
│ ┌───< 0x004015e0 0f8516000000 jne 0x4015fc
│ ││╎ 0x004015e6 8b75d4 mov esi, dword [var_2ch]
│ ││╎ 0x004015e9 488d3d3a0a.. lea rdi, str.Processing_sector__d..._n ; 0x40202a ; "Processing sector %d...\n" ; const char *format
│ ││╎ 0x004015f0 b000 mov al, 0
│ ││╎ 0x004015f2 e869faffff call sym.imp.printf ; int printf(const char *format)
│ ┌────< 0x004015f7 e911000000 jmp 0x40160d
│ │││╎ ; CODE XREF from check_killswitch @ 0x4015e0(x)
│ │└───> 0x004015fc 8b75d4 mov esi, dword [var_2ch]
│ │ │╎ 0x004015ff 488d3d3d0a.. lea rdi, str.Sector__d_analysis_complete._n ; 0x402043 ; "Sector %d analysis complete.\n" ; const char *format
│ │ │╎ 0x00401606 b000 mov al, 0
│ │ │╎ 0x00401608 e853faffff call sym.imp.printf ; int printf(const char *format)
│ │ │╎ ; CODE XREF from check_killswitch @ 0x4015f7(x)
│ └┌───< 0x0040160d e900000000 jmp 0x401612
│ ││╎ ; CODE XREF from check_killswitch @ 0x40160d(x)
│ └───> 0x00401612 8b45d4 mov eax, dword [var_2ch]
│ │╎ 0x00401615 83c001 add eax, 1
│ │╎ 0x00401618 8945d4 mov dword [var_2ch], eax
│ │└─< 0x0040161b e9a6ffffff jmp 0x4015c6
│ │ ; CODE XREF from check_killswitch @ 0x4015ca(x)
│ └──> 0x00401620 c745cc0000.. mov dword [var_34h], 0
このあたりです。最初の0x4015a6
でrax
に入れられた値0xe3a2a3c4d3e0aebe
がエンコードされたkillswitchだと推測できます。
というわけで8文字で改めて動的解析を試してみましょう
gdb-peda$ r hogefuga
Starting program: /home/ctf/BioHack/insulin_pump_malware2/malware_insulin_pump_adv hogefuga
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Sector 0 analysis complete.
Sector 1 analysis complete.
Sector 2 analysis complete.
Sector 3 analysis complete.
Sector 4 analysis complete.
Sector 5 analysis complete.
Sector 6 analysis complete.
Sector 7 analysis complete.
Sector 8 analysis complete.
Sector 9 analysis complete.
[----------------------------------registers-----------------------------------]
RAX: 0x1
RBX: 0x0
RCX: 0x7f8
RDX: 0x7fffffffdf01 --> 0x6f68007664615f70 ('p_adv')
RSI: 0x7fffffffd9f7 ("P@ncR34s")
RDI: 0x7fffffffdf07 ("hogefuga")
RBP: 0x7fffffffda10 --> 0x7fffffffda30 --> 0x2
RSP: 0x7fffffffd9c0 --> 0x1
RIP: 0x4016ce (call 0x401070 <strcmp@plt>)
R8 : 0x0
R9 : 0x7fffffffd897 --> 0xbc1cd7085aa00039
R10: 0x0
R11: 0x246
R12: 0x7fffffffdb48 --> 0x7fffffffdeb3 ("/home/ctf/BioHack/insulin_pump_malware2/malware_insulin_pump_adv")
R13: 0x401740 (push rbp)
R14: 0x0
R15: 0x7ffff7ffd040 --> 0x7ffff7ffe2e0 --> 0x0
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x4016c1: jmp 0x40170f
0x4016c6: lea rsi,[rbp-0x19]
0x4016ca: mov rdi,QWORD PTR [rbp-0x28]
=> 0x4016ce: call 0x401070 <strcmp@plt>
0x4016d3: mov DWORD PTR [rbp-0x40],eax
0x4016d6: cmp DWORD PTR [rbp-0x40],0x0
0x4016da: jne 0x4016fa
0x4016e0: lea rdi,[rip+0x9a3] # 0x40208a
Guessed arguments:
arg[0]: 0x7fffffffdf07 ("hogefuga")
arg[1]: 0x7fffffffd9f7 ("P@ncR34s")
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffd9c0 --> 0x1
0008| 0x7fffffffd9c8 --> 0x1
0016| 0x7fffffffd9d0 --> 0x800400040
0024| 0x7fffffffd9d8 --> 0x9a6342a400000008
0032| 0x7fffffffd9e0 --> 0xa00000051 ('Q')
0040| 0x7fffffffd9e8 --> 0x7fffffffdf07 ("hogefuga")
0048| 0x7fffffffd9f0 --> 0x50007ffff7fc1000
0056| 0x7fffffffd9f8 --> 0x73343352636e40 ('@ncR34s')
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 1, 0x00000000004016ce in ?? ()
gdb-peda$
というわけでP@ncR34s
がKillswitchのようです。
$ ./malware_insulin_pump_adv P@ncR34s
Sector 0 analysis complete.
Sector 1 analysis complete.
Sector 2 analysis complete.
Sector 3 analysis complete.
Sector 4 analysis complete.
Sector 5 analysis complete.
Sector 6 analysis complete.
Sector 7 analysis complete.
Sector 8 analysis complete.
Sector 9 analysis complete.
Killswitch matched: Access granted.
pancreas
って何かと思ったら膵臓らしいです。最初の問題でも出てきましたね。
優勝
初日に圧倒的な差で1位だったので油断していたら、終盤に怒涛の追い上げをされ、終了30分くらい前には一度1位の座を奪われました。油断してはいけない。
なんとか取り返し、無事賞品のバッジとメダル、ステッカーをもらうことができました。
セキュリティカンファレンスでは、このような"Badge"と呼ばれる基板が作成されることが多くあります。
機能は様々で、例えば以前のCODE BLUE 2019で作成したバッジは現地ではんだ付けをし、スイッチを入れるとLEDが光るというものでした。
今回もらったバッジは光る以外に、アルコールチェッカーとしての機能がある他、CTFの問題も内包しているようです。
発表
Piloting Edge Copilot : Jun Kokatsu
発表者のKokatsu Jun氏は、元MicrosoftでEdgeのセキュリティチームに所属しており、現在はGoogleでブラウザとAIのセキュリティを研究しています。
今回は、Microsoft EdgeのCopilotに関する機能の脆弱性についての発表でした。
WebブラウザのAI機能はユーザーにとって便利だが、攻撃者にとってはどうだろうか? この講演では、Microsoft Edgeブラウザに搭載されたAI機能であるEdge Copilotによって発生した複数の脆弱性を紹介する。例えば、あらゆるサイトの情報を盗む方法や、ユーザーの許可なしにマイクやカメラへのアクセスを行う方法などである。 さらにこの講演では、比較的安全なシステム(Edge)と安全なシステムではない(Bing)の統合により、安全なシステムに実装された緩和策が安全ではないシステムを介してどのようにバイパスされるかを説明する。さらに、従来のセキュリティ緩和策では防げない、LLM(大規模言語モデル)固有のリーク手法も解説する。 https://codeblue.jp/program/time-table/day1-002/
EdgeにはEdge Copilot
と呼ばれる機能が搭載されており、この機能では現在開いているページの要約などができる。
この機能にはedge://discover-chat
が使われており、ここでは強力なCSPとTrusted Typesが有効化されているため、XSSの可能性は現実的ではなかった。
さらに、そこで使われるedgeservices.bing.com
についても強力な保護が施されていたため、bing.com
自体の脆弱性を探すことに。
結果、bing.com
には複数の脆弱性が見つかり、bing.com
から使えるようになっていたブラウザのAPIを使用してさらなる攻撃にも繋げられた。
通常、Edge Copilotに送られるページの内容は保護されていて聞き出せないが、一度関係ない質問を挟むことでリークさせる、プロンプトインジェクションのような手法も通じた。
安全なアプリケーションを新たに作成したとしても、依存している古いコンポーネントが脆弱な状態だと、新しいアプリケーションにも被害が生じる。ということが実感できる発表でした。
また、LLMに対する攻撃としてプロンプトインジェクションなどが取り上げられることが多いものの、XSSなどのプリミティブな脆弱性も通用することを忘れてはいけないと感じました。
PkgFuzzプロジェクト:オープンソースソフトウェアのための新たな継続的ファジング : Yuhei Kawakoya, Eitaro Shioji, Yuta Otsuki
NTTセキュリティホールディングス及びNTT社会情報研究所の研究者チームによる発表です。
Googleが2016年に開始したOSS-Fuzzプロジェクトは2023年8月時点でさまざまなソフトウェアのバグを3万6000件以上見つけている。このプロジェクトでは「ファジング」という自動的にソフトウェアのバグを検出する技術が中核として使われている。ファジングは、自動的なバグ検出技術と言われるが、実際にファジングを実施するためにはハーネス(検査対象ソフトウェアを呼び出すプログラム)の作成、ファジング用バイナリの生成、コマンドライン引数の調整、初期シードの準備、などの人手による作業が必要である。特に、異なる複数のソフトウェアをファジングする場合、これらの作業が障害(自動化阻害要因)となり、ファジングワークフロー全体を自動化できない。このため、効率的に多種多様なソフトウェアのファジングを行うことが難しい。 われわれは、これら自動化阻害要因を解消し、ファジング・ワークフロー全体を自動化するシステム、PkgFuzzを開発した。PkzFuzzでは、ソフトウェア・パッケージのビルドプロセスを監視し、ファジング可能なパッケージを選び出す。また、同時にファジングに必要な情報を収集する。このPkgFuzzを利用したファジングキャンペーン、PkgFuzz Projectについて発表する。PkgFuzz Projectでは、多種多様なソフトウェアが含まれるUbuntu 23.10のDebianパッケージに対するファジングキャンペーンを実施したところ、人間の介在なしに、265のパッケージから、6万4658件のクラッシュを得た。これらを精査したところ、攻撃に利用可能な4件の脆弱性を発見した。これらをIPAへ報告し、3件のアドバイザリとCVEの発行へとつなげた。 PkgFuzzがファジングを自動実行した265のパッケージのうち、OSS-Fuzzでもファジングが実施されていたOSSプロジェクトはわずか32件(12%程度)であった。これは、PkgFuzz Projectが、OSS-Fuzzが今までファジング対象としていなかったOSSをファジングできていることを示している。PkgFuzz Projectではファジングの準備を自動化できる。つまり、OSS-Fuzzとは異なり、ハーネスの作成やコマンドライン引数の調整など、OSS開発者によるファジングのためだけの開発コストを要求しないため、ファジング導入のハードルが低い。そのため、今までファジングによるセキュリティテストを導入できていなかったOSSプロジェクト(特に今までOSS-Fuzzの恩恵をあまり受けていない小規模なOSS)に対してもファジングキャンペーンを実施することができる。PkgFuzz Projectにより、バグを発見、修正していくことで、より多くのOSSプロジェクトに対してセキュリティレベルの向上を促進し、安心なソフトウェア基盤の実現を可能にすると考える。
https://codeblue.jp/program/time-table/day2-002/
OSSに対するファジングは、現在Googleが行っているOSS-Fuzzなどのプロジェクトが存在するが、OSSメンテナ側にも負荷が存在する。
それらの自動化を阻害している要因を特定・解析し、より簡単に自動化を行えるようにしようというプロジェクトがPkgFuzz。
自動化阻害要因
- ハーネスの準備
- ライブラリ関数などのファジングを行うためには、その関数を呼び出すようなコードを書く必要がある。
- コマンドライン引数の調整
- ファジング用バイナリのビルド
- 初期シードファイルの作成
- ファイルを入力に受け取る場合、ある程度正確なファイルを用意しないと効率が大幅に落ちてしまう。
これらの要因に対して、ソフトウェアパッケージ(.deb
など)のビルドプロセスを監視し、必要な情報を収集するというアイデア。
テストとしてライブラリ関数を呼んでいるソースコードや、テスト用ファイルの活用、ビルド用オプションの特定などが比較的容易になる。
アイデアを元に、
- パッケージの解析
- ファジングの実行
- クラッシュのトリアージ
をUbuntu23.10のパッケージのうち265件に対して行った。
結果、 計606回のファジングが実行され、合計で64658件のクラッシュを検出、AFLTriageによるトリアージを行った結果1186件のクラッシュを特定した。
一つ以上のクラッシュが観測されたパッケージの割合は65.6%、深刻度中以上のクラッシュを一つ以上含むパッケージは24.5%となった。
また、解析結果から、クラッシュの主な原因はメモリの読み書きであった。
自動化阻害要因に対して、メンテナが自力で解決して対応を行っているOSS-Fuzzと比較しても同程度の結果を自動的に得られた。
Fuzzingは「自動でバグを発見する夢の方法」のように思われがちですが、その導入や、正しく効率的なFuzzingを行うための作業は簡単なものではなく、無限の計算資源さえあれば誰でもできる。というものではありませんでした。
OSSのFuzzing導入に対するハードルを下げる、社会へ貢献する研究だと思います。
発表資料はこちらで公開されています。
まとめ
今年のCODE BLUEのタイムテーブルを見てみると、昨年に引き続き「LLM・ASM・Fuzzing」の3つが大きなトレンドになっているのを感じます。
非常に個人的な感想を述べると、今年は会場が家に近くてありがたかったです。来年もぜひ参加したい思っています。