Skip to content

3. Simple Lambdas

Github Repo with Tools and Files

Track3_1

This sample aims to show what it looks like in assembly when variable is initialised as a pointer and as an actual value.

Additionally, it aims to show us how to manipulate types in IDA to make the output look like the source code that we are reversing.

Track3_1.c

In track3_1, we are given the following C file, which contains 2 structs, SimpleContext and SimpleLambda. As can be seen below, SimpleLambda will be making use of the SimpleContext and in the main function, we will simply be passing values into the

struct SimpleContext{
   int Arg1;
   int Arg2;
};
struct SimpleLambda{
   void *pCallback;
   short Priority;
   struct SimpleContext *pContext
};
int main(){
   struct SimpleContext c;
   c.Arg1 = 10;
   c.Arg2 = 12;
   struct SimpleLambda l;
   l.priority =1;
}

Track3_1.exe

In the case of Track3_1.exe, the .pdb file exists, and hence we can find the structure of SimpleContext and SimpleLambda in local types (Shift + F1). Pasted image 20230224092635.png However, at this stage, looking at the disassembly below, Pasted image 20230224092844.png We simply just need to go to the "local types" window select the types we want, in this case SimpleContext and SimpleLambda then hit enter to import the structures. Pasted image 20230224092923.png And as can be seen below, the structs have been imported to our structure window. Pasted image 20230224093110.png We then go to the stack where the variables are stored (double-click on the variable) and then alt+q to convert the variables to the structure we want it to be. Pasted image 20230224110221.png As can be seen below, Arg1 and Arg2 is labeled on the disassembler view. Pasted image 20230224110345.png We also notice an interesting phenomena, where for Priority variable, we see the following instructions:

mov eax,1
mov [rbp+120h+var_F0], ax

For this we will need to reference #Track3_1.c, we know that Priority variable is declared as a short Priority which is 16bit or 2 bytes. EAX is 4 bytes or 32bits, and in this case, the initial value of Priority is transmitted to the EAX register first before 2 bytes from EAX, AX is being copied to the stack.

Quiz

Q1. Please find the size of Arg1 field of SimpleContext Structure. How many bytes is it?

Ans : 4

Q2. To initialize the Priority field of SimpleLambda structure the compiler firstly puts 1 in EAX general purpose register, but then takes only AX part of it. Why?

Ans : Priority field has short data type - two bytes long. The AX has the same length and compiler used this part of the register in instruction

Q3. You could easily find the initial values for Arg1 and Arg2 on your program’s stack. Could you do the same for the initial value for Priority?

Ans : Yes, the initial value for Priority 1 the compiler decided to transmit through the general purpose register, not the stack, but anyway the resulting local var is on a stack

Track3_2

This track aims to show how it looks like in assembly when there is a function call in assembly and also that pointer can point to pointers and pointing can be nested.

Track3_2.c

struct SimpleContext{
   int Arg1;
   int Arg2;
};
struct SimpleLambda{
   void *pCallback;
   short Priority;
   struct SimpleContext *pContext
};
int SimpleCallback(struct SimpleContext *Context){
   Context->Arg1 = 14;
   return 0;
}

int main(){
   struct SimpleContext c;
   c.Arg1 = 10;
   c.Arg2 = 12;
   struct SimpleLambda l;
   l.pCallback = simpleCallback;
   l.priority =1;
   l.pContext = &c;
}

Track3_2.exe

We have the following assembly. Pasted image 20230227000921.png We follow what we did for the first binary, where we import local types SimpleContext and SimpleLambda. However, this time we will attempt to utilise the local type SimpleLambda. To do so, we will need to identify Priority variable as we already did above, and follow it in the stack. Pasted image 20230227004317.png As can be seen above, the stack structure closely resembles that of the one in SimpleLambda. (We can refer to our source code too). Pasted image 20230227004426.png Therefore, we can guess that var_F8 is likely the pointer to callback variable. As such, we can apply the structure starting from var_F8. Pasted image 20230227014945.png Shown above is the disassembly after all the renaming and we can see above that after adding all the structures, and renaming, the disassembly is resembles the actual code.

Quiz

