Created
November 19, 2014 03:31
-
-
Save tandasat/a9ce7a7b4bfa7cf4f860 to your computer and use it in GitHub Desktop.
Answers of exercises in Practical Reverse Engineering Chapter 1
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/////////////////////////////////////////////////////////////////////////////// | |
// | |
// p11 | |
// | |
//////////////////////////////// 1 | |
; ASM | |
edi = s->0x8_charp | |
edx = edi | |
eax = 0 | |
ecx = 0xffffffff | |
while (ecx) | |
{ | |
ecx-- | |
if (edi == eax) break; | |
edi++ | |
} | |
ecx =+ 2 | |
ecx = -ecx ; gets length | |
al = s->0xc_uchar | |
edi = s->0x8_charp | |
memset(edi, al, ecx) | |
eax = s->0x8_charp | |
// C | |
uint str_length = strlen(s->0x8_charp) | |
memset(s->0x8_charp, | |
s->0xc_uchar_fill, | |
length) | |
return s->0x8_charp; | |
// Just notes | |
means quotient remainder | |
div ecx edx:eax / ecx eax edx | |
div cx dx: ex / cx ax dx | |
div cl ax / cl al ah | |
mul ecx edx:eax = eax * ecx | |
mul cx dx: ax = ax * cx | |
mul cl ax = al * cl | |
/////////////////////////////////////////////////////////////////////////////// | |
// | |
// p17 | |
// | |
//////////////////////////////// 1 | |
call f | |
; f() | |
mov eax, [esp] | |
ret | |
You cannot do mov eax, eip because eip cannot be a source operand of mov. | |
//////////////////////////////// 2 | |
push 0xaabbccdd | |
ret | |
mov eax, 0xaabbccdd | |
jmp eax | |
//////////////////////////////// 3 | |
The context will jump (ret) to somewhere unintended location pointed by [esp]. | |
//////////////////////////////// 4 | |
In case of VS, 64 bit value is returned by edx:eax. | |
Interestingly, returning a struct is transformed into pointer passing. For | |
example, see following code: | |
struct Obj | |
{ | |
unsigned __int64 x; | |
unsigned __int64 y; | |
}; | |
Obj x() | |
{ | |
return Obj{}; | |
} | |
x(); | |
This is transformed into the following code: | |
Obj obj; | |
x(&obj) | |
It is probably because of move-semantics in C++14. I suppose it can vary from | |
compiler to compiler. | |
/////////////////////////////////////////////////////////////////////////////// | |
// | |
// p35 | |
// | |
//////////////////////////////// 1 | |
0x0000 (Lo) | |
Saved edi | |
/ PROCESSENTRY32A (0x128) | |
| idt_lo padding, base (hi) | |
Local 0x130 \ idt_hi base(lo), short | |
Saved EBP | |
Ret Addr | |
hinstDLL | |
fdwReason | |
lpvReserved | |
0xffff (Hi) | |
//////////////////////////////// 2 | |
BOOL __stdcall DllMain( | |
HINSTANCE hinstDLL, | |
DWORD fdwReason, | |
LPVOID lpvReserved) | |
{ | |
IDT idt; | |
PROCESSENTRY32 pe32; | |
sidt(&idt); | |
if (idt.base <= 0x8003F400 || idt.bast >= 0x80047400) | |
{ | |
return FALSE; | |
} | |
pe32.dwSize = 0; | |
memset(&pe32i.cntUsage, 0, sizeof(pe32) - 4); | |
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); | |
if (snapshot == INVALID_HANDLE_VALUE) | |
{ | |
return FALSE; | |
} | |
pe32.dwSize = sizeof(pe32); | |
for (Process32First(snapshot, &pe32); | |
Process32Next(snapshot, &pe32); /**/) | |
{ | |
if (_stricmp(pe32.szExeFile, "explorer.exe") == 0) | |
{ | |
if (pe32.th32ProcessID == pe32.th32ParentProcessID) | |
{ | |
return FALSE; | |
} | |
goto End; | |
} | |
} | |
if (fdwReason != fdwReason) | |
{ | |
return FALSE; | |
} | |
End: | |
if (fdwReason == DLL_PROCESS_ATTACH) | |
{ | |
CreateThread(nullptr, 0, 0x100032d0, nullptr, 0, nullptr); | |
} | |
return TRUE; | |
} | |
/* | |
It is miserably bad because: | |
- it checks existence of explorer.exe but does not practically change | |
behaviour | |
- it does not close snapshot | |
- it does not check return value of CreateThread; thus does not close the | |
handle | |
- it initializes pe32.dwSize twice | |
- it initializes pe32 in an inefficient way | |
- it has two meaningless comparisons | |
*/ | |
//////////////////////////////// 3 | |
@N represents the number of bytes that function takes as parameters and how | |
much stack the function will rewind. | |
//////////////////////////////// 4 | |
// For Visual Studio | |
__declspec(naked) | |
size_t __stdcall my_strlen(const char* Str) | |
{__asm{ | |
push ebp | |
mov ebp, esp | |
push edi | |
mov edi, [ebp + 8]; string | |
xor eax, eax | |
or ecx, 0xffffffff | |
repne scasb | |
add ecx, 2 | |
neg ecx | |
mov eax, ecx | |
pop edi | |
pop ebp | |
ret 4 | |
}} | |
__declspec(naked) | |
char* __stdcall my_strchr(const char* Str, int Ch) | |
{__asm{ | |
push ebp | |
mov ebp, esp | |
push edi | |
mov edi, [ebp + 8] ; Str | |
push edi | |
call my_strlen | |
mov ecx, eax | |
mov al, [ebp + 12] ; Ch | |
repne scasb | |
test ecx, ecx | |
jz my_strchr_not_found | |
mov eax, edi | |
dec eax | |
jmp my_strchr_end | |
my_strchr_not_found: | |
xor eax, eax | |
my_strchr_end: | |
pop edi | |
pop ebp | |
ret 8 | |
}} | |
__declspec(naked) | |
char* __stdcall my_memcpy(char* Dest, const char* Source, size_t Bytes) | |
{__asm{ | |
push ebp | |
mov ebp, esp | |
push edi | |
push esi | |
mov edi, [ebp + 8] | |
mov esi, [ebp + 12] | |
mov ecx, [ebp + 16] | |
rep movsb | |
mov eax, [ebp + 8] | |
pop esi | |
pop edi | |
pop ebp | |
ret 12 | |
}} | |
__declspec(naked) | |
char* __stdcall my_memset(char* Dest, int Value, size_t Bytes) | |
{__asm{ | |
push ebp | |
mov ebp, esp | |
push edi | |
push esi | |
mov edi, [ebp + 8] | |
mov al, [ebp + 12] | |
mov ecx, [ebp + 16] | |
rep stosb | |
mov eax, [ebp + 8] | |
pop esi | |
pop edi | |
pop ebp | |
ret 12 | |
}} | |
__declspec(naked) | |
int __stdcall my_strcmp(const char* Lsh, const char* Rhs) | |
{__asm{ | |
push ebp | |
mov ebp, esp | |
push edi | |
push esi | |
push ebx | |
mov eax, [ebp + 8] | |
push eax | |
call my_strlen | |
mov edi, eax | |
mov eax, [ebp + 12] | |
push eax | |
call my_strlen | |
cmp edi, eax | |
jb my_strcmp_1st_is_below_2nd | |
ja my_strcmp_1st_is_above_2nd | |
xor esi, esi | |
mov eax, [ebp + 8] | |
mov ecx, [ebp + 12] | |
my_strlen_loop: | |
cmp esi, edi | |
jae my_strcmp_both_are_equal | |
mov bl, [eax + esi] | |
mov dl, [ecx + esi] | |
cmp bl, dl | |
jb my_strcmp_1st_is_below_2nd | |
ja my_strcmp_1st_is_above_2nd | |
inc esi | |
jmp my_strlen_loop | |
my_strcmp_both_are_equal: | |
xor eax, eax | |
jmp my_strcmpy_end | |
my_strcmp_1st_is_below_2nd: | |
xor eax, eax | |
dec eax | |
jmp my_strcmpy_end | |
my_strcmp_1st_is_above_2nd: | |
xor eax, eax | |
inc eax | |
my_strcmpy_end: | |
pop ebx | |
pop esi | |
pop edi | |
pop ebp | |
ret 8 | |
}} | |
__declspec(naked) | |
char* __stdcall my_strset(char* String, int Fill) | |
{__asm{ | |
push ebp | |
mov ebp, esp | |
push esi | |
mov esi, [ebp + 8] | |
push esi | |
call my_strlen | |
push eax | |
push [ebp + 12] | |
push esi | |
call my_memset | |
pop esi | |
pop ebp | |
ret 8 | |
}} | |
void asm_test() | |
{ | |
const char s[] = "123456"; | |
int strlen_result = my_strlen(s); | |
char* strchr_result = my_strchr(s, '3'); | |
char memcpy_buffer[100] = {}; | |
char* memcpy_result = my_memcpy(memcpy_buffer, s, strlen_result); | |
char memset_buffer[100] = {}; | |
char* memset_result = my_memset(memset_buffer, 'A', 10); | |
printf("strlen(\"%s\") = %d\n", s, strlen_result); | |
printf("strchr(\"%s\", %c) = %s\n", s, '3', strchr_result); | |
printf("memcpy(\"%s\") = %s\n", memcpy_buffer, memcpy_result); | |
printf("memset(\"%s\") = %s\n", memset_buffer, memset_result); | |
char a1[] = "Meow"; | |
const char a2[] = "Purr"; | |
const char a3[] = "Meow Purr"; | |
printf("strcmp(\"%s\", \"%s\") = %d\n", a1, a2, my_strcmp(a1, a2)); | |
printf("strcmp(\"%s\", \"%s\") = %d\n", a3, a3, my_strcmp(a3, a3)); | |
printf("strcmp(\"%s\", \"%s\") = %d\n", a2, a3, my_strcmp(a2, a3)); | |
printf("strset(\"%s\", '*') = %s\n", a1, my_strset(a1, '*')); | |
printf("strset(\"%s\", '\\0') = %s\n", a1, my_strset(a1, '\0')); | |
} | |
//////////////////////////////// 5 | |
void __stdcall KeInitializeDpc( | |
PRKDPC Dpc, | |
PKDEFERRED_ROUTINE DeferredRoutine, | |
PVOID DeferredContext) | |
{ | |
Dpc->Lock = 0; | |
Dpc->DeferredRoutine = DeferredRoutine; | |
Dpc->Type = 0x13; | |
Dpc->Number = 0; | |
Dpc->Importance = 1; | |
Dpc->DeferredContext = DeferredContext; | |
} | |
void __stdcall KeInitializeApc( | |
KAPC* Apc, | |
KTHREAD* Thread, | |
long ApcStateIndex, | |
void* KernelRoutine, | |
void* RundownRoutine, | |
void* NormalRoutine, | |
BYTE ApcMode, | |
void* NormalContext) | |
{ | |
Apc->Type = 0x12; | |
Apc->Size = 0x30; | |
long apcStateIndex = ApcStateIndex; | |
if (apcStateIndex != 2) | |
{ | |
apcStateIndex = Thread->ApcStateIndex | |
} | |
Apc->Thread = Thread; | |
Apc->KernelRoutine = KernelRoutine; | |
Apc->ApcStateIndex = apcStateIndex; | |
Apc->RundownRoutine = RundownRoutine; | |
Apc->NormalRoutine = NormalRoutine; | |
if (NormalRoutine) | |
{ | |
Apc->ApcMode = ApcMode; | |
Apc->NormalContiext = NormalContext; | |
} | |
else | |
{ | |
Apc->ApcMode = 0; | |
Apc->NormalContext = nullptr; | |
} | |
Apc->Inserted = 0; | |
} | |
// The first second parameters are passed via ecx and edx respectively. | |
// This function does not make sense to me :/ | |
void __fastcall ObFastDereferenceObject( | |
arg_0, arg_4) | |
{ | |
while (arg_0->off_0x0 ^ arg_4 > 7) | |
{ | |
long esi = arg_0->off_0x0 + 1; | |
long eax = arg_0->off_0x0; | |
// lock cmpxchg [ecx], esi | |
if (eax == arg_0->off_0x0) // cmp eax, [ecx] | |
{ // jnz goto_else | |
arg_0->off_0x0 = esi // mov [ecx], esi | |
} | |
else | |
{ | |
eax = esi // mov eax, [ecx] | |
} | |
if (eax == arg_0->off_0x0) | |
{ | |
return; | |
} | |
} | |
ObfDereferenceObject(arg_4) | |
} | |
void __stdcall KeInitializeQueue( | |
KQUEUE* Queue, | |
long Count) | |
{ | |
Queue->Header.SignalState = 0; | |
Queue->Header.Type = 4; | |
Queue->Header.Size = 0xa; | |
LIST_ENTRY* list = &Queue->Header.WaitListHead; | |
list->Blink = list; | |
list->Flink = list; | |
list = &Queue->EntryListHead; | |
list->Blink = list; | |
list->Flink = list; | |
list = &Queue->ThreadListHead; | |
list->Blink = list; | |
list->Flink = list; | |
Queue->CurrentCount = 0; | |
Queue->MaximumCount = (Count) ? Count : KeNumberProcessors; | |
} | |
void __stdcall KeReadyThread( | |
ETHREAD* Thread) | |
{ | |
BYTE irql = KeAcquireQueuedSpinLockRaiseToSynch(0); | |
KiReadyThread(Thread); | |
KiUnlockDispatcherDatabase(irql); | |
} | |
void __stdcall KiInitializeTSS( | |
KTSS* Tss) | |
{ | |
Tss->Flags = 0; | |
Tss->LDT = 0; | |
Tss->IoMapBase = 0x20ac; | |
Tss->Ss0 = 0x10; | |
} | |
NTSTATUS NTAPI RtlValidateUnicodeString( | |
ULONG Reserved, | |
UNICODE_STRING* String) | |
{ | |
if (Reserved) | |
{ | |
return STATUS_INVALID_PARAMETER; | |
} | |
if (!String) | |
{ | |
return STATUS_SUCCESS; | |
} | |
if (String->Length % 2 || | |
String->MaximumLength % 2 || | |
String->Length > String->MaximumLength) | |
{ | |
return STATUS_INVALID_PARAMETER; | |
} | |
if (String->Length || String->MaximumLength) | |
{ | |
if (String->Buffer == nullptr) | |
{ | |
return STATUS_INVALID_PARAMETER; | |
} | |
} | |
return STATUS_SUCCESS; | |
} | |
//////////////////////////////// 6 | |
struct EdxEcx | |
{ | |
struct Edx* off_0x18; | |
}; | |
struct Edx | |
{ | |
void* off_0x8_function_array[]; | |
EdxEcx* off_0xb0; | |
}; | |
struct Eax | |
{ | |
Edx off_0x14; | |
}; | |
struct Ecx | |
{ | |
long off_0x4; | |
long off_0x8_magic; | |
long off_0xc; | |
uchar off_0x20; | |
uchar off_0x21; | |
uchar off_0x23; | |
uchar off_0x24; | |
long* off_0x28; | |
long off_0x2c; | |
long off_0x30; | |
long off_0x34; | |
long off_0x38; | |
long off_0x3c; | |
long off_0x50; | |
long off_0x54; | |
Eax* off_0x60; | |
Edx* off_0x64; | |
}; | |
NTSTATUS NTAPI sub_13842( | |
Ecx* Ecx, | |
Edx* Edx) | |
{ | |
long eax = ecx->off_0x60; | |
long esi = edx->off_0x8; | |
(*ecx->off_0x23)--; | |
eax -= 0x24; | |
ecx->off_0x60 = eax; | |
eax->off_0x14 = edx; | |
eax = (ulong)*eax->off_0x0; | |
return esi->off_0x38[eax](edx, ecx) | |
} | |
//////////////////////////////// 7 | |
void* __stdcall sub_10BB2( | |
IMAGE_DOS_HEADER* ImageBase, | |
const char* SectionName); | |
//////////////////////////////// 8 | |
void __stdcall sub_1172E( | |
long Edx, | |
long Ecx, | |
long Eax, | |
long CommandNumber) | |
{ | |
switch (CommandNumber) | |
{ | |
case 1: | |
*Ecx = Eax->off_0x3c / 2; | |
*Edx = Eax; | |
break; | |
case 2: | |
*Ecx = Eax->off_0x3c / 2; | |
*Edx = Eax + 0x44; | |
break; | |
case 3: | |
*Ecx = Eax->off_0x3c / 2; | |
*Edx = Eax + 0x5e; | |
break; | |
case 12: | |
*Ecx = Eax->off_0x8 / 2; | |
*Edx = Eax + 0xc; | |
break; | |
} | |
} | |
//////////////////////////////// 9 | |
int __stdcall sub_1000CEA0( // strrstr | |
const char* String, | |
char Search) | |
{ | |
size_t pos_of_last_char = 0; | |
while (String[pos_of_last_char++]) {} | |
pos_of_last_char--; | |
while (String[pos_of_last_char--] != Search && pos_of_last_char) {} | |
size_t pos_of_search_end = pos_of_last_char + 1; | |
if (String[pos_of_search_end] == Search) | |
{ | |
return pos_of_search_end; | |
} | |
return 0; | |
} | |
//////////////////////////////// 10 | |
Is not it possible via call gate? Calling call gate changes CPL which is old | |
fashion to switch to the kernel mode. In case of modern Windows, there is no | |
call gate by default, and it is not possible to add it (modify GDT) from the | |
user mode; thus it is not possible to change CPL. | |
//////////////////////////////// 11 | |
kd> r cr3 | |
cr3=00303000 | |
kd> .formats 8052b709 | |
Evaluate expression: | |
... | |
Binary: 10000000 01010010 10110111 00001001 | |
PDPT 10 = 2 | |
PD 000000 010 = 2 | |
PT 10010 1011 = 0x12b | |
OFFSET 0111 00001001 = 0x709 | |
kd> !dq @cr3+8*2 L1 | |
# 303010 00000000`00306001 | |
kd> !dq 00306000+8*2 L1 | |
# 306010 00000000`00312163 | |
kd> !dq 00312000+8*0x12b L1 | |
# 312958 00000000`0052b163 | |
=> 0052B000 | |
709 | |
=> 0052b709 | |
// Let's check the answer! | |
kd> !vtop 00303000 8052b709 | |
X86VtoP: Virt 8052b709, pagedir 303000 | |
X86VtoP: PAE PDPE 303010 - 0000000000306001 | |
X86VtoP: PAE PDE 306010 - 0000000000312163 | |
X86VtoP: PAE PTE 312958 - 000000000052b163 | |
X86VtoP: PAE Mapped phys 52b709 | |
Virtual address 8052b709 translates to physical address 52b709. | |
//////////////////////////////// 12 | |
Sorry, I am do not particulary like BeaEngine, so I used pefile and pydasm here. | |
DEP is implemented using a Non eXecutable (NE) bit in the PTE. | |
A processor checks a memory protection of a page if that access is allowed | |
beforehand. When NX is 1 in the PTE, this page is not accesible for executeion | |
(instruction fetch), and thus it causes a general protection exception. | |
/////////////////////////////////////////////////////////////////////////////// | |
// | |
// p38 | |
// | |
//////////////////////////////// 1 | |
; MASM | |
GetRip PROC | |
mov rax, [rsp] | |
ret | |
GetRip ENDP | |
AsmCode PROC | |
call GetRip ; This gets RIP | |
lea rax, location ; This also gets RIP | |
location: | |
nop | |
ret | |
AsmCode ENDP | |
//////////////////////////////// 2 | |
// Just a note | |
Sign extention 16 bits | |
Page map level 4 selector 9 bits | |
Page directory pointer selector 9 bits | |
Page directory selector 9 bits | |
Page table selector 9 bits | |
Byte within page 12 bits | |
0: kd> r cr3 | |
cr3=000000007b311000 | |
0: kd> r | |
rax=0000000000000000 rbx=fffff80002a41e80 rcx=fffff80002a41fa0 | |
rdx=0000000000000400 rsi=000000000023e0d0 rdi=0000000000000001 | |
rip=fffff88003530a18 rsp=fffff880038c97f0 rbp=fffff880038c9ca0 | |
r8=fffffa80047880e8 r9=0000000000000000 r10=fffffffffffffffe | |
r11=0000000000004bd8 r12=fffff880038c9a88 r13=0000000000000000 | |
r14=0000000000000000 r15=0000000000000001 | |
0: kd> .formats fffff88003530a18 | |
Evaluate expression: | |
... | |
Binary: 11111111 11111111 11111000 10000000 00000011 01010011 00001010 00011000 | |
~~~~~~~~~~^^^^^^^ ^^~~~~~~ ~~~^^^^^ ^^^^~~~~ ~~~~~~~~ | |
PML4 PDPT PD PT offset | |
0: kd> !dq @cr3 + 0y111110001*8 L1 | |
#7b311f88 00000000`b9c44863 | |
0: kd> !dq b9c44000 + 0y000000000*8 L1 | |
#b9c44000 00000000`b9c43863 | |
0: kd> !dq b9c43000 + 0y000011010*8 L1 | |
#b9c430d0 00000000`303b5863 | |
0: kd> !dq 303b5000 + 0y100110000*8 L1 | |
#303b5980 00000000`bdfc9121 | |
0: kd> ? 0y101000011000 | |
Evaluate expression: 2584 = 00000000`00000a18 | |
bdfc9000 | |
a18 | |
-------- | |
bdfc9a18 | |
// Let's check the answer (Part 1) | |
0: kd> !vtop 000000007b311000 fffff88003530a18 | |
Amd64VtoP: Virt fffff880`03530a18, pagedir 7b311000 | |
Amd64VtoP: PML4E 7b311f88 | |
Amd64VtoP: PDPE b9c44000 | |
Amd64VtoP: PDE b9c430d0 | |
Amd64VtoP: PTE 303b5980 | |
Amd64VtoP: Mapped phys bdfc9a18 | |
Virtual address fffff88003530a18 translates to physical address bdfc9a18. | |
// Let's check the answer (Part 2) | |
0: kd> !pte fffff88003530a18 | |
VA fffff88003530a18 | |
PXE @ FFFFF6FB7DBEDF88 PPE at FFFFF6FB7DBF1000 PDE at FFFFF6FB7E2000D0 PTE at FFFFF6FC4001A980 | |
contains 00000000B9C44863 contains 00000000B9C43863 contains 00000000303B5863 contains 00000000BDFC9121 | |
pfn b9c44 ---DA--KWEV pfn b9c43 ---DA--KWEV pfn 303b5 ---DA--KWEV pfn bdfc9 -G--A--KREV | |
The main difference is that x64 address translation uses one more table called | |
PML4. Also, since a memory range is extended from 32 bit to 64 bits, a way to | |
separate them for indexing is different. | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sys | |
import pefile | |
import pydasm | |
def usage(argv): | |
print('>python %s <pe_file>' % (argv[0])) | |
def main(argv): | |
if len(argv) == 1: | |
usage(argv) | |
exit(0) | |
pe = pefile.PE(argv[1]) | |
ep = pe.OPTIONAL_HEADER.AddressOfEntryPoint | |
ep_ava = ep+pe.OPTIONAL_HEADER.ImageBase | |
data = pe.get_memory_mapped_image()[ep:ep+100] | |
offset = 0 | |
while offset < len(data): | |
i = pydasm.get_instruction(data[offset:], pydasm.MODE_32) | |
asm = pydasm.get_instruction_string( | |
i, pydasm.FORMAT_INTEL, ep_ava + offset) | |
print('%08x : %s' % (ep_ava + offset, asm)) | |
offset += i.length | |
if __name__ == '__main__': | |
main(sys.argv) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment