Sunday, August 30, 2015

Python - Built a Maze Creator and a Server to Allow Clients to Connect and Navigate the Maze

Over 2 years ago I participated in a CTF where in one of the challenges you were presented with an IP Address and a port to connect to.  Upon connecting to the port you were presented with some navigational information with 3-5 seconds to make a decision of where to go inside of a maze.

I was impressed by the challenge and decided a couple of days ago I would build it in python.  First I started out with the code to be able to build, save, load, and modify mazes.  The create.py file contains the ability to initialize a maze if you have not.

From a non-privileged account run ./create.py.  You will then be presented with a greeting, then if you would like to load a maze from a file you are prompted to do so.  Then you are prompted for the number of columns and rows you would like.  This then draws the map using a '#' as a character symbolizing a wall that can not be passed through.

Then you can proceed by creating the maze through typing in 'n', 's', 'w', or 'e'.  Notice that with every maze you start on the last row in the middle.  Everything is based on an x,y coordinate system as shown in the code.  In creating the maze anywhere it touches the outside border the server will issue a win, so remember to leave a barrier of '#' between the maze and the outer rim of the maze you are creating.  The code for create.py is below:



#!/usr/bin/python

# Maze Structure
#
#  x ---------------------->
#  y  (0,0)   (1,0)  (2,0)
#  |
#  |  (0,1)   (1,1)  (2,1)
#  |
#  |  (0,2)   (1,2)  (2,2)
# \_/

import sys

def intro():
 print "Welcome to the Maze Creator"
 print
 print "This will assist you in creating a maze that is the size in columns and rows"
 print "specified.  Then you can create the navigation through the maze as you start"
 print "on the bottom row in the middle."
 print
 print "The starting spot of the maze will always be in the bottom middle.  Wherever"
 print "a '.' touches the edge it will be an exit point."
 print

def promptSize(val):
 print
 print "Input the number of " + val + " you would like your maze to have."
 output = raw_input("~: ")
 print
 return output

def createMaze(m, x, y):
 # x is columns, y is rows
 for i in range (0, y):
  for j in range (0, x):
   dictKey = str(i) + '-' + str(j)
   m[dictKey] = '#'



def loadMaze(m, x, y, f):
 try:
  prevMaze = open(f, 'r') 
 except:
  print "Failed to open the file specified. Goodbye!"
  sys.exit()
 lineCount = 0   # Number of rows or y
 charCount = 0   # Number of columns or x
 for line in prevMaze:
  if lineCount == 0:
   x = int(line.strip())
  elif lineCount == 1:
   y = int(line.strip())
  else:
   charCount = 0
   for i in line.strip(): 
    dictKey = str((lineCount-2)) + '-' + str(charCount)
    m[dictKey] = i
    charCount = charCount + 1
  lineCount = lineCount + 1
 return m, x, y 



def outputMaze(m, x, y, posX, posY, t):
 # x is columns, y is rows
 if t == 'final':
  print
  saveFile = raw_input("Save the maze as file (default: final.txt): ")
  try:
   file = open(saveFile, 'w')
  except:
   file = open('final.txt', 'w')
   saveFile = 'final.txt'
  print "Saving the below maze as " + saveFile
  print
  file.write(str(x) + '\n')
  file.write(str(y) + '\n')
 row = ''
 for i in range (0, y):
  for j in range (0, x):
   dictKey = str(i) + '-' + str(j)
   if ((posX == j) and (posY == i)):
    if t == 'redraw': row = row + '$'
    elif t == 'final': row = row + '.'
    m[dictKey] = '.' 
   else:
    row = row + m[dictKey]
  if t == 'redraw':
   print row
  if t == 'final':
   file.write(row + '\n')
  row = ''
 if t == 'final':
  file.close()



def findDirections(m, x, y, posX, posY):
 dirN = False  # Can I go North?
 dirS = False  # Can I go South?
 dirE = False  # Can I go East?
 dirW = False  # Can I go West?
 dirTravel = ''  # Return the directions that can be traveled.
 if (posY <> 0):  # Determine if you can navigate North
  dirN = True
  dirTravel = dirTravel + 'n '
 if (posY <> (y - 1)): # Determine if you can navigate South
  dirS = True
  dirTravel = dirTravel + 's '
 if (posX <> (x - 1)): # Determine if you can navigate East
  dirE = True
  dirTravel = dirTravel + 'e '
 if (posX <> 0):  # Determine if you can navigate West 
  dirW = True
  dirTravel = dirTravel + 'w '
 return dirTravel


  
