시작! 리버싱
김 종범
2016 Kerference
차례
소개 쓰임새 어셈블리 시연 끄읕
소스코드를 역추적하는 것
개발자가 코딩을 완료하고 빌드를 하게 되면 소스코드를 컴파일과정을 거
쳐 오브젝트 파일이 만들어지며, 여러 링크된 라이브러리와 오브젝트 파일
을 결합해 최종적으로 EXE DLL등이 생성된다.
빌드된 EXE, DLL등을 바이너리 분석을 거쳐서 소스코드가 어떤 식으로
만들어져 있는지 파악이 가능해진다. PE Header 분석을 통해 어떤 라이브
러리가 링크돼 있는지 분석하고, 버튼을 눌렀을 때 가동되는 코드의 흐름을
추적해 원래의 코드가 어떤 식으로 제작돼 있는지 알 수 있게 된다.
• 소스코드의 구조를 분석해서 코드변조를 통해 허가되지 않은 액
션이 가능한지등 취약점 발견
모의해킹 취약점 발견
• 해커의 침입 여부를 역분석해서 그에 상응하는 보안 모듈 개발보안코드 개발
• 코드상에선 문제가 없지만 다른 모듈이 결합했을 때 등, 예상치 못
한 상황에서 예외가 발생했을 때 등에 대해 원인 파악 가능
버그 수정
• 새로운 프로그램이 등장했는데, 구글링해도 나오지 않을 때, 리버
싱하면 기술에 대한 궁금증을 해결
신기술 연구와 학습
어셈블리어(Assembly language)
• 기본 구조
• 명령 포맷
• 레지스터
• 외울 필요없는 명령어
• 스택
• 함수의 호출
• 리턴 주소
Void 물마심()
{
BOOL bOpen = 냉장고문오픈();
if (bOpen)
{
물을꺼냄();
마심();
}
}
__asm{
냉장고앞으로간다
냉장고문을잡는다
냉장고문을연다
오픈성공:
냉장고안을본다
손을든다
냉장고안에넣는다
물병을잡는다
물병을꺼낸다
뚜껑을연다
물을컵에따른다
컵을손에든다
컵에든것을마신다.
C/C++ 코드와의 결정적인 차이
: “한 번에 한 가지 동작”
[어셈블리는 간단명료하다]
기본 구조(feat. 물마심)
명령 포맷
• X86 CPU의 기본 구조인 IA-32를 기본 플랫폼으로 삼아 소개함
 대부분의 PC가 Intel CPU를 사용하며, AMD의 경우도 대부분의 코드가 Intel
Processor와 호환되기 때문이다.
• “명령어 + 인자”
 IA-32의 기본 형태
 명령어 mov, push 같은 것들. 옵코드(opcode)
 인자: “어떤 장소로 값을 넣을 것인지”, “명령어에 해당하는 값.” 오퍼랜드(operand)
• Push 337
 Push는 옵코드, 337는 오퍼랜드
• Mov eax, 1
 Mov는 옵코드, eax와 1이 오퍼랜드
 모든 오퍼랜드는 앞의 것이 destination 뒤의 것이 source
레지스터
EAX
산술 계산을 하며, 리턴 값을 전달하는 변수.
A = Accumulator
EDX
각종 연산에 쓰이나,
리턴 값에 쓰이지 않는 변수
D = Data
ECX
C = Count
For 문에서 int I 라고 선언할 때의 i의 역할
카운팅 할 필요 없을 때는 그저 변수.
EBX
레지스터가 하나 더
필요할 때 사용되는 것.
• ESI (Source Index)
 시작지 인덱스
 데이터를 조작하거나, 복사 시에 데이
터의 주소가 저장되는 변수
• EDI (Destination Index)
 목적지 인덱스
 복사 시에 목적지의 주소가 저장되는
변수
• EBP(Stack Pointer)
 스택 프레임의 끝 지점 주소가 저장됨.
• ESP(Baste Pointer)
 스택프레임의 시작 주소가 저장됨.
기본적인명령어
• ADD, SUB
 ADD : Src에서 dest로 값을 더하는 명령어
 SUB : Src에서 dest로 값을 빼는 명령어
• INT
 인터럽트를 일으키는 명령어
• CALL
 함수를 호출하는 명령어
 CALL뒤에 오퍼랜드로 번지가 붙는다. 해당
번지를 호출하고 끝나면 call다음 번지로 돌
아온다.
• INC, DEC
 INC = i++; DEC = i--;
• PUSH, POP
 PUSH : 스택에 값을 넣는 명령어
 POP : 스택에 있는 값을 가져오는 명령어
• MOV
 값을 넣는 명령어
 Mov eax, 1 : eax에 1을 넣는 코드
 Mov ebx, ecx : ebx에 ecx를 넣는 코드
• LEA
 주소를 넣는 것
 Lea eax, dword ptr ds:[esi] : eax에 0x401000
 Mov eax, dword ptr ds:[esi] : eax에 5640EC83
 Lea eax, dword ptr ss:[esp+8] :eax에 0x13FF40
 Mov eax, dword ptr ss:[esp+8] : eax에 33
기본적인 명령어
• AND, OR ,XOR
 비트연산자
 XOR는 동일한 오퍼랜드로 처리가능
 Ex : XOR EAX, EAX : EAX = 0
• NOP
 아무것도 하지 말라는 명령어
• CMP, JMP
 비교해서 점프하라는 명령어
• 그 외의 명령어는 구글링
스택(stack)
• 지역 변수 사용
• 함수 호출 시 파라미터가 들어가는 방향
• 리턴 주소
지역 변수 사용
Push ebp
Mov ebp, esp
Sub esp, 50h
• Ebp 레지스터를 스택에 넣는다.
• 그리고 현재 esp의 값을 ebp에 넣는
다.
• Ebp와 esp가 같아지면서 이제 이 함
수에서 지역변수는 ebp에서부터 얼
마든지 계산할 수 있다.
• Ebp를 기준으로 오프셋을 더하고 빼
는 작업으로 스택을 처리할 수 있게
된다.
• Sub esp, 50h LIFO특성으로 인해
특정 값만큼 뺀다는 것은 그만큼 스
택을 사용하겠다는 것이고, 즉 50h
만큼 지역변수를 사용하는 것이다.
함수의 호출
DWORD HelloFunction(DWORD
dwParam1, DWORD dwParam2,
DWORD dwParam3)
HelloFunctioni 호출
Main(){
DWORD dwRet =
HelloFunction(0x37,0x38,0x39);
If(dwRet)
//….
}
Push 39h
Push38h
Push 37h
Call 401300h
• 스택에 값을 LIFO 순으로 넣기 떄문에
실제 소스코드에서 호출한 것과는 반
대로 들어간다.
리턴 주소
• DWORD HelloFunction(DWORD
dwParam1, DWORD dwParam2,
DWORD dwParam3){
• DWROD dwRetAddr = 0;
• __asm
• {
push eax
mov eax,[ebp+4]
mov dwRetAddr, eax
pop eax
}
Printf(“dwRetAddr: %08xn”,
dwRetAddr);
}
• 결과를 보면 HelloFunction()을 호출
한 뒤 호출한 쪽의 다음 번지가 바로 리
턴 주소다.
시연
크랙 미 1번 • 크랙 미 5번
끄읕

[Kerference] 시작! 리버싱 - 김종범(KERT)

  • 1.
  • 2.
  • 3.
    소스코드를 역추적하는 것 개발자가코딩을 완료하고 빌드를 하게 되면 소스코드를 컴파일과정을 거 쳐 오브젝트 파일이 만들어지며, 여러 링크된 라이브러리와 오브젝트 파일 을 결합해 최종적으로 EXE DLL등이 생성된다. 빌드된 EXE, DLL등을 바이너리 분석을 거쳐서 소스코드가 어떤 식으로 만들어져 있는지 파악이 가능해진다. PE Header 분석을 통해 어떤 라이브 러리가 링크돼 있는지 분석하고, 버튼을 눌렀을 때 가동되는 코드의 흐름을 추적해 원래의 코드가 어떤 식으로 제작돼 있는지 알 수 있게 된다.
  • 4.
    • 소스코드의 구조를분석해서 코드변조를 통해 허가되지 않은 액 션이 가능한지등 취약점 발견 모의해킹 취약점 발견 • 해커의 침입 여부를 역분석해서 그에 상응하는 보안 모듈 개발보안코드 개발 • 코드상에선 문제가 없지만 다른 모듈이 결합했을 때 등, 예상치 못 한 상황에서 예외가 발생했을 때 등에 대해 원인 파악 가능 버그 수정 • 새로운 프로그램이 등장했는데, 구글링해도 나오지 않을 때, 리버 싱하면 기술에 대한 궁금증을 해결 신기술 연구와 학습
  • 5.
    어셈블리어(Assembly language) • 기본구조 • 명령 포맷 • 레지스터 • 외울 필요없는 명령어 • 스택 • 함수의 호출 • 리턴 주소
  • 6.
    Void 물마심() { BOOL bOpen= 냉장고문오픈(); if (bOpen) { 물을꺼냄(); 마심(); } } __asm{ 냉장고앞으로간다 냉장고문을잡는다 냉장고문을연다 오픈성공: 냉장고안을본다 손을든다 냉장고안에넣는다 물병을잡는다 물병을꺼낸다 뚜껑을연다 물을컵에따른다 컵을손에든다 컵에든것을마신다. C/C++ 코드와의 결정적인 차이 : “한 번에 한 가지 동작” [어셈블리는 간단명료하다] 기본 구조(feat. 물마심)
  • 7.
    명령 포맷 • X86CPU의 기본 구조인 IA-32를 기본 플랫폼으로 삼아 소개함  대부분의 PC가 Intel CPU를 사용하며, AMD의 경우도 대부분의 코드가 Intel Processor와 호환되기 때문이다. • “명령어 + 인자”  IA-32의 기본 형태  명령어 mov, push 같은 것들. 옵코드(opcode)  인자: “어떤 장소로 값을 넣을 것인지”, “명령어에 해당하는 값.” 오퍼랜드(operand) • Push 337  Push는 옵코드, 337는 오퍼랜드 • Mov eax, 1  Mov는 옵코드, eax와 1이 오퍼랜드  모든 오퍼랜드는 앞의 것이 destination 뒤의 것이 source
  • 8.
  • 9.
    EAX 산술 계산을 하며,리턴 값을 전달하는 변수. A = Accumulator EDX 각종 연산에 쓰이나, 리턴 값에 쓰이지 않는 변수 D = Data ECX C = Count For 문에서 int I 라고 선언할 때의 i의 역할 카운팅 할 필요 없을 때는 그저 변수. EBX 레지스터가 하나 더 필요할 때 사용되는 것.
  • 10.
    • ESI (SourceIndex)  시작지 인덱스  데이터를 조작하거나, 복사 시에 데이 터의 주소가 저장되는 변수 • EDI (Destination Index)  목적지 인덱스  복사 시에 목적지의 주소가 저장되는 변수 • EBP(Stack Pointer)  스택 프레임의 끝 지점 주소가 저장됨. • ESP(Baste Pointer)  스택프레임의 시작 주소가 저장됨.
  • 11.
    기본적인명령어 • ADD, SUB ADD : Src에서 dest로 값을 더하는 명령어  SUB : Src에서 dest로 값을 빼는 명령어 • INT  인터럽트를 일으키는 명령어 • CALL  함수를 호출하는 명령어  CALL뒤에 오퍼랜드로 번지가 붙는다. 해당 번지를 호출하고 끝나면 call다음 번지로 돌 아온다. • INC, DEC  INC = i++; DEC = i--; • PUSH, POP  PUSH : 스택에 값을 넣는 명령어  POP : 스택에 있는 값을 가져오는 명령어 • MOV  값을 넣는 명령어  Mov eax, 1 : eax에 1을 넣는 코드  Mov ebx, ecx : ebx에 ecx를 넣는 코드 • LEA  주소를 넣는 것  Lea eax, dword ptr ds:[esi] : eax에 0x401000  Mov eax, dword ptr ds:[esi] : eax에 5640EC83  Lea eax, dword ptr ss:[esp+8] :eax에 0x13FF40  Mov eax, dword ptr ss:[esp+8] : eax에 33
  • 12.
    기본적인 명령어 • AND,OR ,XOR  비트연산자  XOR는 동일한 오퍼랜드로 처리가능  Ex : XOR EAX, EAX : EAX = 0 • NOP  아무것도 하지 말라는 명령어 • CMP, JMP  비교해서 점프하라는 명령어 • 그 외의 명령어는 구글링
  • 13.
    스택(stack) • 지역 변수사용 • 함수 호출 시 파라미터가 들어가는 방향 • 리턴 주소
  • 14.
    지역 변수 사용 Pushebp Mov ebp, esp Sub esp, 50h • Ebp 레지스터를 스택에 넣는다. • 그리고 현재 esp의 값을 ebp에 넣는 다. • Ebp와 esp가 같아지면서 이제 이 함 수에서 지역변수는 ebp에서부터 얼 마든지 계산할 수 있다. • Ebp를 기준으로 오프셋을 더하고 빼 는 작업으로 스택을 처리할 수 있게 된다. • Sub esp, 50h LIFO특성으로 인해 특정 값만큼 뺀다는 것은 그만큼 스 택을 사용하겠다는 것이고, 즉 50h 만큼 지역변수를 사용하는 것이다.
  • 15.
    함수의 호출 DWORD HelloFunction(DWORD dwParam1,DWORD dwParam2, DWORD dwParam3) HelloFunctioni 호출 Main(){ DWORD dwRet = HelloFunction(0x37,0x38,0x39); If(dwRet) //…. } Push 39h Push38h Push 37h Call 401300h • 스택에 값을 LIFO 순으로 넣기 떄문에 실제 소스코드에서 호출한 것과는 반 대로 들어간다.
  • 16.
    리턴 주소 • DWORDHelloFunction(DWORD dwParam1, DWORD dwParam2, DWORD dwParam3){ • DWROD dwRetAddr = 0; • __asm • { push eax mov eax,[ebp+4] mov dwRetAddr, eax pop eax } Printf(“dwRetAddr: %08xn”, dwRetAddr); } • 결과를 보면 HelloFunction()을 호출 한 뒤 호출한 쪽의 다음 번지가 바로 리 턴 주소다.
  • 17.
    시연 크랙 미 1번• 크랙 미 5번
  • 18.