第14章サイバー資源の品質とサイバーコンピューティングのガバナンス
Traditional Software Solutions Security従来型ソフトウェアソリューションのセキュリティー
To protect systems, software solutions architects and software developers must consider security as a property of the systems they build. The way software safeguards system resources, including data, to provide access to only authorized users is through software security. Security should be part of the software design process to take a proactive approach to cyber threats and risks. This includes prioritizing security in software requirements, programming, testing, implementation, and maintenance. Practices that should be part of software security include threat modeling and vulnerability management.
英語のヒント
システムを保護するため、ソフトウェアソリューションの設計者と開発者は、セキュリティーを、作るシステムの性質として考えなければならない。データを含むシステム資源を守り、許可された利用者だけにアクセスを提供するのが、ソフトウェアセキュリティーである。サイバー脅威とリスクに先手を打つため、セキュリティーをソフトウェア設計過程の一部にすべきである。要件、プログラミング、テスト、実装、保守でセキュリティーを優先することを含む。脅威モデリングと脆弱性管理も、ソフトウェアセキュリティーの実務に含めるべきである。
Generally, to implement security in the software development process, software architects and developers should follow these steps:
一般に、ソフトウェア開発過程でセキュリティーを実現するには、設計者と開発者は次の手順に従うべきである。
• Make software security a priority and focus of the development process.
• ソフトウェアセキュリティーを、開発過程の優先事項と中心課題にする。
• Identify security risks that should be addressed with software.
• ソフトウェアで対処すべきセキュリティーリスクを特定する。
20 For a recent assessment of the state of the practice of this technology, see https://research.aimultiple.com/secure-multi-party-computation/ 21 For a recent assessment of the state of the practice of this technology, see https://research.aimultiple.com/homomorphic-encryption/
20 この技術の実務の現状に関する最近の評価は、https://research.aimultiple.com/secure-multi-party-computation/ を参照。21 この技術の実務の現状に関する最近の評価は、https://research.aimultiple.com/homomorphic-encryption/ を参照。
• Identify vulnerabilities.
• 脆弱性を特定する。
• Use the appropriate standards, best practices, and frameworks to guide the development process.
• 適切な標準、最善の慣行、枠組みを使い、開発過程の指針とする。
• Review and analyze the code extensively with an emphasis on cybersecurity.
• サイバーセキュリティーを重視し、コードを広範にレビュー・分析する。
• Implement penetration testing, which includes the following steps:
• 次の手順を含む侵入テストを実施する。
◦ Planning process to gather information about the system and define testing goals.
◦ システムの情報を集め、テストの目標を定める計画過程を設ける。
◦ Scan the system with tools to learn how the system responds to threats.
◦ ツールでシステムを走査し、脅威への応答を調べる。
◦ Execute attacks and make every effort to gain access to the system, revealing its weaknesses.
◦ 攻撃を実行し、アクセスの獲得にあらゆる努力を払い、弱点を明らかにする。
◦ If accessed is gain, make every effort to maintain that access without detection.
◦ アクセスを獲得した場合は、検知されずに維持するため、あらゆる努力をする。
◦ Analyze test results and make system changes before testing again.
◦ 結果を分析し、システムを変更してから再テストする。
◦ Repeat this process over and over to identify system weaknesses.
◦ この過程を何度も繰り返し、弱点を特定する。
Software Securityソフトウェアセキュリティー
Software solutions architects and developers must consider security as a property of the systems they build. Many attacks begin by exploiting a vulnerability. In this case, a vulnerability is a software defect that yields an undesired behavior (i.e., the code does not behave correctly). Software defects arise due to flaws in the design or bugs in the implementation. Unfortunately, software can’t be completely bug-free, and fixing every known bug may be too expensive. In general, the focus is to fix what is likely to affect normal users and not focus on bugs that normal users never see or avoid. Because attackers are not normal users, they look for bugs and flaws and try to exploit them. Therefore, to achieve software security, it is necessary to eliminate bugs and design flaws and/or make them harder to exploit. Doing so requires thinking like attackers and developing a foundation for deeply understanding the systems built and used.
英語のヒント
ソフトウェアソリューションの設計者と開発者は、セキュリティーを、構築するシステムの性質として考えなければならない。多くの攻撃は、脆弱性の悪用から始まる。ここでいう脆弱性は、望ましくない動作を生むソフトウェアの欠陥、つまりコードが正しく動作しないことである。欠陥は、設計上の不備や実装のバグから生じる。残念ながら、ソフトウェアからバグを完全になくすことはできず、既知のバグをすべて直すのは高くつきすぎるかもしれない。一般には、通常の利用者に影響しやすいものの修正に力を注ぎ、通常の利用者が遭遇しない、または回避するバグには重点を置かない。攻撃者は通常の利用者ではないため、バグや欠陥を探して悪用を試みる。したがって、ソフトウェアの安全性を実現するには、バグと設計上の欠陥を取り除くか、悪用を難しくする必要がある。そのためには、攻撃者のように考え、構築・利用するシステムを深く理解する土台を育てなければならない。
Most (interesting) software takes inputs from various sources, and any of these inputs may be malicious, such as the following:
有用なソフトウェアの大半は、さまざまな情報源から入力を受ける。そのどれにも、次のように悪意ある入力がありうる。
• direct user interaction (e.g., user interfaces with software via a command line interface or opens a document)
• 利用者との直接のやり取り。コマンドライン経由での操作や、文書を開くことなど。
• third-party libraries that are linked to the software
• ソフトウェアへリンクされた第三者のライブラリー。
• future code updates
• 将来のコード更新。
Securing software in this context should result in correct operation despite malicious inputs. In order to study how to secure software, we will focus on what should be done to secure software written using the C programming language and investigate program control flow hijacking via buffer overflows, code injection, and other memory safety vulnerabilities. This is motivated by the fact that the C language is consistently used widely, and many mission-critical systems are written in C (e.g., most operating systems kernels such as Linux, high-performance servers such as Microsoft SQL server, many embedded systems such as Mars rover). Furthermore, the same techniques apply more broadly.
英語のヒント
ここでソフトウェアを安全にするとは、悪意ある入力があっても正しく動くようにすることである。その方法を学ぶため、C言語で書かれたソフトウェアの保護に何が必要かを取り上げる。バッファーオーバーフロー、コード注入、その他のメモリー安全性の脆弱性による、プログラムの制御フローの乗っ取りを調べる。C言語が継続して広く使われ、多くの任務に不可欠なシステムがCで書かれていることが、その理由である。LinuxなどのOSカーネルの大半、Microsoft SQL Serverのような高性能サーバー、火星探査車のような多数の組み込みシステムが例となる。同じ技法は、さらに広く適用できる。
Buffer Overflow Attacks and Defenses
バッファーオーバーフロー攻撃と防御
Many buffer overflow attacks were perpetrated over the years against software programs written in C. Buffer overflows are prevalent and constitute a significant percentage of all vulnerabilities.22 In 1988 for example, Robert Morris sent a special string via a buffer overflow attack to the fingered daemon on a computer running the VAX operating system and caused it to execute code that created a worm copy that propagated over the network and affected Sun machines running the BSD operating systems. This resulted in $100M worth of damages as well as probation and community services. Robert Morris subsequently became a professor at MIT. In 2001, the Code Red worm leveraged a buffer overflow error in the MS-IIS server. As a result, 300,000 machines were infected within 14 hours. In 2003, the SQL Slammer worm leveraged a similar buffer overflow error in the MS-SQL server. As a result, 75,000 machines were infected within ten minutes. In 2008–2009, the Conficker worm exploited a buffer overflow in Windows RPC, which infected more than ten million machines. In 2009–2010, Stuxnet exploited several buffer overflows in the Windows print spooler service, LNK shortcut display, task scheduler, and the same RPC buffer overflow as Conficker, which led to legitimate cyber warfare. Between 2010 and 2012, Flame exploited the same print spooler and LNK buffer overflows as Stuxnet, which resulted in a cyber-espionage virus. On January 8, 2014, a 23-year-old discovered an X11 server security stack buffer overflow vulnerability (i.e., scanf used when loading early 1990s BDF bitmap fonts) from 1991. The GHOST glibc vulnerability was introduced in 2000 but was only discovered many years later. One last example is the Syslog logging infrastructure daemon bug in macOS and iOS, which relied on the fact that running programs would issue log messages and Syslog would handle storing and disseminating them. The problem was that Syslog used a buffer to propagate these messages, which was not large enough and would sometimes write beyond the end of the buffer.
英語のヒント
長年にわたり、C言語のプログラムには多くのバッファーオーバーフロー攻撃が行われてきた。これは広く見られ、脆弱性全体のかなりの割合を占める。22 たとえば1988年、Robert Morrisは、VAX OSのコンピューターのfingeredデーモンへ、バッファーオーバーフロー攻撃で特殊な文字列を送った。その結果、ワームのコピーを作るコードが実行され、ネットワークを通じて広がり、BSD OSを動かすSunの機械が被害を受けた。損害は1億ドルに達し、保護観察と社会奉仕が科された。Robert Morrisは、後にMITの教授になった。2001年のCode RedワームはMS-IISサーバーのバッファーオーバーフローを利用し、14時間以内に30万台が感染した。2003年のSQL SlammerワームはMS-SQLサーバーの類似の不具合を利用し、10分以内に7万5千台が感染した。2008~2009年のConfickerワームはWindows RPCのバッファーオーバーフローを悪用し、1千万台以上に感染した。2009~2010年のStuxnetは、Windowsの印刷スプーラーサービス、LNKショートカット表示、タスクスケジューラー、Confickerと同じRPCにある、複数のバッファーオーバーフローを悪用し、本格的なサイバー戦争につながった。2010~2012年のFlameは、Stuxnetと同じ印刷スプーラーとLNKの問題を悪用し、サイバー諜報ウイルスとなった。2014年1月8日、23歳の人物が、1991年以来存在したX11サーバーのスタックバッファーオーバーフロー脆弱性を発見した。1990年代初頭のBDFビットマップフォントを読み込む際のscanfに関するものである。glibcのGHOST脆弱性は2000年に入り込んだが、発見は何年も後だった。最後の例は、macOSとiOSのSyslogログ基盤デーモンのバグである。実行中のプログラムがログメッセージを出し、Syslogが保存と配布を扱う。問題は、その配布に使うバッファーが十分に大きくなく、末尾を越えて書き込むことがあった点である。
Based on this, understanding how C programs buffer overflow attacks work and how to defend against them is critical and requires knowledge (refer to Chapter 4 Linguistic Realization of Algorithms: Low-Level Programming Languages and Chapter 5 Hardware Realizations of Algorithms: Computer Systems Design) of the C software compiler, the operating system on which the program is run, and the computer system architecture on which the operating system runs—in other words, a whole-systems view. As a refresher, the stack layout on a 32-bit (Intel IA32) computer when calling a sample C function (Figure 14.18) is shown. Note that in this case, there are two 4-B values between the arguments and the local variables.
英語のヒント
Image omitted because its reuse rights could not be verified.権利の確認ができないため画像を割愛。
Figure 14.18 Local function variables are pushed on the stack in the order they appear in the code, while function arguments are pushed in the reverse order or their appearance in the code. (attribution: Copyright Rice University, OpenStax, under CC BY 4.0 license; credit: reproduced with permission from Dave Levin)
図14.18 関数の局所変数は、コードに現れる順でスタックへ積む。一方、関数の引数は、コードに現れる逆順で積む。(帰属表示:著作権 Rice University、OpenStax。CC BY 4.0ライセンス。出典:Dave Levinの許可を得て転載。)
The function func can access variable loc2, using the stack frame pointer %ebp (Figure 14.19). Note that the same would apply on a 64-bit computer by using a 64-bit memory layout and changing the names of the registers accordingly (e.g., %rbp vs. %ebp).
関数funcは、スタックフレームポインター%ebpを使い、変数loc2へアクセスできる(図14.19)。64ビットコンピューターでも同じ考え方が当てはまる。64ビットのメモリー配置を使い、レジスター名を、それに応じて変更すればよい。たとえば、%ebpに対する%rbpである。
22 Search for “buffer overflow” in the national vulnerability database at https://nvd.nist.gov/vuln/ search?adv_search=true&cves=on&cwe_id=CWE-119 and look for MITRE’s top-25 most dangerous software errors for 2011 as an example at https://cwe.mitre.org/top25/
22 国の脆弱性データベース https://nvd.nist.gov/vuln/search?adv_search=true&cves=on&cwe_id=CWE-119 で「buffer overflow」を検索してほしい。また、一例として、MITREによる2011年の最も危険なソフトウェアエラー上位25件を https://cwe.mitre.org/top25/ で参照してほしい。
Image omitted because its reuse rights could not be verified.権利の確認ができないため画像を割愛。
Figure 14.19 The location of the loc2 variable in the stack frame is always 8 B before the address contained in %ebp. (attribution: Copyright Rice University, OpenStax, under CC BY 4.0 license; credit: reproduced with permission from Dave Levin)
図14.19 スタックフレーム内の変数loc2の位置は、常に%ebp内のアドレスの8 B手前にある。(帰属表示:著作権 Rice University、OpenStax。CC BY 4.0ライセンス。出典:Dave Levinの許可を得て転載。)
Figure 14.20 illustrates how to properly return from a call to the function
func.図14.20は、関数funcの呼び出しから正しく戻る方法を示す。
Image omitted because its reuse rights could not be verified.権利の確認ができないため画像を割愛。
Figure 14.20 This figure shows how to properly return from a call to the function
func. (attribution: Copyright Rice University, OpenStax, under CC BY 4.0 license; credit: reproduced with permission from Dave Levin)図14.20 関数funcの呼び出しから正しく戻る方法。(帰属表示:著作権 Rice University、OpenStax。CC BY 4.0ライセンス。出典:Dave Levinの許可を得て転載。)
In summary, the steps to follow in order to call and return from a function are as follows:
まとめると、関数を呼び出して戻るための手順は、次のとおりである。
Calling function:
呼び出す側の関数:
1. Push arguments onto the stack (in reverse).
1. 引数を逆順にスタックへ積む。
2. Push return address onto the stack (i.e., the address of the instruction that needs to be run once control returns to the calling program: %eip + something).
2. 戻り先アドレスをスタックへ積む。制御が呼び出し元へ戻ったとき、実行する必要がある命令のアドレス、つまり%eipにある値を加えたものである。
3. Jump to the function’s address. Called function:
3. 関数のアドレスへジャンプする。呼び出された側の関数:
4. Push old frame pointer onto the stack: %ebp.
4. 古いフレームポインター%ebpをスタックへ積む。
5. Set frame pointer %ebp to where the end of the stack is right at this time: %esp.
5. フレームポインター%ebpを、その時点のスタック末尾%espへ設定する。
6. Push local variables onto the stack; access them as offsets from %ebp. Returning function:
6. 局所変数をスタックへ積み、%ebpからのオフセットでアクセスする。戻る側の関数:
7. Reset previous stack frame: %ebp = (%ebp) /* copy it off first */.
7. 以前のスタックフレームを復元する:%ebp = (%ebp)。まずコピーしておく。
8. Jump back to return address: %eip = 4(%ebp) /* use the copy */.
8. 戻り先アドレスへジャンプする:%eip = 4(%ebp)。コピーを使う。
Let us investigate a buffer overflow example based on understanding the stack memory layout when calling and returning from functions. Buffers are commonly used in C to store sets of values of a given data type. For example, strings are buffers of characters in C. A buffer overflow occurs when more values are put into the buffer than it can hold. Let us consider this buffer overflow example (Figure 14.21) and right before the “strcpy(buffer, arg1);” statement is executed in the function
func.英語のヒント
関数を呼び出して戻るときのスタックのメモリー配置を踏まえ、バッファーオーバーフローの例を調べよう。Cでは、あるデータ型の値の集まりを保存するため、バッファーをよく使う。たとえば文字列は、文字のバッファーである。収容できる以上の値を入れると、バッファーオーバーフローが起こる。図14.21の例で、関数func内の「strcpy(buffer, arg1);」文を実行する直前を考える。
Image omitted because its reuse rights could not be verified.権利の確認ができないため画像を割愛。
Figure 14.21 The strcpy statement executed in the function
func overflows the size of the buffer specified. (attribution: Copyright Rice University, OpenStax, under CC BY 4.0 license; credit: reproduced with permission from Dave Levin)図14.21 関数func内で実行されたstrcpy文は、指定されたバッファーの大きさを超過する。(帰属表示:著作権 Rice University、OpenStax。CC BY 4.0ライセンス。出典:Dave Levinの許可を得て転載。)
Once the “strcpy(buffer, arg1);” statement is executed in the function func (Figure 14.22), it overwrites the stack memory location were %ebp was stored, which causes a segmentation violation when the function returns.
英語のヒント
関数funcで「strcpy(buffer, arg1);」文を実行すると(図14.22)、%ebpを保存していたスタック内のメモリー位置を上書きし、関数から戻るときにセグメンテーション違反が起こる。
Image omitted because its reuse rights could not be verified.権利の確認ができないため画像を割愛。
Figure 14.22 The strcpy statement executed in the function
func sets %ebp to the wrong address upon return due to the buffer overflow. (attribution: Copyright Rice University, OpenStax, under CC BY 4.0 license; credit: reproduced with permission from Dave Levin)図14.22 関数func内で実行されたstrcpy文は、バッファーオーバーフローにより、戻るときの%ebpを誤ったアドレスに設定する。(帰属表示:著作権 Rice University、OpenStax。CC BY 4.0ライセンス。出典:Dave Levinの許可を得て転載。)
Using the GNU debugger (i.e., gdb) is useful to debug programs that run into buffer overflows. It enables users to show information about the current frame and registers, examine bytes of memory starting at a given address, set a breakpoint at a given function address, and step through a call to it.
英語のヒント
GNUデバッガー(gdb)は、バッファーオーバーフローを起こすプログラムのデバッグに役立つ。現在のフレームやレジスターの情報を表示し、指定アドレスからのメモリーのバイトを調べ、関数のアドレスへブレークポイントを設定し、その呼び出しを一段階ずつ実行できる。
A safe version of the function
func will never cause a buffer overflow. It will simply limit the number of characters read from the command line and could be easily adapted to replace the function func:関数funcの安全な版は、バッファーオーバーフローを決して起こさない。単に、コマンドラインから読む文字数を制限する。容易に調整してfuncを置き換えられる。
Void nooverflow() {
char buflimit[100];
fgets (buflimit,
sizeof(buflimit));
}Note that strcpy lets you write as many characters as you want until it reads and end-of-string character (i.e., null character “\0” in a C string); therefore the problem could get worse than just overwriting %ebp. Figure 14.23 illustrates a different type of buffer overflow that would occur if the function
func were to execute the code provided. In that case, the input writes from low to high addresses.英語のヒント
strcpyは、文字列終端の文字、つまりCの文字列のヌル文字「\0」を読むまで、好きなだけ文字を書き込める。そのため、問題は%ebpの上書きだけにとどまらない場合がある。図14.23は、示されたコードをfuncが実行すると生じる、別種のバッファーオーバーフローである。この場合、入力は低いアドレスから高いアドレスへ書き込まれる。
Image omitted because its reuse rights could not be verified.権利の確認ができないため画像を割愛。
Figure 14.23 The execution of the code shown is such that the input writes from low to high addresses. (attribution: Copyright Rice University, OpenStax, under CC BY 4.0 license; credit: reproduced with permission from Dave Levin)
図14.23 このコードでは、入力は低いアドレスから高いアドレスへ書き込まれる。(帰属表示:著作権 Rice University、OpenStax。CC BY 4.0ライセンス。出典:Dave Levinの許可を得て転載。)
Code Injection Attacks and Defenses
コードインジェクション攻撃と防御
The example of buffer overflow shown earlier uses a string provided by the program itself but, in general, inputs could come from different sources (e.g., text input, network packets, environment variables, file inputs). Therefore, the existence of a buffer overflow bug in a program can lead to a code injection attack (Figure 14.24).
先のバッファーオーバーフローの例では、プログラム自身が与えた文字列を使った。しかし一般に、入力は、テキスト入力、ネットワークパケット、環境変数、ファイル入力など、さまざまな情報源から来る。したがって、この種のバグがあると、コードインジェクション攻撃につながりうる(図14.24)。
Image omitted because its reuse rights could not be verified.権利の確認ができないため画像を割愛。
Figure 14.24 A code injection attack can be staged by loading code into memory and point %eip to it. (attribution: Copyright Rice University, OpenStax, under CC BY 4.0 license; credit: reproduced with permission from Dave Levin)
図14.24 コードをメモリーへ読み込み、%eipをそこへ向けることで、コードインジェクション攻撃を実行できる。(帰属表示:著作権 Rice University、OpenStax。CC BY 4.0ライセンス。出典:Dave Levinの許可を得て転載。)
Pulling off this type of attack requires overcoming a few challenges, which we will discuss in detail. In general, making it really hard to overcome the various challenges is key to defending programs against code injection attacks. The challenges are as follows:
英語のヒント
この種の攻撃を成功させるには、いくつかの難題を乗り越える必要がある。これから詳しく取り上げる。一般に、各難題の克服を本当に難しくすることが、コードインジェクション攻撃からプログラムを守る鍵である。難題は次のとおりである。
1. Loading the code into memory: The code that is loaded into memory must be machine code that is ready to run. It should not contain any all-zero bytes. Alternatively, some library functions (e.g., sprintf, gets, scanf) will stop copying. The loader cannot be used because the code is injected. Finally, the code injected cannot use the stack because it is designed to smash it. The best type of code for this is full-purpose shellcode that can be launched as a shell (Figure 14.25). There are many examples of such code, and there is a lot of competition to write the smallest amount of code. Also, a way to ensure that the injected code will work most effectively is to attempt privilege escalation and go from guest (or nonuser) to root.
英語のヒント
1. コードをメモリーへ読み込む。読み込むコードは、すぐ実行できる機械語でなければならず、全ビットがゼロのバイトを含めるべきではない。含むとsprintf、gets、scanfなど一部のライブラリー関数がコピーを止める。コードは注入するため、ローダーを使えない。さらに、注入コードはスタックを破壊するよう設計されているため、スタックを使えない。最適なのは、シェルとして起動できる汎用のシェルコードである(図14.25)。そのようなコードには多数の例があり、最小のコード量を競う動きも盛んである。また、注入コードを最も効果的に動かす方法として、ゲストや非ユーザーからrootへの権限昇格を試みることがある。
Image omitted because its reuse rights could not be verified.権利の確認ができないため画像を割愛。
Figure 14.25 The best type of code for code injection is full-purpose shellcode that can be launched as a shell. (attribution: Copyright Rice University, OpenStax, under CC BY 4.0 license; credit: reproduced with permission from Dave Levin)
図14.25 コード注入に最適なのは、シェルとして起動できる汎用シェルコードである。(帰属表示:著作権 Rice University、OpenStax。CC BY 4.0ライセンス。出典:Dave Levinの許可を得て転載。)
Concerning privileged escalation, the idea is to exploit knowledge of permissions on the targeted operating system. In the case of Linux, files have read/write/execute permissions owner, group, and others. Permissions are defined for userid and groupid, and the root userid is p. The command passwd may be used as part of an attack by making it possible for any user to execute that command rather than just its owner (i.e., root). The idea is to have a root-owned process run setuid(0) or seteuid(0) in order to get root permissions. While root owns “passwd,” users can run it, and getuid() will return the userid of the person who ran it. Executingseteuid(0) next will set the effective userid to root, which is allowed because root is the process owner.
権限昇格では、対象OSの権限に関する知識を悪用する。Linuxのファイルには、所有者・グループ・その他に対する読み取り・書き込み・実行権限がある。権限はuseridとgroupidに対して定義され、rootのuseridはpである。passwdコマンドは、所有者のrootだけでなく誰でも実行できるようにすることから、攻撃の一部に使われうる。root所有のプロセスでsetuid(0)またはseteuid(0)を実行し、root権限を得るという考え方である。rootがpasswdを所有していても、利用者は実行でき、getuid()は実行者のuseridを返す。続いてseteuid(0)を実行すると、実効useridがrootになる。プロセスの所有者がrootなので、これは許される。
2. Getting injected code to run: Because it is only possible to write forward into a memory buffer, the running code must already be used to jump to the injected code. The typical approach is to hijack the saved %eip and change it to point to the address of the injected code (Figure 14.26).
2. 注入したコードを実行させる。メモリーバッファーへは前方にしか書けないため、実行中のコードを利用して、注入コードへジャンプしなければならない。典型的には、保存された%eipを乗っ取り、注入コードのアドレスを指すよう変更する(図14.26)。
Image omitted because its reuse rights could not be verified.権利の確認ができないため画像を割愛。
Figure 14.26 Code injection is performed by hijacking the saved %eip and changing it to point to the address of the injected code. (attribution: Copyright Rice University, OpenStax, under CC BY 4.0 license; credit: reproduced with permission from Dave Levin)
図14.26 保存された%eipを乗っ取り、注入コードのアドレスを指すよう変更して、コード注入を行う。(帰属表示:著作権 Rice University、OpenStax。CC BY 4.0ライセンス。出典:Dave Levinの許可を得て転載。)
However, getting to know the address of the save %eip is a challenge. Furthermore if the %eip is wrong and points to data, the CPU will panic when it attempts to execute an invalid instruction.
しかし、保存された%eipのアドレスを知ることは難しい。さらに、%eipが誤ってデータを指していると、無効な命令を実行しようとしたCPUはパニックを起こす。
3. Finding the return address: Because the code cannot be accessed, there is no way to know where the buffer starts based on the saved %ebp. One possibility is to try a lot of different values, and the worst-case situation for a 64-bit memory space involves computing 264 possible answers. If address space layout randomization (ASLR) is disabled, which you cannot count on today, the stack always starts from the same fixed address and then grows. Still, it does not usually grow very deeply unless the code is heavily recursive. Another approach consists of using nop sleds. Because nop is a single-byte instruction, it makes it possible to move to the next instruction, thereby improving chances to hit the address of %eip (Figure 14.27).
英語のヒント
3. 戻り先アドレスを見つける。コードへアクセスできないため、保存された%ebpからバッファーの開始位置を知る方法はない。一つの可能性は、さまざまな値を多数試すことである。64ビットのメモリー空間では、最悪の場合、264通りの候補を計算することになる。アドレス空間配置のランダム化(ASLR)が無効なら、スタックは常に同じ固定アドレスから始まって伸びるが、今日それを当てにはできない。また、コードが強く再帰的でなければ、通常はさほど深く伸びない。別の方法は、NOPスレッドを使うことである。nopは1バイト命令なので次の命令へ進められ、%eipのアドレスに当たる可能性を高める(図14.27)。
Image omitted because its reuse rights could not be verified.権利の確認ができないため画像を割愛。
Figure 14.27 Introducing single-byte nop instructions improves the chances to hit the address of %eip. (attribution: Copyright Rice University, OpenStax, under CC BY 4.0 license; credit: reproduced with permission from Dave Levin)
図14.27 1バイトのnop命令を入れることで、%eipのアドレスに当たる可能性が高まる。(帰属表示:著作権 Rice University、OpenStax。CC BY 4.0ライセンス。出典:Dave Levinの許可を得て転載。)
Finally, putting it all together, the recipe for code injection is to achieve that shown in Figure 14.28.
最後に、これらをまとめたコードインジェクションの手順が、図14.28である。
Image omitted because its reuse rights could not be verified.権利の確認ができないため画像を割愛。
Figure 14.28 The recipe for code injection is to guess the stack return address and use nop sleds to improve the chances to hit the address of %eip. (attribution: Copyright Rice University, OpenStax, under CC BY 4.0 license; credit: reproduced with permission from Dave Levin)
図14.28 コード注入では、スタックの戻り先アドレスを推測し、NOPスレッドで%eipのアドレスに当たる可能性を高める。(帰属表示:著作権 Rice University、OpenStax。CC BY 4.0ライセンス。出典:Dave Levinの許可を得て転載。)
A typical way to protect a program against code injection is to prevent data execution by marking memory pages as nonexecutable or make it impossible to put code into the memory by detecting overflows with canaries. Using a canary amounts to placing a known string of characters in memory at the end of the buffer and aborts the program execution if the expected value at that location is changed (Figure 14.29).
英語のヒント
コードインジェクションを防ぐ典型的な方法は、メモリーページを実行不可にしてデータの実行を防ぐか、カナリアでオーバーフローを検出し、メモリーへのコード配置を不可能にすることである。カナリアでは、既知の文字列をバッファー末尾のメモリーに置き、そこにあるはずの値が変わったら、プログラムの実行を中止する(図14.29)。
Image omitted because its reuse rights could not be verified.権利の確認ができないため画像を割愛。
Figure 14.29 Placing a canary at the end of the buffer makes it possible to detect whether the content at the end of the buffer has been changed. (attribution: Copyright Rice University, OpenStax, under CC BY 4.0 license; credit: reproduced with permission from Dave Levin)
図14.29 バッファーの末尾にカナリアを置くと、末尾の内容が変更されたか検出できる。(帰属表示:著作権 Rice University、OpenStax。CC BY 4.0ライセンス。出典:Dave Levinの許可を得て転載。)
There are a few possibilities for canary values as indicated:
カナリアの値には、次の選択肢がある。
1. Terminator canaries (e.g., CR, LF, NULL, -1) leverage that scanf and other similar functions do not allow these values.
1. 終端記号のカナリア(CR、LF、NULL、-1など)は、scanfなどの類似関数がこれらの値を許容しないことを利用する。
2. Random canaries write a new random value when each process starts and saves the real value somewhere in memory; it is necessary to write-protect the stored value in that case.
2. ランダムなカナリアは、各プロセスの開始時に新しいランダム値を書き、本来の値をメモリー内の別の場所に保存する。この場合、保存値は書き込み保護が必要である。
3. Random XOR canaries work the same way as random canaries but store “canary XOR <some control info> instead.
3. ランダムXORカナリアは、ランダムなカナリアと同じように動作するが、代わりに「カナリア XOR <何らかの制御情報>」を保存する。
Integer Overflow Attacks and Defenses
整数オーバーフロー攻撃と防御
Programmers have a tendency to think about integers as mathematical integers. It is, therefore, easy for them to write C code that causes integer overflows (e.g., assigning larger types to smaller types, arithmetic overflow). For example, the following multiplication using integers represented by the two complement notations causes an arithmetic overflow that yields a negative result: 15000000*500 = -1089934592. Knowing this, attackers may simply control the value of an integer and cause software to behave unexpectedly. Defending against integer overflow requires using appropriate types (e.g., using sizet in the C language).
英語のヒント
プログラマーは、整数を数学的な整数として考えがちである。そのため、大きい型から小さい型への代入や算術オーバーフローなど、整数オーバーフローを起こすCコードを書きやすい。たとえば、2の補数表現の整数による15000000*500 = -1089934592という乗算は、算術オーバーフローで負の結果を生む。これを知る攻撃者は、整数の値を制御するだけで、予想外の動作を起こせる。防御には、C言語のsizetなど、適切な型を使う必要がある。
Format String Vulnerability and Defenses
書式文字列の脆弱性と防御
A format function is a special kind of ANSI C function used as a conversion function to represent primitive C data types as human-readable strings. Format functions are used in most C programs to output information, print error messages, and process strings. A format string vulnerability occurs when an attacker can provide the format string to an ANSI C format function in part or as a whole. If the attacker can do so, the behavior of the format function is changed, and the attacker may get control over the target application.
英語のヒント
書式関数は、Cの基本データ型を人が読める文字列で表す変換関数として使う、特殊なANSI C関数である。大半のCプログラムで、情報の出力、エラーメッセージの表示、文字列処理に使われる。書式文字列の脆弱性は、攻撃者がANSI Cの書式関数へ与える書式文字列の一部または全部を指定できるときに生じる。その場合、関数の動作が変わり、攻撃者が対象アプリケーションを制御できる可能性がある。
For example, by calling the following function using a command line parameter:
たとえば、コマンドラインのパラメーターを使い、次の関数を呼ぶとする。
int func (char *user) {
printf (user);
}an attacker can get control over the entire ASCII string of the printf function (i.e., the part that contains text and format parameters). To avoid this problem, the function should be written as follows:
攻撃者はprintf関数のASCII文字列全体、つまりテキストと書式パラメーターの部分を制御できる。この問題を避けるには、関数を次のように書くべきである。
int func (char *user) {
printf ("%s", user);
}This kind of vulnerability is more dangerous than the common buffer overflow vulnerability.23
この種の脆弱性は、一般的なバッファーオーバーフロー脆弱性よりも危険である。23
Heap Control Data Vulnerability
ヒープの制御データの脆弱性
The heap is managed by the malloc() function, which requests pages of memory from the operating system, manages free chunks, and allocates memory for programs. Attackers can use the malloc function to overwrite heap metadata and abuse it to exploit buffer overflows by injecting control data in malloc space.24
英語のヒント
ヒープを管理するmalloc()関数は、OSへメモリーページを要求し、空き領域を管理し、プログラムへメモリーを割り当てる。攻撃者はmalloc関数でヒープのメタデータを上書きし、mallocの空間へ制御データを注入して、バッファーオーバーフローを悪用できる。24
Code Reuse Attacks and Return-Oriented Programming (ROP)
コード再利用攻撃とリターン指向プログラミング(ROP)
We have discussed earlier ways to prevent an attacker from executing any injected code using canaries. While attackers may attempt to bypass stack canaries last time, they may also simply focus on bypassing data execution prevention (DEP) measures by executing existing code such as the program code itself, dynamic libraries, or libc. In particular, libc contains valuable functions such as system (runs a shell command) or protect (changes the memory protection on a region of code). Rather than returning to shellcode, an attacker may decide to return to a standard library function like system and cause a system to crash to exit. Another alternative is to return to protect, inject code, and make it executable. An attacker may also chain two functions together. In fact, attackers may not need to limit themselves to functions and cleanup code. An alternative is to encode arbitrary computation, including conditionals and loops, by returning to sequences of code ending in
ret. This last approach is referred to as return-oriented programming (ROP)25 and relies on the following steps:英語のヒント
先に、カナリアを使って、攻撃者による注入コードの実行を防ぐ方法を扱った。攻撃者はスタックカナリアを迂回しようとする場合もあるが、プログラム自身、動的ライブラリー、libcなどの既存コードを実行し、データ実行防止(DEP)の迂回に集中することもある。特にlibcには、シェルコマンドを実行するsystemや、コード領域のメモリー保護を変えるprotectなど、有用な関数が含まれる。シェルコードへ戻る代わりに、systemのような標準ライブラリー関数へ戻り、システムをクラッシュさせて終了させる場合がある。別の選択肢は、protectへ戻ってコードを注入し、実行可能にすることだ。二つの関数を連結することもできる。実際、攻撃者は関数や後始末コードに限定する必要さえない。retで終わる命令列へ戻ることで、条件分岐やループを含む任意の計算を表現する方法がある。この最後の方法を、リターン指向プログラミング(ROP)という。25 次の手順に基づく。
1. Disassemble code (i.e., library or program).
1. ライブラリーまたはプログラムのコードを逆アセンブルする。
2. Identify useful code sequences (e.g., code sequences usually ending in
ret).2. 有用な命令列を見つける。通常はretで終わる命令列である。
3. Assemble useful sequences into reusable gadgets.
3. 有用な命令列を、再利用可能なガジェットに組み立てる。
4. Assemble gadgets into desired shellcode.
4. ガジェットを、望みのシェルコードに組み立てる。
Time of Check/Time of Use Problem
確認時点と使用時点のずれの問題
Figure 14.30 illustrates the time-of-check to time-of-use (TOCTOU) problem.
図14.30は、確認時点と使用時点のずれ(TOCTOU)の問題を示す。
23 See https://julianor.tripod.com/bc/formatstring-1.2.pdf for more details. 24 See https://web.archive.org/web/20220911001330/http://www.phrack.org/issues/57/8.html for more details on this vulnerability. 25 See https://hovav.net/ucsd/dist/geometry.pdf for more details on ROP.
23 詳細は https://julianor.tripod.com/bc/formatstring-1.2.pdf を参照。24 この脆弱性の詳細は https://web.archive.org/web/20220911001330/http://www.phrack.org/issues/57/8.html を参照。25 ROPの詳細は https://hovav.net/ucsd/dist/geometry.pdf を参照。
Image omitted because its reuse rights could not be verified.権利の確認ができないため画像を割愛。
Figure 14.30 Access is intended to check whether the real user who executed the setuid program would normally be allowed to read the file, so access checks the real uid instead of the effective userid euid. (attribution: Copyright Rice University, OpenStax, under CC BY 4.0 license; credit: reproduced with permission from Dave Levin)
図14.30 accessは、setuidプログラムを実行した実ユーザーが通常そのファイルを読めるか確認するためのものである。そのため、実効ユーザーIDのeuidではなく、実uidを確認する。(帰属表示:著作権 Rice University、OpenStax。CC BY 4.0ライセンス。出典:Dave Levinの許可を得て転載。)
The code should be modified as in Figure 14.31 to avoid the TOCTOU problem.
TOCTOU問題を避けるには、図14.31のようにコードを修正すべきである。
Image omitted because its reuse rights could not be verified.権利の確認ができないため画像を割愛。
Figure 14.31 The code is modified to switch the user (uid) that executed the setuid command to the effective user (euid) to ensure that proper permissions are used when accessing the document. (attribution: Copyright Rice University, OpenStax, under CC BY 4.0 license; credit: reproduced with permission from Dave Levin)
図14.31 文書へのアクセス時に適切な権限が使われるよう、setuidコマンドを実行したユーザー(uid)を実効ユーザー(euid)へ切り替えるコードに変更する。(帰属表示:著作権 Rice University、OpenStax。CC BY 4.0ライセンス。出典:Dave Levinの許可を得て転載。)
Playing Cat and Mouse to Secure Software
ソフトウェアを守る、いたちごっこ
The following illustrates how securing software is like playing cat and mouse:
ソフトウェアの保護が、いたちごっこのようであることを次に示す。
• Defense: Make stack or heap nonexecutable to prevent code injection.
• 防御:コード注入を防ぐため、スタックやヒープを実行不可にする。
• Attack response: Return to libc.
• 攻撃側の応答:libcへリターンする。
• Defense: Hide the address of the desired libc code or return the address using ASLR.
• 防御:ASLRで、利用したいlibcコードのアドレス、または戻り先アドレスを隠す。
• Attack response: Perform a brute force search (for 32- and 64-bit systems) or information leak (i.e., format string vulnerability)
• 攻撃側の応答:32ビット・64ビットシステムで総当たり探索を行うか、書式文字列の脆弱性などによる情報漏えいを利用する。
• Defense: Avoid using libc code entirely and use code in the program text instead.
• 防御:libcコードの利用を完全に避け、代わりにプログラムのテキスト内のコードを使う。
• Attack response: Construct needed functionality using return-oriented programming (ROP).
• 攻撃側の応答:リターン指向プログラミング(ROP)で必要な機能を組み立てる。