2012-07-16 9 views
4

저는 보안 관련 일을 공부하고 있습니다. 지금은 저의 스택으로 놀고 있습니다. 내가하고있는 일은 매우 사소한 것이어야한다. 스택을 실행하려고하지도 않는다. 64 비트 시스템에서 명령어 포인터를 제어 할 수 있다는 것을 보여 주기만하면된다. 나는 (NX-bit, ASLR, 또한 -fno-stack-protector -z execstack으로 컴파일하면서) 그걸 가지고 놀 수 있다는 것을 알고있는 모든 보호 메커니즘을 해제했다. 64 비트 어셈블리에 대한 많은 경험이 없으며 검색 및 실험 시간을 보내고 난 후에 누군가가 내가 겪고있는 문제에 대해 밝힐 수 있는지 궁금합니다.64 비트 스택 상주 버퍼 오버플로?

나는 경계 검사없이 문자열을 스택 상주 버퍼에 단순히 복사하는 프로그램 (아래 소스 코드)을 가지고 있습니다. 그러나 0x41 시리즈로 덮어 쓸 때 RIP가 0x4141414141414141로 설정되어있을 것으로 예상하고 대신 RBP가이 값으로 설정된다는 것을 알게됩니다. 세분화 오류가 발생하지만 RSP가 올바른 값으로 설정된 경우에도 RET 명령 실행시 RIP가이 (잘못된) 값으로 업데이트되지 않습니다. 심지어 GDB에서 RET 명령 바로 전에 RSP에서 일련의 0x41을 포함하는 읽을 수있는 메모리가 있는지 확인했습니다. 나는 LEAVE 명령이했다는 인상을

이었다

MOV (E) SP (E) BP

POP (E) BP 64 비트에 그러나

은 " LEAVEQ은 "명령 (유사) 할 것 같다

MOV의 RBP를 QWORD PTR은 [RSP]

나는 단순히 전에 모든 레지스터의 내용을 관찰에서이 작업을 수행 생각하고 있어요 d를 실행합니다. LEAVEQ는 여전히 RET 명령어의 컨텍스트 종속적 인 이름 인 것처럼 보이지만 (GDB의 디스어셈블러는 그것을 제공한다), 여전히 0xC9이므로.

그리고 RET 명령은 RBP 레지스터에서 뭔가를 수행하는 것처럼 보입니다. 아마도 역 참조를 할 것입니까? RET는 (유사에)했다는 것을 나는 인상이었다

MOV의 RIP을 QWORD PTR은 [RSP] 내가 언급하지만 같은

, 그것은 역 참조 RBP 것 같다, 나는 내가이 얻을 않기 때문에 생각하고 있어요 다른 레지스터에 부정한 값이 포함되어 있지 않은 경우의 세그멘테이션 폴트 프로그램에 대한

소스 코드 :

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

int vuln_function(int argc,char *argv[]) 
{ 
    char buffer[512]; 

    for(int i = 0; i < 512; i++) { 
     buffer[i] = 0x42; 
    } 

    printf("The buffer is at %p\n",buffer); 

    if(argc > 1) { 
     strcpy(buffer,argv[1]); 
    } 

    return 0; 
}  

int main(int argc,char *argv[]) 
{ 
    vuln_function(argc,argv); 

    return 0; 
} 

다음은 오버 플로우하기 전에, 그것이 디버거에서 볼이 쉽게하는 0x42와 버퍼의 법적 부분을 채우기 위해 단지가 루프 . 디버깅 세션의

발췌은 다음과 같습니다 버퍼가하는 0x42 년대 충전 한 후

(gdb) disas vulnerable 
Dump of assembler code for function vulnerable: 
    0x000000000040056c <+0>:  push rbp 
    0x000000000040056d <+1>:  mov rbp,rsp 
    0x0000000000400570 <+4>:  sub rsp,0x220 
    0x0000000000400577 <+11>: mov DWORD PTR [rbp-0x214],edi 
    0x000000000040057d <+17>: mov QWORD PTR [rbp-0x220],rsi 
    0x0000000000400584 <+24>: mov DWORD PTR [rbp-0x4],0x0 
    0x000000000040058b <+31>: jmp 0x40059e <vulnerable+50> 
    0x000000000040058d <+33>: mov eax,DWORD PTR [rbp-0x4] 
    0x0000000000400590 <+36>: cdqe 
    0x0000000000400592 <+38>: mov BYTE PTR [rbp+rax*1-0x210],0x42 
    0x000000000040059a <+46>: add DWORD PTR [rbp-0x4],0x1 
    0x000000000040059e <+50>: cmp DWORD PTR [rbp-0x4],0x1ff 
    0x00000000004005a5 <+57>: jle 0x40058d <vulnerable+33> 
    0x00000000004005a7 <+59>: lea rax,[rbp-0x210] 
    0x00000000004005ae <+66>: mov rsi,rax 
    0x00000000004005b1 <+69>: mov edi,0x40070c 
    0x00000000004005b6 <+74>: mov eax,0x0 
    0x00000000004005bb <+79>: call 0x4003d8 <[email protected]> 
    0x00000000004005c0 <+84>: cmp DWORD PTR [rbp-0x214],0x1 
    0x00000000004005c7 <+91>: jle 0x4005e9 <vulnerable+125> 
    0x00000000004005c9 <+93>: mov rax,QWORD PTR [rbp-0x220] 
    0x00000000004005d0 <+100>: add rax,0x8 
    0x00000000004005d4 <+104>: mov rdx,QWORD PTR [rax] 
    0x00000000004005d7 <+107>: lea rax,[rbp-0x210] 
    0x00000000004005de <+114>: mov rsi,rdx 
    0x00000000004005e1 <+117>: mov rdi,rax 
    0x00000000004005e4 <+120>: call 0x4003f8 <[email protected]> 
    0x00000000004005e9 <+125>: mov eax,0x0 
    0x00000000004005ee <+130>: leave 
    0x00000000004005ef <+131>: ret  

내가 바로의 strcpy() 호출하기 전에 휴식하지만.

(gdb) break *0x00000000004005e1 

이 프로그램은 인수로 650 0x41을의의와 실행,이 스택에 리턴 주소를 덮어을 충분히해야한다.

(gdb) run `perl -e 'print "A"x650'` 

반송 주소 0x00400610 (메인의 디스 어셈블리 참조)에서 메모리를 검색합니다.

(gdb) find $rsp, +1024, 0x00400610 
0x7fffffffda98 
1 pattern found. 

나는 X/200X와 메모리를 검사하고 나는 그 크기 때문에 생략 한 멋진 개요를,하지만 난 분명히 버퍼의 법적 크기를 나타내는는 0x42, 및 반환 주소를 볼 수 있습니다 .

0x7fffffffda90: 0xffffdab0  0x00007fff  0x00400610  0x00000000 

새로운 브레이크 포인트 단지()의 strcpy 후 :

(gdb) x/4x 0x7fffffffda90 
0x7fffffffda90: 0x41414141  0x41414141  0x41414141  0x41414141 
(gdb) x/4x $rsp   
0x7fffffffda98: 0x41414141  0x41414141  0x41414141  0x41414141 
:

(gdb) break *0x00000000004005e9 
(gdb) set disassemble-next-line on 
(gdb) si 
19 } 
=> 0x00000000004005ee <vulnerable+130>: c9  leave 
    0x00000000004005ef <vulnerable+131>: c3  ret  
