Monday, May 26, 2014

Of Binary Bombs (part 4)

Part 3 of this series explored phase_3 of this riddle which marked the first phase that accepted more than a single correct input sequence. Here I'll continue with the next phase: sym.phase_4.


At first, some arguments are loaded onto the stack to prep for a call to sscanf but rather than an immediate string as the format string argument, an address is provided. Recall in in phase_2 that we could get a hint of the input by printing str._d_d_d_d_d_d (ps @ str._d_d_d_d_d_d) - here we need to understand the arguments to sscanf a little to know that the address 0x8049808 should contain a format string. Indeed, it does (ps @ 0x8049808 yields %d) - our input string needs to be a number. That number needs to be larger than 0.

    cmp dword [ebp-0x4], 0x0
    jg 0x8048d0e

After the input is checked, its value is pushed to the stack and control is passed to sym.func4.


The second requirement of our input is now revealed:

    cmp ebx, 0x1
    jle 0x8048cd0

For values larger than 1 sym.func4 is called recursively.

    lea eax, [ebx-0x1]
    push eax
    call sym.func4

So the input value to sym.func4 (our input, initially) is decremented by 1 and passed to the recursive call. The result of that call is saved to esi and then sym.func4 is called recursively yet again - this time after decrementing the input by 2.

    lea eax, [ebx-0x2]
    push eax
    call sym.func4

The return value of the second recursion is added to the result of the first and that sum becomes the return value of this depth of the recursive call. In C, this looks something like:

int func4 (int n) {
    if (n < 2) { return 1; }
    return func4 (n - 1) + func4 (n - 2);
}

Back in sym.phase_4 there is the following check:

    cmp eax, 0x37

The input, when fed to sym.func4, should return 55. The recursion of n-1 + n-2 is one way to calculate the Fibonacci number at index n. That, coupled with the fact that 55 is a Fibonacci number, allows us to derive the necessary input value of 9.


Next up, part 5.

Thursday, May 22, 2014

Of Binary Bombs (part 3)

In part 2 I explained both sym.read_line and the solution to sym.phase_2. Here I work through the third phase of Dr. Evil's nasty binary bomb.


I'll assume to start directly at sym.phase_3 beyond the input handling routines previously discussed.



Phase 3 starts with a call to sscanf with a format string of "%d %c %d". So we should provide a number, character, and a number. The first number should be less than or equal to 7

    cmp dword [ebp-0xc], 0x7

and is used to calculate a jump address

    mov eax, [ebp-0xc]
    jmp dword [eax*4+0x80497e8]

If we start with eax containing 0 this jumps us to a location stored at 0x80497e8. That address is 0x08048be0 - or just over the next instruction.

    mov bl, 0x71
    cmp dword [ebp-0x4], 0x309

This sets bl to 0x71 (ASCII 'q') and compares the third input to 777. If the third input is 777 control jumps to

    cmp bl, [ebp-0x5]

So, to avoid the bomb we can provide the following input: 0 q 777 and we have a valid solution.


But what about setting eax to something other than 0 to start? Let's look at the other possible jump addresses for values less than 8 but greater than 0 for the first input. I've abbreviated the code and commented what is different from the above description.

; eax == 1 - 0x08048c00
    mov bl, 0x62                ; ASCII 'b'
    cmp dword [ebp-0x4], 0xd6   ; 214

; eax == 2 - 0x08048c16
    mov bl, 0x62                ; ASCII 'b'
    cmp dword [ebp-0x4], 0x2f3  ; 755
        
; eax == 3 - 0x08048c28
    mov bl, 0x6b                ; ASCII 'k'
    cmp dword [ebp-0x4], 0xfb   ; 251
        
; eax == 4 - 0x08048c40
    mov bl, 0x6f                ; ASCII 'o'
    cmp dword [ebp-0x4], 0xa0   ; 160
  
; eax == 5 - 0x08048c52
    mov bl, 0x74                ; ASCII 't'
    cmp dword [ebp-0x4], 0x1ca  ; 458

; eax == 6 - 0x08048c64
    mov bl, 0x76                ; ASCII 'v'
    cmp dword [ebp-0x4], 0x30c  ; 780

; eax == 7 - 0x08048c76
    mov bl, 0x62                ; ASCII 'b'
    cmp dword [ebp-0x4], 0x20c  ; 524

So it looks as if there are several answers to this part of the riddle. Let's verify at least one of the others work.

Next, phase 4.

Wednesday, May 21, 2014

Of Binary Bombs (part 2)

In part 1 I worked out the setup of the binary bomb and walked through the solution to the first phase of the riddle. This post continues from there to examine phase 2.

After defusing phase 1 the program reads the next line from the user (sym.read_line) and calls sym.phase_2. In my first post I glazed over the sym.read_line with some hand-waving so I'd like to revisit that here with a little more attention. As a reminder, in radare2 you can search for a symbol while in visual mode with s (s sym.read_line).


