📜 ⬆️ ⬇️

We rule bug without source codes

image

In the previous article, we discussed how reverse engineering can help in gaining any advantages over other users. Today we will talk about one more application of reverse engineering - fixing bugs in the absence of application source codes. The reasons for doing such things can be the whole sea - the development of the program has long been abandoned, and the author has not provided the public with it. The development is carried out in a completely different way, and the authors do not care about your bug / etc, but they are united by a common The goal is to fix broken functionality that constantly annoys you.

Well, more to the point. There is such a widely known program in narrow circles called “Govorilka” . As the author explains, this is nothing more than a “voice reading program”. In fact, the way it is. With the help of it, a lot of popular and not-so-wide videos were distributed throughout the network. The program has a console version called “Govorilka_cp” , which is convenient to call from your own applications, which, in fact, I did in one of my projects.
')
Unfortunately, in the process of distributing my software, a rather strange moment was discovered - on some machines a talker falls absolutely on any phrases, and the fall was caused not by my interaction with this program, but by the talker itself. In an attempt to find out as much detail as possible about the error that was occurring, I discovered that on two seemingly completely identical systems, the talker behaves in the opposite way — on the one it works stably without any errors, and on the other it falls on every report sent to it. as an argument to the phrase. This situation was pretty much bothering me, and I decided to deal with this problem at all costs.

Considering that the speaker has not been updated for several years, and the author himself left this “message” on his website

image

, I realized that I had no hope in anyone, and I would have to solve the problem myself.

How was the process, and what came of it, read under the cut (carefully, a lot of screenshots ).

Before loading the speaker to OllyDbg , let's see if it is protected by some protector. We take in the hands of DiE and see the following picture:

image

Judging by his conclusion, the program is packed with ASPack . For credibility, we will use another analyzer - PEiD :

image

Hoping that two analyzers cannot be wrong at the same time, we come to the conclusion that the talker is really covered with ASPack. Well, nothing, let's take it off.

Getting rid of an executable file from ASPack can be divided into three main stages:

We begin, of course, with the OEP search. One of the easiest ways to find the original entry point is to put a hardware breakpoint on ESP-4 , because most of the packers restore the stack after their work. We start the talker in OllyDbg, open the Command Line with Alt-F1 (the window of the standard plug-in that comes with the debugger itself), enter the command hr esp-4 , press F9 and stop at this place:

image

Now we need to go to the nearest return using Ctrl-F9, press F8 and ... find ourselves in OEP, which in this case is located at 0x0045A210 :

image

It's time to take a dump. Download and install a plugin for OllyDbg called OllyDump , restart debugging with Ctrl-F2, stop at OEP again and select the menu item “Dump debugged process” located in Plugins -> OllyDump:

image

Press the "Dump" button, select the name of the output executable file and ... we get this when trying to start it:

image

It means that something is wrong with imports. Nothing, restore them manually.

Dump the talker process again, but this time we remove the checkmark from the Rebuild Import Checkbox. Download ImpREC , select the talk process from the list, enter the address 5A210 in the field for OEP - 0x0045A210 - image base ( 0x400000 ) = 0x5A210 and click on the "IAT AutoSearch" button:

image

We do, as they said, and see the following picture:

image

Obviously, this is not normal. We try to specify the RVA and size, which the program itself offered us to use in case of failure, and click on the “Get Imports” button again:

image

Something is clearly not going as expected. Let's try to find the boundaries of IAT manually. We are looking for a call to any WinAPI function in the disassembled listing. For example, this one:

image

Jump on it (left click -> Enter) and see where all the JMPs are going:

image

Go to any of the addresses specified in this place (right-click -> Follow in Dump -> Memory address), select the appropriate view (right-click on the Memory Dump window -> Long -> Address) and see the following:

image

We run our eyes to the beginning of the list of addresses of functions:

image

Double-click on ntdll.RtlDeleteCriticalSection and look for the end of the list:

image

Specify the start address of IAT ( 0005F168 ) and size ( 0000066C ), again click on the “Get Imports” button and get the following result:

image

Already better, but still there are invalid imports, and the strangest thing is that we seem to have done everything correctly ... Let's use another application for IAT rebuilding - Scylla :

