1.17 GOTO operator
Let's first understand what GOTO is in C quickly
It simply jumps to a specific place in the code without any condition or comparison or anything
The author started saying that theGOTO operator is often considered an anti-pattern, and the meaning of anti-pattern is a famous or widespread solution
But in reality, it's a bad solution and causes more problems than it solves
This is a simple example:
#include <stdio.h> // include the standard I/O header; this directive includes the header file for standard input/output functions
int main() // program entry point; this is the main function where program execution begins
{
printf("begin\\n"); // print "begin" followed by a newline; this calls printf to output the string
goto exit; // jump to the label 'exit'; this performs an unconditional jump to the specified label
printf("skip me!\\n"); // print "skip me!" followed by a newline; this call is skipped due to the goto
exit: // label for the jump target; this is the point where execution jumps to
printf("end\\n"); // print "end" followed by a newline; this calls printf to output the string
};
And this is what came out with MSVC 2012:
Listing 1.107: MSVC 2012
$SG2934 DB 'begin', 0aH, 00H ; define string "begin\n" in data segment; this allocates a null-terminated string
$SG2936 DB 'skip me!', 0aH, 00H ; define string "skip me!\n" in data segment; this allocates a null-terminated string
$SG2937 DB 'end', 0aH, 00H ; define string "end\n" in data segment; this allocates a null-terminated string
_main PROC ; start of main procedure
push ebp ; push EBP onto stack; save base pointer
mov ebp, esp ; move ESP to EBP; set up base pointer
push OFFSET $SG2934 ; 'begin' ; push address of "begin\n" onto stack
call _printf ; call printf function
add esp, 4 ; add 4 to ESP; clean up stack after call
jmp SHORT $exit$3 ; jump to label $exit$3; unconditional jump
push OFFSET $SG2936 ; 'skip me!' ; push address of "skip me!\n" (skipped code)
call _printf ; call printf (skipped)
add esp, 4 ; clean up stack (skipped)
$exit$3: ; label for jump target
push OFFSET $SG2937 ; 'end' ; push address of "end\n"
call _printf ; call printf
add esp, 4 ; clean up stack
xor eax, eax ; XOR EAX with itself; set EAX to 0 (return value)
pop ebp ; pop EBP; restore base pointer
ret 0 ; return from function with 0
_main ENDP ; end of main procedure
The goto instruction was simply transformed into a JMP instruction, which has the same effect:
An unconditional jump to another place.
The second printf() cannot be executed except by human intervention, either using a debugger or by patching the code
And this can be a simple and useful exercise in patching.
We open the resulting executable in x32dbg (he did it on Hiew but I will do it on x32dbg)
First, we compile the C code with this command:
cl /Od /Zi teest.c ; compile teest.c with optimization disabled (/Od) and debug info (/Zi); this command compiles the C file into an executable with debugging symbols
And run the exe file on x32dbg and go to main and set Breakpoint at the beginning of Main
The instruction that jmp 0x001071FF which is this
This is supposed to go to the function that goes to Exit
Now we will try to cancel the jmp operation so that it prints what's after it without going to Exit
And we will do this in two ways:
- We change the Assembly from
jmp 0x001071FF to NOP by selecting it and pressing Space and changing it to Nop like this
And it will run the program normally and output like this
- We change the Hex value and we do as follows:
We select the jmp instruction then Right Click then Follow in dump then Selected Address
Then we see the value in Hex it will be like this
We change the value 07 to 00 which is the same as nop exactly
And we start doing F8 until ret
1.17.1 Dead code
The second printf() call is also called “dead code” in compiler terms.
And this means that this code will never be executed.
That's why, when you compile this example with optimizations enabled,
The compiler removes the dead code, and leaves no trace of it:
Listing 1.109: Optimizing MSVC 2012
$SG2981 DB 'begin', 0aH, 00H ; define "begin\n"; string constant
$SG2983 DB 'skip me!', 0aH, 00H ; define "skip me!\n"; unused string constant
$SG2984 DB 'end', 0aH, 00H ; define "end\n"; string constant
_main PROC ; start of main
push OFFSET $SG2981 ; 'begin' ; push "begin\n"
call _printf ; call printf
push OFFSET $SG2984 ; 'end' ; push "end\n"
$exit$4: ; label (no jump here since optimized)
call _printf ; call printf
add esp, 8 ; clean up 8 bytes (two pushes)
xor eax, eax ; set EAX to 0
ret 0 ; return 0
_main ENDP ; end of main
But, the compiler forgot to remove the string "skip me!"