source

char 배열에 저장된 머신 코드를 호출하려면 어떻게 해야 합니다.

gigabyte 2022. 8. 15. 21:25
반응형

char 배열에 저장된 머신 코드를 호출하려면 어떻게 해야 합니다.

네이티브 기계어 코드를 호출하려고 합니다.지금까지의 정보는 다음과 같습니다(버스 에러가 표시됨).

char prog[] = {'\xc3'}; // x86 ret instruction

int main()
{
    typedef double (*dfunc)();

    dfunc d = (dfunc)(&prog[0]);
    (*d)();
    return 0;
}

함수를 올바르게 호출하여 재지침에 도달합니다.그러나 ret 명령을 실행하려고 하면 SIGBUS 오류가 발생합니다.실행이 허가되지 않은 페이지에서 코드를 실행하고 있기 때문입니까?

그럼 내가 여기서 뭘 잘못하고 있는 거지?

memprotect를 호출하여 프로그램이 존재하는 페이지를 실행 가능하게 해야 합니다.다음 코드는 이 콜을 발신하여 텍스트를 프로그래밍 방식으로 실행할 수 있습니다.

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

char prog[] = {
   0x55,             // push   %rbp
   0x48, 0x89, 0xe5, // mov    %rsp,%rbp
   0xf2, 0x0f, 0x10, 0x05, 0x00, 0x00, 0x00,
       //movsd  0x0(%rip),%xmm0        # c <x+0xc>
   0x00,
   0x5d,             // pop    %rbp
   0xc3,             // retq
};

int main()
{
    long pagesize = sysconf(_SC_PAGE_SIZE);
    long page_no = (long)prog/pagesize;
    int res = mprotect((void*)(page_no*pagesize), (long)page_no+sizeof(prog), PROT_EXEC|PROT_READ|PROT_WRITE);
    if(res)
    {
        fprintf(stderr, "mprotect error:%d\n", res);
        return 1;
    }
    typedef double (*dfunc)(void);

    dfunc d = (dfunc)(&prog[0]);
    double x = (*d)();
    printf("x=%f\n", x);
    fflush(stdout);
    return 0;
}

첫 번째 문제 중 하나는 프로그램 데이터가 저장되어 있는 위치가 실행 가능하지 않다는 것입니다.

Linux 에서는 적어도 결과 바이너리가 글로벌 변수의 내용을 "데이터" 세그먼트 또는 여기에 배치합니다.이것은 대부분의 경우 실행할 수 없습니다.

두 번째 문제는 호출하고 있는 코드가 유효하지 않다는 것입니다.C 로 메서드를 호출하는 순서에는, 호출 규약이라고 불리는 것이 있습니다(예를 들면, 「cdecl」을 사용하고 있는 경우가 있습니다).호출된 함수가 단순히 "재시도"하는 것만으로는 충분하지 않을 수 있습니다.또한 스택 정리 등을 수행해야 할 수도 있습니다.그렇지 않으면 프로그램이 예기치 않게 동작합니다.첫 번째 문제를 해결하면 문제가 발생할 수 있습니다.

거의 모든 C 컴파일러에서 표준 어셈블리 언어를 코드에 포함시킴으로써 이 작업을 수행할 수 있습니다.물론 C에 대한 비표준 확장이지만 컴파일러 라이터는 이것이 종종 필요하다는 것을 인식하고 있습니다.비표준 확장자이므로 컴파일러 매뉴얼을 읽고 방법을 확인해야 하지만 GCC의 "asm" 확장자는 상당히 표준적인 접근법입니다.

 void DoCheck(uint32_t dwSomeValue)
 {
    uint32_t dwRes;

    // Assumes dwSomeValue is not zero.
    asm ("bsfl %1,%0"
      : "=r" (dwRes)
      : "r" (dwSomeValue)
      : "cc");

    assert(dwRes > 3);
 }

어셈블러에서 스택을 폐기하는 것은 쉽기 때문에 컴파일러를 사용하면 어셈블러의 일부로 사용할 레지스터를 식별할 수 있습니다.그러면 컴파일러는 그 함수의 나머지 부분을 이들 레지스터에서 제거할 수 있습니다.

어셈블러 코드를 직접 쓰는 경우 어셈블러를 바이트 배열로 설정할 이유가 없습니다.코드 냄새뿐만 아니라 C에 어셈블러를 삽입하는 올바른 방법인 "asm" 확장자를 모르기 때문에 발생할 수 있는 진정한 오류라고 생각합니다.

모두가 이미 말했듯, 당신은 반드시 그 일을prog[]실행 가능하지만 JIT 컴파일러를 쓰는 경우를 제외하고 적절한 방법은 링커 스크립트를 사용하거나 컴파일러가 허용하는 경우 C 코드에 섹션을 지정하여 실행 가능 영역에 기호를 넣는 것입니다.예를 들어 다음과 같습니다.

const char prog[] __attribute__((section(".text"))) = {...}

한 가지 명백한 오류는 이다\xc3를 반환하지 않습니다.double다시 돌아온다고 주장하시는군요

기본적으로 이것은 바이러스 작성자에게 공개적인 초대였기 때문에 단속되어 왔다.단, 스트레이트 C의 네이티브 머신코드로 할당 및 버퍼링하여 설정할 수 있습니다.문제 없습니다.문제는 그것을 부르는 것이다.버퍼의 주소를 사용하여 함수 포인터를 설정하고 호출할 수 있지만, 이 포인터는 동작하지 않을 가능성이 높으며, 원하는 것을 실행하도록 유도할 수 있다면 다음 버전의 컴파일러가 고장날 가능성이 매우 높습니다.따라서 가장 좋은 방법은 단순히 인라인어셈블리 비트에 의존하여 자동으로 생성된 코드로 돌아가도록 설정하는 것입니다.그러나 시스템이 이에 대해 보호한다면 Rudi가 답변에서 설명한 바와 같이 보호를 회피하는 방법을 찾아야 합니다(그러나 특정 시스템에 매우 특이합니다).

언급URL : https://stackoverflow.com/questions/39867396/how-to-call-machine-code-stored-in-char-array

반응형