image

What is going on? And let's try to do the same thing on another system - for example, in Windows 7 x32 (before this, experiments were conducted in Windows 8 x64).

In a miraculous way, we got valid imports:

image

Honestly, I'm not sure that this is a bug of ImpRec and Scylla, a feature of Windows 8 x64 or something else, but the main thing is that we restored the IAT. More precisely, for this we need to click on the “Fix Dump” button, select the dump made earlier and try to run the resulting executable file. Yes, when launching a speaker without arguments, it works as it should be, but in the case of the transfer of any phrases for dubbing, it falls, as in the packed version.

So ASPack is filmed. What's next? And then, in fact, we have to deal with a bug arising on some computers.

First of all, let's take a look at the standard Windows window, which reports that the application is more beautiful:

image

You should notice that Exception Offset is 0x000591D4 . We load the govorilka into OllyDbg, put the software breakpoint on this address (more precisely, on the image base + 0x000591D4 - in my case it is 0x004591D4 ), press F9 and we get the following:

image

As you can see, the value of the ESI register at the time of the read operation at ESI + 38 is zero, which, of course, causes the application to crash. Two instructions above noticeably that the value in ESI falls from the EAX register. The value of the EAX register before this procedure does not change, so we look at the Call Stack, from where we were called:

image

Jump there (right click -> Show Call) and see that just before the procedure call is the command MOV EAX, DWORD PTR DS: [45EC5C] :

image

As can be seen from the previous screenshot, at the address 0x45EC5C is also zero. We put a hardlock breakpoint on the record at this address (right click -> Breakpoint -> Hardware, on write -> Dword), run the application again and find that no entry is made at this address. Let's analyze how an application behaves if it is successful in another system. The entry at 0x45EC5C in this case does occur:

image

, with the result that ESI + 38 gives some meaningful meaning. Let's find out how we got to this place. Call Stack at the time of execution of this instruction is empty, so I had the impression that this is the main procedure of the program. To understand exactly where the application began to behave differently, let's run Trace over (Ctrl-F12) on both systems (where the application crashes and where it works).

In the case of a system where the application is constantly falling, it reached a bang on access to ESI + 38 , where it fell. The last line of code in the trace log was CALL at 0x004597C8 :

image

while in the system where the application is working normally, after calling this procedure ( 0x004597C8 ), other instructions are executed, on one of which the hardware breakpoint is triggered at the address 0x45EC5C :

image

Perhaps something goes wrong in the procedure 0x004597C8 . We put a software breakpoint on its call ( 0x0045AD5B ) and find that installing it leads to the fact that on the system where everything worked fine, the hardware breakpoint does not work at the address 0x45EC5C , as a result of which the application crashes, as in the case another system. With hardware breyk to perform everything is the same.

It was experimentally found that installing breakpoints on several preceding 0x0045AD5B instructions leads to the same result. Install the breakpoint with the subsequent normal operation of the application was obtained only on this instruction:

image

Perhaps, by installing breakpoints in these places, we interfered with the measurement of time, which is exactly what is done with the help of calls to the GetTickCount function.

We start Trace into (Ctrl-F11), starting with this instruction, and we find out that the differences in execution start from this place:

;     ,    004597EC Main MOV EAX,DWORD PTR DS:[45EC98] ; EAX=F2B47801 004597F1 Main ADD EAX,64 ; EAX=F2B47865 004597F4 Main CDQ ; EDX=FFFFFFFF 004597F5 Main CMP EDX,DWORD PTR SS:[ESP+4] 004597F9 Main JNZ SHORT Govorilk.00459807 00459807 Main POP EDX ; EDX=F2B47802 

 ;     ,     004597EC Main MOV EAX,DWORD PTR DS:[45EC98] 004597F1 Main ADD EAX,64 ; EAX=064A1501 004597F4 Main CDQ 004597F5 Main CMP EDX,DWORD PTR SS:[ESP+4] 004597F9 Main JNZ SHORT Govorilk.00459807 004597FB Main CMP EAX,DWORD PTR SS:[ESP] 