(gdb) i r 
rax   0x0  0 
rbx   0x0  0 
rcx   0x4141414141414141  4702111234474983745 
rdx   0x414141 4276545 
rsi   0x7fffffffe17a 140737488347514 
rdi   0x7fffffffdb00 140737488345856 
rbp   0x7fffffffda90 0x7fffffffda90 
rsp   0x7fffffffd870 0x7fffffffd870 
r8    0x1  1 
r9    0x270 624 
r10   0x6  6 
r11   0x7ffff7b9fff0 140737349550064 
r12   0x400410 4195344 
r13   0x7fffffffdb90 140737488346000 
r14   0x0  0 
r15   0x0  0 
rip   0x4005ee 0x4005ee <vulnerable+130> 

    0x00000000004005ee <vulnerable+130>: c9  leave 
=> 0x00000000004005ef <vulnerable+131>: c3  ret  
(gdb) i r 
rax   0x0  0 
rbx   0x0  0 
rcx   0x4141414141414141  4702111234474983745 
rdx   0x414141 4276545 
rsi   0x7fffffffe17a 140737488347514 
rdi   0x7fffffffdb00 140737488345856 
rbp   0x4141414141414141  0x4141414141414141 
rsp   0x7fffffffda98 0x7fffffffda98 
r8    0x1  1 
r9    0x270 624 
r10   0x6  6 
r11   0x7ffff7b9fff0 140737349550064 
r12   0x400410 4195344 
r13   0x7fffffffdb90 140737488346000 
r14   0x0  0 
r15   0x0  0 
rip   0x4005ef 0x4005ef <vulnerable+131> 
(gdb) si 

Program received signal SIGSEGV, Segmentation fault. 
    0x00000000004005ee <vulnerable+130>: c9  leave 
=> 0x00000000004005ef <vulnerable+131>: c3  ret  
(gdb) i r 
rax   0x0  0 
rbx   0x0  0 
rcx   0x4141414141414141  4702111234474983745 
rdx   0x414141 4276545 
rsi   0x7fffffffe17a 140737488347514 
rdi   0x7fffffffdb00 140737488345856 
rbp   0x4141414141414141  0x4141414141414141 
rsp   0x7fffffffda98 0x7fffffffda98 
r8    0x1  1 
r9    0x270 624 
r10   0x6  6 
r11   0x7ffff7b9fff0 140737349550064 
r12   0x400410 4195344 
r13   0x7fffffffdb90 140737488346000 
r14   0x0  0 
r15   0x0  0 
rip   0x4005ef 0x4005ef <vulnerable+131> 

나는 반환 주소를 덮어 쓴 나는이 주소로 설정 얻을 RIP를 볼 것으로 예상해야하는지 확인

그러나 RIP는 분명합니다.

rip   0x4005ef 0x4005ef <vulnerable+131> 

내가 예상 한대로 RIP가 업데이트되지 않은 이유는 무엇입니까? LEAVEQ와 RETQ는 64 비트에서 실제로 무엇을합니까? 간단히 말해, 나는 무엇을 여기에서 놓치고 있냐? 어떤 차이가 있는지보기 위해 컴파일 할 때 컴파일러 인수를 생략하려고 시도했지만 아무런 차이가없는 것 같습니다.

답변

6

이 두 명령어는 예상 한대로 정확하게 수행하고 있습니다. rbp 전에했던 곳으로

mov rsp, rbp 
pop rpb 

이제 rsp 점 : 당신은 당신이 leaveq을 쳤을 때 그래서, 당신이하고있는 0x41 년대와 이전 스택 프레임을 덮어왔다. 그러나 메모리의 영역을 덮어 쓰기, 그래서 당신이 pop rbp을 수행 할 때, 하드웨어는 기본적으로이

mov rbp, [rsp] 
add rsp,1 

그러나 [rsp] 지금 0x41 '의의를 가지고하고있다. 따라서 이것이 rbp이 그 값으로 가득 차는 것을 보는 이유입니다. ret이 명령에 인출 0x41rip을 설정 한 후 예외 (페이지 오류)를 생성하기 때문에 당신이 기대처럼 rip가 설정되지 않습니다 이유에 대해서는

