HTB Bombs Landed

Kaddate |

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.

84d46abaae1314bf859aabeca463b69c.png

However, objdump shows more meaningful disassembly:

63d895b3ae064f0189488d36fd04daa5.png

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:

1add7d76a21d4c2217b89529994ce407.png

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

f3670074f8ba33e0e8151bd271e83a55.png

00c991e5b014df55a767d81db4074a63.png

This function checks if the program is being debugged and sets the global variable accordingly.

The main

Returning to main(), it becomes clearer:

8d71728ee172c069279e959ff0163d4d.png

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:

b89aa575f985be45d2cbfd90eac40ad4.png

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:

80562890bc8b1e5bf1ae413246c9e6e5.png

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():

f16642be1ed9c8976ad952d52fa39d25.png

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:

52418337b044e58a749596ac6186eb3c.png

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:

dc38ac7d6abe8327e90759d034e6f8f7.png

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