본문 바로가기

RVS

Reversing.kr 10번 문제 (Twist1)

Twist1 프로그램 디버깅 시작 부분은 아래 그림에서 볼 수 있듯이, OEP (Original Entry Point) 위치가 아니다.

그렇다면 프로그램이 패킹되어 있을 것으로 예상된다.

그러나, 다음 그림의 Detect It Easy을 통해 확인한 Twist1 프로그램에 패킹된 정보는 없다.

그렇다면, 커스텀으로 패킹된 것이 아닐까라는 생각이 든다.

 

 

ANTI DEBUGGING 기법-1 (POP SS)


우선, [F9] 단축키를 통해 프로그램을 실행해보았다.

그러면 아래 그림 00402052 위치에서 프로그램이 멈추고 다음 그림과 같이 EXCEPTION_ACCESS_VIOLATION 예외가 발생한다.

계속 이렇게 동적 디버깅할 때만 예외가 발생해서, 'pop ss'에 대해 검색해보았다.

찾아보니 왠걸.. 'pop ss'는 동적 안티 디버깅 기법으로 사용되는 명령이라고 한다.

POP SS 명령은 다음 명령어가 수행될 때까지 모든 인터럽트가 실행되는 것을 막는다. 
POP SS 명령을 수행하게 되면 바로 다음 명령을 건너뛰고 그 다음 명령어에서 멈춘다.

 

 

다시 처음으로 돌아가서 살펴보면, 아래 그림에서 볼 수 있듯이 00407008, 0040700E, 0040701D 주소에서 함수가 세번 호출된다. [F7] 단축키를 통해 함수 내부까지 차례로 디버깅하다보면, 0040701D 함수 내에서 반복되는 루틴이 존재한다. 

 

아래 그림은 0040701D 함수 내 반복적인 루틴이 발생하는 구간이다(00407063~0040706D).

반복 루틴 다음 명령부터 살펴보기 위해서 0040706F에 Breakpoint를 걸어두고 실행해보았다.

 

ANTI DEBUGGING 기법-2

(PEB_ProcessHeap의 Flag/ForceFlag 이용)


이후 주목해서 봐야하는 부분은 위 그림의 00407077 명령 부분이다.

eax 레지스터에 fs:30h 주소를 세팅하는 것을 알 수 있다. FS:0x30은 PEB 주소를 말한다.

즉, eax에는 프로세스의 PEB 구조체를 가리키는 포인트가 세팅된다.

PEB (Process Environment Block)는 프로세스 정보(로드된 모듈, 프로세스 시작 매개변수, 힙 주소 등)를 담는 사용자 모드 데이터 구조이다. FS 레지스터의 0x30을 통해 PEB에 접근하여 프로세스 구조체에 접근할 수 있다. 
또한, 아래와 같은 방식으로도 PEB의 주소를 획득한다고 한다.
MOV EAX, DWORD PTR FS:[18]
MOV EAX, DWORD PTR DS:[EAX+30]

64비트 환경에서는 FS 레지스터 대신 GS 레지스터가 사용되고 GS 레지스터의 0x60을 통해 PEB 주소를 구할 수 있다. 또한 아래와 같은 방식으로도 64비트 환경에서 PEB 주소를 획득한다고 한다.
MOV RAX, QWORD PTR GS:[30]
MOV RAX, QWORD PTR DS:[RAX+60]

 

아래 그림의 EAX 레지스터에는 00305000 값이 세팅되어 있다. 그렇다면 00305000 주소는 PEB 주소를 말하는 것이다. 한번 확인해보자.

IDA의 Windbg를 통해 !peb 명령으로 프로세스의 peb를 확인해보았다. "Wow64 PEB32 at 305000" 문자열을 통해 PEB 구조체의 주소가 305000인 것을 알 수 있다. 위 그림의 EAX 레지스터에 세팅된 주소와 동일하다.

 

이후 00407081 주소에서 0으로 셋팅되어 있는 edx에 0x28을 더해준 후에, 00407084 주소에서 0x28과 0x30을 XOR 연산한다. 연산 결과 아래 그림에서 볼 수 있듯이 edx에 0x18이 세팅되어 있다.

바로 다음 주소인 00407087에서 edx과 eax를 더해준 값이 edx에 세팅된다.