The first thing that happens in sym.read_line (after managing the pointers and local variables) is a call to sym.skip. sym.skip has a little more to it than just this but basically determines which input line is to be read by looking at the global variable sym.num_input_strings and using that as an index into sym.input_strings (a global holding each input string in an 80-byte char array). It returns the string read in eax.

Once a line is read from the input stream the following instructions check the return value of sym.skip.

    test eax, eax
    jne 0x804925f

Only if the return value of sym.skip is zero (when null is returned) does execution not jump to 0x0804925f. I will cover later what happens in the null return case. For now, non-null returns follow this explanation.

    mov eax, [sym.num_input_strings]

At this point, we have entered 2 strings but the global counter sym.num_input_strings has only been updated once so contains the value 1.

    lea eax, [eax+eax*4]
    shl eax, 0x4
    lea edi, [eax+sym.input_strings]

The above sets eax to eax * 5, multiplies the result by 16, and loads the string at sym.input_strings+eax into edi. In this case (sym.num_input_strings == 1) this yields the second index in the sym.input_strings array (recall that each entry there is 80 bytes).

The next set of instructions calculate the length of the input string by decrementing a counter for each non-null byte in the string. The calculation uses some shortcuts for efficiency so is not directly mapped to traditional C-like counting (google 'asm string length' for details).

    mov al, 0x0
    cld
    mov ecx, 0xffffffff 
    repne scasb
    mov eax, ecx
    not eax
    lea edi, [eax-0x1]

Our input strings must fit within the 80-byte array so checking that the string (less newline) is less than 0x4f (79) bytes is done next

    cmp edi, 0x4f

Finally, the newline is replaced with a zero byte

    mov byte [edi+eax+0x804b67f], 0x0

the input string is placed in eax and the global sym.num_input_strings is incremented by 1.

At this point eax contains the input string and I can resume with the second phase of the bomb.


After the prologue and setting aside 32 bytes of local space the input string is copied to edx and the local buffer address is loaded into eax. These two arguments are pushed onto the stack and sym.read_six_numbers is invoked. To avoid making this post too long I'll skip over the detail of sym.read_six_numbers and just mention that the numbers in the input string are read into the local array via sscanf with a format string of "%d %d %d %d %d %d" (Hint #1). 

The following check verifies that the first entry in the local array has the value 1 and is the second hint to solving this phase.

    cmp dword [ebp-0x18], 0x1                

The next section of code is the meat of phase 2 and how we determine what numbers to include in our string.

    mov ebx, 0x1
    lea esi, [ebp-0x18] 
    lea eax, [ebx+0x1]  ; target of jump below (0x8048b76)
    imul eax, [esi+ebx*4-0x4] 
    cmp [esi+ebx*4], eax   
    je 0x8048b88 
    call sym.explode_bomb
    inc ebx             ; target of jump above (0x8048b88)
    cmp ebx, 0x5  
    jle 0x8048b76 

This is a loop from 1 to 5 using ebx as the counter. The initialization sets ebx to 1 and esi to the beginning of the array of converted numbers (the local array). On each iteration of the loop eax is set to ebx + 1 then multiplied by the nth entry of the local array (n is ebx - 1). The result of that is compared to the next entry in the array. In C parlance this might look like:

    int xs[] = {...}; 
    for (i = 0; i < 5; ++i)
        if ((i+1) * xs[i] != xs[i+1]) 
            FAIL();

If we know that xs[0] must be 1 we can now use the above to calculate the remainder of the array.

    x[1] = 2 * x[0] => 2
    x[2] = 3 * x[1] => 6 
    x[3] = 4 * x[2] => 24
    ...

Ultimately, our input requirements become: 1 2 6 24 120 720. This works as expected and phase 2 is now solved.


On to phase 3.

Tuesday, May 6, 2014

Of Binary Bombs (part 1)