def findPath(m, x, y, posX, posY):
 dirN = False  # Can I go North?
 dirS = False  # Can I go South?
 dirE = False  # Can I go East?
 dirW = False  # Can I go West?
 dirTravel = ''  # Return the directions that can be traveled.
 if (posY <> 0):  # Determine if you can navigate North
  dictKey = str(posY-1) + '-' + str(posX)
  if m[dictKey] == '.':
   dirN = True
   dirTravel = dirTravel + 'n '
 if (posY <> (y - 1)):  # Determine if you can navigate South
  dictKey = str(posY+1) + '-' + str(posX)
  if m[dictKey] == '.':
   dirN = True
   dirTravel = dirTravel + 's '
 if (posX <> 0):  # Determine if you can navigate East
  dictKey = str(posY) + '-' + str(posX+1)
  if m[dictKey] == '.':
   dirN = True
   dirTravel = dirTravel + 'e '
 if (posX <> (x - 1)):  # Determine if you can navigate West
  dictKey = str(posY) + '-' + str(posX-1)
  if m[dictKey] == '.':
   dirN = True
   dirTravel = dirTravel + 'w '
 return dirTravel



def main():
 maze = {}
 sizeX = 0 #Size of the Maze X - Number of columns
 sizeY = 0 #Size of the Maze Y - Number of rows
 posX = 0 #Position of '$' on the Maze X based on x,y coordinates
 poxY = 0 #Position of '$' on the Maze Y based on x,y coordinates
 option = '' #Option Selected for Movement or to Quit
 directions='' #The directions in which can be navigated
 listDir = [] #Create a list of the directions possible
 loadOption = ''   #Option to load a previously created maze or create a new one
 mazeFile = ''   #Filename of what the maze is called

 intro()
 loadOption = raw_input("Would you like to load a previously created maze from file (y, n)? ")
 if loadOption == 'y':
  mazeFile = raw_input("Enter the filename of the maze to load: ")
  maze, sizeX, sizeY = loadMaze(maze, sizeX, sizeY, mazeFile)
 else:
  sizeX = promptSize('columns')
  sizeX = int(sizeX)
  sizeY = promptSize('rows')
  sizeY = int(sizeY)
  createMaze(maze, sizeX, sizeY)
 posX = int(sizeX) / 2
 posY = int(sizeY) - 1 # The range goes from 0 to the number before the sizeY given so you need to subtract 1
 while (option <> "q"):
  print
  outputMaze(maze, sizeX, sizeY, posX, posY, 'redraw')
  directions = findDirections(maze, sizeX, sizeY, posX, posY)
  print
  print "You can travel in the following directions: " + directions
  option = raw_input("Select direction to navigate or 'q' to quit: ")
  listDir = directions.split()
  if option in listDir:
   if option == 'n': posY = posY - 1
   elif option == 's': posY = posY + 1
   elif option == 'e': posX = posX + 1
   elif option == 'w': posX = posX - 1
  elif option <> 'q':
   print 'Invalid direction!'
 print
 outputMaze(maze, sizeX, sizeY, posX, posY, 'final')
 print


if __name__ == "__main__":
    main()



Then the server portion of the challenge, I had the following objectives.
1.  I wanted multiple clients to be able to connect to it.
2.  It needed to be able to load a maze and then be able to return to the client the directions of where it could go next

Below is the server code that I utilized.  I was able to reuse some of the functions that are in the create.py file so you will notice that I import from that file.  If you save the file for the create portion of the maze as something else, you will need to update the import file entry at the beginning and as it is referenced throughout the code.  I also start the listener on TCP port 30,000 which can be modified.

Also as far as a client connecting to it, I initially used netcat.  At the end of this I have provided a python client that can be utilized.  The ideal goal would be to create a python client to navigate through the maze and then automate it.  Also in the challenge there was a timeout of 3-5 seconds between making decisions of the direction to go in the maze.  I have left that out of the code but can easily be put in.