Hence, it is noticeable that the result of executing the instruction CMP EDX, DWORD PTR SS: [ESP + 4] influences the further work of the procedure. In the case of the system where it works, the flag register flag Z was set at the time of execution of this instruction, while in the case of the system where the application crashes, this flag was not set:

image

Let's see what affects it. So, what is happening here?


Putting the hardware breakpoint on the record at 0x45EC98 , you can see that the result of the GetTickCount function is also placed there :

image

By the way, why does GetTickCount return so much value? After all, the system was turned off by me even this morning. In fact, hybrid boot and shutdown play a role here in Windows 8. You can read more about it, for example, here .

But back to the main topic of our discussion. What is the problem then? Let's take a closer look at the contents of general registers in this piece of code on both systems. You should notice that after executing the CDQ instruction on the system where the application is working correctly, the EDX register takes the value 0x0 , and in the system where the program crashes, 0xFFFFFFFF , as can be seen, for example, in the previous screenshot. Carefully read the description of the instructions CDQ :

Converts a signed word in EDX: EAX by EAX through EDX

And now let's take a look at the signature of the GetTickCount function:

 DWORD WINAPI GetTickCount(void); 

As you can see, it returns a DWORD that is “expanded” in an unsigned long, i.e. unsigned type

I don’t know if the bug is a developer or compiler, but the author obviously didn’t count on such behavior, because in the current situation, the talk will fall on systems that run longer than 24.8 and less than 49.7 days. Why so? The lower limit is determined by the maximum value that can fit in the signed 4-byte variable - 2147483647:

2147483647/1000/60/60/24 = 24.8551348032

The upper bound is determined by the GetTickCount function itself , which is described in the documentation (in fact, it is determined by the maximum value that can fit into an unsigned 4-byte variable):

The elapsed time is stored as a DWORD value. Therefore, the system will run continuously for 49.7 days.

Well, it's time to fix this assumption. One of the ways to correct the problem is to somehow “replace” the call to the GetTickCount function with our code, in which we will “reduce” the value returned from it so that it fits into a significant 4-byte variable.

There are several options for solving this problem, but let's write a code cave. We are looking for free space (the easiest way is to find it at the “end” of the CPU window) and put the following code there:

 ;        0045BAAA 60 PUSHAD ;       0045BAAB 9C PUSHFD ;   EIP 0045BAAC E8 00000000 CALL Govorilk.0045BAB1 0045BAB1 5E POP ESI ;   GetTickCount 0045BAB2 FF96 2F380000 CALL DWORD PTR DS:[ESI+382F] ;        ESP-4 0045BAB8 894424 FC MOV DWORD PTR SS:[ESP-4],EAX ;     0045BABC 9D POPFD ;      0045BABD 61 POPAD ;    EAX    GetTickCount 0045BABE 8B4424 D8 MOV EAX,DWORD PTR SS:[ESP-28] ;    "AND"  ,    EAX 0045BAC2 25 FFFFFF0F AND EAX,0FFFFFFF 0045BAC7 C3 RETN 

The value of 382F was obtained by calculating the difference between the address in the IAT, which stores the address of the GetTickCount function ( 0x0045F2E0 ), and the current address ( 0x0045BAB1 ).

To get the value of the EIP register directly will not work - it can not be accessed either for reading or writing.

Now go to the place where JMP is implemented on the function GetTickCount

image

and replace the address to which the JMP jumps to the start address of our code cave (in my case it is 0x0045BAAA ):

image

Save the modified executable file (right-click on the CPU window -> Copy to executable -> All modifications -> Copy all -> right-click on the appeared window -> Save file) and check its performance. Yes, it does work, but when you start talking, the UAC window, familiar to almost all Windows users, is now displayed. Why this is happening can be read, for example, here , and to solve the problem in our case, it is enough just to give our executable file the original name - “Govorilka_cp.exe”.

Afterword


As you can see, reverse engineering can be used not only for cheating or searching for vulnerabilities, but also for quite useful purposes. Try to correct the problems, and not to score on them - it is quite possible that in the process of correcting the next mistake you will discover something new. And I, in turn, again hope that the article was useful to someone.

I also express my deep gratitude to the person with the nickname tihiy_grom - without him this article simply would not exist.

Source: https://habr.com/ru/post/257591/


All Articles