2016-07-18 4 views
3

참조로 함수를 복사 할 수는 있지만 segfault를 생성하는 다음 코드에서 무슨 일이 벌어지고 있는지 이해하고 싶습니다.함수 포인터가있는 Memcpy는 segfault로 연결됩니다.

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

int return0() 
{ 
    return 0; 
} 

int main() 
{ 
    int (*r0c)(void) = malloc(100); 
    memcpy(r0c, return0, 100); 
    printf("Address of r0c is: %x\n", r0c); 
    printf("copied is: %d\n", (*r0c)()); 
    return 0; 
} 

다음은 내 생각에 작동해야하는 정신 모델입니다.

프로세스가 r0c에 할당 된 메모리를 소유합니다. 우리는 return0에 해당하는 데이터 세그먼트에서 데이터를 복사하고 있으며 복사가 성공적입니다.

함수 포인터를 역 참조하는 것이 함수 포인터가 가리키는 데이터 세그먼트를 호출하는 것과 같다고 생각했습니다. 이 경우 명령어 포인터는 함수 return0에 대한 명령어를 포함하는 r0c에 해당하는 데이터 세그먼트로 이동해야합니다. return0에 해당하는 바이너리 코드는 return0의 주소에 의존하는 임의의 점프 또는 함수 호출을 포함하지 않으므로 0을 반환하고 ip를 복원해야합니다 ... 100 바이트는 함수 포인터로는 충분하고 0xc3은 정상입니다 r0c의 범위 내 (바이트 11에 있음).

왜 세분화 오류입니까? 이것은 C의 함수 포인터의 의미에 대한 오해입니까, 아니면 내가 모르는 자체 수정 코드를 방지하는 보안 기능이 있습니까?

+0

'printf ("r0c의 주소는 : % x \ n", r0c);'잘 정의되지 않았습니다. – chux

+3

뭔가가 전체가 잘 정의되어 있지 않다는 것을 알려줍니다. – HolyBlackCat

+2

먼저 함수 (코드)는 실행 가능으로 표시된 메모리 세그먼트에 상주합니다. 할당 된 데이터는 특히 시스템에서 DEP (데이터 실행 방지)를 사용하는 경우 표시되지 않습니다. 데이터 세그먼트에있는 코드를 실행하려면 해당 데이터를 실행 가능으로 표시하는 방법을 알아야합니다. 두 번째로,'memcpy (r0c, return0, 100); '는 아마도 메모리의 끝에서부터 복사하고있을 것이다. 셋째, 코드를 포함하는 메모리 위치가 액세스로부터 보호 될 가능성이 높습니다. – GreatAndPowerfulOz

답변

6

malloc이 메모리를 할당하는 데 사용하는 메모리 페이지가 실행 가능으로 표시되어 있지 않습니다. 코드를 힙에 복사하여 실행할 수 없습니다.

이와 비슷한 작업을 원할 경우 운영 체제에 대해 더 깊이 들어가서 직접 페이지를 할당해야합니다. 그런 다음 실행 가능으로 표시해야합니다. 메모리 페이지에 실행 플래그를 설정할 수 있으려면 관리자 권한이 필요합니다.

그리고 정말 위험합니다. 프로그램에서이 작업을 수행하고 공격자가 우리의 프로그램을 사용하여 할당 된 메모리 페이지에 쓸 수있는 버그가 있으면 침입자는 관리자 권한을 얻어 컴퓨터를 제어 할 수 있습니다.


코드에 다른 문제가 있습니다. 예를 들어 함수에 대한 포인터가 모든 플랫폼의 일반 포인터로 잘 변환되지 않을 수 있습니다. 함수의 크기를 예측하거나 그렇지 않으면 얻는 것은 매우 어렵습니다 (비표준은 말할 것도 없습니다). 또한 코드 예제에서 포인터를 잘못 인쇄합니다. void *을 인쇄하려면 "%p" 형식을 사용하고 void *에 대한 포인터를 캐스팅해야합니다.

또한 int fun()과 같은 함수를 선언 할 때 인수를 사용하지 않는 함수를 선언하는 것과는 다릅니다. 인수를 사용하지 않는 함수를 선언하려면 int fun(void)과 같이 void을 명시 적으로 사용해야합니다.

+0

나는 단지 최소한의 예를 제공하려고 노력했을뿐입니다 ... 이것은 프로그램의 의미를 이해하는 것이 었습니다. –

+0