#!/usr/bin/python

import socket
import sys
import thread  
import create    # Import the functions from the create.py file

mazeFilename='final.txt'  # Change this for the server to load a different maze file.



def handleClient(c, addr):
 maze = {}
        sizeX = 0       #Size of the Maze X - Number of columns
        sizeY = 0       #Size of the Maze Y - Number of rows
        posX = 0        #Position of '$' on the Maze X based on x,y coordinates
        poxY = 0        #Position of '$' on the Maze Y based on x,y coordinates
        startX = 0        #Position of '$' on the Maze X based on x,y coordinates
        startY = 0        #Position of '$' on the Maze Y based on x,y coordinates
        directions=''   #The directions in which can be navigated
 listDir = []    #Create a list of the directions possible

 print "Accepted connection from: " + str(addr)
 msg = "Welcome to the maze.  Navigate through the maze based on the directions returned.\r\n"
 msg += "You will only have 10 seconds between moves to make a decision.\r\n\r\n"
 msg += "The size of the maze selected is below:\r\n"
 maze, sizeX, sizeY = create.loadMaze(maze, sizeX, sizeY, mazeFilename) 
 msg += str(sizeX) + ',' + str(sizeY) + "\r\n\r\n"
 c.send(msg)
 posX = int(sizeX) / 2
        posY = (sizeY - 1)      # The range goes from 0 to the number before the sizeY given so you need to subtract 1
 startX = int(sizeX) / 2
        startY = (sizeY - 1)      # The range goes from 0 to the number before the sizeY given so you need to subtract 1
 while 1:
  create.outputMaze(maze, sizeX, sizeY, posX, posY, 'network')  # redraw or network
  directions = create.findPath(maze, sizeX, sizeY, posX, posY)
  directionInfo = "You can travel in the following directions: " + directions + "\r\n"
  c.send(directionInfo)
  listDir = directions.split()
  option = c.recv(1024)
  #print listDir
  option = option.strip()
                if option in listDir:
                        if option == "n": posY = posY - 1
                        elif option == 's': posY = posY + 1
                        elif option == 'e': posX = posX + 1
                        elif option == 'w': posX = posX - 1
  else:
   c.send("Invalid direction received.\r\n")
   break
  # Check to see if the player figured a way out of the maze
  #print "startX,Y: " + str(startX) + "," + str(startY)
  #print "posX,Y: " + str(posX) + "," + str(posY)
  if not ((startX == posX) & (startY == posY)):
   if ((posX == 0) | (posY == 0) | (posX == (sizeX - 1)) | (posY == (sizeY - 1))): 
    c.send("You Win!\r\n")
    break
 c.close()


def main():
 host = ''
 port = 30001
 try:
  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 except:
  print "Failed to create socket."
  sys.exit()
 print 'Listening for connections on port ' + str(port)
 s.bind((host, port))
 s.listen(5)  # Allows up to 5 connections at a time
 while 1:
  (client, address) = s.accept()
  thread.start_new_thread(handleClient, (client, address))
 s.close() 
 



if __name__ == "__main__":
    main()




I have included both of these files and a few mazes that I have created to test the code on my google drive located here.  An example of a maze that can be downloaded with the preceding link and use is below.

15
15
##.############
##........#####
##.######.#####
##.######.#####
##.######.#####
##.####.......#
##.####.#.#####
##.####.#....##
##.####.####.##
##.####.####.##
##.##...####.##
##.##.#.####.##
##......####.##
#######......##
#######.#######

The first line of the maze is the size in columns, the next is the size in rows, on the 3rd line the 3rd dot in is the exit to the maze.  It does not have to be on the top line, it could also be on the size of the maze.  Then the beginning of the maze is on the last line in the middle.

Below is the quick client that I designed to navigate through the maze, however it is not an automated method to navigate through the maze.



#!/usr/bin/python

import socket

host = '127.0.0.1'
port = 30001

def createMaze(m, x, y):
        # x is columns, y is rows
 x = int(x)
 y = int(y)
        for i in range (0, y):
                for j in range (0, x):
                        dictKey = str(i) + '-' + str(j)
                        m[dictKey] = ' '

