Skip to content

Instantly share code, notes, and snippets.

@tandasat
Created November 19, 2014 03:31
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tandasat/a9ce7a7b4bfa7cf4f860 to your computer and use it in GitHub Desktop.
Save tandasat/a9ce7a7b4bfa7cf4f860 to your computer and use it in GitHub Desktop.
Answers of exercises in Practical Reverse Engineering Chapter 1
///////////////////////////////////////////////////////////////////////////////
//
// 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.
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