I was recently turned on to radare2 (http://www.radare.org/y/) and, in an attempt to use-it-to-learn-it, I am going to try and solve the sample binary bomb provided as an example lab for the CMU computer systems course. I'm aiming to make my next few posts an account of trying to solve that puzzle (and learn this new tool).

Caveat lector: I've basically no experience with binary tools and assembly language so this will be deliberate and likely full of amateur mistakes.


I expect to be able to analyze this as the program would be run so I'm staring at the main entry point and assuming that there is nothing tricky happening prior to that. Load up radare2 and enter visual mode (press 'V' at the command prompt) then press 'p' until the view is one that resembles assembly instructions. Once there search for the main entry point (at the command prompt: s main). That will bring you to a screen that looks similar to:


First thing to happen in this program is to check the arguments to main to determine whether to read from stdin or from a file. This progresses from the function prologue: saving the base pointer (ebp); copy the stack pointer (esp); and reserve 20 bytes for local use (sub esp, 0x14). The program then continues to look at the value of argc and acts accordingly eventually assigning the input stream (stdin or otherwise) to sym.infile.

Without error, control flow reaches address 0x08048a30 (in my version) which is a call to sym.initialize_bomb. Radare2 allows you to set marks much like you would in vim so if you are familiar with that, set a mark here with ma and hit enter at this address to follow to sym.initialize_bomb. Alternatively, at the command prompt, search for the function symbol with s sym.initialize_bomb. In either case you should see the following:


Here, the program simply sets up to handle SIGINT with the function sig_handler and returns. If you are using marks you can return to the previous address with 'a.

The program continues with a couple of generic prompts, proceeds to read a line of input (which does some internal bookkeeping), and jumps to phase 1 (sys.phase_1). Place a local mark (ma) and jump to the first phase code: we are now ready to start the actual process of solving Dr. Evil's nasty riddle.


Phase 1 is intended to be easy. The first line of input provided to the program is copied to eax, and then two arguments are set up: a global string (at address 0x080497c0) and eax. These arguments are sent to strings_not_equal which returns 0 if the two arguments are the same length and contain the same strings. The check test eax, eax (true when eax is 0) avoids exploding the bomb so we want eax (the first input string) to be the same as whatever is in 0x080497c0.

The radare2 command to print memory as a string is ps. Entering command mode (:) and typing ps @ 0x080497c0 yields: 'Public speaking is very easy.' (the period is significant). Entering that provides:


Next, I'll tackle phase 2.

Saturday, March 29, 2014

Something's not write

This is a recount of a recent high pressure debugging session I helped with. As with most difficult debugging sessions it ended up being educational and fun. I'm posting here mostly for posterity and to enlighten the future me.

Two coworkers and I were called in to support a project (not our own) that was going to demo in two days but was experiencing a variety of system-halting problems. This was a system with which I had almost no experience and to complicate matters, since demo was so close, there was no chance we could take down the system to investigate. Our instructions were to engage the production system while the entire user base was attempting to squeeze in last minute testing. Time to go spelunking.

The tasks were broken up between the three of us and my focus was a master-slave setup supporting system automation through a web interface. The machines were running FreeBSD with code written in a variety of languages including C, Python, Perl, and PHP all tied together and coordinated with databases and XML-RPC. The symptom I had to go on was that every once in a while when trying to create a data capture (a file that could ultimately be several Gbs) the resulting file would end up being 0 bytes on disk representing a complete loss of data for that day. There was also a message in the log file that appeared when the data capture failed: 'write failed: Permission Denied'. The strange thing was that this message would appear approximately half way through the transfer so by that time the file had to exist and would have already been written to.

I started by looking at the source code for the offending process. The error was being reported as a result of a failed write call. Strangely, 'Permission Denied' (EACCES) is not listed as a valid error in the write man pages. I wanted to avoid tearing apart the build system to recompile a single source file so I started investigating ways other than editing source to try and figure out why this was happening.

I'm quite unfamiliar with FreeBSD but on Linux I would use something like strace to try an establish a baseline of what is happening. Luckily, FreeBSD has truss which provided close to the same functionality. Unfortunately, the process I wanted to examine was exec'd from deep within some Perl code that kept failing when I tried to prefix the command with truss -o truss.out. Since it was a long running process I ended up attaching to the running process with truss -o truss.out -p 12345.

Now I had a running process that I could monitor and all I needed was to trigger the effect - enter: the heisenbug. Several hours of creating large files without issue forced me to look elsewhere.

I started collecting what information I could about the system and df showed that the data capture file was being written to an NFS mounted location. Since nothing else was out of the ordinary I tried to understand why a write to an NFS file would fail. My first instinct was that there was an overload so I tried several parallel writes of substantial size without issues - no dice. Setting up a tcpdump between the master and slave would have also been tough since all communication throughout the system (including NFS) was handled between these two machines.

Since the process I was monitoring was writing to the master machine and the local code seemed fine I tried digging into why mounted locations would experience random interruptions. I ended up finding a thread on a FreeBSD mailing list that described this exact problem. According to the dialog there, mountd could cause this problem if it received a SIGHUP while the transfer was taking place. I ran a test where I tried transferring a large file while sitting on the destination machine executing kill -s HUP `cat /var/run/mountd.pid`. It took three attempts but I finally triggered the bug - it seems to be a race condition even when doing the very thing that will cause the error.

The only thing left was to determine why mountd might be receiving a SIGHUP. This would happen when calling mount by hand and, as it turns out, some of the tests in the system mount new locations as part of spinning up a test environment. So in the very rare case of creating a data capture while another test mounts a new location and that mount happens to be at the precice moment to interfere with network writes to NFS you get issues. In the general operation this might not ever occur; however, given the load on the system during prep for demo it became a more noticeable (and repeatable) problem.

Considering the the mailing list thread was discussing the issue in FreeBSD 8.3 and this system is still seeing it in 9.1, the ultimate solution was to modify the C code to handle the EACESS case on error from write. As to why the man pages for write do not list EACESS as an error condition... they probably aren't allowed.

Thursday, February 27, 2014

Vertical Histogram

In the process of munging data for my current project I came across the need to compare (visually) the difference between two modes within the same dataset. I was using a simple scatterplot and setting the alpha in the hopes that the over-plotting would indicate which was the major mode. Unfortunately, the size of the data overwhelmed this approach.

I only wanted to use a single image and it was important that I keep the scatterplot to show other features of the data. I started looking for a way to combine a histogram (rotated 90 degrees) with the scatterplot to help describe the density within the plot. A quick search for how to do this in R turned up empty so I decided to implement my own version of such a plot.

Certainly, there are other ways to describe the features that I am trying to present here but in this particular case the following code worked out nicely. Hopefully it proves useful to others as well.


plot.vertical.hist <- function(data,breaks=500) {

    agg <- aggregate(data$Y, by=list(xs=data$X), FUN=mean)
    hs <- hist(agg$x / 10000, breaks=breaks, plot=FALSE)

    old.par <- par(no.readonly=TRUE)
    mar.default <- par('mar')
    mar.left <- mar.default
    mar.right <- mar.default
    mar.left[4] <- 0
    mar.right[2] <- 0

    # Main plot 
    par (fig=c(0,0.8,0,1.0), mar=mar.left)
    plot (agg$xs, agg$x / 10000,
          xlab="X", ylab="Y",
          main="Vertical Histogram Side Plot",
          pch=19, col=rgb(0.5,0.5,0.5,alpha=0.5))
    grid ()

    # Vertical histogram of the same data
    par (fig=c(0.8,1.0,0.0,1.0), mar=mar.right, new=TRUE)
    plot (NA, type='n', axes=FALSE, yaxt='n',
          xlab='Frequency', ylab=NA, main=NA,
          xlim=c(0,max(hs$counts)),
          ylim=c(1,length(hs$counts)))
    axis (1)
    arrows(rep(0,length(hs$counts)),1:length(hs$counts),
           hs$counts,1:length(hs$counts),
           length=0,angle=0)

    par(old.par)
    invisible ()
}

Results look similar to the following:



Initially, I experimented with rug or barplot(..., horiz=TRUE). Unfortunately, rug isn't available on the left or right side and would suffer from the same problem that the alpha settings did and I was unable to get the alignment worked out when using barplot.


Friday, January 24, 2014

Probably not the Best

Probabilities are tricky - for me, at least. The fact that I can not use them like other numbers although they are sometimes represented as floating point numbers often trips me up.

Case in point: I'm designing an approach to a packet forwarding problem that includes calculating the probability of a packet making it through a portion of a network. An example that helps describe the solution is in the following diagram:



Where \(I\) is the ingress node, \(E\) is the egress node and the other nodes are represented by the probability that a given packet will be forwarded (\(P_{f}\)) to \(E\) when received from \(I\).

For any given path \(I\) to \(E\) through a single node, the probability the packet will be delivered (\(P(I \Rightarrow E)\)) is equal to the probability that the intermediary node will forward the packet (ignoring all other details of the medium).

What I [incorrectly] claimed in my presentation of the solution was: if you use all nodes simultaneously and discard duplicates at \(E\), the probability of a packet successfully passing from \(I\) to \(E\) then becomes \(max(N)\). Where \(N\) is the set of nodes between \(I\) and \(E\). Considering the example above that equation results in \(P(I \Rightarrow E) = 0.82\). This is better than choosing the node with \(P_{f} = 0.21\) but it is not the true probability of a packet getting through. 

This design made several rounds of internal review where this error was not noticed; perhaps there is some solace to be had in knowing I am not the only one who is tripped up by this.

When it finally came time to implement this I was discussing the solution with another engineer and he quickly pointed out that my calculation was incorrect. In fact, my estimation was much lower than the actual likelihood of getting a packet through. The true probability of not getting a packet through is the probability of all intermediate nodes dropping the packet.

\[\prod_{i=0}^{n} (1 - P_{f_{n}})\]

The ultimate effect of my proposed approach is the probability of not having that happen or:

\[1 - (\prod_{i=0}^{n} (1 - P_{f_{n}}))\]

For this specific example that is:

1 - ((1 - 0.76) * (1 - 0.21) * (1 - 0.82) * (1 - 0.33))

or 0.977. A much higher value than my originally proposed 0.82.