Tuesday, October 22, 2024

Wherein We Share Some Useful GDB Commands

 

Expectations were like fine pottery. The harder you held them, the more likely they were to crack.                                                                               



New to GDB, the Linux Debugger, or just looking for a quick reference guide? Then I got you covered.

Here are some useful commands and tips that will help you navigate and debug your programs efficiently:



GDB Debugger Quick Reference Guide


Essential GDB Commands

Program Control

  • break [breakpoint] - Set a breakpoint
    • Example: break main, break *0x4004a0
    • Tip: Use break file.c:42 to break at specific source lines
  • run [args] - Start program with optional arguments
  • continue (c) - Continue execution
  • next (n) - Step over function calls
  • step (s) - Step into function calls
  • stepi - Step one assembly instruction
  • finish - Run until current function returns

Inspection

  • print [expression] - Print value
    • Example: print x, print *ptr, print $eax
  • display [expression] - Auto-print at each stop
  • x/[n][f][u] [address] - Examine memory
    • n: Number of units to display
    • f: Format (x=hex, d=decimal, s=string)
    • u: Unit size (b=byte, h=halfword, w=word, g=giant)
    • Example: x/32xb $esp - Show 32 bytes at stack pointer
  • info registers - Show register values
  • bt [full] - Show backtrace (call stack)

Interface

  • layout asm - Show assembly view
  • layout src - Show source code view
  • layout regs - Show registers view
  • layout split - Split view (source/assembly)
  • focus cmd/src/asm/regs - Switch between views
  • refresh - Refresh screen

Data & Variables

  • info locals - Show local variables
  • info args - Show function arguments
  • watch [expression] - Break on value change
  • set variable [name]=[value] - Modify variable
  • whatis [variable] - Show variable type


Compilation for Debugging

gcc -g -O0 program.c -o program

Key flags:

  • -g - Include debug symbols
  • -O0 - Disable optimization
  • -fno-stack-protector - Disable stack protection
  • -no-pie - Disable position-independent code
  • -m32 - Force 32-bit compilation


Advanced Features

Core Dumps

# Enable core dumps ulimit -c unlimited # Load core dump gdb ./program core

ASLR Control

# Disable ASLR for debugging echo 0 | sudo tee /proc/sys/kernel/randomize_va_space # Or temporarily: setarch `uname -m` -R ./program

Remote Debugging

# On target machine gdbserver :2345 ./program # On host machine gdb (gdb) target remote target_ip:2345


Tips for Effective Debugging

  1. Use conditional breakpoints:
    break main if argc > 1
  2. Save common commands in .gdbinit:
    set disassembly-flavor intel set history save on set print pretty on
  3. Create command aliases:
    define reg info registers end
  4. Use Python scripting for complex debugging:
    python class MyCommand(gdb.Command): def __init__(self): super(MyCommand, self).__init__("mycommand", gdb.COMMAND_USER) MyCommand() end


I think that these commands will serve you well in your journey with a debugger.


Whether you're stepping through code, inspecting memory, or trying to exploit vulnerabilities, remember to keep experimenting with this stuff! It's all about hands-on practice.
If you have any questions, doubts or ideas to improve this list, just send them my way.

Enjoy!

Sunday, October 20, 2024

Wherein We Study A Buffer Overflow And Ready Our Aim: testing the waters

 


Initial disclaimer: please check the link below, as it will be necessary when following along the pdf.


Hi, again! Ready for some more low-level code goodness?
Today we'll take a look at some very simple, yet purposefully flawed C programs in order to learn a bit more about buffer overflows, grasping control of the return address and disrupting a program's flow.

In the first example, we'll see that the program will result in a segmentation fault, and understand why that's happening exactly, by looking at the disassembled code under the GDB debugger. We'll then talk a little about registers, the return address, its importance, and how to grab control of it.

