セキュリティ イベントレポート

CODE BLUE 2024参加レポート

セキュリティ イベントレポート

こんにちは。セキュリティ対策室のn01e0です。11/14~11/15にかけて開催された国際的な情報セキュリティカンファレンスであるCODE BLUE 2024昨年に続き参加してきました。

CODE BLUEとは、世界トップクラスの情報セキュリティ専門家による最先端の講演と、国や言語の垣根を越えた情報交換・交流の機会を提供する国際会議です。

欧米の著名な研究者を招へいし、最新の成果を共有するとともに、日本をはじめとするアジア各国の優れた研究者を発掘し、その研究成果を世界へと発信していきます。

医療の世界で使われるCODE BLUEという言葉は、「緊急事態発生」や「関係者招集」を意味します。

インターネットの世界においても、IoT(Internet of Things)の時代を迎えるなど、セキュリティ対策の重要性が高まっており、世界各国の研究者を招集し、事態への対処や解決策を共に考える場が必要とされています。

CODE BLUEは国際的なコミュニティ形成の場となることを目的にするとともに、CODE(技術)によってBLUE(海)を超えて人と人をつなぎ、よりよいインターネットの世界作りに貢献していきます。 https://codeblue.jp/

この記事では、特に印象に残っている発表やワークショップ、コンテストをいくつかピックアップして紹介します。

  1. ワークショップ
    1. BioHacking Village
  2. コンテスト
    1. Capture the Flag "Code D.A.R.K.: Biohacking Village CTF Challenge":
  3. 発表
    1. Piloting Edge Copilot : Jun Kokatsu
    2. PkgFuzzプロジェクト:オープンソースソフトウェアのための新たな継続的ファジング : Yuhei Kawakoya, Eitaro Shioji, Yuta Otsuki
  4. まとめ

ワークショップ

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として参加し、優勝しました。

scoreboard

問題数が非常に多く、解答すると問題が増えるスタイルは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)

ここでは0x004016cestrcmpが怪しいので、ここに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

このあたりです。最初の0x4015a6raxに入れられた値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。

自動化阻害要因

  1. ハーネスの準備
    • ライブラリ関数などのファジングを行うためには、その関数を呼び出すようなコードを書く必要がある。
  2. コマンドライン引数の調整
  3. ファジング用バイナリのビルド
  4. 初期シードファイルの作成
    • ファイルを入力に受け取る場合、ある程度正確なファイルを用意しないと効率が大幅に落ちてしまう。

これらの要因に対して、ソフトウェアパッケージ(.debなど)のビルドプロセスを監視し、必要な情報を収集するというアイデア。

テストとしてライブラリ関数を呼んでいるソースコードや、テスト用ファイルの活用、ビルド用オプションの特定などが比較的容易になる。

アイデアを元に、

  1. パッケージの解析
  2. ファジングの実行
  3. クラッシュのトリアージ

をUbuntu23.10のパッケージのうち265件に対して行った。

結果、 計606回のファジングが実行され、合計で64658件のクラッシュを検出、AFLTriageによるトリアージを行った結果1186件のクラッシュを特定した。

一つ以上のクラッシュが観測されたパッケージの割合は65.6%、深刻度中以上のクラッシュを一つ以上含むパッケージは24.5%となった。

また、解析結果から、クラッシュの主な原因はメモリの読み書きであった。

自動化阻害要因に対して、メンテナが自力で解決して対応を行っているOSS-Fuzzと比較しても同程度の結果を自動的に得られた。


Fuzzingは「自動でバグを発見する夢の方法」のように思われがちですが、その導入や、正しく効率的なFuzzingを行うための作業は簡単なものではなく、無限の計算資源さえあれば誰でもできる。というものではありませんでした。

OSSのFuzzing導入に対するハードルを下げる、社会へ貢献する研究だと思います。

発表資料はこちらで公開されています。

まとめ

今年のCODE BLUEのタイムテーブルを見てみると、昨年に引き続き「LLM・ASM・Fuzzing」の3つが大きなトレンドになっているのを感じます。

非常に個人的な感想を述べると、今年は会場が家に近くてありがたかったです。来年もぜひ参加したい思っています。