위에서 eax에는 PEB 구조체를 가르키는 포인트가 세팅되어 있었고, edx에는 0x18이 세팅되어 있다.

즉, PEB + 0x18의 주소 가리키는 것이다.

PEB 구조체는 x32과 x64 버전마다 조금씩 다르게 구성된다. 이 문제에서는 Readme.txt에서 우리에게 x86 프로그램이라는 힌트를 주었으므로 아래의 x32 구조체를 살펴볼 수 있겠다.

PEB 구조체에서 +0x18은 ProcessHeap을 가리킨다. edx에는 PEB 구조체의 ProcessHeap 주소를 가리키는 포인트가 세팅된다.

 

struct _PEB {
    0x000 BYTE InheritedAddressSpace;
    0x001 BYTE ReadImageFileExecOptions;
    0x002 BYTE BeingDebugged;
    0x003 BYTE SpareBool;
    0x004 void* Mutant;
    0x008 void* ImageBaseAddress;
    0x00c _PEB_LDR_DATA* Ldr;
    0x010 _RTL_USER_PROCESS_PARAMETERS* ProcessParameters;
    0x014 void* SubSystemData;
    0x018 void* ProcessHeap;
    0x01c _RTL_CRITICAL_SECTION* FastPebLock;
    0x020 void* FastPebLockRoutine;
    0x024 void* FastPebUnlockRoutine;
    0x028 DWORD EnvironmentUpdateCount;
    0x02c void* KernelCallbackTable;
    0x030 DWORD SystemReserved[1];
    0x034 DWORD ExecuteOptions:2; // bit offset: 34, len=2
    0x034 DWORD SpareBits:30; // bit offset: 34, len=30
    0x038 _PEB_FREE_BLOCK* FreeList;
    0x03c DWORD TlsExpansionCounter;
    0x040 void* TlsBitmap;
    0x044 DWORD TlsBitmapBits[2];
    0x04c void* ReadOnlySharedMemoryBase;
    0x050 void* ReadOnlySharedMemoryHeap;
    0x054 void** ReadOnlyStaticServerData;
    0x058 void* AnsiCodePageData;
    0x05c void* OemCodePageData;
    0x060 void* UnicodeCaseTableData;
    0x064 DWORD NumberOfProcessors;
    0x068 DWORD NtGlobalFlag;
    0x070 _LARGE_INTEGER CriticalSectionTimeout;
    0x078 DWORD HeapSegmentReserve;
    0x07c DWORD HeapSegmentCommit;
    0x080 DWORD HeapDeCommitTotalFreeThreshold;
    0x084 DWORD HeapDeCommitFreeBlockThreshold;
    0x088 DWORD NumberOfHeaps;
    0x08c DWORD MaximumNumberOfHeaps;
    0x090 void** ProcessHeaps;
    0x094 void* GdiSharedHandleTable;
    0x098 void* ProcessStarterHelper;
    0x09c DWORD GdiDCAttributeList;
    0x0a0 void* LoaderLock;
    0x0a4 DWORD OSMajorVersion;
    0x0a8 DWORD OSMinorVersion;
    0x0ac WORD OSBuildNumber;
    0x0ae WORD OSCSDVersion;
    0x0b0 DWORD OSPlatformId;
    0x0b4 DWORD ImageSubsystem;
    0x0b8 DWORD ImageSubsystemMajorVersion;
    0x0bc DWORD ImageSubsystemMinorVersion;
    0x0c0 DWORD ImageProcessAffinityMask;
    0x0c4 DWORD GdiHandleBuffer[34];
    0x14c void (*PostProcessInitRoutine)();
    0x150 void* TlsExpansionBitmap;
    0x154 DWORD TlsExpansionBitmapBits[32];
    0x1d4 DWORD SessionId;
    0x1d8 _ULARGE_INTEGER AppCompatFlags;
    0x1e0 _ULARGE_INTEGER AppCompatFlagsUser;
    0x1e8 void* pShimData;
    0x1ec void* AppCompatInfo;
    0x1f0 _UNICODE_STRING CSDVersion;
    0x1f8 void* ActivationContextData;
    0x1fc void* ProcessAssemblyStorageMap;
    0x200 void* SystemDefaultActivationContextData;
    0x204 void* SystemAssemblyStorageMap;
    0x208 DWORD MinimumStackCommit;
);

 

