Introduction
It is no small secret that reverse engineering is an important tool in an exploiter’s kit.
You can’t simply craft shellcode out of thin air, you must understand the binary and its actions before you find that one vulnerability.
In this article, we will explore the exploitation method for breaking HTB’s business CTF 2025’s Power Greed challenge. We will discuss memory management issues and a few mitigation techniques.
In the cybersecurity industry, CTFs are a format of competition (Capture the Flag). They are a way for cybersecurity experts, penetration testers and hackers to test their skills by completing a series of challenges.
This challenge’s category is “PWN” which refers to RE & binary exploitation. You need to understand x64 assembly for full comprehensibility though we will attempt not to dig too deep into it.
The challenge is completed when the contestant attains a “flag” which is usually a text that when submitted to the CTF’s management page – awards you with points.
Tools used
- IDA
- Linux’s “file” command
- Python – The pwntools library
- GDB
Research
We start by downloading the challenge's ZIP directory and notice that it contains a single file:
challenge/power_greed
Executing the command file ./power_greed![]()
So we have a 64bit ELF (Software for Linux/Unix systems). We also notice that the file is “not stripped“. This means that we have debug symbols which would heavily assist us in understanding the logic of the software. We boot up IDA (Reverse engineering and memory analysis tool, often used for Forensics & Incident Response) and load the binary.
The “main” function
This shows a few functions (diagnostics, panel, log_console, info). We first notice that there’s a check for the number of arguments the software has. If the number isn’t 0, the software leaves immediately. This is great, it means that the remote server executing this software isn’t running it with arguments which may complex our understanding of the binary. It increases complexity when arguments affect the flow of the program. In our case – no arguments means less complexity.
The “panel” function

The function starts with pushing a weird string to the stack “\x1B[1;33m”. We recognize this as a way to add colors to a linux shell, specifically, the color “yellow”.
We rename the pointer to make it easier for us to reverse the binary. We also apply this to other “color-strings” we find in the binary.

This function seems to be a simple printf with static values. A printf with static values and no user input doesn’t put the program at risk of exposing any pointers.

So we move on to the next one.
The “info” function
The function receives a string as a parameter, and prints it with an “INFO” prefix. It is referenced 3 times in our software. The main function uses this to print “Goodbye” incase you choose to enter the number “3” in the panel.

The “log_console” function
This function once again contains static prints with colors.

The output looks like this:

Doesn’t look like we’ll have anything useful here.
Or maybe… there is?
It is talking about detecting a buffer overflow that was triggered with an attempt to execute /bin/sh
Then the classic segmentation fault (program termination after detection of issues with memory)
Did you recognize it? The most important part here is that we have a “/bin/sh” substring. Not only that, but its also null terminated! If we actually have a buffer overflow here, we already have one pointer fixed for us for a later function call to execve (More in this in part2)
The “diagnostics” function

Once again we have colored prints, followed by input for either:
- Vulnerability Scan
- Firmware update
- Change grids
We immediately target the firmware update, but that’s just a printout of
“Firmware update is available, contact the administrator to perform this action.” ![]()
Static text, no additional logic. We move on to “Change grids“, which is another static printout of text. This time; however, we have some pointers and their relative values.

Okay, this could be used to do some address computations and give us an offset we can work with. We’ll get back to that later.
Finally, we move on to…
The “vuln_scan” function
Starts off with a progress bar for a random duration (imitates an actual scan, but doesn’t interact with a even a single block of memory)

At the end of the scan it prints out some security information about the process. Looks like quite a few anti-exploitation techniques have been implemented in this binary. This is once again a static printout, probably to assist the exploiters in the CTF. Since this is a static printout, lets confirm with an actual checksec:
Stack canaries seem to exist, but our reverse engineering showed nothing on the main module (0x4000000), we can ignore this if we use functions from this module for our exploit.
SHSTK: Enabled – Shadowstack is enabled.
This is a tough one. With this enabled, we may not be able to use ROP chaining (the RET command will verify stack pointers against a hardware-maintained stack).
There is some hope though, this requires hardware support, perhaps the server executing our code doesn’t support it yet.
IBT: Enabled – Indirect branching
This means that we can’t simply CALL or JMP to a random address in memory. It must land on a special CPU command “ENDBR32” or “ENDBR64”. This poses an issue for exploitation, but we don’t really know if we even need a random JMP or CALL
Stripped: No – Well… we know this already, we see symbols as we debug.
Finally, the vuln_scan “finds” a potentially vulnerable Buffer and asks you if you want to test it.

Okay, might have done a bit of an overkill with the research and reverse engineering. The challenge looks like an exploitation of a buffer overflow with some mitigations in place.
With that, dear reader, we will end part 1.
Part 2 will be coming soon!