def createWall(m, px, py, dir):
 if (dir == 'n'): py = py - 1
 elif (dir == 's'): py = py + 1
 elif (dir == 'w'): px = px - 1
 elif (dir == 'e'): px = px + 1
 dictKey = str(py) + '-' + str(px)
 #print m
 #print dictKey
 m[dictKey] = '#'
 return m

def outputMaze(m, x, y, posX, posY):
        # x is columns, y is rows
 x = int(x)
 y = int(y)
        row = ''
        for i in range (0, y):
                for j in range (0, x):
                        dictKey = str(i) + '-' + str(j)
                        if ((posX == j) and (posY == i)):
                                row = row + '$'
                                m[dictKey] = '.'
                        else:
                                row = row + m[dictKey]
                print row
                row = ''



def main():
 recvInfo = ''
 maze = {}
 sizeX = 0
 sizeY = 0
 newSizeX = 0
 newSizeY = 0
 coord = []
 posX = 0
 poxY = 0
 option = ''
 directions = ''
 listDir = []
        
 while 1:
  try:
   s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  except:
   print "Failed to create socket."
   sys.exit()
  s.connect((host, port))
  recvInfo = s.recv(1024)
  print recvInfo
  size = recvInfo[191:]
  coord = size.split(',')
  #print str(coord[0]) + ' ' + str(coord[1])
  newSizeX = coord[0]
  newSizeY = coord[1]
  if not ((newSizeX == sizeX) & (newSizeY == sizeY)):
   createMaze(maze, newSizeX, newSizeY)
   sizeX = newSizeX
   sizeY = newSizeY
  posX = int(sizeX) / 2
         posY = int(sizeY) - 1     
  option = ''
  while 1:
   recvInfo = s.recv(1024)
   if not recvInfo:
    break
   else:
    if recvInfo[:7] == 'Invalid':
     break
    else:
     directions = recvInfo[44:]
     listDir = directions.split()
     if option == 'n': posY = posY - 1
     elif option == 's': posY = posY + 1
     elif option == 'e': posX = posX + 1
     elif option == 'w': posX = posX - 1
     if not ('n' in listDir): 
      maze = createWall(maze, posX, posY, 'n')
     if not ('s' in listDir): 
      maze = createWall(maze, posX, posY, 's')
     if not ('e' in listDir): 
      maze = createWall(maze, posX, posY, 'e')
     if not ('w' in listDir): 
      maze = createWall(maze, posX, posY, 'w')
     outputMaze(maze, sizeX, sizeY, posX, posY)
     print recvInfo
     option = raw_input("~: ")
     s.send(option)
  s.close() 
 



if __name__ == "__main__":
    main()





Thursday, August 13, 2015

Cryptowall 3.0 downloaded and executed from "William_Isabella_resume.doc"

Recently I was provided a phishing email with an attachment called "William_Isabella_resume.doc".  As I had done in the previous post I used "./oledump William_Isabella_resume.doc" to examine the contents for a malicious macro.  Below is the output of that command.

./oledump.py William_Isabella_resume.doc
 A: word/vbaProject.bin
 A1:       375 'PROJECT'
 A2:        41 'PROJECTwm'
 A3: M   40002 'VBA/ThisDocument'
 A4:      8271 'VBA/_VBA_PROJECT'
 A5:       514 'VBA/dir'

As you can see, item A3 contains a macro.  Then by selecting A3, I output the macro to a text file.

./oledump.py -s A3 -v William_Isabella_resume.doc > macro.txt

I immediately started looking at the macro and noticed a pattern of lines that were repeated with varying length of variables but contained a similar structure.  Two examples of the pattern are below:

Dim ACIZjEo8pvcIr As Long, I3wVba3qWHhnCuC As Long
ACIZjEo8pvcIr = 80
I3wVba3qWHhnCuC = 19
If ACIZjEo8pvcIr + I3wVba3qWHhnCuC > 4 Then
I3wVba3qWHhnCuC = ACIZjEo8pvcIr + 29
Else
MsgBox 45
End If

