C: How to change my own program in my program in runtime?

空扰寡人 提交于 2019-12-13 11:09:31

问题


At runtime, either the assembler or machine code (which is it?) should be somewhere in RAM. Can I somehow get access to it, and read or even write to it?

This is just for educational purposes.

So, I just could compile this code. Am I really reading myself here?

#include <stdio.h>
#include <sys/mman.h>

int main() {
    void *p = (void *)main;
    mprotect(p, 4098, PROT_READ | PROT_WRITE | PROT_EXEC);
    printf("Main: %p\n Content: %i", p, *(int *)(p+2));
    unsigned int size = 16;
    for (unsigned int i = 0; i < size; ++i) {
        printf("%i ", *((int *)(p+i)) );
    }
}

Though, if I add

*(int*)p =4;

then it's a segmentation fault.


From the answers, I could construct the following code which modifies itself during runtime:

#include <stdio.h>
#include <sys/mman.h>
#include <errno.h>
#include <string.h>
#include <stdint.h>

void * alignptr(void * ptr, uintptr_t alignment) {
    return (void *)((uintptr_t)ptr & ~(alignment - 1));
}

// pattern is a 0-terminated string
char* find(char *string, unsigned int stringLen, char *pattern) {
    unsigned int iString = 0;
    unsigned int iPattern;
    for (unsigned int iString = 0; iString < stringLen; ++iString) {
        for (iPattern = 0;
            pattern[iPattern] != 0
            && string[iString+iPattern] == pattern[iPattern];
            ++iPattern);
        if (pattern[iPattern] == 0) { return string+iString; }
    }
    return NULL;
}

int main() {
    void *p = alignptr(main, 4096);
    int result = mprotect(p, 4096, PROT_READ | PROT_WRITE | PROT_EXEC);
    if (result == -1) {
        printf("Error: %s\n", strerror(errno));
    }

    // Correct a part of THIS program directly in RAM
    char programSubcode[12] = {'H','e','l','l','o',
                                ' ','W','o','r','l','t',0};
    char *programCode = (char *)main;
    char *helloWorlt = find(programCode, 1024, programSubcode);
    if (helloWorlt != NULL) {
        helloWorlt[10] = 'd';
    }   
    printf("Hello Worlt\n");
    return 0;
}

This is amazing! Thank you all!


回答1:


Machine code is loaded into memory. In theory you can read and write it just like any other part of memory your program as access to.

There can be some roadblocks to doing this in practice. Modern OSes try and limit the data sections of memory to read/write operations but no execution, and machine code sections of memory to read/execute but no writing. This is to try and limit potential security vulnerabilities that come with allowing executing what ever the program feels like putting into memory (like random stuff it might pull down from the Internet).

Linux provides the mprotect system call to allow some amount of customization for memory protection. Windows provides the SetProcessDEPPolicy system call.

Edit for updated question

It looks like you're trying this on Linux, and using mprotect. The code you posted is not checking the return value from mprotect, so you don't know if the call is succeeding or failing. Here is an updated version that checks the return value:

#include <stdio.h>
#include <sys/mman.h>
#include <errno.h>
#include <string.h>
#include <stdint.h>

void * alignptr(void * ptr, uintptr_t alignment)
{
    return (void *)((uintptr_t)ptr & ~(alignment - 1));
}

int main() {
    void *p = alignptr(main, 4096);
    int result = mprotect(p, 4096, PROT_READ | PROT_WRITE | PROT_EXEC);

    if (result == -1) {
        printf("Error: %s\n", strerror(errno));
    }
    printf("Main: %p\n Content: %i", main, *(int *)(main+2));
    unsigned int size = 16;
    for (unsigned int i = 0; i < size; ++i) {
        printf("%i ", *((int *)(main+i)) );
    }
}  

Note the changes to the length parameter passed to mprotect and the function aligning the pointer to a system page boundary. You'll need to investigate on your specific system. My system has an alignment of 4096 bytes (determined by running getconf PAGE_SIZE) and after aligning the pointer and changing the length parameter to mprotect to the page size this works, and lets you write over your pointer to main.

