[Weird simplified C Language Program] Hide the main function

Source: Internet
Author: User

Ah, I haven't written a blog for a few months. It's too time-consuming and time-consuming. Today is exactly the time. I plan to write a question that I will discuss online today.

I think everyone should have heard of the "International C language chaotic code competition (Ioccc, The International obfuscated C code contest) ", I accidentally discussed this issue online today. I intentionally changed the main function and compiled it. So I wanted to use this feature to write a "weird" code. (After writing, I found that there were similar winning works in the ioccc competition. I knew I had already joined the competition ...)

The Code is as follows:

#include <stdio.h>int main[] = { 232,-1065134080,5138447,285147200,50008,(int)printf };

Copy the two codes to XXX. C and compile them with VC. The output result is as follows:

This program cannot be run in DOS mode.
$

Or another method:

#include <stdio.h>int ______ = ( int )printf;int main[] = { 232,-394045440,5138441,285147200,50008 };

The output result is consistent.

So why can these two strange codes be compiled and run? Two questions can be summarized:

1. why can it be compiled?

2. Why can I output such a string? It is useless to see the call printf, not to see this string.

We solve the first question one by one:

First of all, the two sections of code must be compiled using the. c file. The. cpp file cannot be compiled, that is, it must be compiled using the C compiler, and the c ++ compiler cannot pass.

Secondly, because it is a C compiler and A Visual Studio environment, the checks on the number and type of function parameters are not very strict. For the entry function main, the compiler does not strictly check when it finds and links the main function symbol. Therefore, the main function can be expressed as an array in this place. GCC can also be smoothly compiled and passed, but the Code must be modified to run successfully. This article will not go into detail. This is not the focus.

At this point, the first question is solved. The second question is much more complicated. Let's analyze it step by step.

First, the main array is linked to the main function, so the integer (INT, 4 bytes) inside the main array is the code byte (machine code) executed by the main function, which is similar to the shellcode principle here. As for what the machine code is, I will not explain it here. You can find the answer in my previous blog or on the Internet. Since the machine code is used, these integers must represent the specific execution logic. Because they are directly written machine codes (integers), we can only look at the disassembly code. Let's take the first example as an example:

00492000 E8 00 00 00 00 call _ main + 5 (492005 H)
00492005 58 pop eax
00492006 83 C0 0f add eax, 0fh
00492009 68 4E 00 40 00Push 40004eh
0049200e FF 10 call dword ptr [eax]
00492010 58 pop eax
00492011 C3 RET
00492012 00 00 add byte PTR [eax], Al
00492014 E0 B0 loopne 00491fc6
00492016 42 Inc edX
00492017 00 E0 add Al, ah

Check the memory of the main array:

Zero X 00492000E8 00 00 0000 58 83 C00f 68 4E 0040 00 FF 1058 C3 00 00E0 B0 42 00 ?.... X ??. Hn. @... X ?..?? B.

0x00492018 E0 B0 42 00 -------------------------------?? B .....

The horizontal bar is omitted part of the memory. We can find that the 24 bytes (6 INT) in the first row are the six integers in the main array, the last four bytes are the first address of the printf function: 0x0042b0e0 (usually different on your platform ). Let's look at the disassembly code above. The previous machine code is also 24 bytes in the memory. Let's analyze the disassembly code:

The first two sentences of red assembly code:

Call 0x00492005

Pop eax

These two statements are mainly used to obtain the value of the EIP register. After executing the pop eax code, the eax value is 0x00492005, so that the EIP address is obtained.

As to why the two codes can obtain the EIP address, we also have some explanations in the previous blog. We know that the call command can be understood in two steps, one is to press the Code address of the next command of the Call Command to stack, and the other is to jump.

The next code of the Call Command isPop eaxThe Code address is 0x00492005. After the call command is pushed to the stack, it jumps to pop eax. At this moment, pop eax will pop up the Code address (0x00492005) that has just been pushed into eax, so that eax will get the Code address of POP eax. Why is it so troublesome? It is because the restrained assembly does not support operations such as mov eax and EIP, so EIP can be obtained through the call feature.

So, why do we need to get this address to get the location of the address stored by the printf function (that is, the offset relative to the first address of the main array, that is, the memory address of the last int of the main array), that isZero X 00492014. This address is the EIP value obtained earlier.0x00492005 + 0x0f. Therefore, the green assembly code aboveAdd eax, 0fhYou don't have to explain it again. After adding, the value of eax isZero X 00492014.

At this point, the location where the address value of the printf function is stored is also known. In this case, we should consider how to call printf to output the previous string of characters. At first glance, this string of characters seems very familiar, right! I am really familiar with it. This string of characters is exactly the information in the PE file header. The EXE file contains such information. Where can we find the memory address of this string and then pass it into printf?

