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 DWORDCOMM _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 procedureHe 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 xy = 456 (0x1C8) ; value of ysum = address ; address of sumproduct = address ; address of productAfter 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 ; returnWe 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 EBPmov ebp, esp ; move ESP to EBPsub 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.
If this article helped you, please share it with others!
Some information may be outdated





