programing

C에서 자체 수정 코드를 작성하는 방법은?

oldcodes 2023. 10. 26. 21:39
반응형

C에서 자체 수정 코드를 작성하는 방법은?

변화가 미미하더라도 계속 변화하는 코드를 쓰고 싶습니다.

예를 들어, 아마도 어떤 것이


for i in 1 to  100, do 
begin
   x := 200
   for j in 200 downto 1, do
    begin
       do something
    end
end

첫 번째 반복 후에 코드가 라인을 변경해야 한다고 가정합니다.x := 200다른 방면으로x := 199다음 반복 후에 다음으로 변경합니다.x := 198등등.

그런 코드를 쓰는 것이 가능합니까?그것을 위해서 인라인 어셈블리를 사용해야 합니까?

편집 : C에서 하고 싶은 이유는 다음과 같습니다.

이 프로그램은 실험적인 운영 체제에서 실행될 예정인데, 다른 언어로 컴파일된 프로그램을 사용할 줄 모릅니다.이러한 코드가 필요한 진짜 이유는 이 코드가 가상 시스템의 게스트 운영 체제에서 실행되고 있기 때문입니다.하이퍼바이저는 코드 덩어리를 변환하는 이진 변환기입니다.번역기가 최적화 작업을 수행합니다.코드 덩어리를 한 번만 번역합니다.다음 번에 게스트에서 동일한 청크를 사용할 때 번역기는 이전에 번역된 결과를 사용합니다.코드가 즉시 수정되면 번역자는 이를 알아차리고 이전 번역이 오래된 것으로 표시합니다.따라서 동일한 코드의 재번역을 강제합니다.이것이 제가 이루고 싶은 것입니다, 번역가가 많은 번역을 하도록 강요하는 것입니다.일반적으로 이러한 청크는 분기 명령(점프 명령 등) 사이의 명령입니다.나는 단지 코드를 스스로 수정하는 것이 이것을 달성하는 환상적인 방법이 될 것이라고 생각합니다.

C에서 자체 수정 코드를 구축할 수 있는 가상 시스템을 작성하는 것을 고려해 볼 수 있습니다.

자체 수정 실행 파일을 작성하고자 할 경우 대상이 되는 운영 체제에 따라 많은 부분이 달라집니다.인메모리 프로그램 이미지를 수정하여 원하는 솔루션에 접근할 수 있습니다.그렇게 하려면 프로그램의 코드 바이트의 메모리 내 주소를 구해야 합니다.그러면 이 메모리 범위에서 운영 체제 보호를 조작하여 Access Violation(액세스 위반)이나 ''을 겪지 않고 바이트를 수정할 수 있습니다.SIG_SEGV"'.마지막으로, 컴파일된 프로그램의 opcode를 수정하기 위해 포인터("부호 문자 *" 포인터, RISC 기계에서와 같이 "부호 긴 *" 포인터)를 사용합니다.

핵심은 대상 아키텍처의 머신 코드를 수정한다는 것입니다.실행 중인 C 코드에 대한 표준 형식이 없습니다. C는 컴파일러에 대한 텍스트 입력 파일의 사양입니다.

죄송합니다. 답변이 조금 늦었습니다만, 찾으시는 물건을 정확히 찾은 것 같습니다. https://shanetully.com/2013/12/writing-a-self-mutating-x86_64-c-program/

이 기사에서는 스택에 어셈블리를 주입하여 상수 값을 변경합니다.그런 다음 스택에 있는 함수의 메모리를 수정하여 셸 코드를 실행합니다.

다음은 첫번째 코드입니다.

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

void foo(void);
int change_page_permissions_of_address(void *addr);

int main(void) {
    void *foo_addr = (void*)foo;

    // Change the permissions of the page that contains foo() to read, write, and execute
    // This assumes that foo() is fully contained by a single page
    if(change_page_permissions_of_address(foo_addr) == -1) {
        fprintf(stderr, "Error while changing page permissions of foo(): %s\n", strerror(errno));
        return 1;
    }

    // Call the unmodified foo()
    puts("Calling foo...");
    foo();

    // Change the immediate value in the addl instruction in foo() to 42
    unsigned char *instruction = (unsigned char*)foo_addr + 18;
    *instruction = 0x2A;

    // Call the modified foo()
    puts("Calling foo...");
    foo();

    return 0;
}

void foo(void) {
    int i=0;
    i++;
    printf("i: %d\n", i);
}

