HTB Bombs Landed
Difficulty : Medium
Category : Reversing
We are given a 32-bit ELF binary that has been heavily stripped — it no longer contains section headers.
➜ Bombs Landed readelf --wide --segments ./BombsLanded
Elf file type is EXEC (Executable file)
Entry point 0x8048790
There are 8 program headers, starting at offset 52
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x08048034 0x08048034 0x00100 0x00100 R E 0x4
INTERP 0x000134 0x08048134 0x08048134 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD 0x000000 0x08048000 0x08048000 0x00e28 0x00e28 R E 0x1000
LOAD 0x001000 0x08049000 0x08049000 0x00344 0x00350 RW 0x1000
DYNAMIC 0x001010 0x08049010 0x08049010 0x00108 0x00108 RW 0x4
NOTE 0x000148 0x08048148 0x08048148 0x00044 0x00044 R 0x4
GNU_EH_FRAME 0x000cac 0x08048cac 0x08048cac 0x00044 0x00044 R 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x10
I decided to study the ELF format in more detail and used the program headers to infer the potential sections:
We can identify two important segments of type LOAD — likely corresponding to the .text and .data sections, as indicated by their permissions (R E and RW).
Ghidra FTW
Once loaded into Ghidra, a few issues arise. The absence of section headers makes analysis harder since we have to manually determine where elements like .plt, .got, .bss, and .rodata are located.
Additionally, for reasons I don't fully understand, Ghidra fails to disassemble some instructions correctly:
A lot of instructions references some addresses at 0x10091xxx.

However, objdump shows more meaningful disassembly:

These addresses reference program data (strings, global booleans, etc.). Since Ghidra can’t resolve them — even after memory map adjustments—it makes the code harder to understand.
The Anti debug mecanism
The main function first checks whether a global variable is set:

After analysis, we see that this variable is also referenced in another function. Let's check it :


This function checks if the program is being debugged and sets the global variable accordingly.
The main
Returning to main(), it becomes clearer:

The program requires that it is not being debugged and that argc > 4. If these conditions are met, it proceeds to execute some common logic involving mmap(), memset(), and xor operations. The resulting memory region is then executed.
Because Ghidra doesn’t resolve addresses correctly — and I’m a bit lazy — I simply break at the call instruction in a debugger and dump the memory page to load it manually in Ghidra:
print-format --lang hex -l 0xfff --bitlen 8 0xf7fbc000
The password checker
Here is the decompiled logic:

The function takes user input and compares it to an "encrypted" buffer. However, the strncmp() used isn't the standard libc version. Here's a closer look:

This custom strncmp() variant XORs the second argument if the third is 10, then calls the real strncmp(). So we can manually XOR the strings to recover the plaintext:
package main
func main() {
flag := []byte("se\x7fdo|oxmecdm~elcdngo")
str1 := []byte("}zda`4duggc{fp.4")
str2 := []byte("m{a4c}z:")
for _, e := range str1 {
print(string(e ^ 20))
}
print("\n")
for _, e := range str2 {
print(string(e ^ 20))
}
print("\n")
for _, e := range flag {
print(string(e ^ 10))
}
print("\n")
}
➜ Bombs Landed go run dec.go
input password:
you win.
younevergoingtofindme
And we've got our flag!
Understanding this anti debug function call
While analyzing the binary, I couldn't find a direct call to SetIsDebugged(). It runs before main(), but where exactly?
Digging through Ghidra, I discovered it's registered in the _init() routine, passed as the fourth argument to __libc_start_main():

int __libc_start_main(int (*main) (int, char * *, char * *), int argc, char * * ubp_av, void (*init) (void), void (*fini) (void), void (*rtld_fini) (void), void (* stack_end));
Examining the fourth argument reveals a pointer to the init function. Let's inspect it:

This init function iterates over an array of function pointers and calls them. Looking at it, we can see that one of them is the anti-debugging function:

The fourth argument in __libc_start_main() points an _init() function. It's worth checking it when reversing stripped binaries. If symbols are present, also inspect the __DT_INIT_ARRAY section that contains the sub functions called by _init().
References :
- https://blog.k3170makan.com/2020/11/elf-necromancy-0x0-tricks-for.html
- https://gist.github.com/x0nu11byt3/bcb35c3de461e5fb66173071a2379779
- https://docs.thecodeguardian.dev/operating-systems/linux-operating-system/understanding-plt-and-got