, 그것은이다. 이 경우에는 GDB에 의존해서 올바른 것을 보여주지 않을 것입니다. 반환 값을 프로그램의 텍스트 세그먼트 내에서 유효한 주소로 덮어 쓰려고 시도해야하며이 이상한 동작을 보지 못할 것입니다.

+0

leaveq가 이전에 할당 된 스택 공간을 해제하는 방법을 "알았"지 궁금해 할 때 답을 찾았습니다. 당신의 설명이 명확 해졌습니다. SP가 BP로 변경되어 스택 할당이 제거되면 새 BP가 스택에서 팝 아웃되고 함수가 시작될 때 POP가 팝되었습니다. 감사. – suprjami

2

x32에서 EIP 0 x 41414141의 크래시가 발생하는 이유는 프로그램이 이전에 저장된 EIP 값을 스택에서 꺼내 EIP로 다시 가져올 때 CPU가 메모리 주소 0 × 41414141에서 명령을 실행하려고하기 때문입니다. segfault가 발생합니다. (물론 실행 전에 페이지를 가져와야 함)

x64 실행 중에 프로그램이 이전에 저장된 RIP 값을 RIP 레지스터로 다시 팝하면 커널은 메모리 주소 0 x 4141414141414141에서 명령을 실행하려고 시도합니다. 첫째, 정규 형식 어드레싱으로 인해, 어떤 가상 주소의 비트 48에서 63까지는 (부호 확장과 유사한 방식으로) 비트 47의 사본이어야하며 그렇지 않으면 프로세서가 예외를 발생시킵니다. 이것이 문제가되지 않는다면 커널은 최대 사용자 공간 주소가 0x00007FFFFFFFFFF이기 때문에 페이지 폴트 처리기를 호출하기 전에 추가 검사를 수행합니다.

x32 아키텍처에서 주소는 페이지 폴트 처리기에 "유효성 검사"없이 전달되어 커널이 프로그램 segfault를 보내도록 트리거하는 페이지를로드하려고 시도하지만 x64는이 결과를 얻지 못합니다.

0 x 0000414141414141으로 RIP를 덮어 쓰면 커널이 사전 검사를 통과 한 후 페이지 폴트 처리기가 x32 케이스와 같이 호출되기 때문에 예상 값이 RIP에 놓이는 것을 볼 수 있습니다 프로그램을 중단시킵니다.)

2

"kch"및 "import os.boom.headshot"의 대답은 정확하지 않습니다.

실제로 일어나고있는 것은 RET 명령에 의해 RIP에 팝되는 스택의 값 (0x4141414141414141)에 프로세서의 '비표준'주소 범위에있는 주소가 포함되어 있다는 것입니다. 이로 인해 CPU는 커널 사전 점검에 의해 생성 된 오류가 아닌 일반 보호 오류 (GPF) 인터럽트를 생성합니다. GPF는 RIP가 실제로 업데이트되기 전에 커널이 세그멘테이션 오류를보고하도록 트리거합니다. 이것이 GDB에서 볼 수 있습니다.

대부분의 최신 CPU는 주소 범위 0x0000000000000000 ~ 0x00007FFFFFFFFFFF와 0xFFFF800000000 ~ 0xFFFFFFFFFFFFFFFF를 (를) 차지하는 상위와 하위 사이에 분할 된 48 비트 주소 범위 만 제공합니다. 자세한 내용은 this wikipedia link을 참조하십시오.

주소가 비표준 범위 (0x00008FFFFFFFFFFF에서 0xFFFF7FFFFFFFFFFF)를 벗어났다면 RIP가 예상대로 업데이트되었을 것입니다. 물론 새로운 주소가 다른 이유 (즉, 프로세스의 주소 범위를 벗어남)에 대해 유효하지 않은 경우 커널에서 후속 오류가 생성되었을 수 있습니다.