Previously, I implemented a function to skip the ReadFile call and stuff arbitrary passwords into the challenge binary's input stream. By experimenting with this, I discovered a potential tell that might allow me to brute force the password by counting the number of times a particular instruction was executed. To make use of this, I needed to execute the binary several times with different passwords (aaaa..., abaa..., acaa...), moving on to the next character each time an additional sahf instruction was executed.
To do this, I knew I would first need to figure out which characters are valid for email addresses. I settled on a variant of the information available from a stackoverflow inquiry:
g_valid = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.#-_~!$&\'()*+,;=:%{|}\\/?@'
The next step was to initialize a buffer that would serve as my brute force password. How long could the password be? Based on the length comparison at the start of the password processing function...
It looked as if the program was seeking a 37-byte password. So, I whipped one up:
In order to move through candidate passwords, I iterated through valid characters for each character position of the brute force password:
To measure my progress, I executed the binary with one breakpoint set to stuff the brute force password into the program's input buffer and the other breakpoint set to count sahf instructions:
And finally, I compared the number of sahf breakpoints hit in each successive occurrence against the previous maximum. If an additional breakpoint was hit, I advanced to the next character position in the brute force password and began working on that character. The script in its entirety looked something like this:
The result was a little bit like a cinematic depiction of a password being brute forced, because the characters kept rolling by and locking in as the attack progressed:
In the screenshot above, you can see the output of the CTF binary (very_success.exe) interspersed with the script indicating what password was being tried (e.g. "Trying a_Little_b1t_baaaa...").
Using vtrace made this an incredibly cool learning experience, but it wasn't without its pitfalls. For instance, I was really excited when I found this snippet in the help for the Trace class:
However, when I tried using this, I was disappointed to learn that I was out of luck:
It could be that running a 32-bit binary in a WoW64 environment causes this condition, but I didn't have time to investigate, so I just went with executing the program over and over and over again, which worked fine.
There was also a flaw in my own process. I noticed that at various character positions, my assumptions broke down and the brute forcer ran off into the weeds. I suspect this was a function of the binary itself and not of the instrumentation tool (vtrace). With a little guesswork and some script adjustment, I was able to resume from various points in the process and pull the remainder of the password out of the binary:
After I submitted the flag, FLARE sent me the next binary and I celebrated by abandoning the CTF altogether, as I ultimately knew might happen due to my busy life. But I was glad I took the approach of figuring out how to create a scalable and repeatable instrumentation process for analyzing and abusing binaries. There are other tools out there, but vtrace is unique in that it doesn't require you to compile a DLL, and it allows you to make use of Python to instrument applications. Very useful!
It looked as if the program was seeking a 37-byte password. So, I whipped one up:
g_pw = list('a' * 37)
In order to move through candidate passwords, I iterated through valid characters for each character position of the brute force password:
for pos in range(0,37): for c in g_valid: g_pw[pos] = c
To measure my progress, I executed the binary with one breakpoint set to stuff the brute force password into the program's input buffer and the other breakpoint set to count sahf instructions:
trace = vtrace.getTrace() trace.execute(sys.argv[1]) trace.addBreakpoint(MyBreakpoint(0x400000 + 0x1046, skipread)) trace.addBreakpoint(MyBreakpoint(0x400000 + 0x10ae, count_iters)) trace.setMode("RunForever", True) trace.run()
And finally, I compared the number of sahf breakpoints hit in each successive occurrence against the previous maximum. If an additional breakpoint was hit, I advanced to the next character position in the brute force password and began working on that character. The script in its entirety looked something like this:
1 # coding: utf-8 2 import vtrace 3 import vdb 4 import sys 5 import struct 6 7 # Offsets for d88dafdaefe27e7083ef16d241187d31 *very_success.exe: 8 # cs:0x4010ae <-- sahf instruction: single iteration of password character scan 9 # baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - breakpoint hits once 10 # aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - breakpoint hits twice 11 # Trying brute force by counting breakpoint hits 12 # cs:0x401046 <-- call kernel32!ReadFile, len at ebp-4 13 # cs:0x40104c <-- return from kernel32!ReadFile, len at ebp-4 14 # ds:0x402159 <-- gPasswordFromCONIN$[] 15 16 # Props to: http://www.limited-entropy.com/stuff/drmless.py.txt 17 class MyBreakpoint(vtrace.Breakpoint): 18 def __init__(self, address, callback): 19 vtrace.Breakpoint.__init__(self, address) 20 self.address = address 21 self._cb = callback 22 23 def notify(self, event, trace): 24 self._cb(trace) 25 26 # Count of times sahf instruction was encountered in character scan loop 27 g_bp_count = 0 28 29 # Count how many times sahf instruction was encountered in character scan loop 30 def count_iters(trace): 31 global g_bp_count 32 g_bp_count = g_bp_count + 1 33 34 # Skip past ReadFile() call and tell the program that it received the bytes in 35 # the array g_pw 36 def skipread(trace): 37 trace.setProgramCounter(0x400000 + 0x104c) 38 print "\nTrying " + ''.join(g_pw) 39 tmp = ''.join(g_pw) 40 trace.writeMemory(0x400000 + 0x2159, tmp) 41 trace.writeMemory(trace.parseExpression("rbp-4 & 0xffffffff"), struct.pack("@i", 37)) 42 43 if len(sys.argv) != 2: 44 print "Usage: test.py filename" 45 sys.exit(1) 46 47 # http://stackoverflow.com/questions/2049502/what-characters-are-allowed-in-email-address 48 g_valid = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.#-_~!$&\'()*+,;=:%{|}\\/?@' 49 50 g_pw = list('a' * 37) 51 g_pw.append("\0") 52 g_breaks = 0 53 while True: 54 for pos in range(0,37): 55 for c in g_valid: 56 g_pw[pos] = c 57 g_bp_count = 0 58 59 trace = vtrace.getTrace() 60 trace.execute(sys.argv[1]) 61 trace.addBreakpoint(MyBreakpoint(0x400000 + 0x1046, skipread)) 62 trace.addBreakpoint(MyBreakpoint(0x400000 + 0x10ae, count_iters)) 63 trace.setMode("RunForever", True) 64 trace.run() 65 print "Breaks " + str(g_breaks) 66 67 if (g_bp_count > g_breaks): 68 g_breaks = g_bp_count 69 break
The result was a little bit like a cinematic depiction of a password being brute forced, because the characters kept rolling by and locking in as the attack progressed:
Very Success! |
Using vtrace made this an incredibly cool learning experience, but it wasn't without its pitfalls. For instance, I was really excited when I found this snippet in the help for the Trace class:
call(self, address, args, convention=None) Setup the "stack" and call the target address with the following arguments. If the argument is a string or a buffer, copy that into memory and hand in the argument.
However, when I tried using this, I was disappointed to learn that I was out of luck:
Well, crap. |
It could be that running a 32-bit binary in a WoW64 environment causes this condition, but I didn't have time to investigate, so I just went with executing the program over and over and over again, which worked fine.
There was also a flaw in my own process. I noticed that at various character positions, my assumptions broke down and the brute forcer ran off into the weeds. I suspect this was a function of the binary itself and not of the instrumentation tool (vtrace). With a little guesswork and some script adjustment, I was able to resume from various points in the process and pull the remainder of the password out of the binary:
a_Little_b1t_harder_plez@flare-on.com
After I submitted the flag, FLARE sent me the next binary and I celebrated by abandoning the CTF altogether, as I ultimately knew might happen due to my busy life. But I was glad I took the approach of figuring out how to create a scalable and repeatable instrumentation process for analyzing and abusing binaries. There are other tools out there, but vtrace is unique in that it doesn't require you to compile a DLL, and it allows you to make use of Python to instrument applications. Very useful!
No comments:
Post a Comment