1.16 Pointers
A quick reminder of what Pointers are: it is a variable that stores an address in memory (Memory Address) Not the value itself
1.16.1 Returning values
Pointers are often used to return values from functions (remember the case of scanf() that we talked about before)
For example, when a function needs to return two values.
This is an example using Global Variables
#include <stdio.h> // include the standard I/O header (this line includes the necessary library for input/output functions like printf)
void f1 (int x, int y, int *sum, int *product) // function definition that takes two integers and two pointers to integers
{
*sum = x + y; // dereference the sum pointer and assign the sum of x and y to it
*product = x * y; // dereference the product pointer and assign the product of x and y to it
};
int sum, product; // declare global variables sum and product
void main() // main function (note: should be int main() for standard compliance)
{
f1(123, 456, &sum, &product); // call f1 with 123, 456, and addresses of global sum and product
printf("sum=%d, product=%d\\n", sum, product); // print the values of sum and product
};
This code comes out in this form after compilation:
Listing 1.103: Optimizing MSVC 2010 (/Ob0) ; this is the assembly listing label
COMM _product:DWORD ; declare common storage for _product as DWORD
COMM _sum:DWORD ; declare common storage for _sum as DWORD
$SG2803 DB 'sum=%d, product=%d', 0aH, 00H ; define the format string for printf
_x$ = 8 ; size = 4 ; parameter offset for x
_y$ = 12 ; size = 4 ; parameter offset for y
_sum$ = 16 ; size = 4 ; parameter offset for sum pointer
_product$ = 20 ; size = 4 ; parameter offset for product pointer
_f1 PROC ; start of f1 procedure
mov ecx, DWORD PTR _y$[esp-4] ; move y value to ECX
mov eax, DWORD PTR _x$[esp-4] ; move x value to EAX
lea edx, DWORD PTR [eax+ecx] ; load effective address of sum (EAX + ECX) to EDX
imul eax, ecx ; multiply EAX by ECX (product)
mov ecx, DWORD PTR _product$[esp-4] ; move product pointer to ECX
push esi ; push ESI to stack
mov esi, DWORD PTR _sum$[esp] ; move sum pointer to ESI
mov DWORD PTR [esi], edx ; store sum at address pointed by ESI
mov DWORD PTR [ecx], eax ; store product at address pointed by ECX
pop esi ; pop ESI from stack
ret 0 ; return from function
_f1 ENDP ; end of f1 procedure
_main PROC ; start of main procedure
push OFFSET _product ; push address of _product
push OFFSET _sum ; push address of _sum
push 456 ; 000001c8H ; push 456
push 123 ; 0000007bH ; push 123
call _f1 ; call f1
mov eax, DWORD PTR _product ; move _product value to EAX
mov ecx, DWORD PTR _sum ; move _sum value to ECX
push eax ; push product
push ecx ; push sum
push OFFSET $SG2803 ; push format string address
call DWORD PTR __imp__printf ; call printf
add esp, 28 ; add 28 to ESP (stack cleanup)
xor eax, eax ; set EAX to 0
ret 0 ; return 0
_main ENDP ; end of main procedure
He saw it at that time on OllyDbg, we will do it on x32dbg as usual
First, of course, after we write the C code and compile it, we run it on x32dbg and open the Main Function and stop until the call teeest.3B36F2
Of course, at first, the addresses of the global variables are sent to the function f1() we can press Follow in dump
On an element in the stack and then we will see the place reserved in the data segment for these two variables
We will find here that these variables are zeroed because the data that is not initialized is zeroed before execution starts
We start pressing F7 and this will enter us inside f1
The Stack will be in this form
Which is this
x = 123 (0x7B) ; value of x
y = 456 (0x1C8) ; value of y
sum = address ; address of sum
product = address ; address of product
After we reach the end of f1 and then return to main again after that these values are placed in the registers
And then sent to printf() via the stack
Local variables example
Let's modify our example a bit:
Listing 1.104: Now the sum and product variables are local
void main() // main function
{
int sum, product; // now these variables are local inside this function (declare local variables sum and product)
f1(123, 456, &sum, &product); // call f1 with addresses of local sum and product
printf("sum=%d, product=%d\\n", sum, product); // print sum and product
};
The f1() code will not change. Only the main() code will change:
Listing 1.105: Optimizing MSVC 2010 (/Ob0)
_product$ = -8 ; size = 4 ; offset for local product
_sum$ = -4 ; size = 4 ; offset for local sum
_main PROC ; start of main
; Line 10
sub esp, 8 ; allocate 8 bytes on stack for locals
; Line 13
lea eax, DWORD PTR _product$[esp+8] ; load address of product
push eax ; push product address
lea ecx, DWORD PTR _sum$[esp+12] ; load address of sum
push ecx ; push sum address
push 456 ; 000001c8H ; push 456
push 123 ; 0000007bH ; push 123
call _f1 ; call f1
; Line 14
mov edx, DWORD PTR _product$[esp+24] ; move product value to EDX
mov eax, DWORD PTR _sum$[esp+24] ; move sum value to EAX
push edx ; push product
push eax ; push sum
push OFFSET $SG2803 ; push format string
call DWORD PTR __imp__printf ; call printf
; Line 15
xor eax, eax ; set EAX to 0
add esp, 36 ; stack cleanup
ret 0 ; return
We will try this on x32dbg too
Of course the C code will be like this
#include <stdio.h> // include stdio.h
void f1(int a, int b, int* sum, int* product) // function f1
{
*sum = a + b; // assign sum
*product = a * b; // assign product
}
int main() // main function
{
int sum; // local sum
int product; // local product
f1(123, 456, &sum, &product); // call f1
printf("sum=%d, product=%d\\n", sum, product); // print
return 0; // return 0
}
And we start to see the Main
We will notice at first in main
push ebp ; push EBP
mov ebp, esp ; move ESP to EBP
sub esp, 8 ; subtract 8 from ESP (allocate for locals)
Which means that 8 bytes are reserved, sum will take 4 bytes and product will take 4 bytes
And if we come to the Stack window, we will find the real addresses of the local variables
And when we do DUMP for one of them, it will show garbage like this because f1() hasn't started yet
We start going to call teeest.6936f2, press F7 to enter F1()
And we keep pressing F8 to run it all and at the end we return to do DUMP again and we will find the DUMP in this form
002EF854 = 00000243 ; 579 (sum value)
002EF858 = 0000DB18 ; 56088 (product value)
The summary (Conclusion)
The function f1() can return pointers to any place in memory, existing anywhere.
And this is basically the use and importance of pointers.
And by the way, references in C++ work in exactly the same way.
1.16.2 Swap input values
This code will do what is required:
#include <memory.h> // include memory.h for strdup
#include <stdio.h> // include stdio.h for printf
void swap_bytes (unsigned char* first, unsigned char* second) // function to swap two bytes using pointers
{
unsigned char tmp1; // temporary variable 1
unsigned char tmp2; // temporary variable 2
tmp1 = *first; // store value at first pointer in tmp1
tmp2 = *second; // store value at second pointer in tmp2
*first = tmp2; // assign tmp2 to location pointed by first
*second = tmp1; // assign tmp1 to location pointed by second
};
int main() // main function
{
// copy the string to the heap, so we can modify it
char *s = strdup("string"); // duplicate string and assign pointer to s
// swap the second and third characters
swap_bytes(s + 1, s + 2); // call swap_bytes with pointers to second and third chars
printf("%s\\n", s); // print the modified string
};
As we see, the bytes are loaded into the least 8 bits of the registers ECX and EBX using the MOVZX instruction
(And this means that the higher parts of these registers are zeroed), and then the bytes are written again but after they are swapped.
Listing 1.106: Optimizing GCC 5.4
swap_bytes: ; start of swap_bytes
push ebx ; push EBX
mov edx, DWORD PTR [esp+8] ; move first pointer to EDX
mov eax, DWORD PTR [esp+12] ; move second pointer to EAX
movzx ecx, BYTE PTR [edx] ; move byte from EDX with zero extend to ECX
movzx ebx, BYTE PTR [eax] ; move byte from EAX with zero extend to EBX
mov BYTE PTR [edx], bl ; move low byte of EBX to address in EDX
mov BYTE PTR [eax], cl ; move low byte of ECX to address in EAX
pop ebx ; pop EBX
ret ; return
The addresses of the two bytes are taken from the arguments, and during the execution of the function they are in the registers EDX and EAX.
Meaning we are using pointers here, and often there is no better way than this to solve this task without them.