Post

Reverse Engineering for Beginners : Pointers

Reverse Engineering for Beginners : Pointers

1.16 Pointers

pointer

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

returnValues

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

C

#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:

Assembly

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

3_1

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

3_2

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

3_3

The Stack will be in this form

3_4

Which is this

Assembly

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

3_5

Local variables example

Let's modify our example a bit:

Listing 1.104: Now the sum and product variables are local

C

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)

Assembly

_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

C

#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

3_6

We will notice at first in main

Assembly

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

3_7

And when we do DUMP for one of them, it will show garbage like this because f1() hasn't started yet

3_8

We start going to call teeest.6936f2, press F7 to enter F1()

3_9

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

Assembly

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

Swapinputvalues

This code will do what is required:

C

#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

Assembly

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.

This post is licensed under CC BY 4.0 by the author.

Trending Tags