Q1. Find the real start address of SimpleCallback function in track3_2.exe. Type your answer in the 0xXXXXXXXXX format without the initial zeros.

Pasted image 20230227021037.png Ans : 0x140011770

Q2. Which fields of SimpleLambda structure are pointers?

Ans : pCallback and pContext fields are pointers, they contain addresses

Q3. The pContext field in SimpleLambda structure is the pointer to SimpleContext custom data type. Could SimpleContext in its turn contain some fields with pointers?

Ans : Yes, sure, C allows pointers to pointers to pointers and so on, it’s not a problem

Track3_3

Track3_3 attempts to show us the difference in disassembly when "shallow copying" is done vs "deep copying". Shallow copying is seen where variable l is directly assigned to variable l2 and deep copying is seen in the later part of the code where memcpy() is used to copy the value of l to l2.

In the case of shallow copying, we see that what was copied was simply the address/pointers of the values(values in l2 are simply pointed from l) which might not be ideal whereas for "deep copying" the address/pointers are not copied but instead different from each other. The values are stored in different addresses, even though they are the same value.

Track3_3.c

struct SimpleContext{
   int Arg1;
   int Arg2;
};
struct SimpleLambda{
   void *pCallback;
   short Priority;
   struct SimpleContext *pContext
};

int SimpleCallback(struct SimpleContext *Context){
   Context->Arg1 = 14;
   return 0;
}

int main(){
   struct SimpleContext c;
   c.Arg1 = 10;
   c.Arg2 = 12;
   struct SimpleLambda l;

   l.pCallback = SimpleCallback;
   l.Priority = 1;
   l.pContext = &c;
   struct SimpleLambda l2 = l;

   printf("new lambda priority val: %d and address: 0x%x\n", l2.Priority, &l2.priority);
   printf("old lambda priority val: %d and address: 0x%x\n", l.Priority, &l.priority);

   printf("new lambda context address: 0x%x\n", l2.pContext);
   printf("old lambda context address: 0x%x\n", l.pContext);

   l2.pContext = malloc(sizeof(struct SimpleContext));
   memcpy(l2.pContext,l.pContext,sizeof(struct SimpleContext));

   printf("new lambda context address: 0x%x\n", l2.pContext);
   printf("old lambda context address: 0x%x\n", l.pContext);
   printf("new lambda context arg1: %d\n", l2.pContext->Arg1);
}

Track3_3.exe

Below is the function renamed initially, with the knowledge we gained till track3_2. Shallow Copy Pasted image 20230227021742.pngPasted image 20230227021835.png

As can be seen before rep movsb, the function looks exactly, like what is happening at the source code. Another thing we can notice here, when renaming are instructions :

mov rdi, rax
mov rsi, rcx
rep movsb
Where, rdi is the destination index and rsi, source index. Before rep movsb which means copying of bytes repeatedly from source to destination is executed.

We trace the register rcx since the value in it is moved into rsi and we see above that the address of simpleLambda is loaded into rcx which is then loaded into rsi.

rdi is loaded from rax which is loaded from var_F8 and 0x18 (hex) is moved into ecx before rep movsb is executed. From here it seems like ecx is being used as a counter. The above operation is the copying of values in simpleLambda l to simpleLambda l2.

Therefore, we can deduce that var_F8 is the address for the new lambda.

Deep Copy In deep copy below, Pasted image 20230227021912.png

The disassembly below is a nicer representation after the renaming and also going to the stack and giving l2 the simpleLambda structure.

Shallow Copy Pasted image 20230227023909.png Pasted image 20230227025421.png

Deep Copy Pasted image 20230227025513.png

Quiz

Q1. Inside the binary find the copying of a SimpleLambda structure. How many bytes will be taken to the new structure?

Ans : 24

Q2. Would such a default way of C copying pose some possible problems to us?

Ans : Yes, the value fields would be good, but all the pointer fields would still reference the same memory addresses. So in this case changing would affect both structures - original and copied one

Q3. Please locate in the resulting executable the malloc() call you added for deep copying. How many bytes does it allocate for SimpleContext and why?

Ans : 8 bytes, you provided sizeof(struct SimpleContext) as argument and SimpleContext contains just 2 fields 4 bytes each