Dim AI9EyT0hz As Long, LDwIMi8qU8zv As Long
AI9EyT0hz = 55
LDwIMi8qU8zv = 92
If AI9EyT0hz + LDwIMi8qU8zv > 4 Then
LDwIMi8qU8zv = AI9EyT0hz + 8
Else
MsgBox 87
End If

With this pattern I wanted to created a regular expression to 1st find only the matches for the following pattern:  Dim <var> As Long <var> As Long

So I used the following grep statement to build my population: cat macro.txt | egrep "Dim.*":
**Dim Nz7m0PxKYWw As Long, HbS1melaNAS7H As Long, L7N3MtqbtznJ As Byte, HmKeQofQiXVxVGCT() As Byte, L9EY5PIy8XL As Long
Dim LvAzbXXkWUDtDQ() As Byte
Dim ACIZjEo8pvcIr As Long, I3wVba3qWHhnCuC As Long
**Dim FP3AL83PNIVhYqp As Long, Bhu6AVZ As Long, HzD7nSW As Long, IFHWQgDBF4yZ As String * 8162, JkOPSi As String, I0xSQ As Integer, YBoXWN0Xv As Double
Dim AI9EyT0hz As Long, LDwIMi8qU8zv As Long
Dim BkbpvlCIZj As Long, VS1rCLO33mU As Long
Dim ILWUKAm As Long, JRN9ZgNj6Yf As Long
Dim WqZT6KvAVkyO As Long, OHPW2b0ZGFa As Long
Dim HbUu4uL As Long, GfUsdMAvdCRNJDXy8 As Long
Dim AizpYIBM As Long, BYtWR9n2OwKs As Long
Dim Izv2Cof As Long, Ntxt3NxnUsY As Long
Dim V0fF89S As Long, HVcFPWU6 As Long
Dim K1Xi As Long, QZlNuLHUlh7Q2xe As Long
<..smip..>

So I started with the above population.  Notice that there are a couple of lines that I have preceded with a double asterisk that I need to eliminate with the regular expression. Using rubular.com I derived a regular expression that would match the Dim <var> As Long <var> As Long.


Then I used egrep to test the regular expression and display the next 7 lines after the regular expression to see if I was pulling into my population any false positives.

$ cat macro.txt | egrep '^Dim\s[A-Za-z0-9]{4,20}\sAs\sLong,\s[A-Za-z0-9]{4,20}\sAs\sLong\s$' -A 7
Dim ACIZjEo8pvcIr As Long, I3wVba3qWHhnCuC As Long
ACIZjEo8pvcIr = 80
I3wVba3qWHhnCuC = 19
If ACIZjEo8pvcIr + I3wVba3qWHhnCuC > 4 Then
I3wVba3qWHhnCuC = ACIZjEo8pvcIr + 29
Else
MsgBox 45
End If
--
Dim AI9EyT0hz As Long, LDwIMi8qU8zv As Long
AI9EyT0hz = 55
LDwIMi8qU8zv = 92
If AI9EyT0hz + LDwIMi8qU8zv > 4 Then
LDwIMi8qU8zv = AI9EyT0hz + 8
Else
MsgBox 87
End If
--

Scanning through the results no false positives were noticed.  Now the trick is using the regular expression and eliminating the 7 lines after it.  A tool called 'sed' can help us with it.  With sed you can use the regular expression but you need to escape the '{' and '}' characters.  So by running the following command in sed with the regular expression and telling it to remove the 7 lines following the regular expression I was able to remove the repeating pattern in the macro.

$ cat macro.txt | sed '/^Dim\s[A-Za-z0-9]\{4,20\}\sAs\sLong,\s[A-Za-z0-9]\{4,20\}\sAs\sLong\s$/,+7d' > modified.macro.txt

With utilizing the command above I was able to decrease the size of the macro in lines from 671 lines to 287 lines of code:

$ wc -l macro.txt 
671 macro.txt

$ wc -l modified.macro.txt 
287 modified.macro.txt

