I would like to start learning reverse engineering. So I decided to start simple. I created this simple program:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
printf ("Hello World!\n");
system("PAUSE");
return 0;
}
And I dissembled it in Ollydbg. So I wanted to try and change the printf to "World Hello". But I don't know what to do now. Can you guide me, or at least tell me what I should theoretically do?
The Reverser allows you to reverse engineer compilable C code to a model, which you may want to do for the following reasons: To view the structure of the C code in Modeler. To develop the C code further in Modeler before regenerating the code. To move the C code to another platform, such as C++ or Java.
In order to perform reverse engineering, you need to combine skills in disassembling, network monitoring, debugging, API integration, several program languages, compilers, etc. You also have to be very careful when reversing software in order not to break copyright laws or harm your system.
Reverse-engineering is often used to create replacement parts when the original parts for legacy equipment are no longer available. Reverse-engineering of computer parts is also done to enhance security. For example, Google's Project Zero identified vulnerabilities in microprocessors using reverse-engineering.
When applied to software development, reverse engineering usually means using a tool called a decompiler to translate machine code into a programming language opens in new window like Java or C#, so that a developer can study the code and learn how it works.
In this case you'd need to edit the character string that is passed to printf
, but first we need to get its address. looking ad the code fro the call to printf
, we will see something along the lines of this:
PUSH mymodule.1234567
CALL printf
ADD ESP,4
so if we go to the address 0x1234567
, via ctrl + g, we would see:
01234567 48 65 6C 6C 6F 20 57 6F 72 6C 64 0A 00 00 00 00 Hello World.....
so now you can edit that string to whatever you want, so long as you don't overflow the space available and you keep the null terminator.
Saving the changes depends on how you loaded the binary (either by attaching or just cold viewing), the easiest way is via cold viewing (using olly purely as a disassembler/assembler). Its access via view -> file
then right-clicking and choosing disassemble
from the context menu. In this mode saving is done by right clicking and selecting save file
from the context menu .
In debugger mode (aka when you attach), saving is done using right-click, and selecting one of the options from the copy to executable
context menu option.
If you are debugging GCC generated code, it generally avoids generating PUSH
's and favours putting the variables directly into the stack, using MOV [ESP+c],c/r/m
. Compiling your example with GCC, you'd see code similar to (for main
):
00401AFC /$ PUSH EBP
00401AFD |. MOV EBP,ESP
00401AFF |. AND ESP,FFFFFFF0
00401B02 |. SUB ESP,10
00401B05 |. CALL GCCOllyT.0040182C
00401B0A |. MOV DWORD PTR SS:[ESP],GCCOllyT.00403024 ; ||ASCII "Hello World!"
00401B11 |. CALL <JMP.&msvcrt.puts> ; |\puts
00401B16 |. MOV DWORD PTR SS:[ESP],GCCOllyT.00403031 ; |ASCII "PAUSE"
00401B1D |. CALL <JMP.&msvcrt.system> ; \system
00401B22 |. XOR EAX,EAX
00401B24 |. LEAVE
00401B25 \. RETN
Its important here to notice that GCC optimized the call to printf
into a call to puts
. In a case like this where you know the string you are looking for, you can use olly in debugger mode, right-click and select search for -> all referenced text strings
, then simple select your desired string from the list to find the code using it, or follow its address to find its .data
section entry so you can alter it. A longer way to find it is to use the binary search available from the right-click context menu, but this generally is a waste for text strings.
And to cover all bases, lets assume we needed to get to the code from the entry point. If we where to navigate to the code from the module entry point, we would follow the chain like so:
GCCOllyT.<ModuleEntryPoint> 0> $ >PUSH EBP
0040126D . >MOV EBP,ESP
0040126F . >SUB ESP,18
00401272 . >MOV DWORD PTR SS:[ESP],1
00401279 . >CALL DWORD PTR DS:[<&msvcrt.__set_app_type>; msvcrt.__set_app_type
0040127F . >CALL GCCOllyT.00401000
00401284 . >PUSH EBP
00401285 . >MOV EBP,ESP
00401287 . >SUB ESP,18
0040128A . >MOV DWORD PTR SS:[ESP],2
00401291 . >CALL DWORD PTR DS:[<&msvcrt.__set_app_type>; msvcrt.__set_app_type
00401297 . >CALL GCCOllyT.00401000
0040129C $ >PUSH EBP
0040129D . >MOV EBP,ESP
0040129F . >SUB ESP,8
004012A2 . >MOV EAX,DWORD PTR DS:[<&msvcrt.atexit>]
004012A7 . >LEAVE
004012A8 . >JMP EAX
from here we see the only viable call as GCCOllyT.00401000
, following that, we end up here (this is the GCC mainCRTstartup
):
00401000 /$ >PUSH EBP
00401001 |. >MOV EBP,ESP
00401003 |. >PUSH EBX
00401004 |. >SUB ESP,34
00401007 |. >MOV EAX,DWORD PTR DS:[403038]
0040100C |. >TEST EAX,EAX
0040100E |. >JE SHORT GCCOllyT.0040102C
00401010 |. >MOV DWORD PTR SS:[ESP+8],0
00401018 |. >MOV DWORD PTR SS:[ESP+4],2
00401020 |. >MOV DWORD PTR SS:[ESP],0
00401027 |. >CALL EAX
00401029 |. >SUB ESP,0C
0040102C |> >MOV DWORD PTR SS:[ESP],GCCOllyT.00401110 ; |
00401033 |. >CALL <JMP.&KERNEL32.SetUnhandledExceptionFilter> ; \SetUnhandledExceptionFilter
00401038 |. >PUSH EAX
00401039 |. >CALL GCCOllyT.004013CC
0040103E |. >CALL GCCOllyT.004014AC
00401043 |. >MOV DWORD PTR SS:[EBP-10],0
0040104A |. >LEA EAX,DWORD PTR SS:[EBP-10]
0040104D |. >MOV DWORD PTR SS:[ESP+10],EAX
00401051 |. >MOV EAX,DWORD PTR DS:[402000]
00401056 |. >MOV DWORD PTR SS:[ESP+C],EAX
0040105A |. >LEA EAX,DWORD PTR SS:[EBP-C]
0040105D |. >MOV DWORD PTR SS:[ESP+8],EAX
00401061 |. >MOV DWORD PTR SS:[ESP+4],GCCOllyT.00404004
00401069 |. >MOV DWORD PTR SS:[ESP],GCCOllyT.00404000
00401070 |. >CALL <JMP.&msvcrt.__getmainargs>
00401075 |. >MOV EAX,DWORD PTR DS:[404018]
0040107A |. >TEST EAX,EAX
0040107C |. >JNZ SHORT GCCOllyT.004010C8
0040107E |> >CALL <JMP.&msvcrt.__p__fmode>
00401083 |. >MOV EDX,DWORD PTR DS:[402004]
00401089 |. >MOV DWORD PTR DS:[EAX],EDX
0040108B |. >CALL GCCOllyT.004015E4
00401090 |. >AND ESP,FFFFFFF0
00401093 |. >CALL GCCOllyT.0040182C
00401098 |. >CALL <JMP.&msvcrt.__p__environ>
0040109D |. >MOV EAX,DWORD PTR DS:[EAX]
0040109F |. >MOV DWORD PTR SS:[ESP+8],EAX
004010A3 |. >MOV EAX,DWORD PTR DS:[404004]
004010A8 |. >MOV DWORD PTR SS:[ESP+4],EAX
004010AC |. >MOV EAX,DWORD PTR DS:[404000]
004010B1 |. >MOV DWORD PTR SS:[ESP],EAX
004010B4 |. >CALL GCCOllyT.00401AFC
004010B9 |. >MOV EBX,EAX ; |
004010BB |. >CALL <JMP.&msvcrt._cexit> ; |[msvcrt._cexit
004010C0 |. >MOV DWORD PTR SS:[ESP],EBX ; |
004010C3 |. >CALL <JMP.&KERNEL32.ExitProcess> ; \ExitProcess
004010C8 |> >MOV DWORD PTR DS:[402004],EAX ; |||
004010CD |. >MOV DWORD PTR SS:[ESP+4],EAX ; |||
004010D1 |. >MOV EBX,DWORD PTR DS:[<&msvcrt._iob>] ; |||msvcrt._iob
004010D7 |. >MOV EAX,DWORD PTR DS:[EBX+10] ; |||
004010DA |. >MOV DWORD PTR SS:[ESP],EAX ; |||
004010DD |. >CALL <JMP.&msvcrt._setmode> ; ||\_setmode
004010E2 |. >MOV EAX,DWORD PTR DS:[404018] ; ||
004010E7 |. >MOV DWORD PTR SS:[ESP+4],EAX ; ||
004010EB |. >MOV EAX,DWORD PTR DS:[EBX+30] ; ||
004010EE |. >MOV DWORD PTR SS:[ESP],EAX ; ||
004010F1 |. >CALL <JMP.&msvcrt._setmode> ; |\_setmode
004010F6 |. >MOV EAX,DWORD PTR DS:[404018] ; |
004010FB |. >MOV DWORD PTR SS:[ESP+4],EAX ; |
004010FF |. >MOV EAX,DWORD PTR DS:[EBX+50] ; |
00401102 |. >MOV DWORD PTR SS:[ESP],EAX ; |
00401105 |. >CALL <JMP.&msvcrt._setmode> ; \_setmode
0040110A \.^>JMP GCCOllyT.0040107E
Now we know the signature for the call to main
takes 3 args, we also know it will be called before app cleanup and exit, thus we get GCCOllyT.00401AFC
. As you can see, it pays heavily to have symbols enabled, this can be done from the disassembly section of the debugging options menu.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With