페이지를 실행 파일로 설정하는 경우 대부분의 운영 체제에서 관리자 권한이 필요하지 않습니다. – Dani

-1

Malloc은 할당 된 메모리 (귀하의 경우 100 바이트)에 대한 포인터를 반환합니다. 이 메모리 영역은 초기화되지 않습니다. 메모리가 CPU에 의해 실행될 수 있다고 가정하면 코드가 작동하기 위해 함수가 구현하는 실행 가능 명령어로 100 바이트를 채워야합니다 (실제로 100 바이트로 유지 될 수있는 경우). 그러나 지적 된 바와 같이 할당량은 텍스트 (프로그램) 세그먼트가 아니라 힙에 있으므로 명령으로 실행할 수 있다고 생각하지 않습니다.

int return0() 
{ 
    return 0; 
} 

typedef int (*r0c)(void); 

int main(void) 
{ 
    r0c pf = return0; 
    printf("Address of r0c is: %x\n", pf); 
    printf("copied is: %d\n", pf()); 
    return 0; 
} 
+0

답을 고맙게 생각하지만, 나는 참조로 함수를 호출 할 수 있다는 것을 알고 있다고 설명했다. 그냥 함수 포인터를 사용하여 실제 데이터를 실행할 수 있는지 여부를 알고 싶었습니다. 함수 포인터를 참조로 호출하는 것이 아닙니다. –

2

표준은 말한다 : 다음 객체에서

memcpy 기능이 복사 n 문자 객체가 가리키는으로 s2가 가리키는 아마도 이것은 당신이 원하는 무엇을 달성 할 것 s1.

[C2011, 7.24.2.1/2; 중점 추가]

표준 용어에서 함수는 "객체"가 아닙니다. 표준은 소스 포인터가 함수를 가리키는 경우에 대한 동작을 정의하지 않으므로 memcpy() 호출은 정의되지 않은 동작을 생성합니다.

또한 malloc()에 의해 반환 된 포인터는 개체 포인터입니다. C는 오브젝트 포인터를 함수 포인터로 직접 변환하는 기능을 제공하지 않으며 함수로 호출 할 오브젝트를 제공하지 않습니다. 중간의 정수 값을 사용하여 객체 포인터와 함수 포인터를 변환 할 수는 있지만, 그렇게하는 효과는 최소 구현 정의에서 두 배입니다. 어떤 경우에는 정의되지 않습니다.

다른 경우와 마찬가지로 UB가 원하는 동작으로 바뀔 수 있지만 그 것에 의존하는 것은 안전하지 않습니다. 이 특별한 경우에, 다른 대답은 에 대한 좋은 이유가 아니며은 당신이 바라는 행동을 기대합니다.

+0

이것은 모두 사실이지만 GCC는 느리며 segfault가 발생하지 않았습니다. –

+0

@AndrewSalmon, 반대로 - segfault * always *는 구현 정의 또는 정의되지 않은 동작에서 발생하며 정확히 어떤 작업이 프로그램에서 이러한 동작을 발생시키는 지 지적하고 있습니다. 구현 확장을 사용하여 원하는 동작을 얻을 수도 있지만 그렇게하면 본래 휴대 할 수 없습니다. 그럼에도 불구하고 그것은 귀하가 받아 들일 수 있습니다. –

0

일부 의견에서 말했듯이 데이터를 실행 가능하게 설정해야합니다. 이를 위해서는 데이터 보호를 변경하기 위해 OS와 통신해야합니다. Linux에서이 시스템 호출은 int mprotect(void* addr, size_t len, int prot)입니다 (http://man7.org/linux/man-pages/man2/mprotect.2.html 참조).

다음은 VirtualProtect를 사용하는 Windows 솔루션입니다.

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <stdint.h> 
#ifdef _WIN32 
#include <Windows.h> 
#endif 

int return0() 
{ 
    return 0; 
} 

int main() 
{ 
    int (*r0c)(void) = malloc(100); 
    memcpy((void*) r0c, (void*) return0, 100); 
    printf("Address of r0c is: %p\n", (void*) r0c); 
#ifdef _WIN32 
    long unsigned int out_protect; 
    if(!VirtualProtect((void*) r0c, 100, PAGE_EXECUTE_READWRITE, &out_protect)){ 
     puts("Failed to mark r0c as executable"); 
     exit(1); 
    } 
#endif 
    printf("copied is: %d\n", (*r0c)()); 
    return 0; 
} 

그리고 작동합니다.

관련 문제