As others have said, this is a bad way to dynamically load code. Dynamic libraries, or plugins, are the preferred method.




回答2:


In principle it is possible, in practice your operating system will protect itself from your dangerous code!

Self-modifying code may have been regarded as a "neat-trick" in the days when computers had very tiny memories (in the 1950's). It later (when it was no longer necessary) came to be regarded as bad practice - resulting in code that was hard to maintain and debug.

In more modern systems (at the end of the 20th Century) it became a behaviour indicative of viruses and malware. As a consequence all modern desktop operating systems disallow modification of the code space of a program and also prevent execution of code injected into data space. Modern systems with an MMU can mark memory regions as read-only, and not-executable for example.

The simpler question of how to obtain the address of the code space - that is simple. A function pointer value for example is generally the address of the function:

int main()
{
    printf( "Address of main() = %p\n", (void*)main ) ;
}

Note also that on a modern system this address will be a virtual rather then physical address.




回答3:


The most straightforward and practical way to accomplish this is to use function pointers. You can declare a pointer such as:

void (*contextual_proc)(void) = default_proc;

Then call it with the syntax contextual_proc();. You can also assign a different function with the same signature to contextual_proc, say contextual_proc = proc_that_logs;, and any code that calls contextual_proc() will then (modulo thread-safety) call the new code instead.

This is a lot like self-modifying code in effect, but it is easier to understand, portable, and actually works on modern CPUs where executable memory is not writable and instructions are cached.

In C++, you would use subclasses for this; static dispatch will implement it the same way under the hood.




回答4:


On most operating systems (Linux, Windows, Android, MacOSX, etc...), a program don't execute (directly) in RAM but has its virtual address space and runs in it (stricto sensu, the code is not -always or necessarily- in RAM; you can have code which is not in RAM and which gets executed, after some page fault bring it transparently in RAM). The RAM is (directly) managed by the OS, but your process only sees its virtual address space (initialized at execve(2) time and modified with mmap(2), munmap, mprotect, mlock(2)...). Use proc(5) and try cat /proc/$$/maps in a Linux shell to understand more the virtual address space of your shell process. On Linux, you could query the virtual address space of your process by reading the /proc/self/maps file (sequentially, it is a textual pseudo-file).

Read Operating Systems: Thee Easy Pieces to learn more about OSes.


In practice, if you want to augment the code inside your program (running on some common OS) you'll better use plugins and the dynamic loading facilities. On Linux and POSIX systems you'll use dlopen(3) (which uses mmap etc...) then with dlsym(3) you'll obtain the (virtual) address of some new function and you could call it (by storing it in some function pointer of your C code).

You don't really define what a program is. I claim that a program is not only an executable, but also made of other resources (such as specific libraries, perhaps fonts or configuration files, etc...) and that is why when you install some program, quite often much more than the executable is moved or copied (look into what make install does for most free software programs even as simple as GNU coreutils). Therefore, a program (on Linux) which generates some C code (e.g. in some temporary file /tmp/genecode.c), compiles that C code into a plugin /tmp/geneplug.so (by running gcc -Wall -O -fPIC /tmp/genecode.c -o /tmp/geneplug.so), then dlopen that /tmp/geneplug.so plugin is genuinely modifying itself. And if you code in C exclusively that is a sane way of writing self-modifying programs.

Generally, your machine code sits in the code segment, and that code segment is read-only (and sometimes even execute-only; read about the NX bit). If you really want to overwrite code (and not to extend it), you'll need to use facilities (perhaps mprotect(2) on Linux) to change that permissions and enable rewriting inside the code segment.

Once some part of your code segment is writable, you could overwrite it.

Consider also some JIT-compiling libraries, such as libgccjit or asmjit (and others), to generate machine code in memory.

When you execve a new fresh executable, most of its code does not (yet) sit in RAM. But (from the point view of user code in the application) you can run it (and the kernel will transparently, but lazily, bring code pages into RAM, thru demand paging). That is what I try to explain by saying that your program run in its virtual address space (not directly in RAM). An entire book is needed to explain that further.

For example, if you have a huge executable (for simplicity, assume it is statically linked) of one gigabyte. When you start that executable (with execve) the entire gigabyte is not brought into RAM. If your program exits quickly, most of the gigabyte have not been brought into RAM and stays on the disk. Even if your program runs for a long time, but never calls a huge routine of a hundred megabyte of code, that code part (the 100Mbyte of the never used routine) won't be in RAM.


BTW, stricto sensu, self modifying code is rarely used these days (and current processors don't even handle that efficiently, e.g. because of caches and branch predictors). So in practice, you don't modify exactly your machine code (even if that would be possible).

And malware don't have to modify the currently executed code. It could (and often does) inject new code in memory and jumps somehow to it (more precisely, call it thru some function pointer). So in general you don't overwrite existing "actively used" code, you create new code elsewhere and you call it or jump to it.

If you want to create new code elsewhere in C, plugin facilities (e.g. dlopen and dlsym on Linux), or JIT libraries, are more than enough.

Notice that the mention of "changing your program" or "writing code" is very ambiguous in your question.

You might just want to extend the code of your program (and then using plugin techniques, or JIT-compilation libraries, is relevant). Notice that some programs (e.g. SBCL) are able to generate machine code at every user interaction.

You could change the existing code of your program, but then you should explain what that exactly means (what does "code" mean for you exactly ? Is it only the currently executed machine instruction or is it the entire code segment of your program?). Do you think of self-modifying code, of generating new code, of dynamic software updating?

Can I somehow get access to it, and read or even write to it?

Of course yes. You need to change protection in your virtual address space for your code (e.g. with mprotect) and then to write many bytes on some "old code" part. Why would you want to do that is a different story (and you have not explained why). I don't see any educational purposes in doing that -you are likely to crash your program quite quickly (unless you take a lot of precautions to write good enough machine code in memory).

I am a great fan of metaprogramming but I generally generate some new code and jump into it. On our current machines, I see no value in overwriting existing code. And (on Linux), my manydl.c program demonstrates that you could generate C code, compile, and dynamically link more than a million plugins (and dlopen all of them) in a single program. In practice, on current laptop or desktop computers, you can generate a lot of new code (before being concerned by limits). And C is fast enough (both in compilation time and in run time) that you could generate a thousands of C lines at every user interaction (so several times per second), compile and dynamically load it (I did that ten years ago in my defunct GCC MELT project).

If you want to overwrite executable files on disk (I see no value in doing that, it is much simpler to create fresh executables), you need to understand deeply their structure. For Linux, dive into the specifications of ELF.


In the edited question, you forgot to test against failure of mprotect. It is probably failing (because 4098 is not a power of 2 and a page multiple). So please at least code:

int c = mprotect(p, 4096, PROT_READ | PROT_WRITE | PROT_EXEC);
if (c) { perror("mprotect"); exit(EXIT_FAILURE); };

Even with the 4096 (instead of 4098) that mprotect is likely to fail with EINVAL, because main is probably not aligned to a 4K page. (Don't forget that your executable also contains crt0 code).

BTW, for educational purposes, you should add the following code near the start of your main:

 char cmdbuf[80];
 snprintf (cmdbuf, sizeof(cmdbuf), "/bin/cat /proc/%d/maps", (int)getpid());
 fflush(NULL);
 if (system(cmdbuf)) 
   { fprintf(stderr, "failed to run %s\n", cmdbuf); exit(EXIT_FAILURE));

and you could add a similar code chunk near the end. You might replace the snprintf format string for cmdbuf with "pmap %d".



来源:https://stackoverflow.com/questions/52238441/c-how-to-change-my-own-program-in-my-program-in-runtime

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!