Learning notes on the basis of reverse shelling analysis 9. C language inline assembly and three call protocol naked functions

Posted by dcinadr on Sat, 25 Dec 2021 08:01:34 +0100

This article is for me in Great God Forum Learning reverse cracking shelling is one of the learning notes. It is my review and summary of what I have learned in the past. There may be fallacies. You are welcome to point out.
Notes will be released one after another, hoping to help Mengxin who wants to get started and make progress together

C language inline assembly and call protocol

Previously, we analyzed C language by analyzing disassembly. Now let's explore how to write assembly functions directly in C language

Here we will introduce the concept of naked function in C language

Bare function

Declare bare functions

The difference between a bare function and an ordinary function is that it is declared before the function

__declspec (naked)

This shows that the function is a bare function

Naked function action

To talk about the role of naked functions, we have to mention the difference between naked functions and ordinary functions

Difference between naked function and ordinary function

In the previous disassembly C language notes, we can know that there are not many disassembly codes for an ordinary empty function, such as protecting the site, restoring the site, etc. so how do these disassembly codes come into being?

The answer is: compiler

The compiler will generate this disassembly code for us

In contrast, as long as the ordinary function plus the bare function prefix is converted into a bare function, the compiler will know that this function does not need to generate the disassembly code such as protecting the site and restoring the site, and the disassembly code required for function execution is implemented by ourselves

Therefore, the role of naked functions is imminent:

When we don't want the compiler to generate the assembly code in the function for us, but want to implement the assembly code inside the function ourselves, we can use the bare function to tell the compiler not to generate additional assembly code

The simplest naked function

void __declspec (naked) function(){
        __asm{
                ret
        }
}

The above is the simplest bare function. The disassembly code has only one line ret

Compared with ordinary empty functions, they also do nothing, but add ret. Why?

We can take a look at the execution flow of the simplest naked function and pay attention to the comparison with ordinary empty functions

Analyze the simplest bare functions

Complete code

Give the complete code first

#include "stdafx.h"

void __declspec (naked) function(){
        __asm{
                ret
        }
}
int main(int argc, char* argv[])
{
        function();
        return 0;
}

Then enter the world of disassembly

Function external

13:       function();
00401078   call        @ILT+10(function) (0040100f)
14:       return 0;
0040107D   xor         eax,eax
15:   }
0040107F   pop         edi
00401080   pop         esi
00401081   pop         ebx
00401082   add         esp,40h
00401085   cmp         ebp,esp
00401087   call        __chkesp (004010e0)
0040108C   mov         esp,ebp
0040108E   pop         ebp
0040108F   ret

Function inside