In the second example, we'll take a look at the basics, which will let us finally take advantage of the return address through a buffer overflow.
But, without much ado, let's jump to our first example. Remember that these two first programs are directly taken from Smashing The Stack For Fun And Profit, which you can find here (revised edition! - please follow this link to disable defenses. Alternatively check this. If you don't do this, you won't be able to take advantage of these methods).


This program is creating a char array named large_string which can take up to 256 characters, and then it's filling large_string with A's. After that's done, the program is calling a function named function, using large_string as the argument. The problem becomes immediately obvious since, as we can see, inside that function, we're filling a char array entitled buffer with our A's. But our buffer can only take in 16 characters. Ergo, we have our buffer overflow.

Function was created and its local variable buffer can only hold so many of our A's. But, since we're using strcpy, which has no control for how many values we can enter, we'll just keep on writing A's until we reach the null character (at the end of large_string).

But let's open our debugger and actually see what's happening here.


So, we're looking at main(). Can you see our loop? We're moving 'A' into eax and advancing the counter at ebp-12. A is the ASCII representation of number 41:

   0x000011ea <+50>: mov    BYTE PTR [eax],0x41

Adding +1 to our counter at ebp-0cx:
   0x000011ed <+53>: add    DWORD PTR [ebp-0xc],0x1

And comparing that value with 254 (so as to know when to end the loop):
   0x000011f1 <+57>: cmp    DWORD PTR [ebp-0xc],0xfe

When this is finally done, as I'm sure you can see, we're jumping right into our function, and this is where the fun starts. Let's disassemble it:




Please take a moment to learn what's happening here, and compare it side by side with the C code. But let us move on and actually see what's happening with our memory, as we set a breakpoint in main:



With x/32x $esp, we're checking memory position 0xffffcfa0, to which ESP is pointing, and the addresses 32 bytes above that. I won't go into much detail. You'll see soon enough what will happen when we step forward and finally reach our function breakpoint:



Looking at the memory addresses, it's obvious what happened: these locations were filled with 0x41414141 or, in plain text, AAAA.

It's important to note that the A's are being written from higher memory positions to lower ones. This is crucial because one of the last things overwritten is the return address, which will cause our segmentation fault.

As we move along, at a certain point, the return address will also be filled with A's, and at that moment we won't have a valid return address any longer. As a result, our program will suffer a segmentation fault.


And that's it. We're going nowhere fast. This program has just died on our hands. If we were trying to crack this program, we would have wanted, instead, to take control of the return address stored next to EBP. We'd use that value and point towards some other function we wanted, for example, thus altering the program's flow in our favor.

Yes, we're slowly creeping towards true shellcode. We'll get there eventually, don't worry.

But before we do that, we might as well talk about other interesting tidbits that can prove helpful when using a debugger and watching our shellcode or buffer overflow in action.

I've already shown quite a few pics, so I won't give you another one, but here's the very first function that appears in our "Smashing the Stack" doc. It's pretty simple, but it hopefully shines a light on the function we've been analyzing so far:


void function(int a, int b, int c) {

char buffer1[5];

char buffer2[10];

}

void main() {

function(1,2,3);

}


If we look at the memory locations, as we did before with the other program, we'll see:

0xffffd09c: 0x00000000 0xf7ffcff4         0x0000002c 0x00000000
0xffffd0ac: 0xffffdfc0         0xf7fc7550 0x00000000 0xf7da2a4f
0xffffd0bc: 0xffffd0e8 0x565561e5 0x00000001 0x00000002
0xffffd0cc: 0x00000003 0xffffd110         0xf7fc1688 0xf7fc1b60
0xffffd0dc: 0x00000000 0xffffd100 0xf7fa2ff4 0x00000000
0xffffd0ec: 0xf7da92d5 0x00000000 0x00000070 0xf7ffcff4
0xffffd0fc:         0xf7da92d5 0x00000001 0xffffd1b4 0xffffd1bc
0xffffd10c: 0xffffd120 0xf7fa2ff4 0x565561b6 0x00000001

I have put in bold important addresses and their contents. In order, from left to right, going down...

