Tuesday, December 31, 2024

Wherein We Face A Lindwyrm: don't do LeetCode

 

                    The Lyndwurm. Let's face it sooner. Not later.


Before diving into LeetCode, let me tell you this post was initially going to be about my journey completing a series of 30 Assembly CTFs on pwn.college. It still is, in a way, and I highly recommend that site to anyone interested in hacking and low-level programming (check that site─totally worth it). 

But somewhere along the way, I decided to shift the focus. So, bear with me while we explore LeetCode.

Let’s start with the disclaimers:

  • I’m happily employed.
  • My knowledge of the hiring process is limited to my own experiences and what I’ve observed.
  • Take everything I say here with a grain of salt.

What Are LeetCode Problems?

LeetCode-style problems are algorithmic and data structure challenges, often used in technical interviews to (supposedly) assess problem-solving skills, logic, and coding efficiency.

I’ve tried LeetCode. I even did a fair bit of it back in university while studying Python. And I’m here to tell you: it’s not for me. It's probably not for you, either.


Here are 10 reasons why I’ve left LeetCode by the wayside:


1. I’m not a programmer, nor do I want to be one.

I work in IT and am aiming to become a Cybersecurity professional, specifically a Reverse Engineer/Malware Analyst. In this realm, LeetCode is of very limited relevance, if any. My focus is on low-level code, systems, and security—not cranking out optimal algorithms for abstract, byte-sized, problems.


2. Time is sacred, and LeetCode doesn’t fit my priorities.

Mastering LeetCode takes time—lots of it. As someone who obsesses over how I spend my time, I refuse to pour hours into a skill I find doubtful in utility for my goals. Instead, I could be:

  • Diving deeper into malware analysis.
  • Learning more about CPU internals.
  • Experimenting with Reverse Engineering tools.
  • Or even doing other wholesome hobbies like: fishing, watching paint dry, or fine-tuning push-ups.

3. It consumes mental bandwidth I’d rather use elsewhere.

Focusing on LeetCode takes up space in my brain that I could dedicate to something more relevant or enjoyable. Cybersecurity is vast, and every moment I spend on coding puzzles is a moment I’m not spending on fun puzzles.


4. There are other ways to demonstrate my skills.

I maintain a public GitHub with projects I’ve built. If someone needs proof of my abilities, I can show my work or create something on demand. Writing scripts or automation tools in a real-world context is more relevant to my career than solving arbitrary LeetCode.


5. Secure, readable code > Clever one-liners.

LeetCode often rewards speed and brevity, which can lead to unreadable, messy solutions. Writing secure, stable, maintainable code that other humans can understand is far more valuable in real-world applications.


6. In cybersecurity, CTFs are the way to go.

Capture The Flag challenges (CTFs) are like games. They’re fun, align with the hacker spirit, and teach you practical skills. I’d rather do CTFs all day than grind through LeetCode puzzles.


7. Better ways to assess pressure and skills exist.

In addition to several other assessments, my current company included a 1.5-hour test during the hiring process that challenged me to work under pressure, adapt to new situations, conduct research, and document my process. It was hard, fun, and incredibly insightful. If they had instead asked me to solve 10 LeetCode problems, they wouldn’t have learned anything about my actual skills. No joke—this test was fantastic.


8. I’m not here to compete with kids who have all the time in the world.

If I have two free hours, I’ll use them to play in my malware lab—not grind Python or C snippets on Codewars. I have nothing to prove to anyone, and I’m not interested in chasing someone else’s benchmarks.

Years ago, I participated in a BJJ tournament and won (blue belt +40 years category). At the end of the tournament, all the blue belt winners were allowed to face each other in a 'free-for-all styled' match. I said no. I had nothing to prove. I knew the outcome, too─there was no point in fighting guys who had trained as long as I had but were 20 years younger and weighed 20 kilos more. 

Here's what I looked like after winning in my category:

                                                        Cute, huh? Third place didn't even get a medal. He got a broken rib and a trip to the hospital.


9. I’m not missing meaningful opportunities.

Sure, some companies prioritize LeetCode skills, but those are likely not the places I want to work at. I’d rather focus on preparing for roles that value my expertise in cybersecurity and low-level systems.

On the flip side, if I do receive a job offer from a company looking to assess my "cyber skills," I’ll be in a much stronger position if I’ve invested my time in honing those skills, rather than spending it on LeetCode.