위위 그림에서 00407089 주소의 twist.40709F 함수 내부로 들어가 디버깅을 계속해보자.

[edx]는 아래 그림과 같이 PEB의 ProcessHeap (PEB+0x18) 주소가 세팅되어 있으며, 다음 그림에서 볼 수 있듯이 [edx](305018)에는 ProcessHeap 주소 값인 006B0000가 저장되어 있다.

다음 그림의 PEB 구조의 ProcessHeap 주소가 006B000인 것으로 동일하다.

다음 0040709F~004070A9 명령의 연산을 보았을 때, ProcessHeap+0xC == 2를 의미한다.

 

아래는 ProcessHeap 구조체의 일부를 나타낸다.

이를 통해 ProcessHeap + 0xC는 Flags를 가리키는 것을 알 수 있다.

프로세스가 정상으로 실행 중일 때 ProcessHeap.Flags 멤버의 값은 0x02로 세팅된다.

그러므로 ProcessHeap+0xC == 2를 통해 프로세스가 정상으로 실행 중인지를 확인한다. 

+0x000 Entry : _HEAP_ENTRY

+0x008 Signature : Uint4B

+0x00c Flags : Uint4B

+0x010 ForceFlags : Uint4B

+0x014 VirtualMemoryThreshold : Uint4B

+0x018 SegmentReverse : Uint4B

+0x01c SegmentCommit : Uint4B

+0x020 DecommitFreeBlockThreshold : Uint4B

 

동적 디버깅을 통해서 004070A9 주소의 비교문에서 cl이 2인지를 확인해보았다.

아래 그림에서 볼 수 있듯이 ECX에 0x2가 세팅되어 있으므로 정상으로 Flag는 정상으로 실행된 것으로 확인된다.

프로세스를 다시 실행해서 EAX, EBX, EDX 값이 다르다. 이해해주세용 ^^ㅎ

 

좀 더 디버깅을 해보자.

위 그림에서 디버깅을 수행하다보면 여러개의 jump 문을 통해 왔다갔다 하다가 아래 그림의 주소에 도달하게 된다.

 

([PEB+0x18])

004070CB~004070DB의 연산을 보았을 때, ProcessHeap+0x10 == 0를 의미한다.

ProcessHeap + 0x10은 위의 ProcessHeap 구조를 보면 ForceFlags 멤버를 의미함을 알 수 있다.

ProcessHeap.ForceFlags 멤버의 값은 프로세스가 정상으로 실행 중일 때 0x0이다.

동적 디버깅을 통해 004070DB의 비교문을 통해 cl 레지스터에 세팅된 값은 아래 그림과 같다.

즉, 0이 아닌 'A4'라는 값이 세팅되어 있다. 디버깅 중인 경우에는 'A4'라는 값이 세팅되므로 이렇게 디버깅 중임을 확인하고 디버깅을 수행하지 못하도록 한다.

이를 우회하기 위해서 ECX의 값을 006600A4에서 00660000으로 수정하였다.

 

ANTI DEBUGGING 기법-3

(PEB_Ldr의 InInitializationOrderLinks 이용)


그림의 004070DB 주소에서 ECX의 값을 패치한 후, 쭉 디버깅을 수행하다보면, 아래 그림의 명령 주소부분이 눈에 띈다.

 

위 그림에서 0040712D 명령까지 수행하게 되면 레지스터가 아래와 같이 세팅된다.

EDX 레지스터에 세팅된 002BB0C는 PEB+0xC로 위의 PEB 구조체를 살펴보면 _PEB_LDR_DATA를 가리키는 Ldr이라는 포인터 변수이다.

Ldr은 _PEB_LDR_DATA라는 구조를 가리키는 포인터로 현재 프로세스에서 로드한 모듈에 관한 정보를 담고 있다.

EBX 레지스터의 775C7BA0은 _PEB_LDR_DATA의 주소이다.

 

 

아래 그림에서 00407133 명령까지 실행하게 되면 다음 그림의 레지스터와 같이 세팅된다.

즉, EDX에는 _PEB_LDR_DATA + 0x10이 세팅된다.