int change_page_permissions_of_address(void *addr) {
    // Move the pointer to the page boundary
    int page_size = getpagesize();
    addr -= (unsigned long)addr % page_size;

    if(mprotect(addr, page_size, PROT_READ | PROT_WRITE | PROT_EXEC) == -1) {
        return -1;
    }

    return 0;
}

가능하지만 대부분은 불가능할 수 있으며 실행 중인 코드에 대한 읽기 전용 메모리 세그먼트 및 OS에서 설치한 기타 장애물과 씨름해야 할 수도 있습니다.

이것은 좋은 출발이 될 것입니다.C의 본질적인 Lisp 기능:

http://nakkaya.com/2010/08/24/a-micro-manual-for-lisp-implemented-in-c/

필요한 자유도에 따라 함수 포인터를 사용하여 원하는 작업을 수행할 수 있습니다.의사 코드를 점핑 포인트로 사용하여 해당 변수를 수정하려는 경우를 고려합니다.x루프 인덱스와 같이 다양한 방법으로i변화들, 우리가 할 수 다음과 같습니다우리는 다음과 같은 일을 할 수 있습니다.

#include <stdio.h>

void multiply_x (int * x, int multiplier)
{
    *x *= multiplier;
}

void add_to_x (int * x, int increment)
{
    *x += increment;
}

int main (void)
{
    int x = 0;
    int i;

    void (*fp)(int *, int);

    for (i = 1; i < 6; ++i) {
            fp = (i % 2) ? add_to_x : multiply_x;

            fp(&x, i);

            printf("%d\n", x);
    }

    return 0;
}

프로그램을 컴파일하고 실행할 때의 출력은 다음과 같습니다.

1
2
5
20
25

분명히, 이것은 당신이 하고 싶은 일들의 수가 한정되어 있는 경우에만 효과가 있을 것입니다.x통과할 때마다 "자체 수정"에서 원하는 부분의 일부인 변경 사항을 지속적으로 변수를 전역 또는으로 설정해야 합니다."자체 수정"에서 원하는 부분의 일부인 변경 사항을 지속적으로 유지하려면 함수-포인트 변수를 전역 또는 정적으로 설정해야 합니다.이런 방법을 추천할 수 있을지 모르겠습니다. 왜냐하면 이런 방법을 수행하는 데는 종종 더 간단하고 명확한 방법이 있기 때문입니다.

이를 위해서는 (C처럼 딱딱하게 컴파일되고 연결되지 않은) 자기 해석 언어가 더 나을 수 있습니다.펄,자바스크립트,PHP는악을가지고있습니다.eval()목적에 맞는 기능을 제공합니다.그에 의해, 당신은 당신이 지속적으로 수정하고 실행하는 코드열을 가질 수 있습니다.eval().

LISP를 C에 구현한 후 사용하자는 의견은 휴대성 문제 때문에 확고합니다.하지만 정말 원한다면, 프로그램의 바이트 코드를 메모리에 로드한 다음 다시 메모리로 되돌리는 방식으로 많은 시스템에서 다른 방향으로 구현할 수도 있습니다.

몇 가지 방법이 있습니다.한 가지 방법은 버퍼 오버플로 공격을 통해서입니다.다른 하나는 mprotect()를 사용하여 코드 섹션을 쓰기 가능하게 만든 다음 컴파일러가 만든 함수를 수정하는 것입니다.

이와 같은 기술은 프로그래밍 문제와 난독화된 경쟁에 재미가 있지만 C가 정의되지 않은 동작을 이용한다는 사실과 코드를 읽을 수 없는 수준으로 결합하는 것을 고려할 때 프로덕션 환경에서는 이러한 기술을 사용하는 것이 가장 좋습니다.

표준 C11(읽기 n1570)에서는 자체 수정 코드를 작성할 수 없습니다(적어도 정의되지 않은 동작 없이).적어도 개념적으로는 코드 세그먼트는 읽기 전용입니다.

동적 연결기를 사용하여 플러그인으로 프로그램의 코드를 확장하는 것을 고려해 볼 수 있습니다.이를 위해서는 운영체제 고유의 기능이 필요합니다.POSIX에서는 dlopen(그리고 새로 로드된 함수 포인터를 가져오려면 dlsym)을 사용합니다.그런 다음 함수 포인터를 새 포인터의 주소로 덮어쓸 수 있습니다.

목표를 달성하기 위해 일부 JIT 컴파일 라이브러리(예: libgccjit 또는 asmjit)를 사용할 수도 있습니다.새 함수 주소를 가져와 함수 포인터에 입력합니다.

