Software Reverse Engineering (SRE, Reverse Code Engineering, Reversing)
리버스 엔지니어링 (역공학)
- Legacy Code 혹은 Malware를 분석하는데 이용할 수 있다.
- Software의 Usage Restriction을 제거하거나(해적판, 크랙판)
Software의 Flaw를 찾아 악용하는데 사용될 수도 있다.
- SRE에 필요한 능력은 아래와 같다:
- Target Program Assembly Code에 대한 지식
- SRE Tool들에 대한 경험
- Windows PE File Format에 대한 지식
- PE(Portable Executable)는 Windows OS의 Executables, Object Code, DLLs 등의 File Format을 의미한다.
- 무한한 인내심과 긍정적 사고
- SRE는 Labor-Intensive하기 때문이다.
- Open System에 대한 SRE 공격을 원천차단할 수는 없지만,
아래 방법들로 SRE 공격의 진행을 어렵게 할 수는 있다:
- Anti-Disassembly Techniques
- Disassemble을 방해하여 Code의 Static View를 파악하는데 혼란을 준다.
- Object Code 암호화, Self-Modifying Code 등
- Object Code 암호화의 경우, Decrypt Code가 내재되어 있어야 하기 때문에,
Polymorphic Virus에서의 문제와 동일한 문제가 발생할 수 있다.
- Anti-Debugging Techniques
- Debugging을 방해하여 Code의 Dynamic View를 파악하는데 혼란을 준다.
- Debugger는 Thread를 처리하지 못하기 때문에,
Thread간 Intercommunication을 통해 프로그램을 구현하면 Debugging으로 인한 프로그램 노출을 최소화할 수 있다.
- Tamper-Resistance (Guards)
- Code를 스스로 체크하여 Tampering(무단 수정) 여부를 감지한다.
- Code에 대한 Hash Value 검사를 통한 Tamper-Resistance 등
- Code에 대한 Hashing으로 인한 Overhead가 발생하게 된다.
- Code Obfuscation
- Code를 이해하기 어렵게 작성하여 공격자가 Logic 구조를 파악하기 힘들게 한다.
- 무의미한 조건문 삽입, 명령어 스위칭 등
- 보안상 중요한 부분에 대한 난독화가 이루어져야 하며, 다른 공격 방해 기법들과 혼용되어 사용된다.
Example. Anti-Disassembly
- 무의미한 Junk Code 또한 Disassembly에서는 분석해야 할 의미있는 명령어로 보이기 때문에,
Junk Code를 추가하여 공격자사 분석해야 할 명령어의 양을 늘릴 수 있다.
Example. Anti-Debugging
- Efficiency를 제고하기 위해 inst 1을 실행하는 동안, CPU가 inst 2, inst 3, inst 4를 Memory에 Pre-Fetching하고,
inst 1은 inst 4를 Overwrite하는 동작이라 가정하자.
- inst 4가 Pre-Fetched되어 있기 때문에 Runtime 동안에는 inst 1이 inst 4를 Overwrite해도 프로그램 실행에 문제가 없다.
- 하지만 Pre-Fetching 개념이 적용되지 않은 Debugger 입장에서는
Runtime동안 inst 4에 도달했을 때, Junk Code만을 감지하기 때문에 Anomaly가 발생했다고 생각하게 된다.
- 이처럼, Pre-Fetching 개념을 이용하여 Debugging을 이용한 SRE 공격을 방해할 수 있다.
Example. Code Obfuscation (Opaque Predicate)
int x, y
if ( (x-y)*(x-y) > (x*x - 2*x*y + y*y) )
{ ... }
// The above and below are equivalent (Always false)
if (false)
{ ... }
- 공격자는 위와 같은 Opaque Predicate(Dead Code)를 분석하는데 시간을 낭비하게 된다.
SRE Tools (리버스 엔지니어링 도구)
Tools | Description | Product |
Disassembler | - Execution File을 Assembly로 변환한다. (Static Analysis) - 프로그램의 전체적인 Logic을 파악하는데 유용하다. - 항상 정확히 변환되지는 않는다. - 큰 Complexity를 가진 Software를 분석하는데 한계가 존재한다. - 일반적으로, 변환된 Disassembly를 다시 Assemble하여 Execution File로 만들 수 없다. |
- IDA Pro |
Debugger | - Code를 Step별로 진행시키며 Code에 대한 이해를 돕는다. (Dynamic Analysis) - Black Box Testing을 통해 높은 Complexity를 다룰 수 있다. - 디버깅에 대한 자동화를 이루기가 어려워, 아직도 Labor Cost가 높은 영역이다. |
- SoftICE - OllyDbg |
Hex Editor | - Binary File(Execution File)에 대한 Editing 환경을 제공한다. (Binary File Patching) |
- UltraEdit - HIEW |
SRE Example (SRE for Authentication Program using Serial Number)
- Disassemble와 Hex Editor를 통해
Serial Number를 통해 인증을 수행하는 프로그램의 Executable File에 대해 SRE를 수행하여
공격자가 원하는 바를 이루어낸다.
- 공격 방식은 아래와 같다:
- Detecting Hardcoded Credential (프로그램에 내재된 중요 정보를 이용한 공격)
- Program Patching (프로그램 무단 수정)
1) Program Execution
- 프로그램 사용자는 유효한 Serial Number를 입력해서 권한을 얻을 수 있다.
- 공격자는 유효한 Serial Number를 모르는 상황에서,
무단으로 유효한 Serial Number를 얻어내거나,
이 프로그램의 인증을 우회하고자 한다.
2-1) Program Disassemble
- Program을 Disassemble하여 Hardcoding된 Credential을 찾아낸다.
(Assembly에 "S123N45"이 주석으로 Hardcoding된 것을 쉽게 찾을 수 있다.)
3-1) Broken Program
- Hardcoding된 Serial Number를 통해 Authentication Process를 우회할 수 있다.
2-2) Program Disassemble
- Program을 Disassemble하여 Authentication Process를 우회할 방법을 찾아낸다.
- .text:00401030의 test eax, eax Instruction은 eax AND eax 를 의미한다.
- test 명령의 결과가 0을 return하면, Z Flag가 Setting되어 jz는 true가 되어 인증에 성공하는 부분으로 분기하게 된다.
- 즉, 공격자는 jz가 항상 true값을 갖도록 하여 Authentication Process를 우회하고자 한다.
* Assembly Instruction: \(\texttt{test}\)
- Logical Compare 연산을 수행하는 명령어로, \(\texttt{cmp}\) Instruction과 달리, 결과값을 저장하지 않는다.
(즉, \(\texttt{test eax, eax} \, \iff \, \texttt{eax AND eax}\) 이다. \(\texttt{eax}\)값이 0인지 아닌지를 판단하는 명령이다.)
- \(\texttt{test}\) 연산의 결과는 Z Flag값을 Setting하여 \(\texttt{je}\)나 \(\texttt{jz}\)와 같은 Branch Instruction에 영향을 준다.
* Assembly Instruction: \(\texttt{jz}\)
- Conditional Branch 연산을 수행하는 명령어로, 이전 명령어의 결과값이 0이면 후술된 Address로 Jump한다.
3-2) Program Patching
- \(\texttt{test eax, eax}\) 대신, \(\texttt{xor eax, eax}\) 코드를 Patching하여
결과값이 항상 true를 반환하도록 변경하여 (같은 변수에 대한 XOR 연산은 항상 true를 리턴한다.)
Z Flag가 항상 Setting되어 \(\texttt{jz}\) 명령에서 항상 Jump하도록 프로그램을 변경한다.
(\(\texttt{jz}\) 명령은 이전 명령이 true를 리턴하면 해당 레이블로 분기시키는 명령어이다.)
4-2) Patched Program
- Code Patching을 통해 Logic이 완전히 바뀐 프로그램은
어떤 Serial Number를 입력하든 사용자에게 권한을 부여하는 취약한 프로그램이 되었다.
Reference: Information Security: Principles and Practice 2nd Edition
(Mark Stamp 저, Pearson, 2011)
Reference: Computer Security: Principles and Practice 3rd Edition
(William Stallings, Lawrie Brown, Pearson, 2014)
Reference: 2022학년도 1학기 홍익대학교 네트워크 보안 강의, 이윤규 교수님