ESP

  • Points to the current top of the stack.
  • At 0xffffd09c (the lowest memory address in this snapshot)
  • ESP is showing the exact spot in memory where new data are pushed onto the stack

Saved EBP

  • At 0xffffd0bc
  • The base pointer of the caller function
  • This marks the base of the previous stack frame before the current function was called

Return Address

  • At 0xffffd0c0
  • This is the address the program will jump back to after the current function completes
  • overwriting this location with a malicious address can cause the program to "return" to an arbitrary memory location

Variables

  • Just below the saved EBP and return address are the local variables and parameters
  • At 0xffffd0c4 (0x00000001), 0xffffd0c8 (0x00000002) and 0xffffd0cc (0x00000003)

Buffer

  • Just below these local variables we can see the space that has been assigned to our buffer1 and buffer2 local variables
  • It's not 5+10 bytes in size. Instead, because of padding and alignment, it will be 8+12 bytes in size, for a total of 20 bytes.
---

Next up: We'll learn how to control the return address and force the program to do our bidding. This will set the stage for mastering the art of shellcoding! 

Thursday, October 17, 2024

Wherein We Wade Through A Shellcode Shore: before the dive

 

Open Spotify > Search: 'Call it Fate, Call it Karma', by The Strokes > Play > Thank me later



Are you in the mood for some history and cool computer tales? Just lay back, enjoy the music, and hear me out.

While playing the Narnia game on OverTheWire, I hit a roadblock. I could either hack my way into advancing another level, or I could step back, take a better look at what was being presented, and learn a little bit more in the process. Ask a few questions, you know? Like why is it that...? You catch my drift.

This blogpost marks the beginning of that journey and some of what I learned while studying shellcode, its history, implications, and the tricks of the trade. Since the rabbit hole runs pretty deep, I'll be breaking this post in multiple parts. Don't worry, thoughsince real friends know C (and then break it) I'll be getting back to the C cracking series in no time. Fret not.


The Narnia game is about code injection and shellcode... But what is that?

It's essentially a small piece of code used as a payload in exploiting vulnerabilities.

It’s a means to an end: allowing attackers to exploit certain bugs, like buffer overflows, and thus alter the normal program logic (e.g., skipping a function altogether and steering the program's flow toward the attacker's purpose).

The name says it all: shellcode—spawning a shell and gaining control of the target machine to run commands at will. This was the original purpose (FYI: more modern shellcode can perform many different actions beyond just spawning a shell).

Shellcode was hugely popular in the late '90s and early 2000s when software was (even more) riddled with vulnerabilities that allowed direct code execution (see: the Morris Worm, 1988).

But shellcode is more than that. Over time, it evolved as hackers got more creative, bypassing security mechanisms, evading antivirus tools, etc. There were even competitions to cram the shortest shellcode possible into tiny bits of data. With the right perspective, anything can be fun.


Shellcode is tipically created in ASM - which allows for compact code (very important in limited memory spaces) and also gives much finer control over CPU instructions and memory manipulation.


Fun fact: Shellcode must be position-independent.

Being position-independent means that it can be executed correctly regardless of where in memory it ends up. This is crucial because we don't know where exactly it will be loaded during execution. Position-independent code (PIC) uses relative addresses, making it adaptable no matter where that code is injected.
Cool, right?