10. Burnout is real.

Burnout is pervasive in IT. I’ve seen people so drained by their work that all they want is to clock out and forget anything technical. They crave time for hobbies, family, and friends, leaving the techie stuff for when it’s absolutely necessary. Not me. I dive into reverse engineering because I genuinely enjoy it, but I make sure to balance it with other hobbies, family time, and necessary downtime. Chasing someone else’s dream isn’t worth sacrificing my mental health.




Final Thoughts

For those who insist we must suffer through things we hate to secure a “dream job,” I leave you with this quote from Game of Thrones:



Sunday, December 22, 2024

Wherein We Look At An ELF: Executable and Linkable Format

 


                So, this is an ELF file, huh?


Elves

For the past few days, I've been delving into ELF (Executable and Linkable Format) binaries—specifically focusing on their structure, behavior, and ways to manipulate them. If the subject matter is of any interest to you, then check this out.

What are ELF files?

They are a "common standard file format for executable files, object code, shared libraries, and core dumps, at least according to Wikipedia.
The magic number for ELF files is 0x7f 45 4C 46. Care to guess what's 45 4C and 46 in ASCII?

Let's look at (yet another) terribly simple C script and then look under the covers:



Not terribly impressive, but we're not trying to be terrible or to impress anyone.

Remember when we talked a bit about the 4-step compilation process here? Instead of directly compiling this program, let's jump to the Assembly phase and look at the object file:


So, as we can see, we have a 64-bit ELF object file... relocatable. What does it mean for this to be relocatable?
It means that it's not dependent on specific memory addresses. So this file can be moved around without breaking its code. Our code isn't yet an executable. We're still short of that objective since we've not yet passed through the linking phase, which will or might add to it other object files or libraries, and then yes, produce our executable.

Remember that, for the most part, programmers skip and don't even think about these steps. The compilation process takes care of all of this in the background, and only if something is untoward will the programmer be warned that one of these 4 steps went awry.
And notice as well that this file is 'not stripped'. What's this, you ask? It's informing us that the the file contains the symbol table and debugging information within it. This keeps information that is useful for debugging purposes, making it easier to analyze and understand what's happening, with tools like gdb or objdump. On the other hand, stripping our binary means that both symbol table and debugging info will be removed. The symbol table contains the names of functions, files, variables and other metadata useful for debugging or reverse engineering. And that extra debugging info shows the variable types and line numbers, for example.
So, stripping will reduce the file size, hide implementation details but also make debugging a bit harder.

Under the hood

Let's do it. It's pretty simple, actually. After finally creating our binary, we can strip it with:
strip --strip-all simple_adder

Here we can see the difference between the two files, through the use of the command readelf:



It's a bit rough around the edges, but if you look carefully, you can see two files, one stripped and one not stripped, and the difference is telling, even for such a small binary.
Obviously, stripping is also used as a countermeasure and obfuscation technique.

The readelf command that you see up there is a tool for analyzing and displaying information about ELF files. With it, we can inspect the internal structure of ELF files, such as executables, shared libraries, or object files. Yes, it's what that ELF lady is doing at the beginning of this blog post. I know. Genius.

Readelf comes in handy to debug linking issues or to understand how an executable or a library is laid out.
As per usual, man files are your friend here.


.text, .data, .bss and .rodata

Elf files have critical areas, like .text which contains executable instructions, .data which stores initialized global variables, .bss which holds uninitialized global variables and .rodata, which holds read-only data, such as constant strings.

We can inspect the .text section, which could be considered as the heart of the program (holding the executable code, really), with a tool like radare2 or objdump, for example:




As you can see, this is giving us Assembly code (in AT&T, no less... yuck) revealing function prologues, loops, and system calls.
Of course, recognizing these patterns is a vital skill for the Reverse Engineer.

Try this for yourself. Also remember to check .data and .bss with:
readelf -x .data your_file
readelf -x .bss your_file

But there's more ways to inspect your ELF files. Let's look at ldd which prints shared object dependencies and use readelf with '-r' to check relocation tables:



What's all of this, you say?
With ldd we can check the shared libraries within our binary. Each listed library representing a dependency. And relocation tables are essential for adjusting addresses in a dynamically loaded binary. Entries like R_X86_64_JUMP_SLOT or R_X86_64_GLOB_DAT help resolve function calls or global variables at runtime.

