운영체제?

  • 하드웨어와 애플리케이션 사이에 있는 시스템 소프트웨어
  • 목적?
    • 사용을 편하게 - Low-level 하드웨어를 조작할 있는 간단한, 통합된 인터페이스 제공
    • 공유 - 하드웨어 자원을 여러 프로세스/사용자가 공유할 있도록
    • 보호 - 하드웨어를 잘못된 사용으로부터 보호함
  • Key Interfaces
    • ISA (Instruction Set Architecture) : x86, ARM, MIPS (Machine Language)
      • SW  HW 사이의 인터페이스를 정의
    • ABI (Application Binary Interface)
      • 시스템콜 인터페이스를 정의함
    • API (Application programming interface)
  • CISC (Compex Instruction Set Computer)
    • 복잡한 명령어 집합
    • 명령어의 수가 많음
    • 80년대 중반까지 사용 (x86, VAX)
  • RISC (Reduced Instruction Set Computer)
    • 간단한 명령어 집합
    • 적은 수의 명령어
    • 80년대 이후의 대부분의 아키텍처에 적용
      • ex) PowerPC, Alpha, SPARC,  IA64, ARM
  • Revolution of OS (순서대로)
    • Serial Processing
      • Turing Machine (1936)
        • 새로운 수학적인 컴퓨팅 모델
        • 현대 컴퓨터의 이론적인 배경
        • 현존하는 모든 컴퓨터는 튜링 머신의 Subset
      • Bombe(1940)
        • 독일 에니그마를 해독하기 위해 만들어진 최초의 전자식 컴퓨터
      • Colossus(1943)
        • 1세대 Programmable Digital Computer (Hard-wired Programming)
      • ENIAC (Electronic Numerical Integrator And Computer)
        • 탄도 계산을 위해서 만들어짐
    • Simple Batch Systems
      • Monitor
        • 사용자가 작업(job) 카드 또는 테이프에 작성한 제출 - 순서대로 처리
        • 모니터란 프로그램은 메모리에 상주, 인풋을 하나씩 처리함
        • 프로세서에 비해 입출력이 굉장히 느리기 때문에 idle 상태가 오래 지속됨 - 비효율
    • Multiprogrammed Batch Systems
      • I/O 기다리지 않고 시간에 다른 작업(job) 처리함
      • Processor Utilization 좋음
      • 자신의 작업을 처리하기 위해 이전의 작업을 기다려야
    • Time Sharing Systems
      • 여러 사용자가 동시에 하나의 컴퓨터를 사용 가능
      • 사용자가 기다리는 response time 줄어듬 (Minimizing Response time is more important than maximizing throughput )
      • Time slice / Round Robin
      • Context Switching

 

Competing Processes

  • Sharing a global resources? 다음 문제가 발생 가능
    • Need for mutual exclusion
    • Deadlock
    • Starvation
  • Mutual Exclusion?
    • 상호 배제
    • 한번에 프로세스만 리소스를 사용하도록 제한하는
    • Critical Section 프로세스만 진입하도록 보장해야
    • 데커알고리즘
    • 간단하게 처리하는 방법?
      • 임계 영역에 들어갈 interrupt disable, 나올 enable

 

'Study > OS' 카테고리의 다른 글

4. 쓰레드  (0) 2020.02.28
3. Interrupt / Exception  (0) 2020.02.28
2. 프로세스  (0) 2020.02.28

리버스 엔지니어링 바이블


1. API 후킹 기본

단순히 API의 엔트리 포인트를 jmp 문으로 바꾸면 후킹 탐지에 쉽게 무력화 가능



ws2_32.dll의 send 함수


해당 함수는 위와 같이 mov edi, edi로 시작하며 일반적으로 API 후킹을 하게 되면 아래와 같이 해당 엔트리 포인트를 jmp 문으로 바꾸게됨


API 후킹 이후엔 위와 같이 엔트리 포인트 5바이트만 수정됨


이러한 방법으로는 다른 애플리케이션과 충돌이 가능..



그래서 이렇게 코드 중간에 점프 코드를 삽입하는 방법을 사용하면 코드 후킹 탐지도 피할 수 있고 다른 애플리케이션과 충돌도 피할 수 있음


*사진은 예시를 위해 임의의 명령어와 주소를 삽입함



2. DLL 인젝션


