using(var dbg = new ProcessDebugger())
{
dbg.Start(SimpleCrackMe);
RVA mainRVA = dbg.SymbolManager.FromName("SimpleCrackMe.exe", "main").RVA; Breakpoint breakPoint = dbg.Breakpoints.At("SimpleCrackMe.exe", mainRVA); dbg.BreakingThread.Continue.UntilBreakpoint(breakPoint); var disasm = dbg.BreakingThread.Continue.UntilInstruction("JNZ"); dbg.BreakingThread.WriteInstruction("NOP"); dbg.Patches.ModifyImage(dbg.Debuggee.MainModule, "SimpleCrackMe-Patched.exe"); Assert.Equal(1, dbg.Continue.ToEnd()); var result = StartAndGetReturnCode("SimpleCrackMe-Patched.exe");
Assert.Equal(1, result); }
Why would you need a debugger API ?
I am not the kind of person that can break a new device into piece to know how it
is made. I am not an hardware person. Every person that once gave me a screwdriver regretted
it afterward.
For software, that’s a different story. When a software do cool stuff, I want to
know how they did that… I also use exactly the same toolset to troubleshot bugs,
because software almost never work as expected… Google and MSDN are sometimes not
enough to solve your problem.
So I use lots of different tool to see what is going on inside,
these programs are like your eyes inside windows, and you certainly use them on
a day by day basis… For example, the mighty
Procmon to troubleshoot file system and registry access, the great
API Monitor to troubleshot problematic
API calls –This one should deserve more attention, it’s a truly great tool-, the
famous Wireshark for network or protocol
related problems, and, in the last resort debuggers like
OllyDbg, IDA pro,
and WinDbg.
The problem is that I love my comfortable .NET world, so I thought “Hey, what if
all of these great tool were available within a nice and friendly .NET API ?” so
the this API is my response to this question. Not everything is implemented yet,
but that’s the first bits. For now it only support x86 programs.
At the end of this article, you will be able to :
-
Set breakpoints programmatically in assembler
-
Find the address of modules and functions imported and loaded in a remote process.
-
By extension, setting breakpoint in well known WIN API functions.
-
Apply patches to remote process (changing x86 instructions or data)
- Push the patches back to file.
All of that nicely implemented in comfortable .NET API.
For advanced developers you will understand :
-
How a debugger works (Under the hood)
-
What is a native breakpoint (Under the hood)
- What is a PE file, a module, a PDB.
NHook is using
OllyDbg assembler/disassembler for the assembler part.
NHook and this article as been bought to
you by
Dusan Trtica and me. We are working together on this project. Dusan is a
very talented developer in C++, this project advances smoothly thanks to him ! (I’m
wayyyy toooo slooooow in C++ !)
So let’s get started !
How to crack a simple Crack Me program
Before coding anything, NHook's goal is to make cracking this
simple program easy. Now that the goal is set, coding is just a matter of expressing
the path to this goal.
#include <stdio.h>
int main(int argc, char** argv)
{
if(argc == 3)
{
printf("success");
return 1;
}
printf("miss");
return 0;
}
With NHook, I will bypass the if condition so that even if I invoke
the program without 2 parameters, it will print success.
What does the assembly code of this function look like ?
To see the assembly code let’s run SimpleCrackMe.exe with OllyDbg, and let’s bring up the Executable
modules window.
The code is compiled with the
VC++ 2012 runtime, so please download and install it, or the loader will not
find MSVCR110D (Debug library of the C Runtime library that contains
printf).
You can see that before running any code, several .dll have been
loaded into my process address space. How did the loader know which dll
to load ? Well… that’s just written in the import table of any PE file
(dll or exe file). You can view it with
CFF explorer.
Each imported dll have other dependencies that will be loaded as well. OllyDbg
blocks only when the loader is done.
So in the Executable modules window you can see the SimpleCrackMe
is loaded at 0x3D0000 and the entrypoint is at 0x3E110E.
So you can think that 0x3E110E is the address of main…
but wrong ! It might be that if you disable every setting and feature of ms compiler,
but there are some bootstrapping code. If that is a mixed assembly (C++/CLI), you
have some .NET related stuff, and if that’s time linked with C runtime libraries,
there are some bootstrapping also... Here is the entry point… A just to the bootstrap
mainCRTStartup.
You can also get the entry point of your executable by using CFF Explorer,
the entry point is in the PE file.
0x1110E is the address relative to the base address of module
SimpleCrackMe. It is called an RVA (relative virtual address).
That’s why the effective VA (virtual address) shown by Ollydbg is
0x3D0000 + 0x1110E = 0x3E110E.
So, back to the question : how could you find the address of main
? Response : by using pdb.
PDB is just a database of symbols (a name and a
type) with their RVA and maybe source file and line.
In .NET, PDB does not store RVA, but only
MetadataToken-Source file:line. The JIT compiler
is ultimately responsible to choose where to transform MSIL and how to
decompile it (x86,64 etc…), that’s why the C# or VB .NET compiler can’t predict
RVA in advance and use MetadataToken instead.
I will probably extend NHook to support .NET, so this will be a
subject for the next article.
Let’s right click on the SimpleCrackMe.exe module in the
Executable View, and it will show us all symbols it resolved thanks
to pdb or PE’s export directory.
Search for the main symbol, click on it and you should see the code
in the memory dump.
If you see an hex editor instead of the instructions, right click on the
memory dump, and click on disassemble, to change it’s
view.
The interesting stuff is that : There is a test at 0x3E13EE and
a conditional JMP (JNZ) on it just after at
0x3E13F2… This is the if we need to bypass.
We can bypass it by patching the JNZ with
two NOP instructions.
Don’t worry… NHook link with OllyDbg’s assembler/disassembler library,
so you don’t have to know op codes for these instructions.
Now I exposed the basic, you can understand all you have to know to use NHook,
the following code should be self explanatory.
using(var dbg = new ProcessDebugger())
{
dbg.Start(SimpleCrackMe);
RVA mainRVA = dbg.SymbolManager.FromName("SimpleCrackMe.exe", "main").RVA; Breakpoint breakPoint = dbg.Breakpoints.At("SimpleCrackMe.exe", mainRVA); dbg.BreakingThread.Continue.UntilBreakpoint(breakPoint); var disasm = dbg.BreakingThread.Continue.UntilInstruction("JNZ"); dbg.BreakingThread.WriteInstruction("NOP"); dbg.Patches.ModifyImage(dbg.Debuggee.MainModule, "SimpleCrackMe-Patched.exe"); Assert.Equal(1, dbg.Continue.ToEnd()); var result = StartAndGetReturnCode("SimpleCrackMe-Patched.exe");
Assert.Equal(1, result); }
Let’s dig deeper
If you just want to use NHook you can stop here, I will not expose
new features in this section, if you want to understand how things work under the
hood… you can continue.
The first question I will respond is this one : What is a breakpoint ?
A breakpoint is simply an x86 instruction (INT 3)
that will fire an interrupt that is handled by a debugger.
You can verify it by yourself, try to set a breakpoint with Ollydbg.
Then, check the instruction’s address 0x012E13D0 with another memory
editor. For example, with the excellent API monitor.
So the debugger is reporting value, 0x55 (PUSH EBP), but the memory
editor is reporting 0xCC (INT 3). Conclusion : A debugger
put breakpoints by overwriting a given instruction by 0xCC (INT 3).
So your next question might be : How to write in another’s process memory ?
You can do that with
WriteProcessMemory. Here is a simple C++/CLI wrapper, the parameter processHandle
can easily be found with the
Process.Handle method in .NET.
public:static void WriteMemory(IntPtr processHandle, IntPtr baseAddress, array<Byte>^ input)
{
mauto_handle<BYTE> bytes(Util::ToCArray(input));
SIZE_T readen;
ASSERT_TRUE(WriteProcessMemory(processHandle.ToPointer(),baseAddress.ToPointer(),bytes.get(),input->Length,&readen));
}
WriteProcessMemory and all debug method will not work if the debugger’s
thread security token does not have SeDebugPrivilege. Moreover
this privilege is filtered when you are using UAC.
Here is a screenshot of procexp, that show a process (csrss.exe)
running with the System account. System’s account token has the
SeDebugPrivilege.
You can grant this privilege with Local Computer Policy (enabled
by default for Administrators).
If you don’t have these rights, the debuggee should grant to debugger’s process ACL
should grant PROCESS_VM_OPERATION PROCESS_VM_READ PROCESS_VM_WRITE
rights… But we will not take that path.
Now that you understand what a breakpoint is, how do you attach a debugger to a process
?
The first solution, is to start the process with the debugger. Here is a C++/CLI
wrapper.
static ProcessInformation^ StartDebugProcess(String^ appPath)
{
marshal_context context;
STARTUPINFO startupInfo = {0};
startupInfo.cb = sizeof(STARTUPINFO);
PROCESS_INFORMATION processInformation = {0};
auto directory = System::IO::Path::GetDirectoryName(appPath);
ASSERT_TRUE(CreateProcess(context.marshal_as<LPCTSTR>(appPath),
NULL,
NULL,
NULL,
false,
CREATE_DEFAULT_ERROR_MODE | CREATE_NEW_CONSOLE | DEBUG_ONLY_THIS_PROCESS | NORMAL_PRIORITY_CLASS,
NULL,
context.marshal_as<LPCTSTR>(directory),
&startupInfo,
&processInformation));
return gcnew ProcessInformation(&processInformation);
}
The interesting point is the creation flag DEBUG_ONLY_THIS_PROCESS.
Contrary to DEBUG_PROCESS it will not debug child processes.
The second solution is to attach to a running process with
DebugActiveProcess.
public: static ProcessInformation^ DebugActiveProcess(int pid)
{
ASSERT_TRUE(::DebugActiveProcess(pid));
auto process = System::Diagnostics::Process::GetProcessById(pid);
PROCESS_INFORMATION info;
info.dwProcessId = process->Id;
info.dwThreadId = process->Threads[0]->Id;
info.hProcess = OpenProcess(PROCESS_ALL_ACCESS,false,process->Id);
info.hThread = OpenThread(THREAD_ALL_ACCESS,false, process->Threads[0]->Id);
return gcnew ProcessInformation(&info);
}
Once your debugger is attached, only 2 functions will control how to receive debuggee’s
events and when to continue execution.
The first is
WaitForDebugEvent. It will break debugger’s thread until the next debug
event. Once a debug event is received, you call
ContinueDebugEvent to continue debuggee’s execution.
My C++/CLI wrapper transform the
Debug_Event into CLR types. Current interesting debug events are : Dll Loaded,
Exception thrown, CreateThread, and ExitProcess.
static DebugEvent^ WaitForEvent(ProcessInformation^ processInformation, TimeSpan timeout)
{
DEBUG_EVENT dbgEvent;
BOOL result;
if(timeout == TimeSpan::MaxValue)
result = WaitForDebugEvent(&dbgEvent, INFINITE);
else
result = WaitForDebugEvent(&dbgEvent, (DWORD)timeout.TotalMilliseconds);
if(!result)
return nullptr;
if(dbgEvent.dwDebugEventCode == (DWORD)DebugEventType::LoadDllEvent)
return gcnew DebugEventEx<LoadDllDetail^>(&dbgEvent, gcnew LoadDllDetail(processInformation, &dbgEvent.u.LoadDll));
if(dbgEvent.dwDebugEventCode == (DWORD)DebugEventType::ExceptionEvent)
return gcnew DebugEventEx<ExceptionDetail^>(&dbgEvent, gcnew ExceptionDetail(processInformation, &dbgEvent.u.Exception));
if(dbgEvent.dwDebugEventCode == (DWORD)DebugEventType::CreateThreadEvent)
return gcnew DebugEventEx<CreateThreadDetail^>(&dbgEvent, gcnew CreateThreadDetail(processInformation, &dbgEvent.u.CreateThread));
if(dbgEvent.dwDebugEventCode == (DWORD)DebugEventType::ExitProcessEvent)
return gcnew DebugEventEx<ExitProcessDetail^>(&dbgEvent, gcnew ExitProcessDetail(processInformation, &dbgEvent.u.ExitProcess));
return gcnew DebugEvent(&dbgEvent);
}
ContinueDebugEvent is just asking the process and thread which need
to continue, as well as, if the debugger handle an exception.
static void Continue(DebugEvent^ debugEvent, bool handleException)
{
ContinueDebugEvent(debugEvent->ProcessId, debugEvent->ThreadId, handleException ? DBG_CONTINUE : DBG_EXCEPTION_NOT_HANDLED);
}
So now, how do you break into a breakpoint ?
Simple enough : Loop through all DebugEvent and stop when an “exception”
one is receive, and that the reason is “Breakpoint”, here is the C# code, that do
just that.
public DebugEventEx<ExceptionDetail> UntilNextBreakpoint()
{
return (DebugEventEx<ExceptionDetail>)Until(ev =>
{
if(ev.EventType != DebugEventType.ExceptionEvent)
return false;
return ((DebugEventEx<ExceptionDetail>)ev).Details.Exception.Reason == ExceptionReason.ExceptionBreakpoint;
});
}
The Until method loop until the predicate returns true.(The
Run method ultimately call ContinueDebugEvent and
the NextEvent method ultimately call the WaitDebugEvent
method)
public DebugEvent Until(Func<DebugEvent, bool> filter)
{
DebugEvent lastEvent = null;
bool first = true;
if(_Commandable.CurrentEvent != null)
Run();
while(lastEvent == null || !filter(lastEvent))
{
if(!first)
Run();
first = false;
lastEvent = _Commandable.Wait.NextEvent();
}
return lastEvent;
}
How to dump memory addresses from PDB
Without PDBs, life would be hard for developers. Without PDB you would have no way
to know that your current thread broke inside the main method.
The only thing you would see is a bunch of bytes on the stack.
And so you would have no way to know the value or type of local variables during
your debugging session. Life would be hard. So hard than debugging expert would
tell you that PDB are as important as your source code.
That’s precisely thanks to PDBs I was able to find the RVA of the main
method in this example.
using(var dbg = new ProcessDebugger())
{
dbg.Start(SimpleCrackMe);
RVA mainRVA = dbg.SymbolManager.FromName("SimpleCrackMe.exe", "main").RVA;
Breakpoint breakPoint = dbg.Breakpoints.At("SimpleCrackMe.exe", mainRVA); dbg.BreakingThread.Continue.UntilBreakpoint(breakPoint); var disasm = dbg.BreakingThread.Continue.UntilInstruction("JNZ"); dbg.BreakingThread.WriteInstruction("NOP"); dbg.Patches.ModifyImage(dbg.Debuggee.MainModule, "SimpleCrackMe-Patched.exe"); Assert.Equal(1, dbg.Continue.ToEnd()); var result = StartAndGetReturnCode("SimpleCrackMe-Patched.exe");
Assert.Equal(1, result); }
Sounds cool ? So let’s see how to parse a PDB.
Microsoft ship with visual studio a COM component called DIA (Debug Interface
Access) that does just that. So I just had to generate the COM interop
.NET assembly for this COM component.
Go to C:\ProgF\Microsoft Visual Studio 11.0\DIA SDK\idl.
Generate a tlb file from the idl.
midl /I "C:\ProgF\Microsoft Visual Studio 11.0\DIA SDK\include" dia2.idl /tlb dia2.tlb
Generate the dll from the tlb
tlbimp dia2.tlb
Reference the resulting Dia2Lib.dll in NHook.
The rest is only about creating the one DiaSource object for each
module loaded in the process in the WaitNextEvent method of the
debugger.
if(dbgEvent.EventType == DebugEventType.LoadDllEvent)
{
if(AreSameImage(dbgEvent.As<LoadDllDetail>().ImageName, "kernel32.dll"))
{
Debuggee.SetProcess();
SymbolManager.LoadSymbolsFromExe(Debuggee.MainModule.FileName);
}
SymbolManager.LoadSymbolsFromExe(dbgEvent.As<LoadDllDetail>().ImageName);
}
Loading the symbols are easy:
public bool LoadSymbolsFromExe(string exePath)
{
var pdb = Path.ChangeExtension(exePath, "pdb");
if(File.Exists(pdb))
{
var source = LoadSymbols(pdb);
_Sources.Add(Path.GetFileName(exePath), source);
return true;
}
return false;
}
private DiaSource LoadSymbols(string pdbPath)
{
if(!File.Exists(pdbPath))
throw new FileNotFoundException(pdbPath);
var diaSource = COMHelper.RegisterIfNotExistAndCreate(() => new DiaSource(), "msdia110.dll");
diaSource.loadDataFromPdb(pdbPath);
return diaSource;
}
RegisterIfNotExistAndCreate is just a method that will deploy the
COM component automatically on the machine, so the user does not have a weird
exception.
The SymbolManager.FromName just need to get the right DIASource,
fetch the right symbol, and return a nice plain old .NET object.
public SymbolInfo FromName(string moduleName, string name)
{
return FromName(moduleName, name, null);
}
public SymbolInfo FromName(string moduleName, string name, SymTagEnum? type)
{
_Debugger.EnsureProcessLoaded();
DiaSource diaSource = FindSource(moduleName);
IDiaSession session;
IDiaEnumTables tables;
diaSource.openSession(out session);
session.getEnumTables(out tables);
return tables.ToEnumerable()
.OfType<IDiaEnumSymbols>()
.SelectMany(s => s.ToEnumerable())
.Where(s => s.name == name && (type == null || (uint)type.Value == s.symTag))
.Select(s => new SymbolInfo(s))
.FirstOrDefault();
}
Patching
What if you modified the behavior of a program and you want to persist the changes
you made to the binary ?
You can also do that easily ! Each time that the debugger write on the Debuggee’s
memory, the change is tracked by Debugger.Patches each Patch
is one change, and you can decide to remove them, or apply them to binaries.
using(var dbg = new ProcessDebugger())
{
dbg.Start(SimpleCrackMe);
RVA mainRVA = dbg.SymbolManager.FromName("SimpleCrackMe.exe", "main").RVA; Breakpoint breakPoint = dbg.Breakpoints.At("SimpleCrackMe.exe", mainRVA); dbg.BreakingThread.Continue.UntilBreakpoint(breakPoint); var disasm = dbg.BreakingThread.Continue.UntilInstruction("JNZ"); dbg.BreakingThread.WriteInstruction("NOP"); dbg.Patches.ModifyImage(dbg.Debuggee.MainModule, "SimpleCrackMe-Patched.exe");
Assert.Equal(1, dbg.Continue.ToEnd()); var result = StartAndGetReturnCode("SimpleCrackMe-Patched.exe");
Assert.Equal(1, result); }
How it works under the hood need some explanation about how a dll
or exe is stored in RAM vs on the disk.
A byte inside a dll have two locations : A file offset and an
RVA. The file offset is its location on the disk file,
RVA is its location in the RAM from the load address of your module.
Why is it ?
Two main reasons :
-
The file does not have to take space to store global variable. These values are
decided at runtime.
-
The file need to be compact
- On the other hand, the processor need to load sections of the DLL
on page boundary (4 Kb) for security and performance reason. The
processor can prevent code to execute within some pages, so that a buffer or stack
overflow cannot be easily exploited.
Let’s take an example :
You can see with CFF explorer that an executable (dll or exe) have multiple sections.
The Virtual Address of the section is the RVA. The Raw Address
is the File Offset.
The .text section is commonly where is the assembly code.
Now imagine that a method called IVssAdmin::RegisterProvider is
in this section in the RAM at address E3BC.
Then, given the information about Raw Size and Raw Address (file offset) of the
.text section, the IVssAdmin::RegisterProvider will be located
at D7BC on the disk.
So here is the math to convert an RVA to a file offset.
E3BC (RVA) - 1000 (RVA of .text section) = D3BC
(Relative address to the .text section)
D3BC + 400 (File offset of .text section) = D7BC (File Offset)
Or in code :
public FileOffset ToImageOffset(RVA rva)
{
var section = GetSectionAt(rva);
if(section == null)
throw new InvalidOperationException("This RVA belongs to no section");
var addressInSection = rva.Address - section.VirtualAddress;
var offset = section.PointerToRawData + addressInSection;
if(offset >= section.PointerToRawData + section.SizeOfRawData)
throw new InvalidOperationException("This RVA is virtual");
return new FileOffset(this, offset);
}
This is how a Patch converts tracked changes on RVA,
changes on binary files.
----New : now NHook can find RVA from the export directory of dll.
Conclusion
Hope you liked it, I think it is not a bad thing that .NET developers understand
what is going on on lower level. That’s the start of an API I will use for my own
needs whenever I need a way to manipulate a process memory or behavior for whatever
reason.
There are tons of important things to do like :
-
x64 supports
- .NET supports
Check
NHook home !