Starting to Write Immunity Debugger Pycommands My Cheatsheet

download Starting to Write Immunity Debugger Pycommands My Cheatsheet

of 10

Transcript of Starting to Write Immunity Debugger Pycommands My Cheatsheet

  • 7/27/2019 Starting to Write Immunity Debugger Pycommands My Cheatsheet

    1/10

    https://www.corelan.be - Page 1 / 10

    Corelan Team:: Knowledge is not an object, it's a flow ::

    Starting to write Immunity Debugger PyCommands : my

    cheatsheetCorelan Team (corelanc0d3r) Tuesday, January 26th, 2010

    When I started Win32 exploit development many years ago, my preferred debugger at the time was WinDbg (and some Olly). While Windbg is a greatand fast debugger, I quickly figured out that some additional/external tools were required to improve my exploit development experience.

    Despite the fact that the command line oriented approach in windbg has many advantages, it appeared not the best tool to search for good jumpaddresses, or to list non-safeseh compiled / non-aslr aware modules, etc. Ok, looking for a simple jmp esp is trivial, but what if you are looking for

    all pop pop ret combinations in non-safeseh compiled modules Not an easy task.It is perfectly possible to build plugins for Windbg, but the ones that I have found (MSEC, byakugan (Metasploit)) dont always work the way I wantthem to work, and would still not solve some issues I was having while writing exploits.

    OllyDbg and Immunity Debugger are quite different than windbg. Not only the GUI is very much different, the number of plugins for these debuggersis substantially higher. After evaluating both of them (they pretty much have the same look and feel), and evaluating the way plugins can be added, Imade the decision to focus on Immunity Debugger.

    That does not mean OllyDbg is a bad debugger or is limited in what you can do in terms of writing plugins I just found it harder to quickly tweak aplugin while building an exploit. OllyDbg plugins are compiled into dlls, so changing a plugin would require me to recompile and test. ImmunityDebugger uses python scripts. I can go into the script, make a little change, and see the results right away. Simple.

    Both OllyDbg and Immunity Debugger have a good amount of plugins (either contributed by the community or made available when you installed theproduct). While there is nothing wrong with these plugins (or PyCommands in case of Immunity Dbg), I really wanted to have a single plugin thatwould help me building all (stack based) exploits, from A to Z. This idea got translated into my pvefindaddr PyCommand.

    My obvious choice was Immunity & Python. Im not a great python developer (at all), but I have been able to build my own PyCommand in a relativelyshort amount of time. This proves that its really easy to build custom PyCommands for Immunity, even if you are not an expert developer. If I can doit, then you can for sure.

    The only issue I faced (apart from learning python syntax and getting used to pythons hmmmm slightly annoying indentation requirement) wasfinding out how the Immunity-specific APIs/methods/attributes work. To be honest, the API help included with Immunity Debugger was not a greathelp. It basically only lists the available methods/attributes/ and thats it. No explanation on what these methods & attributes behave, on whattheyre supposed to do, or how to use them for that matter. Ok, once you start understanding how things work, they are still a good reference, but

    when you are learning from scratch, a little persistence may be required.Luckily, since Immunity comes with a lot of PyCommands, those can be used as a reference.

    Anyways, its still a good idea to use the ImmDbg Python API help file too. You can get access to the API help in Immunity by navigating to Help,select Select API help file, and selecting the IMMLIB.HLP file from the Documentation folder.

    From that point forward, you can access this help file via Help Open API help file

    Immunity also has an online version of the API : http://debugger.immunityinc.com/update/Documentation/ref/ (online version contains a little bit moreinformation)

    My main goal today is putting together a reference/cheatsheet for anyone interested in writing pycommands, so you can start building your own

    Corelan Team - Copyright - All rights reserved. Terms Of Use are applicable to this pdf file and its contents. See https://www.corelan.be/index.php/terms-of-use 10/07/2011 - 1 / 10

    https://www.corelan.be/https://www.corelan.be/index.php/2010/01/26/starting-to-write-immunity-debugger-pycommands-my-cheatsheet/https://www.corelan.be/index.php/2010/01/26/starting-to-write-immunity-debugger-pycommands-my-cheatsheet/http://www.metasploit.com/users/pusscat/Byakugan.pdfhttp://www.ollydbg.de/http://debugger.immunityinc.com/http://www.corelan.be:8800/index.php/security/pvefindaddr-py-immunity-debugger-pycommand/http://debugger.immunityinc.com/update/Documentation/ref/http://debugger.immunityinc.com/update/Documentation/ref/http://www.corelan.be:8800/wp-content/uploads/2010/01/image53.pnghttp://www.corelan.be:8800/wp-content/uploads/2010/01/image52.pnghttp://www.corelan.be:8800/index.php/security/pvefindaddr-py-immunity-debugger-pycommand/http://debugger.immunityinc.com/http://www.ollydbg.de/http://www.metasploit.com/users/pusscat/Byakugan.pdfhttps://www.corelan.be/index.php/2010/01/26/starting-to-write-immunity-debugger-pycommands-my-cheatsheet/https://www.corelan.be/index.php/2010/01/26/starting-to-write-immunity-debugger-pycommands-my-cheatsheet/https://www.corelan.be/
  • 7/27/2019 Starting to Write Immunity Debugger Pycommands My Cheatsheet

    2/10

    https://www.corelan.be - Page 2 / 10

    plugins faster than I did :-) ). This is not going to be the complete reference, but it should help you start to build your own plugins.

    First of all, the python syntax that you will need to write (or learn and write) is based on python 2.x (If you are not familiar with python : v2.x and v3.xare quite different in certain areas, so if you want to get yourself a reference/book on writing python, make sure to pick one that is based on the 2.xbranch)

    Building a PyCommand from scratch

    Start by creating a file in the Immunity Debugger PyCommands folder : .py

    This filename is important, because you will need to launch the PyCommand based on the filename.

    Launching a PyCommand

    Launching a PyCommand is really easy : just type in the filename (without the .py extension, but prepended with an exclamation mark), in thecommand box at the bottom of Immunity Debugger

    So if you named your plugin plugin1.py, then you can launch the script by executing

    !plugin1

    Basic structure

    The basic structure of a plugin looks like this :

    - load the Immunity Libraries (and perhaps other libraries, depending on what you want to do)- write a main() function that will read the command line arguments (if any) and call functions based on what you want the plugins to do

    - write the functions that will perform the required actions

    #!/usr/bin/env python"""(c) Peter Van Eeckhoutte 2009U{Peter Van Eeckhoutte - corelan.}

    [email protected]

    """__VERSION__ = '1.0'import immlibimport getoptimport immutilsfrom immutils import *

    """"""""""""Functions

    """"""""""""

    """"""""""""""""""Main application""""""""""""""""""def main(args):

    Next, you will need to specify you want to use the Immunity Debugger libraries and use them in your script. The best way to do is by declaring avariable that hooks into the Immunity debugger class:

    imm = immlib.Debugger()

    I usually make this one global, so I keep it outside of the main() function. (You can put it directly below the import statements for example)

    The list of available classes are :

    Corelan Team - Copyright - All rights reserved. Terms Of Use are applicable to this pdf file and its contents. See https://www.corelan.be/index.php/terms-of-use 10/07/2011 - 2 / 10

    http://www.corelan.be:8800/wp-content/uploads/2010/01/image54.png
  • 7/27/2019 Starting to Write Immunity Debugger Pycommands My Cheatsheet

    3/10

    https://www.corelan.be - Page 3 / 10

    Handling arguments

    The arguments/parameters specified when launching the PyCommand are captured in the args array.

    You can get the number of arguments using

    len(args)

    Accessing the arguments themselves is as easy as iterating through the args[] array and getting the contents of an element in the array:

    def main(args): if not args:

    usage() else: print "Number of arguments : " + str(len(args))

    cnt=0while (cnt < len(args)):

    print " Argument " + str(cnt+1)+" : " + args[cnt]cnt=cnt+1

    Writing to log, tables, files

    You may have noticed that the script above does not seem to output anything. While the syntax is perfectly correct, there is no visible default outputwindow, so you will have to tell the plugin where to write the output to.

    There are a couple of options : you can write to the Immunity Log window (which is the most commonly used technique), a new/separate table (whichis nothing more than a new window that can list the information in a table), or to a file (which may be a good idea if the amount of output you aregenerating would overflow the Log window buffer.

    Writing output to the Log Window

    The Log Window is a part of the debugger. So we will need to use the debugger instance that we have declared earlier (imm) to write to the Log.

    The method to do so is simple : imm.Log()

    Corelan Team - Copyright - All rights reserved. Terms Of Use are applicable to this pdf file and its contents. See https://www.corelan.be/index.php/terms-of-use 10/07/2011 - 3 / 10

    http://www.corelan.be:8800/wp-content/uploads/2010/01/image55.png
  • 7/27/2019 Starting to Write Immunity Debugger Pycommands My Cheatsheet

    4/10

    https://www.corelan.be - Page 4 / 10

    There are a couple of things you can do, so lets have a look at this example :

    def main(args): print "Number of arguments : " + str(len(args))

    imm.Log("Number of arguments : %d " % len(args))cnt=0

    while (cnt < len(args)):imm.Log(" Argument %d : %s" % (cnt+1,args[cnt]))

    if (args[cnt] == "world"):imm.Log(" You said %s !" % (args[cnt]),focus=1, highlight=1)

    cnt=cnt+1

    You can specify a custom memory address in the left column of the Log Window. (If nothing is specified, this address will be 0BADF00D.) All you needto do is specify the memory address (integer !) as second argument for the Log() method :

    def main(args): print "Number of arguments : " + str(len(args))

    imm.Log("Number of arguments : %d " % len(args))cnt=0while (cnt < len(args)):

    imm.Log(" Argument %d : %s" % (cnt+1,args[cnt]),12345678) if (args[cnt] == "world"):

    imm.Log(" You said %s !" % (args[cnt]),focus=1, highlight=1)cnt=cnt+1

    A good way to start your plugin, handling no arguments by displaying a Usage text, could be something like this :

    __VERSION__ = '1.0'import immlibimport getoptimport immutilsfrom immutils import *imm = immlib.Debugger()

    """

    Functions"""

    def usage():

    Corelan Team - Copyright - All rights reserved. Terms Of Use are applicable to this pdf file and its contents. See https://www.corelan.be/index.php/terms-of-use 10/07/2011 - 4 / 10

    http://www.corelan.be:8800/wp-content/uploads/2010/01/image58.pnghttp://www.corelan.be:8800/wp-content/uploads/2010/01/image57.pnghttp://www.corelan.be:8800/wp-content/uploads/2010/01/image56.png
  • 7/27/2019 Starting to Write Immunity Debugger Pycommands My Cheatsheet

    5/10

    https://www.corelan.be - Page 5 / 10

    imm.Log(" ** No arguments specified ** ")imm.Log(" Usage : ")imm.Log(" blah blah")

    """Main application"""def main(args): if not args:

    usage() else:

    imm.Log("Number of arguments : %d " % len(args))

    cnt=0while (cnt < len(args)):

    imm.Log(" Argument %d : %s" % (cnt+1,args[cnt])) if (args[cnt] == "world"):

    imm.Log(" You said %s !" % (args[cnt]),focus=1, highlight=1)cnt=cnt+1

    Updating log/table

    You will notice, as you continue to build your plugin, that when you search through memory (or perform any other CPU intensive task), the log windowmay not get updated right away. Immunity may appear to be hanging for a while, and when everything completes, everything is shown in the logwindow (or table for that matter).

    There is a way to influence this behaviour. After each imm.Log() call, you can force Immunity Debugger to update the log window. This is as easy aswriting the following statement in your code :

    imm.updateLog()

    The only (big) disadvantage is that it will also slow down the CPU intensive task that you were performing. So its a trade off between seeing what

    happens in real time, and speed. Its up to you to decide.

    Writing output to a table

    Writing data to the Log allows you to display both structured and unstructured data. First, you must define the table (Table title + colum titles), andthen you can use the .add() method on the table object to add data to the table :

    def main(args): if not args:

    usage() else:

    #create tabletable=imm.createTable('Argument table',['Number','Argument'])imm.Log("Number of arguments : %d " % len(args))cnt=0while (cnt < len(args)):

    table.add(0,["%d"%(cnt+1),"%s"%(args[cnt])])cnt=cnt+1

    Writing output to a file

    Writing output to the Log or a table works fine, as long as the amount of data doesnt overflow the available amount of Log buffer space. If that is thecase, you could consider writing output to file as well as to Log or a table.

    This is not Immunity specific in any way its just python code.

    When writing files, by default, the files will be written into the Immunity Debugger program folder. The technique is simple :

    filename="myfile.txt"FILE=open(filename,"a") #this will append to the fileFILE.write("Blah blah" + "\n")

    FILE.close()

    What I usually do is this : First, I clear the contents of the file, then I append data to the file using a function (tofile())

    Clearing the contents of a file can be done like this :

    Corelan Team - Copyright - All rights reserved. Terms Of Use are applicable to this pdf file and its contents. See https://www.corelan.be/index.php/terms-of-use 10/07/2011 - 5 / 10

    http://www.corelan.be:8800/wp-content/uploads/2010/01/image59.png
  • 7/27/2019 Starting to Write Immunity Debugger Pycommands My Cheatsheet

    6/10

    https://www.corelan.be - Page 6 / 10

    def resetfile(file1):FILE=open(file1,"w")FILE.write("")FILE.close()

    return ""

    At the beginning of a function, I reset the file, and in the function, I use the tofile() function to write data into the file

    def tofile(info,filename):info=info.replace('\n',' - ')FILE=open(filename,"a")

    FILE.write(info+"\n")FILE.close() return ""

    Working with addresses

    When you have to display a memory address, or use a memory address in a function, then you must verify what the required format of the addressshould be : either a string (if you only want to display the address) or an integer (if the address is used in Immunity methods)

    When Immunity returns an address or expects you to provide an address, it will be an integer. If you want to display this address on screen, write to alog file, etc youll have to use a format string to convert it to readable format :

    def usage():imm.Log(" ** No arguments specified ** ")imm.Log(" Usage : ")imm.Log(" blah blah")

    def tohex(intAddress): return "%08X" % intAddress

    """Main application"""def main(args): if not args:

    usage() else:

    myAddress=1234567 #integer addressimm.Log(" Integer : %d " % myAddress,address=myAddress)imm.Log(" Readable hex : 0x%08X" % myAddress,address=myAddress)hexAddress = tohex(myAddress)imm.Log(" Readable string : 0x%s" % hexAddress,address=myAddress)imm.Log(" Back to integer : %d" % int(hexAddress,16),address=int(hexAddress,16))

    Remember : anytime the Immunity API says a method uses address, you need to specify an integer value.

    Ok, thats all we need to know for now we can start writing code that will actually do something usefull and display the output to log, table, file,

    Using opcodes, assemble and disassemble, and searching memory

    One of the most common things youll probably want to do is search through memory, looking for specific type of information. Some examples couldbe

    - finding jump addresses (jump, call, push+ret, pop pop ret, etc)

    - comparing bytes in memory with bytes in a file (so step through memory locations, read bytes, and do something with it)

    - etc

    In certain cases, youll probably also want to convert opcodes to instructions, and vice versa.

    Lets take it step by step. Suppose you want to search for all jmp esp instructions available in memory. First of all, there has to be a process attachedto Immunity before your search will produce results. No process = no results :-)

    There are 2 ways to perform a search. You can provide the asm code, use Immunity to convert it to opcodes, and perform the search; or you canprovide the opcode and perform the search.

    Example 1 : search for jmp esp

    def main(args):imm.Log("Started search for jmp esp...")imm.updateLog()searchFor="jmp esp"results=imm.Search( imm.Assemble (searchFor) )

    for result in results:imm.Log("Found %s at 0x%08x " % (searchFor, result), address = result)

    That wasnt too bad, was it ?

    Corelan Team - Copyright - All rights reserved. Terms Of Use are applicable to this pdf file and its contents. See https://www.corelan.be/index.php/terms-of-use 10/07/2011 - 6 / 10

    http://www.corelan.be:8800/wp-content/uploads/2010/01/image61.png
  • 7/27/2019 Starting to Write Immunity Debugger Pycommands My Cheatsheet

    7/10

    https://www.corelan.be - Page 7 / 10

    Example 2 : If you want to search for a serie of instructions (such as push esp + ret), then the instructions must be separated by \n :

    def main(args):imm.Log("Started search for push esp / ret...")imm.updateLog()searchFor="push esp\nret"results=imm.Search( imm.Assemble (searchFor) )for result in results:

    imm.Log("Found %s at 0x%08x " % (searchFor.replace('\n',' - '), result), address = result)

    Example 3 :If you want to use opcode directly (instead of instructions), then

    - the search looks a little bit different (no need to Assemble first)

    - you can disassemble the instruction at the found address using the Disasm() + getDisasm() functions

    def main(args):imm.Log("Started search for mov ebp,esp ")imm.updateLog()searchFor="\x8b\xec" #mov ebp,esp / retresults=imm.Search( searchFor )for result in results:

    opc = imm.Disasm( result )opstring=opc.getDisasm()imm.Log("Found %s at 0x%08x " % (opstring, result), address = result)

    Note : when you search through memory, it will search all process memory (loaded modules and outside loaded modules, but always limited to thememory that is used in the process). As stated earlier, searching through memory can make your CPU spike to 100%.

    Writing your own assembler

    is as easy as doing this :

    __VERSION__ = '1.0'import immlibimport getoptimport immutilsfrom immutils import *imm = immlib.Debugger()importre

    """Main application"""def main(args): if (args[0]=="assemble"): if (len(args) < 2):

    imm.Log(" Usage : !plugin1 compare instructions")imm.Log(" separate multiple instructions with #")

    else:cnt=1cmdInput=""while (cnt < len(args)):

    cmdInput=cmdInput+args[cnt]+""cnt=cnt+1

    cmdInput=cmdInput.replace("'","")cmdInput=cmdInput.replace('"','')splitter=re.compile('#')instructions=splitter.split(cmdInput)for instruct in instructions:

    try:assembled=imm.Assemble( instruct )strAssembled=""for assemOpc in assembled:

    strAssembled = strAssembled+hex(ord(assemOpc)).replace('0x', '\\x')imm.Log(" %s = %s" % (instruct,strAssembled))

    except:imm.Log(" Could not assemble %s " % instruct)continue

    (I added import re at the beginning of the script so I could use the splitter (re.compile()))

    Stepping through memory

    There may be a case where you would l ike to read memory from a given start location and get the bytes in memory. This can be done using thefollowing technique :

    if (args[0]=="readmem"): if (len(args) > 1):

    imm.Log("Reading 8 bytes of memory at %s " % args[1])cnt=0

    Corelan Team - Copyright - All rights reserved. Terms Of Use are applicable to this pdf file and its contents. See https://www.corelan.be/index.php/terms-of-use 10/07/2011 - 7 / 10

    http://www.corelan.be:8800/wp-content/uploads/2010/01/image62.png
  • 7/27/2019 Starting to Write Immunity Debugger Pycommands My Cheatsheet

    8/10

    https://www.corelan.be - Page 8 / 10

    memloc=int(args[1],16)while (cnt < 8):

    memchar = imm.readMemory(memloc+cnt,1)memchar2 = hex(ord(memchar)).replace('0x','')imm.Log("Byte %d : %s" % (cnt+1,memchar2))cnt=cnt+1

    The readMemory() method requires 2 arguments : the location where you want to read, and the amount of bytes to read.

    Registers

    Getting access to the registers is pretty straightforward too :

    regs = imm.getRegs()for reg in regs:

    imm.Log("Register %s : 0x%08X " % (reg,regs[reg]))

    SEH chain

    def main(args): if (args[0]=="sehchain"):

    thissehchain=imm.getSehChain()sehtable=imm.createTable('SEH Chain',['Address','Value'])for chainentry in thissehchain:

    sehtable.add(0,("0x%08x"%(chainentry[0]),("%08x"%(chainentry[1]))))

    Address & Module properties

    When your script is searching through memory, and it retrieves an address, there may be certain things you want to know about the address:

    - does it belong to a module and if so, which one ?

    - what is the baseaddress and size of the module ?

    - is this module compiled with safeseh or not ?

    - is this module alsr aware or not ?

    - what is the access level at a given memory location ?

    Very good questions, and all of them can be solved in your script

    Well pretend you have performed a search, and result is an element in the search results array.

    See if a an address belongs to a module

    module = imm.findModule(result)if not module: module="none"

    else: module=module[0].lower()

    Corelan Team - Copyright - All rights reserved. Terms Of Use are applicable to this pdf file and its contents. See https://www.corelan.be/index.php/terms-of-use 10/07/2011 - 8 / 10

    http://www.corelan.be:8800/wp-content/uploads/2010/01/image64.pnghttp://www.corelan.be:8800/wp-content/uploads/2010/01/image65.png
  • 7/27/2019 Starting to Write Immunity Debugger Pycommands My Cheatsheet

    9/10

    https://www.corelan.be - Page 9 / 10

    Module baseaddress and size

    modbase=module.getBaseAddress()modsize=module.getSize()modtop=modbase+modsize

    Is this module compiled with safeseh ?

    (this piece of code requires you to import struct at the beginning of the code)module = imm.findModule(result)#mod=imm.getModule(module[0])mzbase=mod.getBaseAddress()peoffset=struct.unpack('

  • 7/27/2019 Starting to Write Immunity Debugger Pycommands My Cheatsheet

    10/10

    https://www.corelan.be - Page 10 / 10

    Impact of upgrading to Immunity Debugger v1.74 and above

    Immunity plans to fix some method/attribute inconsistencies (especially with uppercase/lowercase characters in the function/method names) in anewer version of Immunity (and the Immunity libraries). As a result of that, you may have to fix your PyCommand in order to make it work with newerversions of the debugger.

    This is an overview of what you probably will need to fix :

    Current v1.73 newer versions

    .Log .log

    .Assemble .assemble

    .Disassemble .disassemble

    .Search .search

    .getMemoryPagebyAddress .getMemoryPageByAddress

    Final words

    This reference is far from complete and there is a lot more you can do with the Immunity API. I only wanted to give you a head start if you want tobuild your own PyCommands.

    If you have built your own new PyCommand, share it with others Im sure other people can benefit from your work too.

    Useful Links

    http://www.tuts4you.com/download.php?list.72

    http://www.defcon.org/images/defcon-15/dc15-presentations/dc-15-gomez.pdf

    http://www.abysssec.com/blog/2010/01/immunity-debugger-pedetect-and-the-art-of-signature-generation/

    http://beist.org/research/public/immunity1/imm_present_jff.pdf

    This entry was posted

    on Tuesday, January 26th, 2010 at 11:03 pm and is filed under 001_Security, Development, Exploit Writing Tutorials, Scripts

    You can follow any responses to this entry through the Comments (RSS) feed. You can leave a response, or trackback from your own site.

    Corelan Team - Copyright - All rights reserved. Terms Of Use are applicable to this pdf file and its contents. See https://www.corelan.be/index.php/terms-of-use 10/07/2011 - 10 / 10

    http://www.tuts4you.com/download.php?list.72http://www.defcon.org/images/defcon-15/dc15-presentations/dc-15-gomez.pdfhttp://www.abysssec.com/blog/2010/01/immunity-debugger-pedetect-and-the-art-of-signature-generation/http://beist.org/research/public/immunity1/imm_present_jff.pdfhttps://www.corelan.be/securityhttps://www.corelan.be/developmenthttps://www.corelan.be/exploit-writing-tutorialshttps://www.corelan.be/scriptshttps://www.corelan.be/index.php/comments/feed/https://www.corelan.be/index.php/2010/01/26/starting-to-write-immunity-debugger-pycommands-my-cheatsheet/trackback/https://www.corelan.be/index.php/2010/01/26/starting-to-write-immunity-debugger-pycommands-my-cheatsheet/trackback/https://www.corelan.be/index.php/comments/feed/https://www.corelan.be/scriptshttps://www.corelan.be/exploit-writing-tutorialshttps://www.corelan.be/developmenthttps://www.corelan.be/securityhttp://beist.org/research/public/immunity1/imm_present_jff.pdfhttp://www.abysssec.com/blog/2010/01/immunity-debugger-pedetect-and-the-art-of-signature-generation/http://www.defcon.org/images/defcon-15/dc15-presentations/dc-15-gomez.pdfhttp://www.tuts4you.com/download.php?list.72