코드 후킹을 위해선 DLL 인젝션이 선행되어야 함.


대부분 흔히 널려있는 DLL 인젝션 툴들은 백신에 악성코드로 진단되기 때문에 직접 만들어서 구현해보는 것도 좋음


2.1. CreateRemoteThread 

 DLL 인젝션의 핵심 원리는 CreateRemoteThread API임

 해당 API는 타 프로세스의 메모리에 스레드를 생성할 수 있는 기능을 지원함

 일반적으로 DLL 로딩은 다음과 같은 코드를 사용함



 위 코드는 C:\Windows\system32\ws2_32.dll을 메모리에 로딩하는 코드임

LoadLibrary() 를 호출해 DLL 파일의 경로를 전달하면, 그 프로세스 메모리에 위 DLL이 로딩됨

CreateRemoteThread() 호출하고 해당 프로세스에서 LoadLibrary() 실행, 인자로 들어가는 DLL 파일의 전체 경로만 지정하면 DLL 인젝션 가능


구현은 다음과 같은 과정으로 가능함



void InjectionDll(DWORD pid, LPCSTR dll)
cs

위와 같이 타겟이 될 프로세스의 pid와 인젝션 할 dll을 두 번째 인자로 받는 구조로 선언
 

HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
cs

타겟 프로세스의 핸들이 필요하므로 OpenProcess()로 pid를 지정한 후 핸들 hProcess를 얻음


LPVOID lpAddr = VirtualAllocEx(hProcess, NULL, strlen(dll)+1, MEM_COMMIT, PAGE_READWRITE);
cs

타겟 프로세스에 LoadLibrary()로 넘겨줄 dll의 주소를 써 넣기 위해 메모리를 할당해줌
* VirtualAlloc은 자신의 프로세스에 할당, VirtualAllocEx는 타 프로세스에 메모리 할당
크기는 dll 경로 문자열이 들어갈 공간이면 충분하므로 Null 문자를 위한 strlen(dll)+1 로 설정
위 함수의 반환 값인 lpAddr에는 DLL의 전체 경로가 담긴 메모리 번지가 되고 이를  LoadLibrary()와  CreateRemoteThread() 에 전달함



WriteProcessMemory(hProccess, lpAddr, dll, strlen(dll)+1NULL);
cs

할당해준 메모리에 dll의 전체 경로를 써줌