그리고 위 그림의 00407135 명령을 실행하게 되면 EDX에 세팅된 _PEB_LDR_DATA + 0x10에서 값을 EBX 레지스터에 저장한다. 아래 그림은 EDX에 세팅된 775C7BB0 주소에 세팅된 값이다. 006647A0 인 것을 볼 수 있으며 아래 그림의 EBX 레지스터 세팅된 것과 동일함을 확인할 수 있다.

 

_PEB_LDR_DATA 구조를 보기 위해서 Windbg의 dt _LDR_DATA_TABLE_ENTRY 명령을 통해 살펴보았다.

_PEB_LDR_DATA 구조는 아래와 같다. 

즉, EDX에 세팅된 _PEB_LDR_DATA + 0x10은 InInitializationOrderLinks를 가리킨다.

 

ntdll_774b0000!_LDR_DATA_TABLE_ENTRY
   +0x000 InLoadOrderLinks : _LIST_ENTRY
   +0x008 InMemoryOrderLinks : _LIST_ENTRY
   +0x010 InInitializationOrderLinks : _LIST_ENTRY
   +0x018 DllBase          : Ptr32 Void
   +0x01c EntryPoint       : Ptr32 Void
   +0x020 SizeOfImage      : Uint4B
   +0x024 FullDllName      : _UNICODE_STRING
   +0x02c BaseDllName      : _UNICODE_STRING
   +0x034 FlagGroup        : [4] UChar
   +0x034 Flags            : Uint4B
   +0x034 PackagedBinary   : Pos 0, 1 Bit
   +0x034 MarkedForRemoval : Pos 1, 1 Bit
   +0x034 ImageDll         : Pos 2, 1 Bit
   +0x034 LoadNotificationsSent : Pos 3, 1 Bit
   +0x034 TelemetryEntryProcessed : Pos 4, 1 Bit
   +0x034 ProcessStaticImport : Pos 5, 1 Bit
   +0x034 InLegacyLists    : Pos 6, 1 Bit
   +0x034 InIndexes        : Pos 7, 1 Bit
   +0x034 ShimDll          : Pos 8, 1 Bit
   +0x034 InExceptionTable : Pos 9, 1 Bit
   +0x034 ReservedFlags1   : Pos 10, 2 Bits
   +0x034 LoadInProgress   : Pos 12, 1 Bit
   +0x034 LoadConfigProcessed : Pos 13, 1 Bit
   +0x034 EntryProcessed   : Pos 14, 1 Bit
   +0x034 ProtectDelayLoad : Pos 15, 1 Bit
   +0x034 ReservedFlags3   : Pos 16, 2 Bits
   +0x034 DontCallForThreads : Pos 18, 1 Bit
   +0x034 ProcessAttachCalled : Pos 19, 1 Bit
   +0x034 ProcessAttachFailed : Pos 20, 1 Bit
   +0x034 CorDeferredValidate : Pos 21, 1 Bit
   +0x034 CorImage         : Pos 22, 1 Bit
   +0x034 DontRelocate     : Pos 23, 1 Bit
   +0x034 CorILOnly        : Pos 24, 1 Bit
   +0x034 ChpeImage        : Pos 25, 1 Bit
   +0x034 ReservedFlags5   : Pos 26, 2 Bits
   +0x034 Redirected       : Pos 28, 1 Bit
   +0x034 ReservedFlags6   : Pos 29, 2 Bits
   +0x034 CompatDatabaseProcessed : Pos 31, 1 Bit
   +0x038 ObsoleteLoadCount : Uint2B
   +0x03a TlsIndex         : Uint2B
   +0x03c HashLinks        : _LIST_ENTRY
   +0x044 TimeDateStamp    : Uint4B
   +0x048 EntryPointActivationContext : Ptr32 _ACTIVATION_CONTEXT
   +0x04c Lock             : Ptr32 Void
   +0x050 DdagNode         : Ptr32 _LDR_DDAG_NODE
   +0x054 NodeModuleLink   : _LIST_ENTRY
   +0x05c LoadContext      : Ptr32 _LDRP_LOAD_CONTEXT
   +0x060 ParentDllBase    : Ptr32 Void
   +0x064 SwitchBackContext : Ptr32 Void
   +0x068 BaseAddressIndexNode : _RTL_BALANCED_NODE
   +0x074 MappingInfoIndexNode : _RTL_BALANCED_NODE
   +0x080 OriginalBase     : Uint4B
   +0x088 LoadTime         : _LARGE_INTEGER
   +0x090 BaseNameHashValue : Uint4B
   +0x094 LoadReason       : _LDR_DLL_LOAD_REASON
   +0x098 ImplicitPathOptions : Uint4B
   +0x09c ReferenceCount   : Uint4B
   +0x0a0 DependentLoadFlags : Uint4B
   +0x0a4 SigningLevel     : UChar

 