C 컴파일러는 주어진 함수 호출이나 점프에 대해 다양한 크기의 코드를 생성할 수 있으므로, 기계 특정한 방식으로 덮어쓰기도 부서지기 쉽다는 것을 기억하세요.

친구와 저는 코드를 스스로 수정하는 게임을 하다가 이 문제를 겪었습니다.사용자가 x86 어셈블리에서 코드 스니펫을 다시 쓸 수 있게 해줍니다.

이를 위해서는 어셈블러와 디어셈블러, 두 개의 라이브러리만 활용하면 됩니다.

FASM 어셈블러: https://github.com/ZenLulz/Fasm.NET

Udis86 분해기: https://github.com/vmt/udis86

우리는 디어셈블러를 사용하여 명령어를 읽고 사용자가 이를 편집하게 한 후 어셈블러를 사용하여 새로운 명령어를 바이트로 변환한 후 메모리에 다시 씁니다.쓰기-백(write-back)은 다음을 사용해야 합니다.VirtualProtect창에서 코드를 편집할 수 있도록 페이지 권한을 변경합니다.유닉스에서는 사용해야 합니다.mprotect대신.

샘플 코드뿐만 아니라 어떻게 했는지에 대해서도 글을 올렸습니다.

이러한 예는 C++를 사용하는 Windows에 있지만 크로스 플랫폼과 C만 만드는 것은 매우 쉬울 것입니다.

이것은 c++로 윈도우에서 하는 방법입니다.읽기/쓰기 방지 기능이 있는 바이트 배열을 VirtualAllocate하고, 거기에 코드를 복사하고, 읽기/실행 방지 기능이 있는 VirtualProtect를 실행해야 합니다.아무것도 하지 않고 돌아오는 함수를 동적으로 만드는 방법은 다음과 같습니다.

#include <cstdio>
#include <Memoryapi.h>
#include <windows.h>
using namespace std;
typedef unsigned char byte;

int main(int argc, char** argv){
    byte bytes [] = { 0x48, 0x31, 0xC0, 0x48, 0x83, 0xC0, 0x0F, 0xC3 }; //put code here
    //xor %rax, %rax
    //add %rax, 15
    //ret
    int size = sizeof(bytes);
    DWORD protect = PAGE_READWRITE;
    void* meth = VirtualAlloc(NULL, size, MEM_COMMIT, protect);
    byte* write = (byte*) meth;
    for(int i = 0; i < size; i++){
        write[i] = bytes[i];
    }
    if(VirtualProtect(meth, size, PAGE_EXECUTE_READ, &protect)){
        typedef int (*fptr)();
        fptr my_fptr = reinterpret_cast<fptr>(reinterpret_cast<long>(meth));
        int number = my_fptr();
        for(int i = 0; i < number; i++){
            printf("I will say this 15 times!\n");
        }
        return 0;
    } else{
        printf("Unable to VirtualProtect code with execute protection!\n");
        return 1;
    }
}

이 도구를 사용해서 코드를 조립합니다.

C에서 "진정한" 자체 수정 코드는 불가능하지만(조립 방식이 약간 속임수처럼 느껴지기 때문에, 이 시점에서 우리는 원래 질문이었던 C에서가 아니라 조립에서 자체 수정 코드를 쓰고 있기 때문에), 역설적으로 문장이 당신이 해야 할 일을 하지 않는 유사한 효과를 만드는 순수한 C 방법이 있을 수 있습니다.저는 역설적으로 ASM 자체 수정 코드와 다음의 C 스니펫 모두 표면적으로/직관적으로 말이 안 될 수도 있지만, 직관을 제쳐두고 논리적 분석을 하면 논리적이기 때문에 역설을 만드는 불일치를 말합니다.

#include <stdio.h>
#include <string.h>

int main()
{
    struct Foo
    {
        char a;
        char b[4];
    } foo;

    foo.a = 42;
    strncpy(foo.b, "foo", 3);
    printf("foo.a=%i, foo.b=\"%s\"\n", foo.a, foo.b);

    *(int*)&foo.a = 1918984746;
    printf("foo.a=%i, foo.b=\"%s\"\n", foo.a, foo.b);

    return 0;
}
$ gcc -o foo foo.c && ./foo
foo.a=42, foo.b="foo"
foo.a=42, foo.b="bar"

먼저, 우리는 다음의 값을 바꿉니다.foo.a그리고.foo.b구조물을 인쇄합니다.그러면 우리는 오직 값만 바꿉니다.foo.a, 출력을 관찰합니다.

언급URL : https://stackoverflow.com/questions/7447013/how-to-write-self-modifying-code-in-c

반응형