LPTHREAD_START_ROUTINE pfnLoadLibraryA =
 (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");

cs


  CreateRemoteThread() 에 사용할  LoadLibrary()의 주소를  GetProcAddress()를 이용하여 구해옴



HANDLE hThread = CreateRemoteThread(hProcess, NULL0, pfnLoadLibraryA, lpAddr, 0NULL);
cs


  CreateRemoteThread() 를 호출하면 타겟 프로세스에서는   LoadLibrary() 가 호출되며 인젝션할 DLL를 로드한다




3. 코드 후킹 

 3.1 후킹 기본

 실제 Lws_32.dll()  파일의 send()  API는 다음과 같이 후킹할 수 있다.


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
void InitHooking1()
{
    typedef void (WINAPI *Tsend)(SOCKET, const char *intint);
    Tsend fnsend;
    TCHAR szWs2_32Dll[MAX_PATH];
    GetSystemDirectory(szWs2_32Dll, MAX_PATH);
    _tcscat(szWs2_32Dll, _T("\\ws2_32.dll"));
    HMODULE hMod = LoadLibrary(szWs2_32Dll);
    fnsend = (Tsend)GetProcAddress(hMod, "send");
 
 // 엔트리부터 0x16 바이트 떨어진 지점을 대상 주소로 설정
    LPVOID lpTargetAdr = (LPVOID)((DWORD)fnsend + 0x16); 
    TRACEB(_T("lpTargetAdr: %x"), lpTargetAdr);
    DWORD dwOldProtect = 0;
    VirtualProtect(lpTargetAdr, 6, PAGE_READWRITE, &dwOldProtect);
    
    // 기초 설명을 위한 하드코딩
    //719E4C3D           E9 BEC391C1     jmp 33301000
    //719E4C42           90              nop
    *((LPBYTE)lpTargetAdr + 0= 0xE9;
    *((LPBYTE)lpTargetAdr + 1= 0xBE;
    *((LPBYTE)lpTargetAdr + 2= 0xC3;
    *((LPBYTE)lpTargetAdr + 3= 0x91;
    *((LPBYTE)lpTargetAdr + 4= 0xC1;
    *((LPBYTE)lpTargetAdr + 5= 0x90;
}
 
cs


 이를 통해 send() 함수가 호출될 경우 0x33301000에 있는 코드를 실행하게 할 수 있음


 0x33301000에 필요한 DLL를 로드하면 원하는 DLL를 실행시킬 수 있음


#pragma comment(linker, "/base:0x33300000 /fixed")
cs


위와 같은 코드를 소스코드의 위쪽에 배치하면 0x33300000번지가 ImageBase가 됨



그러나 위와 같이 주소를 하드코딩하는 방식은 DLL의 주소가 그 주소에 로딩되지 못하면 무의미함

주소가 변경됐을 때도 주소를 찾아 연결시킬 수 있는 방법이 필요함


위의 코드에서 20~26번째 라인을 다음과 같이 변경하여 동적 주소를 구할 수 있음



1
2
3
    *((LPBYTE)lpTargetAdr + 0= 0xE9;
    DWORD dwBufferAdr = (DWORD)HookSend - (DWORD)fnsend - 0x16 - 5;    
    *((LPDWORD)((LPBYTE)lpTargetAdr + 1)) = dwBufferAdr;
cs



 먼저 타겟 jmp 명령어를 의미하는 0xE9는 타겟 주소에 넣어줌

 주소부분 계산은 삽입할 주소의 위치와 jmp 대상이 되는 주소의 차이와 5바이트의 op code를 고려하여 계산해주면 됨

 

 719E4C3D E9 BEC391C1     jmp 33301000


 앞서 이 명령어의 operand도 이렇게 계산됨


0x33301000 - 0x719E4C3D - 5 = 0xFFFFFFFFC191C3BE

 이를 4바이트 주소인 DWORD로 타입 캐스팅 하면 앞의 0xFFFFFFFF는 제거되고 0xC191C3BE만 남고

 이를 리틀엔디안 방식으로 하면 명령어의 operand 부분인 BEC391C1이 됨


 마지막에는 ((LPBYTE)(lpTargetAdr + 1))  의 위치를 LPDWORD 타입으로 4바이트 묶어서 계산된 주소를 넣어줌



3.2 Send 후킹

 

 send 함수의 프로토 타입을 참고하여 버퍼를 출력하는 코드를 작성할 수 있음


1
2
3
4
5
6
int send(
    __in SOCKET s,
    __in const char *buf,
    __in int len,
    __in int flags
);

cs


 2번째 인자로 전송할 버퍼를 전달받고, 버퍼의 길이를 3번째 인자로 전달받음


 이를 참고하여 버퍼를 OutputDebugString을 통해 출력하는 코드를 다음과 같이 어셈블리 코드로 작성

 

1
2
3
4
5
6
7
8
9
10
11
__declspec(naked) HookSend()
{
    __asm
    {
        mov eax, [ebp+0xC]
        push eax
        call ds:OutputDebugString
        mov eax, 0x719E4C43
        jmp eax
    }
}
cs

 

 [ebp+0xC]에는 함수로 전달되는 두 번째 인자가 존재하니까, 그걸 eax에 넣어주고 OutputDebugString 함수의 인자로 전달하기 위해 스택에 넣음


 그리고 원래 코드로 돌아오기 위한 점프문 배치하고 이 함수를 0x33301000 번지에 올라가게 하면 됨



 그러나 단순히 이렇게 해버리면, send 함수를 통해 나가는 모든 패킷을 모니터링하게됨...


 send  함수를 호출한 부모 함수를 식별해서 어느 함수에서 호출한 send인지 출력 가능

 또한 모니터링 하고자 하는 패킷만 모니터링 할 수 있음


 부모 함수를 식별하기 위한 방법은 send 함수의 return 주소인 [ebp + 0x4]를 참조하면 식별이 가능함



 먼저, 어셈블리 코드에서 사용할 ebx, eax, edx 레지스터가 망가지지 않게 스택에 넣어두고, 각각 레지스터에 메시지의 길이, 메시지 내용, return 주소를 넣음 


1
2
3
4
5
6
push ebx
push eax
push edx
mov ebx, [ebp+0x10] // 패킷 길이
mov ebx, [ebp+0xC] // 패킷 버퍼
mov ebx, [ebp+0x4] // return 주소
cs

 이렇게 원래의 레지스터에 있는 값을 저장하고 레지스터를 마음대로 쓴 이후 다시 pop을 통해 복원해줌

 다음과 같은 코드로 send 함수를 호출한 부모 함수의 주소와, 버퍼를 문자열과 덤프 형식으로 출력이 가능함
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
void PseudoFunc(int retaddr, char *buf, int len)
{
    if (len != 1 && len > 0)
    {
        //TRACEB("[%x] send : [%d]", retaddr, len);
 
        int len_temp = 0;
        int len_temp2 = 0;
        char szMsg1[MAX_PATH] = {0,};
        char szMsg2[MAX_PATH] = {0,};
        for (int i=0; i<len; ++i)
        {
            if (MAX_PATH < i)
                break;

            // null 바이트는 왜 "_"로 바꾸지...
            if (buf[len_temp] == '\0')
                len_temp += sprintf(szMsg1 + len_temp, "_");
            else
                len_temp += sprintf(szMsg1 + len_temp, "%c", buf[i]);
            
            // 200 바이트 까지 버퍼를 Hex dump
            if (len_temp2 < 200)
                len_temp2 += sprintf(szMsg2 + len_temp2, "%02X "*(buf + i));
        }
        
        TRACEB("[%x] send : [%d] %s", retaddr, len, szMsg1);
        TRACEB("     dump : %s", szMsg2);
    }
}
cs

 
 이처럼 작성한 함수를 호출하는 어셈블리 코드를 작성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
__declspec(naked) HookSend()
{
    __asm
    {
        push ebx
        push eax
        push edx
        mov ebx, [ebp+0x10]
        mov eax, [ebp+0xC]
        mov edx, [ebp+0x4]
        push ebx
        push eax
        push edx
        call lpPseudoFunc
        add esp, 0xc
        pop edx
        pop eax
        pop ebx
        mov eax, 0x719E4C43
        jmp eax
    }
}
cs
 15번째 줄의 add esp, 0xc는 함수 호출 과정에서 늘어난 스택 크기를 조정해주기 위함

 







 

 

'Study > Reversing' 카테고리의 다른 글

우회 기법  (0) 2019.03.20
FSC_Level1 풀이  (0) 2019.03.20
Petya Ransomware 분석  (0) 2019.02.19
PE 헤더  (0) 2019.02.18
2. C 문법과 디스어셈블리  (0) 2019.02.14

13. 우회 방법

API Hooking을 통한 우회
  • API를 이용한 디버깅 탐지 기법들은 API Hooking을 이용하여 해당 API를 무력화 하는 방법으로 우회 가능

  • OllyDBG의 OllyAdvanced라는 안티 디버깅 플러그인이 대부분의 무력화를 지원함

    • FindWindow

      : FindWindow()를 Hooking -> Debuger process의 윈도우 핸들을 가리킬 경우 Null을 return

    • IsSoftIceLoaded

      : CreateFile() Hooking -> Softice 드라이버일 경우 null 또는 INVALID_HANDLE 리턴

    • CheckRemoteDebuggerPresent

      : CheckRemoteDebuggerPresent Hooking -> 호출 시 강제로 false 리턴





      함수 호출부를 아래와 같이 수정하여 강제로 0을 반환하도록 함

      xor eax, eax

      ret

    • Debug Oject Handle

      : NtQueryInformationProcess() API를 같은 방법으로 수정

      : 두 번째 인자가 0x1e (ProcessDebugObjectHandle)일 경우에 대해서만 반환하도록

    • NtQueryObject

      : NtQueryObject에서 NTSTATUS 실패를 리턴

      • NtQueryObject() 를 호출할 경우 모든 오브젝트 핸들을 얻어올 수 있음, 이 중 DebugObject가 포함됐을 경우 탐지하는 기법

    • NoDebugInherit

      : NtQueryInformationProcess() API를 같은 방법으로 수정

    • NtSetInformationThread

      : NtSetInformationThread() API를 같은 방법브로 수정

    • 프로세스 체크

      : Process32Next()API를 이용, 프로세스를 찾지 못하도록 수정

    • 버전 체크

      : GetFileVersionInfoSize() 를 실패하도록 수정

    • 부모 프로세스 체크

      : Process32Next()API를 이용, 프로세스를 찾지 못하도록 수정

      : NtQueryInformationProcess() 도 변조

    • SeDebugPrivilege 권한 체크

      : CsrGetProcessId() 를 변조, OpenProcess(), NtQueryObject()

    • 키보드 입력 봉쇄

      : BlockInput() 변조

    • OutputDebugString

      : OutputDebugString() 함수를 변조, 바로 반환하도록 설정해도 가능

    • WinDBG 검출

      : FindWindow() 무력화

    • CloseHandle()을 이용한 안티 리버싱

      : CloseHandle() 을 후킹, 정상 핸들을 Close하는 것을 걸러야 하는데 이 과정이 복잡

      : OllyAdvanced에서도CloseHandle()을 필터링하는 기능이 존재

      : Ignore and skip C0000008h (inv Handle) 기능 활성화

    • 타이머를 이용한 방법

      : GetTickCount() API를 후킹해 시간을 속임, rdtsc 를 사용하면 Win32 API 가 아니기 때문에 APi 후킹으로는 안됨, OllyAdvanced 에서 관련 기능을 제공함

      1. Anti-RDTSC 기능을 Enable

      2. 해당 기능을 사용하면 아래와 같이 커널 드라이버가 설치됨    


3. IDT 후킹을 이용한 rdtsc 우회 기법

4. rdtsc : 시스템 부팅 후 현재 실행 시점까지를 알려주는 명령어, CPU 사이클을 읽어서 계산함

5. CR4 레지스터의 TSD(Time Stamp Disable) 비트를 1로 설정할 경우 rdtsc가 호출될 때마다 GP(General Protection) 예외가 발생

6. 해당 예외를 처리하는 핸들러가 GPF(General Protection Fault)이며 0xD 인테럽트를 사용
7. 즉, rdtsc를 호출하면 0xD번 인테럽트가 호출됨
8. 0xD 인테럽트를 후킹하여 해당 인테럽트가 호출되면 그것이 rdtsc에 의해 호출되었는지 확인, rdtsc일 경우 시간 값 조작을 통해 우회

      그냥 opcode를 찾아 패치하는 방법도 있음

      1. rdtsc를 호출하는 명령어 (0x0F31)를 모두 찾아 0x90(nop)으로 수정함

    • 디버그 레지스터

      : ???

    • API Hook을 이용한 디버깅 감지

      : 디버거가 프로세스를 attach 할 때 주로 사용하는 DebugActiveProcess() 를 후킹해서 attach 를 못 하게 하는 기법으로, 후킹된 해당 DebugActiveProcess() 를 다시 후킹해서 원복시킴

    • Self Debugging

      : 부모 프로세스에서 WaitForDebugEvent() API를 호출함, 해당 API에 DebugActiveProcessStop() 함수를 삽입하여 부모 프로세스가 디버거를 놓아버리게 함

OllyDBG 옵션을 이용한 우회
  • int3을 이용한 디버거 감지

    : OllyDBG 의 메뉴에서 Option - Debugging Option 으로 가서 Exception 탭에서 Ignore memory access violations in KERNEL32 을 켜고 INT3 break 옵션과 Single-step break 옵션을 켜준다

    : 디버거 인테럽트가 발생해도 자동으로 예외 처리 핸들러를 호출하여 디버거 사용이 가능

  • SetUnhandledExceptionFilter

    : 예외 처리 관련 안티 디버깅에 대한 우회는 해당 옵션으로 대부분 우회 가능

플래그 수정으로 우회
  • PEB를 이용한 방법

    : PEBBeginDebugged 플래그 값을 0 으로 패치

  • 리모트 디버깅 감지

    : KD_DEBugGER_NOT_PRESENT 는 커널 전역 변수, 해당 변수를 TRUE 로 패치해버림

  • 0xCC 자체를 탐지

    : 코드 상의 0xCC 를 찾아서 패치


'Study > Reversing' 카테고리의 다른 글

코드 후킹  (0) 2019.03.21
FSC_Level1 풀이  (0) 2019.03.20
Petya Ransomware 분석  (0) 2019.02.19
PE 헤더  (0) 2019.02.18
2. C 문법과 디스어셈블리  (0) 2019.02.14

+ Recent posts