When debugging programs, we should be able to note that EXE usually starts loading from the memory address 0x00400000 by default. Of course, sometimes it does not start with this address, for example, in win7 and Vista, if the random base address option is enabled for the compiler, a random base address will be loaded each time the EXE is run. In this case, the memory address 0x00400000 is not necessarily loaded. This article only analyzes the loading from the address 0x00400000.

Let's take a look at the memory in the memory address 0x00400000:

0x00400000 4D 5A 90 00 03 00 00 04 00 00 00 ff 00 00 B8 00 00 00 00 00 00 00 00 MZ ?........... ..?.......
0x00400018 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 @............ ...........
0x00400030 00 00 00 00 00 00 00 00 00 E8 00 00 00 00 0e 1f Ba 0e 00 B4 09 CD ............? .....?..?.?
0x00400048 21 B8 01 4C CD 21 54 68 69 73 20 70 72 6f 67 72 61 6D 20 63 61 6e 6e 6f !?. L ?! This program canno
0x00400060 74 20 62 65 20 72 75 6e 20 69 6e 20 44 4f 53 20 6D 6f 64 65 2E 0d 0d 0a t be run in DOS mode ....
0x00400078 24 00 00 00 00 00 00 25 3C F5 D5 61 5d 9B 86 61 5d 9B 86 61 5d 9B 86 $ ...... % <?? A]? A]? A]?

It can be seen that the red part above is the string of the previous output starting from 0x0040004e, and ends after the '$' character, that is, the output ends at 0x00400079.

From this analysis, the compilation of the Black bold sentence in the previous disassembly is very clear, it is to pass the address 0x0040004e to the printf function, let it output this string of characters.

The following blue call code calls the printf function. The address value of printf in the main array has been saved to eax, at this moment, you only need to extract the address value under eax, and the call can be done in the past, which is equivalent:

Call Main [5] // pseudocode

After the printf output is called, pop eax aims to balance the stack. Because printf is a _ cdecl call convention, the caller needs to balance the stack. Pop eax is equivalent to add ESP, 4. To save a few bytes, pop eax occupies only one byte.

Now, the disassembly code is almost analyzed. The principle is actually very simple. As for the second method, the location of the address of the first method is different from that of the printf function. Let's look at the disassembly code:

00492000E0 B0Loopne 00491fb2
0049200242INC edX
0049200300DB 00 h
00492004 E8 00 00 00 00 call _ main + 5 (492009 H)
00492009 58 pop eax
0049200a 83 E8 09Sub eax, 9
0049200d 68 4E 00 40 00 push 40004eh
00492012 FF 10 call dword ptr [eax]
00492014 58 pop eax
00492015 C3 RET
00492016 00 00 add byte PTR [eax], Al

A green code is changed to sub, and nine bytes are subtracted from the code.Zero X 00492000, That is, the address of the variable. The address value of the printf function exists here, so we need to subtract 9, that is, 0x00492009-9. The code for other parts is consistent with the previous one. ("______" The addresses of variables and main arrays are sequential in memory)

The strange code is generated like this. In fact, if you translate the main array into an restrained version, it is as follows:

Version 1: int _ declspec (naked) Main (void) {_ ASM {call _ geteip: Pop eax // obtain EIP add eax, 0dh _ entry: Push 0x0040004e // call [eax] Pop eax for the printf function press-in parameter // balance stack RET} version 2: int _ declspec (naked) main (void) {_ ASM {call _ geteip: Pop eax sub eax, 09h _ entry: Push 0x0040004e call [eax] Pop eax RET }}

These two versions cannot run and can only be compiled. Because the location of the printf function address cannot be determined, we can use arrays to determine the location. In addition, if the RET command is not an integer multiple of 4, you need to fill in the byte when writing it into the main array. In the main array, I fill it with 0.

The preceding two versions may fail to run successfully at some time. Because the main array is in the data segment, the memory of the data segment may not have the execution permission, So errors may occur. In practice, you can modify the memory permission.

Summary:

1. The examples in this article are not of practical value and only for research purposes. They aim to understand the nature of the function call model and the utilization of the framework and instructions at the Assembly layer. Focus on the study of principles and expand thinking.

2. I personally think that a lot of things that do not have practical value are not worth studying. The purpose of the study is not the result, but the process, absorbing favorable ones and abandoning useless ones.

3. for the examples in this article, the concept of things such as the function call model is very useful in practice. A typical example is to use the dump file for error search and analysis, here, the dump file may be in a custom dump format.

Now, this article has come to an end. Welcome to the discussion.

--- If You Need To reprint it, please indicate the source. Thank you for your support ----

 

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.