6:    void __declspec (naked) function(){
00401030   ret

Start analysis

00401078   call        @ILT+10(function) (0040100f)

We can find that there is no difference between the call of naked function and the call of ordinary function. They are all the call function address

But then go inside the function and you can see

00401030   ret

The function has and is only used in our code__ The ret statement written by asm has no other code

After the ret statement is executed, the function returns normally. Next, it is no different from the ordinary empty function

come to conclusion

Therefore, we can know that the internal assembly code of the bare function is completely implemented by ourselves. The reason why we need to write a ret is to enable the function to return normally

Let's look at the disassembly code without adding ret statements

We'll look at the inside of the naked function__ asm delete

void __declspec (naked) function(){

}

Then look at the assembly code inside the function

We can see that the function is internally a pile of int 3, that is, CC, that is, the contents of the initialization stack

When the program is executed here, it will be interrupted and cannot be executed normally. Therefore, we need to add an assembly ret statement to enable the function to execute normally and return

After we have a general understanding of the bare functions, let's use inline assembly to implement our own addition functions

Implementation of addition function by inline assembly

Self writing addition function

#include "stdafx.h"

int __declspec (naked) Plus(int x,int y){
                __asm{
                //Preserve pre call stack
                push ebp
                //Lift stack
                mov ebp,esp
                sub esp,0x40
                //Protect the site
                push ebx
                push esi
                push edi
                //Initialize the raised stack and fill the buffer
                mov eax,0xCCCCCCCC
                mov ecx,0x10
                lea edi,dword ptr ds:[ebp-0x40]
                rep stosd
                //Function core function

                //Take out parameters
                mov eax,dword ptr ds:[ebp+8]
                //Parameter addition
                add eax,dword ptr ds:[ebp+0xC]

                //Restore site
                pop edi
                pop esi
                pop ebx

                //Lower stack
                mov esp,ebp
                pop ebp                

                //return
                ret 
        }        
}
int main(int argc, char* argv[])
{
        Plus(1,2);
        return 0;
}

It's not difficult to find that the addition function we implemented ourselves simulates what the compiler does for us. At this time, you will also see it inside the function

Function inside

6:    int __declspec (naked) Plus(int x,int y){
00401030   push        ebp
7:        __asm{
8:            //Preserve pre call stack
9:            push ebp
10:           //Lift stack
11:           mov ebp,esp
00401031   mov         ebp,esp
12:           sub esp,0x40
00401033   sub         esp,40h
13:           //Protect the site
14:           push ebx
00401036   push        ebx
15:           push esi
00401037   push        esi
16:           push edi
00401038   push        edi
17:           //Initialize the raised stack and fill the buffer
18:           mov eax,0xCCCCCCCC
00401039   mov         eax,0CCCCCCCCh
19:           mov ecx,0x10
0040103E   mov         ecx,10h
20:           lea edi,dword ptr ds:[ebp-0x40]
00401043   lea         edi,ds:[ebp-40h]
21:           rep stosd
00401047   rep stos    dword ptr [edi]
22:           //Function core function
23:
24:           //Take out parameters
25:           mov eax,dword ptr ds:[ebp+8]
00401049   mov         eax,dword ptr ds:[ebp+8]
26:           //Parameter addition
27:           add eax,dword ptr ds:[ebp+0xC]
0040104D   add         eax,dword ptr ds:[ebp+0Ch]
28:
29:
30:           //Restore site
31:           pop edi
00401051   pop         edi
32:           pop esi
00401052   pop         esi
33:           pop esi
00401053   pop         esi
34:
35:           //Lower stack
36:           mov esp,ebp
00401054   mov         esp,ebp
37:           pop ebp
00401056   pop         ebp
38:
39:           //return
40:           ret

It executes the code written by ourselves, not generated by the compiler, and can also realize the function of addition function

After the function returns

We can find that the returned function is no different from the ordinary function

00401081   add         esp,8

All have this line of statements to balance the stack, that is, balance outside the stack. However, if we want to balance the stack inside the function, that is, to achieve balance inside the stack, that is, we want the function to return without this external stack balance statement, so that the stack balance can be handled by ourselves. How can we do this?

Here we will introduce the concept of C language call agreement

Call agreement

Several common calling protocols:

Among them__ cdecl is the default calling protocol of C language

Next, let's compare the three invocation protocols

int __cdecl Plus1(int x,int y){
        return x+y;
}
int __stdcall Plus2(int x,int y){
        return x+y;
}
int __fastcall Plus3(int x,int y){
        return x+y;
}

It is also a simple addition function, which adopts three different call protocols. Let's use assembly to observe their differences

__cdecl

The first is what we are most familiar with__ cdecl protocol is different from the simple addition function analyzed in our last note

Observe disassembly:

Function external

Function inside

Here we mainly focus on three areas: parameter stack pressing, function return value, and execution statement after return

Parameter stack pressing: push 2 first and then push 1

Function return value: ret

Execute the statement after returning: add esp,8

__stdcall

Function external

Function inside

Then focus on three places: parameter stack pressing, function return value, and execution statement after return

Parameter stack pressing: push 2 first and then push 1

Function return value: ret 8

Execute statements after return: XOR, eax, eax

__fastcall

Function external

Function inside

Still pay attention to three places: parameter stack pressing, function return value, and execute statement after return

Parameter stack pressing: move EDX first, 2 then mov ecx,1

Function return value: ret

Execute statements after return: XOR, eax, eax

Compare the three protocols

We can conclude that:

__ cdecl is to push parameters into the stack, and then balance the stack after function execution returns, that is, balance out of the stack

__ stdcall also pushes parameters into the stack, but it balances the stack through ret xxx inside the function, that is, balance in the stack

__ fastcall directly uses edx and ecx as the carrier of parameter transfer when the number of parameters is less than or equal to 2. It is not necessary to use the stack, so there is no need to balance the stack. However, when the number of parameters is greater than 2, the extra parameters are handled in the way of stdcall, which also adopts intra stack balance

Let's talk about it later__ The problem of return value in stdcall

We can see that we push two immediate numbers 2 and 1 in the above addition function, and the return value is 8

Does this mean that xxxx = number of parameters * 4 in ret xxxx?

Not at all!!! Here, xxxx in ret xxxx is related to the data width of the pressing parameter

The data width of the two immediate numbers we press here is 4 bytes = 32bit, so here we are ret 4+4=8

If it is changed to push ax, that is, when pressing 2 bytes = 16bit, it should ret 2

Here you can refer to the push instruction of stack related assembly instructions I published earlier

After understanding the above call protocol, we can modify the previous simple addition naked function and change it to in stack balance

Stack balanced addition function

__declspec (naked) __stdcall int  Plus(int x,int y){
                __asm{
                //Preserve pre call stack
                push ebp
                //Lift stack
                mov ebp,esp
                sub esp,0x40
                //Protect the site
                push ebx
                push esi
                push edi
                //Initialize the raised stack and fill the buffer
                mov eax,0xCCCCCCCC
                mov ecx,0x10
                lea edi,dword ptr ds:[ebp-0x40]
                rep stosd
                //Function core function

                //Take out parameters
                mov eax,dword ptr ds:[ebp+8]
                //Parameter addition
                add eax,dword ptr ds:[ebp+0xC]
                //Restore site
                pop edi
                pop esi
                pop ebx                
                //Lower stack
                mov esp,ebp
                pop ebp

                //return
                ret 8
        }        
}
int main(int argc, char* argv[])
{
        Plus(1,2);
        return 0;
}

Compared with the above, RET is modified to ret 8 to achieve stack balance in the function

The basic learning of this series of reverse shelling is in the link below. Welcome to download and communicate

Great God forum reverse shelling analysis basic learning notes 9 C language inline compilation and... - "learning material area" - great God forum | shelling and cracking | easy language | virus analysis | www.dslt tech

Copyright notice: This article was originally created by lyl610abc. Welcome to share this article. Please keep the source for reprint

Topics: Programming Cyber Security