There's more, of course. But this stuff is much more fun to experiment with than just to talk or write about.

Go at it. Experiment with creating your own binary files and examine them with any or all of these tools (or others). Change things, check again.


No elves were harmed in the making of this blogpost. Nor any DWARF, of course.










Sunday, December 1, 2024

Wherein We Pause to Reflect: Simple ASM Review and OS Security Mechanisms

 

Hey! Corny matrix-styled dojo. Why not?


Let’s kick off today’s blog post by writing a very, very simple ASM program. Since I’m reviewing ASM stuff through pwn.college’s course, why not showcase how simple ASM can be? (In a way... math is simple too, but many would argue otherwise).

Our script will be called one-oh-one.s, and our program will be called pausing—since that’s what we’ll want at the end.

Let's start with a program that starts and ends. Simple enough, right? But for that, we need to pass execution to the OS via a syscall (remember those?). To do this, we can move the value 60 into the rax register. Here's how:






Simple enough! But if we try to assemble and link this, we’ll hit an issue:




Ah, much better. Now, our previously failing program is a success... of sorts. We made sure to run with Intel syntax, which is much cleaner (at least to my eyes). We also made the _start label globally visible so that we can indicate where our program begins.

Now no more errors, and we’re ready to tackle the rest of the program. Easy peasy.

Right! While I was trying to set up a pause in my program, I ran into an issue. The syscall I thought was the right one for pausing was actually wrong. So, since I’m working on Linux with x86_64 architecture, I checked /usr/include/asm/unistd_64.h to see which syscalls I should use.

There, I found the exit syscall (60), which needs to be loaded into rax (which we’re already doing). If we want to set an exit code, we need to load it into the rdi register. Fair enough, we can do that. As for the pause syscall, we need syscall 34. I also discovered the alarm syscall (37), which sends a SIGALRM, ending the pause syscall. Without this, we'd have to ctrl+c our way out of the program. We also learned that we need to pass a parameter through rdi. Easy peasy.

Now that we have the tools, let’s create our pausing program, assemble it, link it, and run it while checking for our exit code:




Don't know about you, but I love this!

Now, let's review other concepts. For instance, in pwn.college, you can learn how to point to a specific memory value or the contents at that memory location.

Let's say there’s a memory position 12345 holding the value 42.

If we do:


mov rdi, 12345

We're making it so that rdi will hold that number 12345.

But if we do:

mov rdi, [12345]

Now rdi will hold the value at memory position 12345, which is 42, just like the value stored at that location.

I won’t dive too much into these basics because I’m sure you either already know them or want to experiment with them yourself. Just remember that the OS will have some defenses in place that may make it difficult, if not impossible, to access specific memory locations in your binary.

You can check which defenses have been enabled with a tool called checksec:



Please take your time and check what each of these items does. Here's a quick rundown:

  • RELRO: A security feature that makes it harder to modify certain parts of a program, like its GOT (Global Offset Table), preventing attacks on function pointers.
  • STACK CANARY: A protective value placed on the stack to detect buffer overflow attacks before they overwrite important data.
  • NX: No-Execute; prevents code from running in certain areas of memory, like the stack, to stop exploits that execute malicious code.
  • PIE: Position Independent Executable; allows a program to run at random memory addresses, making it harder for attackers to predict the location of code.
  • RPATH: A runtime setting in executables that specifies directories to search for dynamic libraries before default system paths.
  • RUNPATH: Similar to RPATH, but it is used after LD_LIBRARY_PATH to specify directories for locating shared libraries at runtime.
  • Symbols: These represent function names, variable names, and other references in the program’s code, helpful for debugging or exploitation.
  • FORTIFY: A set of compiler protections that enhance the safety of certain library functions, like checking buffer sizes to prevent overflows.
  • Fortified: Refers to functions that have been modified with additional checks (from FORTIFY_SOURCE) to improve security against buffer overflows.
  • Fortifiable: Functions that can potentially be fortified using additional checks to prevent common vulnerabilities like buffer overflows.
  • FILE: Refers to the executable or object file format, containing the machine code, symbols, and metadata that the operating system uses to load and run the program.


Like I said, a quick rundown, but it’s worth spending time learning about these defenses—what they do and how to set them up to adequately inspect a binary. Also, note that this applies to ELF files.

Alrighty, another short one! Hope you had fun.






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...