아래로 좀 더 살펴보면 다음 그림과 같이 InInitializationOrderLinks의 값과 0xEEFEEEFE와 0xABABABAB가 같은지를 확인한느 루틴이 존재한다.

Ldr은 프로세스가 디버깅 중인지를 확인하는데 사용될 수 있다. 디버깅 수행 중인 프로세스는 힙 메모리 영역에 자신이 디버깅 수행 중인 프로세스라는 표시를 하기 위해 사용되지 않은 부분을 0xFEEEFEEE 혹은 0xABABABAB로 채운다.
이 표시를 보고 프로세스가 디버깅 당하는 중인지를 알 수 있는 것이다.
즉, 해당 표시를 보고 디버깅 당하는 중인 것이 확인되면 디버깅을 못하도록 수행하는 안티 디버깅 기법이 존재한다.

InInitializationOrderLinks은 힙 메모리 주소 영역에서 값을 가져올 수 있다!

 

위 그림에서는 00407155 명령에서 0x1F4번 반복하여 주소의 값이 0xEEFEEEFE 혹은 0xABABABAB인지 확인하고 있다.  그러므로 이를 우회하기 위해서 반복을 한번만 하도록 수정하자. 00407155 주소의 0x1F4를 0x1로 패치하여 반복문을 나가게끔 하였다.

 

계속해서 [F7]로 디버깅하다보면 아래와 같이 00401719 주소로 도달하게 된다.

loc_407183과 loc_407199가 계속해서 반복되는데 0040718E 0040718E와 0040701a4에 breakpoint를 걸어두고 빠르게 디버깅하자.

 

 

 

그러면, 아래의 그림과 같이 EIP가 004071A4 명령의 위치를 가리킨다.

그리고 아래의 004071AF, 004071B6 명령을 수행하게 되면 아래 그림과 같이 004071BD의 명령이 바뀌면서 점프문이 생성된다.

 

점프문으로 가보니 아래 그림과 같이 OEP로 보이는 곳을 드디어 찾았다!!!

여기서 메모리 덤프를 수행하자.

 

 

 

 

 

 

이제 다시 시작이다!!

덤프를 수행한 프로그램을 다시 IDA로 열어보면 아래 그림과 같이 00401270 주소부터 메인 함수가 보인다.

 

 

[F9] 단축키로 프로그램을 실행하면 아래 그림과 같이 오류창이 발생한다.

위 그림의 40129B의 명령인 adc dl, [edx]에 0x0 메모리 주소가 참조된 것이 문제인 듯 하다.

 

0040129B 명령에 Breakpoint를 걸어두고 [F9] 단축키로 실행해보자. 그러면, 아래 그림과 같이 EIP가 00401299에 위치한다. 이 때 [edx]에 0x0 메모리 주소가 참조된 것이 문제가 되었으니 레지스터를 확인해보자.

 

아래 그림에서 볼 수 있듯이 EDX 레지스터에 0x0이 세팅되어 있다.

우선, 다음 그림에서와 같이 헥스 뷰에서 볼 수 있는 메모리 주소를 아무거나 넣어주었다.

아래의 예시에서는 00401220으로 수정해주었다.

 

 

그리고 [F8]로 명령을 실행해보자.

오, 아무런 오류 없이 명령이 다음 명령인 0040129D가 실행되었다.

 

 

위 그림에서 쭉 디버깅을 수행하다보면 004012B4 명령인 sub_401220 함수를 호출하는 부분에서 드디어 input을 요청한다! 아래와 같이 'maam2'를 입력한 후, 엔터를 누르면 아래 그림에서와 같이 EIP가 다음 명령 주소로 넘어간다.

 

아래 그림에서보면, 004012C6 명령 부분에서 sub_401240 함수가 수행되고 리턴된 값이 저장된 eax가 '0' 인지를 확인한다.

아...너무 뭐가 많다 ㅠㅠ 오늘은 여기까지 내일 다시 시작해야겠다.