Then sifting through what is left a couple of key functions are noticed:
FP3AL83PNIVhYqp = InternetOpenA(

Then it checks if FP3AL83PNIVhYqp returns anything.  You can look up InternetOpenA online to understand what it does.  The user agent that can be pulled is the following:
Mozilla/4.0(compatiable; MSIE 6.0; Windows NT 5.1; FSL 7.0.6.01001)

Bhu6AVZ = InternetOpenUrlA(

Then the previous line in the vb will pull down the cryptowall malware as a file called 1.jpg.  The 1.jpg is the cryptowall malware however it is scrambled.  Then it is saved to the "C:\users\<user>\AppData\Roaming" folder as an executable file.

Then in another function as the flow of the application goes calls the executable and runs it as a process with "CreateProcessA".  The executable then sends a POST request to the following URL:

URL: hxxp://fortecegypt.com/blog/wp-content/themes/twentyfourteen/rrr.php?g=pzh8956b2ulluj
POST DATA: z=b8bc08ed80fdb74f01125a4d61b6879af88a5aa0f09dd28ee540b25c983ae350ad13f2a891d7b22fdddc7797fef1800cf4360057


Friday, August 7, 2015

Using oledump.py to pull Malicious Macro's out of Microsoft Word Doc

For the last few months I have been bombarded with Microsoft Word Documents that contain malicious macros.  I wanted to take a couple of minutes and document the use of oledump.py to pull out the malicious macro.  I mainly tear these apart to identify the various indicators of compromise that can be harvested.

Filename: 7ZJ7.doc
File Size: 204,800
SHA1: 086ef96c939968e9b149dab81350a2732b2fdb8f
MD5:  55687ddebba3665dd44eb7be08dc0c7b
Virus Total Detection Ratio: 19/54
Virus Total Link 

The tool oledump.py was created by Didier Stevens and he has maintained the tool as this type of malware has evolved.  To read about the command-line options that are available you can run ./oledump.py -h.  To begin to initialize the doc file you run "./oledump.py 7ZJ7.doc".


We can see from the output that there are a total of 17 objects that can be selected.  I am going to hone in on objects 8-10.

I am going to select object 8 and because it is compressed I am going to use another option to decompress it as shown below:


Then we can do the same thing to extract the macros from objects 9 and 10.  After taking the macros and evaluating them I noticed that it would go out to the following URL's to download files:

hxxp://monitoringinternetu.com/components/com_wrapper/7777.txt   (79.96.83.88)
hxxp://cdinflatables.com/components/com_wrapper/7777.txt     (188.241.222.8)
hxxp://monitoringinternetu.com/components/com_wrapper/rara.txt
hxxp://cdinflatables.com/components/com_wrapper/rara.txt

The rara.txt file contains the following link: https://www.dropbox.com/s/a73az4fj12l7fwo/kslx.exe?dl=1

I was able to find the kslx.exe file on malwr.com which may or may not be the file being requested.

Then by manipulating the macro you can see the following which are the contents of the 7777.txt file broken up into a vbs script then a bat file.


The above vbs script downloads the rara.txt file which is not found as shown above however it is found on the other URL referenced above.  Then the file is written to the filesystem as 9.exe.  Then the following bat file is executed:

Then the batch file is opened and executed to run the vbs script with cscript and then deletes the vbs file and the bat file that is left behind.  I will not go any further with the evaluation of the 9.exe.  However, below are the IP Addresses that were used to remotely control the computer:

91.231.84.120
119.81.87.154 (Most of the traffic)
148.251.157.148
148.251.127.184

Filename: kslx.exe
Size: 194,253
SHA1 - c943cccbeb257d8be5ce82d379fbf5e5e0753e2d
MD5 - 23d73f4bbcdd13ceaa9db30056d5c5a2

I thought I would also utilize a tool published by Corelan called PEFrame located here.  The output of the tool is below:

$peframe kslx.exe 

Short information
------------------------------------------------------------
File Name          kslx.exe
File Size          194253 byte
Compile Time       2015-08-04 06:42:54
DLL                False
Sections           4
Hash MD5           23d73f4bbcdd13ceaa9db30056d5c5a2
Hash SHA-1         c943cccbeb257d8be5ce82d379fbf5e5e0753e2d
Imphash            7309645e4461d38509039c98e4c661ec
Detected           Packer, Anti VM
Directory          Import, Debug

Packer matched [1]
------------------------------------------------------------
Packer             Microsoft Visual C++ 8.0

Anti VM Trick discovered [1]
------------------------------------------------------------
Trick              VMCheck.dll

Suspicious API discovered [4]
------------------------------------------------------------
Function           ExitProcess
Function           GetModuleHandleA
Function           GetProcAddress
Function           GetStartupInfoA

File name discovered [6]
------------------------------------------------------------
Library            GDI32.dll
Library            KERNEL32.dll
Library            SHLWAPI.dll
Library            WINMM.dll
Library            mscms.dll
Database           rect1.pdb

In the output it can be observed that it has an anti-vm mechanism and the trick that is discovered.  I will need to look into this at another time.



Tuesday, August 4, 2015

Python script to combine psscan and pslist Output

I was utilizing volatility the other day and was using some command line kung-fu to sort and organize the output from the module for psscan.  That is where this script came about.  Below are the objectives of the script.  Then below the script that is posted are some methods of how I utilized the script.

# Objective of the script:
# - Create a sorted view for psscan output
# - Identify the processes that are currently located in pslist
# - Number the processes in the order they appear in psscan
# - Sort by PIDs in the psscan output

In the below script after you generate the output for the psscan and pslist -P output you need to modify the file names respectively in the below script.



#!/usr/bin/python

# Objective of the script:
# - Create a sorted view for psscan output
# - Identify the processes that are currently located in pslist
# - Number the processes in the order they appear in psscan
# - Sort by PIDs in the psscan output

# Modify the below files for the output that you receive from volatility
psscanFile='txt.psscan'  # Generated with the psscan volatility 2.4 plugin
pslistFile='txt.pslist-P' # Generated with the pslist -P volatility 2.4 plugin # -P gives you the physical offset instead of the virtual

# Offset(P) # Name  # PID  # PPID  #PDB  # Time   #Created  #Time Exited
# list[0] list[1]  list[2]  list[3]  list[4]  list[5]  list[6]   list[7]

# Function to find the name of the parent process
def findName(ppid, dictP):
 namePID = ''
 for i in range(0,len(dictP['name'])):
  if dictP['pid'][i] == ppid:
   namePID = dictP['name'][i] 
 return namePID

# Function to find if the offset in the psscan output is found in the pslist output
def findPSList(oVal):
 exists = 'False'
 f = open(pslistFile, 'r')
 for line in f:
  if (('Offset' not in line) and ('-------' not in line)):
                 list = line.split()
   psOffset = list[0]
   if (oVal[-8:] == psOffset[-8:]):
    exists='True'
 return exists

# The keys that make up the dictionary for the input of the psscan output
keys = ['offset', 'name', 'pid', 'ppid', 'pdb', 'createDate', 'createTime', 'createTimeZone', 'exitDate', 'exitTime', 'exitTimeZone']
dictPsscan = {}

# Open the File where the psscan output is located
file = open(psscanFile, 'r')
for line in file:
 # Ignore the header lines that are in the output
 if (('Offset' not in line) and ('-------' not in line)):
  list = line.split()
  # The line being read may not have all 12 values so append to the list until it does (Time Exited sometimes does not exist)
  while len(list) != 12:
   list.append('') 
  # Add the values of the list into my dictionary
  for x in range(0,11):
   dictPsscan.setdefault(keys[x], []).append(list[x])

# Identify the length of the dictionary 
dictLen = len(dictPsscan['offset'])
# Take from the dictionary dictPsscan and the values of the PID values. Create a list, sort and remove duplicates
listPID = []
for i in range(0, dictLen):
 listPID.append(dictPsscan['pid'][i])
 # Remove duplicate PID values and then sort them as an integer
 listPID = sorted(set(listPID), key=int)

titleTable = "Offset(P) pslist # name pid pName ppid pdb createdDate cTime cTZ exitDate eTime eTZ"
table = titleTable.split()
# Format the output to make it easier to read
print '{0:20s} {1:6s} {2:3s} {3:16s} {4:5} {5:16} {6:5} {7:12} {8:11} {9:9} {10:9} {11:11} {12:9} {13:9}'.format(table[0], table[1], table[2], table[3], table[4], table[5], table[6], table[7], table[8], table[9], table[10], table[11], table[12], table[13])
print "-"*20 + " " + "-"*6 + " " + "-"*3 + " " + "-"*16 + " " + "-"*5 + " " + "-"*16 + " " + "-"*5 + " " + "-"*12 + " " + "-"*11 + " " + "-"*9 + " " + "-"*9 + " " + "-"*11 + " " + "-"*9 + " " + "-"*9

# Loop through the PID values
for i in range(0, len(listPID)): 
 # The counter is to count the number of instances of the PID coming out of psscan
 counter = 1
 for j in range(0, dictLen):
  if listPID[i] == dictPsscan['pid'][j]:
   outputInfo = dictPsscan['offset'][j] + " "
   outputInfo += findPSList(dictPsscan['offset'][j]) + " "
   outputInfo += str(counter) + " "
   outputInfo += dictPsscan['name'][j] + " "
   outputInfo += dictPsscan['pid'][j] + " "
   ppidName = findName(dictPsscan['ppid'][j], dictPsscan)
   if ppidName == '':
    ppidName = '(Unavailable)'
   outputInfo += ppidName + " "
   outputInfo += dictPsscan['ppid'][j] + " "
   outputInfo += dictPsscan['pdb'][j] + " "
   if (dictPsscan['createDate'][j]):
    outputInfo += dictPsscan['createDate'][j] + " "
   else:
    outputInfo += " * "
   if (dictPsscan['createTime'][j]):
    outputInfo += dictPsscan['createTime'][j] + " "
   else:
    outputInfo += " * "
   if (dictPsscan['createTimeZone'][j]):
    outputInfo += dictPsscan['createTimeZone'][j] + " "
   else:
    outputInfo += " * "
   if (dictPsscan['exitDate'][j]):
    outputInfo += dictPsscan['exitDate'][j] + " "
   else:
    outputInfo += " * "
   if (dictPsscan['exitTime'][j]):
    outputInfo += dictPsscan['exitTime'][j] + " "
   else:
    outputInfo += " * "
   if (dictPsscan['exitTimeZone'][j]):
    outputInfo += dictPsscan['exitTimeZone'][j] 
   else:
    outputInfo += " * "
   table = outputInfo.split()
   # Format the output to make it easier to read
   print '{0:20s} {1:6s} {2:3s} {3:16s} {4:5} {5:16} {6:5} {7:12} {8:11} {9:9} {10:9} {11:11} {12:9} {13:9}'.format(table[0], table[1], table[2], table[3], table[4], table[5], table[6], table[7], table[8], table[9], table[10], table[11], table[12], table[13])
   # Find the relevant PID files for that PPID
   counter += 1



For example I was utilizing grep to hone in on a particular process after running the script and it made it much easier to read.  I narrowed it quickly down to the nc.exe script is associated with the cmd.exe.

$ ./script.py | grep -e '3284\|1944'
Offset(P)            pslist #   name             pid   pName            ppid  pdb          createdDate cTime     cTZ       exitDate    eTime     eTZ      
-------------------- ------ --- ---------------- ----- ---------------- ----- ------------ ----------- --------- --------- ----------- --------- ---------
0x0000000002210da0   True   1   cmd.exe          1944  explorer.exe     840   0x08ac02c0   2014-01-13  03:02:50  UTC+0000  *           *         *        
0x0000000005686da0   False  2   cmd.exe          1944  explorer.exe     840   0x08ac02c0   2014-01-13  03:02:50  UTC+0000  *           *         *        
0x000000001997bda0   False  3   cmd.exe          1944  explorer.exe     840   0x08ac02c0   2014-01-13  03:02:50  UTC+0000  *           *         *        
0x0000000001f72da0   False  1   nc.exe           3284  cmd.exe          1944  0x08ac0280   2014-01-13  03:05:08  UTC+0000  9999-01-13  03:05:08  UTC+0000 
0x000000000dcb7da0   False  2   nc.exe           3284  cmd.exe          1944  0x08ac0280   2014-01-13  03:05:08  UTC+0000  *           *         *




Test Authentication from Linux Console using python3 pexpect

Working with the IT420 lab, you will discover that we need to discover a vulnerable user account.  The following python3 script uses the pex...