Remember that more modern systems (and even some older ones) use protections like ASLR (Address Space Layout Randomization), which randomizes memory addresses. This often drives assembly students to fits of despair when they realize they can’t follow the same memory positions as they debug code unless they turn those protections off (I’ve heard that self-inflicted hair-pulling isn't uncommon).

You might think that with all these modern protections, shellcode would be mostly irrelevant by now. Not quite.

While many classic exploits have been mitigated by security features like ASLR, DEP/NX, stack canaries, shellcode is still widely used in modern attack vectors like buffer overflow attacks, exploits in poorly configured environments, malware, and Return Oriented Programming (ROP).


A blast from the past


I'm currently reading an oldie but goodie, 'Smashing The Stack For Fun An Profit", and thoroughly enjoying it. It's fun, and if this stuff interests you at all, you should definitely check it out.

No code or exercises on this blogpost—yet. I'm still new to this and want to have something a bit more organized before diving into these waters, but another blopost should follow soon.


If you have any advice on books, courses, or other resources on this subject, please hit me up on LinkedIn, Twitter, Mastodon, or right here. I’m always open to learning more.




Thursday, October 10, 2024

Wherein We Crack A Simple Program: level Leviathan

...what in the gibson?
 


As I was about to publish my second entry in this reverse engineering series with a slightly harder program, I felt somewhat disappointed. I had promised some basic obfuscation and environment variable techniques to make the challenge more interesting, but I wanted to push it further.

While contemplating additional extra layers of fun and despair, I was reminded of an fun debugging experience from the final level of OverTheWire's Leviathan game. The level featured an executable that required a 4-digit numeric parameter which, when correct, granted access to a shell with elevated privileges - specifically, becoming the next level's user and accessing a restricted file.

Now, I promised no walkthroughs for OTW challenges, true! But let me offer this caveat: 

While what I'll explain in this blog post is (indeed) a potential solution, it's far from the most straightforward or obvious approach. 

If this was your first solution - well... you're my kind of crazy. Give me a call; my borderline-insane friends would love to meet you. No, really.

Fair warning: if you don't want a potential Leviathan solution, stop reading. But my advice? Read on, consider 'my' approach, then devise your own. Remember: the flag isn't the objective. Learning is.

Back to our executable: After solving the level, curiosity drove me to examine it with GDB. I wondered if I could spot the password in the assembly code.

And there it was, in all its hexadecimal glory:



Did you spot it? Here is our baby in all its hexadecimal glory:

0x080491da <+20>: mov DWORD PTR [ebp-0xc],0x1bd3


The flow...

Convert user input to an integer via atoi():

0x08049212 <+76>: call 0x80490a0 <atoi@plt>


Compare it with the stored password:

0x0804921a <+84>: cmp DWORD PTR [ebp-0xc],eax

0x0804921d <+87>: jne 0x804924a <main+132>


If not equal, then we have a bad password, and that's the end of that. But if we get a correct comparison, we escalate privileges:

0x080491f9 <+51>: call 0x8049050 <geteuid@plt>

0x0804922f <+105>: call 0x8049090 <setreuid@plt>


Fun? Yes. But here's where it gets interesting:



When using GDB to bypass the program, even with the correct password, privilege escalation fails. Let's compare that with the direct execution of the program, using the correct password:



...but why?

 

The Hidden Guardian: setuid and Debugging

This behavior stems from a crucial security feature in Unix-like operating systems.

 When a setuid program (one that runs with the privileges of its owner rather than the executing user) is run under a debugger, the operating system automatically drops the setuid privileges.

This protection mechanism prevents malicious users from exploiting debuggers to manipulate privileged programs. Even if you can see the password and execute the code, the debugging context itself prevents the privilege escalation from succeeding.

So, it's not just about the code we're writing and the programs we're running, but als about the environment in which we're in. The operating system itself provides layers of protection that even debuggers can't (easily) circumvent.


You hope you had fun. I learned a ton while playing these games, so I can only highly recommend them.


Or as they used to say in the good old days: two thumbs up. Way up!

Saturday, October 5, 2024

Wherein We Crack A Simple Program: level 1

 



I'll let you in on a little secret: I don't play chess.
Well... I can, and I have, but I don't anymore.
Not because I lack the ability, but because I get obsessed with it. I mean it.

Let me paint you a picture.

Years ago, I decided to rekindle my childhood passion for chess. As a kid, I was pretty good—not grandmaster level, mind you, but good enough to hold my own (and beat up quite a few adults in the process). 

Fast forward to adulthood, and I downloaded a chess app with daily challenges. You know the type—"Checkmate in X moves." Innocent enough, right? Wrong.

I was immediately hooked. Every spare moment became a chess moment. One day, I hopped on the subway, intending to get off 15 minutes later. When I finally looked up from my phone, I was at the end of the line, having totally missed my stop. "No big deal," you might think. Except I did it again. And again. Six or seven times that day, I rode back and forth, missing my stop each time, completely absorbed in the game.

That's why I can't play chess casually. It's all or nothing for me - books, constant practice, moves (and countermoves) consuming my every waking thought. So, I made a promise to myself: chess would only be for teaching my daughter the game. Nothing more.


Now, you might wonder where I'm going with this. Well, it's simple, really. Back when I was 12 or 13, if I couldn't find a partner, I'd play against myself. Hard to imagine in today's world of one-click online matches, but it wasn't half bad.

So, in that same spirit of playing against oneself (and a Woody Allen quote does come to mind...), I am starting a new series of blog posts where I'll be cracking simple programs. I'll create C programs that require a password to jump to the next level, then attempt to crack them by disassembling the binary and reading it with ASM. Each success will lead to a stronger, harder-to-crack program.

Feel free to spice things up by asking an LLM to create these for you, or better yet, challenge a friend to send you increasingly difficult C programs (remember? Real friends know C).

Here's the program (ignore it and move on if you want to just check the steps taken).



Simplicity itself. The program has a fixed password in plain text. If anything, this one is screaming FIND ME. Right?

Still, always remember that time, days, months, or years ago, when this simple password would be a deterrent enough to keep you from accessing the program.

Let's look at four different ways to find out this password. You might even be considering a fifth: some sort of brute force attack. But this would probably fail in this case. Notice that the password has 16 characters and, a priori, we have no present way to know how many characters it has. It's also not incredibly likely that the password will be in a popular wordlist.

So, assuming that, and assuming that you're only testing for some 60 total characters (uppercase + lowercase + a few favorite symbols), it could take up to 60 to the power of 16 attempts for our loop to capture the correct password. That's a lot. That's really a loooooooooot. So let's not do that.

Our four approaches (remember to man <tool>):

  • xxd


See the password?

xxd is a tool that converts binary files into their hexadecimal representation, which basically gives us a byte-by-byte view of the file.

If the program is storing the password in plain text, like in this case, then... it's Game Over for the program, and Game On for us.


  • strings


This nice tool quickly pulls out readable strings from a binary file. Nifty, no?

Our poor, exposed password is there again, for all to see.

Even if you don't see something as glaringly obvious as a password, taking a look at visible text can give you some good hints on what the program might be doing.


  • ltrace


After having played through the
Leviathan game in OTW, I became a fan of this tool. ltrace is a powerful tool that traces all the function calls made by a running program - allowing us to spy on what's happening under the hood as the program executes. More so, since there is absolutely no obfuscation in our original C program.


  • gdb



This one is starting to feel like an old friend, right?

I won't go into much detail on how to use it, since I've done so here and here.

After exploring we find something slightly odd:

A couple of movabs instructions (this is intel flavor right here. Is it also there if we don't use intel? Check it out)


Now, movabs is an instruction that moves a 64-bit (8-byte) immediate value directly into a 64-bit register. That's interesting, since we use movabs when we 
need to load a large constant value that won't fit in the 32-bit immediate field of a regular mov instruction.

Our registers will be loaded with those values in reverse-order (because of little-endian). And, just to make it plainly obvious (you could do this outside, through a program, etc) we can showcase what we mean:


There we go. Our hexadecimals converted into characters, in all their glory.


Alright. Program cracked with 4 different tools. But this is just our opening move. We're moving on to the next level. 

Whatever was your weapon of choice, we got this result:


Next up:

  • Basic Obfuscation of the Password
  • Environment Variables


Consider Phlebas, who was once handsome and tall as you.

How a Spy Pixel Crashed Into My Friend's Vacation

              So it goes.   A friend of mine, a freelancer, recently went on a much-deserved vacation. Like most of us in today's always...