Or a case study on how the knowledge of the basics of the Ancient Art of Assembly Programming can still save the day. Remember the last time you had to binary patch a program in Linux? Neither did I. But when we came across a binary-only executable doing very stupid things, with no source code in sight, no viable replacement at hand and no time or patience to code our own, binary patching didn’t sound too bad.

The world is full of bad programs, and a great deal of these are binary-only. Which makes sense — if you had the source, you’d fix it instead of piling up hacks to make it work in a minimally acceptable way. I see open source as a huge contribution to improve overall code quality, and to me it causes intense frustration to be forced to use programs that are so obviously wrong and so easily fixable, but for which you have no sources to rebuild. One of these programs was annoying our team for some time now to the point that one of us wrote a kernel module to circumvent its evil deeds. It worked, but… is there an easier way to do that? I took a spare hour to find out, and yes, a simple two-bit flip solves the problem.

I started by placing the unstriped binary under the microscope — in our case, a disassembler program — producing the excerpt of code shown below (I like Intel syntax for i386 code because I was raised on TASM, but I bet you can mentally perform the conversion if you prefer AT&T syntax). It happens that the entire bad behavior, misdetecting a piece of hardware and refusing to work, is triggered by a single function call:

.text:080507F0 B9 05 00 00 00            mov     ecx, 5
.text:080507F5 FC                        cld
.text:080507F6 F3 A6                     repe cmpsb
.text:080507F8 74 16                     jz      short loc_8050810
.text:080507FA 8B 85 E0 FE FF FF         mov     eax, [ebp+var_120]
.text:08050800 89 04 24                  mov     [esp+138h+var_138], eax
.text:08050803 E8 98 C7 00 00            call    perform_our_test
.text:08050808 85 C0                     test    eax, eax
.text:0805080A 0F 84 50 FF FF FF         jz      loc_8050760

A very easy condition to bypass. There are so many different ways we could fix it: changing the return value of the called function, forcing the zero flag to be clear at the conditional jump, overwriting the jump with NOPs, etc. The new challenge was to make it with the least amount of bytes changed. How would you fix it? My best solution changed the value of two bits in a single byte, do you have a solution with a single bit flip?

After considering different alternatives and the hack value of their implementations, I opted for a quite fun approach: note the call instruction again, E8 98 C7 00 00. What if we just change it to load some random value to the eax register, thus causing the following test to fail? The opcode for moving an immediate value to eax is… B8! So replacing this single byte leads us to:

-\-\- bla.lst     2007-08-30 21:36:18.000000000 -0300
+++ bla2.lst    2007-08-30 21:41:53.000000000 -0300
@@ -9350,7 +9350,7 @@
 .text:080507F8 74 16                    jz      short loc_8050810
 .text:080507FA 8B 85 E0 FE FF FF                mov     eax, [ebp+var_120]
 .text:08050800 89 04 24                         mov     [esp+138h+var_138], eax
-.text:08050803 E8 98 C7 00 00                   call    perform_our_test
+.text:08050803 B8 98 C7 00 00                   mov     eax, 0C798h
 .text:08050808 85 C0                            test    eax, eax
 .text:0805080A 0F 84 50 FF FF FF                jz      loc_8050760
 .text:08050810

With the fix in hands, fire up your favorite binary editor, locate the chunk of machine language we must patch and fix it:

000087E0  89 0C 24 89 5C 24 0C 89 44 24 04 E8 10 B5 FF FF ..$..$..D$..\..\..
000087F0  B9 05 00 00 00 FC F3 A6 74 16 8B 85 E0 FE FF FF ..\..\..\..t..\..\..\.
00008800  89 04 24 B8 98 C7 00 00 85 C0 0F 84 50 FF FF FF ..$..\..\..\..\.P.\..
00008810  8B 95 E0 FE FF FF 89 14 24 E8 F2 AB FF FF 8B 8D ..\..\..\..$..\..\..\.
00008820  E4 FE FF FF 89 0C 24 89 44 24 04 E8 90 B6 FF FF ..\..\..$.D$..\..\..

It was very quick and painless, and replaces a kernel module that you had to rebuild at each kernel upgrade. Granted, not as good as having the C++ source, but this is the real world and it solved the problem — a proof that, even if you’re a pure userland programmer in modern Linux. a bit of assembly language can be useful in (the not so frequent, I hope) cases like this.

No licenses were harmed in the patching of our binary.

Leave a Reply