WinAppDbg by Mario Vilas is perhaps one of the most underrated instrumentation frameworks for Windows. In this day and age where everyone write JavaScript code to hook functions (I am looking at you Frida), writing Python code feels great. Just kidding, Frida is pretty cool too.
Going around the web searching for tutorials did not give me many results. The docs are great, they are some of the most practical docs I have seen. But apart from that, I could not find much. There are some random code here and there where people have documented using it but there were no guides to get me started apart from the docs.
Here's the result of my learning. I am sharing it to fill the gap that I encountered while getting started with the tool. We're going to learn as we go using real-world applications and will write code. We will start from the basics, expanding our code-base as we learn more.
Code is in my clone at:
Let's get started.
process
objects are at process.py.install.bat
(optionally in an admin command prompt).python -m pip install capstone-windows
.Now that our environment is ready, we can start learning.
As we can see, We are starting with a working Python script.
|
|
We can run it as follows, the debugger will stop when we close notepad.
$ python 01-Run.py -r c:\windows\system32\notepad.exe
Attached to 1936 - C:\Windows\System32\notepad.exe
Debugger stopped.
Argparse module is easy to use. We can see basic usage in the first three lines.
The output of argparse.run
is a list of strings. In this case, it contains the path to the executable.
WinAppDbg also comes with support for calling a lot of Windows APIs from Python. In this case we are calling PathFileExists instead of os.path
. Our program checks if the input passed by r/run
is valid (e.g. file exists) before running it. Note that this means we need to supply the full path to the executable.
More info:
To start an application and debug, first we must create a winappdbg.Debug
object and then call execv
or execl
.
There are two ways to run applications:
execv
: Accepts a list of strings. First item in the list is the program (including path). Every subsequent item is a command line parameter. We can pass the output of argparse.run directly.['c:\windows\system32\notepad.exe', 'c:\textfile.txt']
.execl
.execl
: Accepts a string containing the command line. This string is the exact input that we type to run the program (including arguments).The result of both methods is a winappdbg.process
. Later we will see what we can do with the process.
For now we are calling two obvious methods to get the process ID and the executable name. All process
methods are here.
To debug newly created application, call debug.loop()
otherwise it will stay suspended. This will instrument the application until it's terminated (or exits).
debug.stop()
will terminate the process.
Just running an application is not usually useful. Most times we want to pass arguments.
|
|
There are only two changes but the functionality is the same:
argparse.run
.execl
instead of execv
.After running the script, it will attempt to create c:\textfile.txt
if it does not exist. Manually close notepad to stop the debugger.
$ python 02-RunWithArgs.py -r c:\windows\system32\notepad.exe c:\textfile.txt
Started 1416 - C:\Windows\System32\notepad.exe
Debugger stopped.
WinAppDbg comes with some helpful utilities. One of them is the System
object. System has a lot more to offer than what we will be using in this part.
Moving forward I will just show the modified parts of the scripts. The complete scrips are in the Github clone.
We will add another switch to our script to print some system information.
|
|
The most important ones are:
system.bits
: This will return 32
or 64
based on the architecture of the current OS. This will come in handy later when we want to modify function parameters and need to adhere to the respective Application Binary Interface (ABI) (e.g. are functions pushed to stack or are stored in registers). Do not worry about it for now.bits
while debugging a 32-bit app on 64-bit Windows will be 64
. In that case we need to check system.wow64
output.system.wow64
: Returns True
if we are running a 32-bit application in emulation mode on a 64-bit Windows and False
otherwise. Returns False
on 32-bit operating systems.system.is_admin()
: Returns True
if we are running as admin.Result in my VM:
$ python 03-SystemInfo.py -i
System Information
------------------
Bits 32
OS Windows 7
Architecture i386
32-bit Emulation False
Admin False
WinAppDbg Version 1.6
Process Count 49
WinAppDbg comes with a built-in table. It's pretty easy to use.
table = winappdbg.Table()
. Constructor supports an optional delimiter between rows. For example \t
will insert a tab between rows.table.addRow("col1", "col2", "col3")
.table.Justify(0, 1, 1)
where 0
is justify right and 1
is justify left.table.Show()
will print the table.table.getOutput()
returns a string that can be printed.One good way of separating headers from the rest of rows with a separator is in code from more examples-4:
header = ("column1", "column2", "column3")
separator = [ "-" * len(x) for x in header ]
table.addRow(*header)
table.addRow(*separator)
More info:
We are adding a new functionality to our script. If no argument is passed, print a list of running processes with their pid and filename sorted by pid. This will come in handy later.
We can accomplish this by creating a System
object and then iterating through the processes.
|
|
We are using the built-in Table
again.
Partial output in my VM:
$ python 04-Processes.py
pid process
---- ----------
0 None
4 System
220 smss.exe
248 C:\Windows\system32\Dwm.exe
296 csrss.exe
344 wininit.exe
352 csrss.exe
380 winlogon.exe
436 services.exe
452 lsass.exe
460 lsm.exe
568 svchost.exe
628 VBoxService.exe
1060 C:\Windows\system32\taskhost.exe
1748 C:\Program Files\Google\Chrome\Application\chrome.exe
1888 C:\Windows\system32\cmd.exe
1944 C:\Program Files\Google\Chrome\Application\chrome.exe
Starting applications is fun but usually we want to attach to a running process. Let's add a pid
switch to our script.
The Debug.attach(pid)
method does what we want. The rest of the code checks if the pid exists before attempting to attach to it.
|
|
Adding the new pid
switch will conflict with our old run
switch. We do not want to do both of them at the same time. This is done in argparse
by creating a mutually exclusive group and adding arguments to it. These arguments cannot be passed together.
Other argument can be added normally via add_argument
to the original parser object.
|
|
Now we can run notepad (or any other application), run the script without any arguments to get a list running processes and then attach to notepad using the pid.
$ notepad.exe
$ python 05-Attach.py | findstr notepad
2924 C:\Windows\system32\notepad.exe
$ python 05-Attach.py -pid 2924
Attached to 2924 - C:\Windows\system32\notepad.exe
Debugger stopped.
We can attach to a process by name with debug.system.find_processes_by_filename
.
We also need to add the new -pname
argument to the mutually exclusive group.
|
|
Usage is similar:
$ notepad.exe
$ python 06-AttachByName.py | findstr notepad
3596 C:\Windows\system32\notepad.exe
$ python 06-AttachByName.py -pname notepad
Found 3596, C:\Windows\system32\notepad.exe
Attached to 3596-C:\Windows\system32\notepad.exe
Up until now we have used print
statements to display information which is barbaric at best (lol). WinAppDbg has a built-in logger. We can also use our own logger but the built-in logger has some extra utilities. However, I wish the built-in logger could disable timestamps.
We will add logging functionality to our script with -o
and use the built-in logger. With -o
we can pass a file to store the logs.
winappdbg.Logger can be created using two arguments. First one is a filename and second one is verbose
. If verbose
is set to True
, output will be logged to stdout.
|
|
Now we can use logger.log_text(str)
to log any string. Just note that we need to pass a string to it (otherwise it will raise an exception), so wrap everything in str
.
More info:
We learned a bunch of basic building blocks. In next part I will introduce my helper code and we will tackle more advanced subjects like function hooking and manipulation. For now, go out and start experimenting with WinAppDbg. As usual, if you have any feedback, please let me know.