>> 소스코드
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | //written by andersonc0d3 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int main(int argc, char **argv){ FILE *fp = fopen("/levels/level10_alt.pass", "r"); // fopen 함수 내부에 malloc을 통해 heap영역 할당 struct {char pass[20], msg_err[20]} pwfile = {{0}}; //구조체가 선언 char ptr[0]; //ptr이라는 배열 포인터가 선언 if(!fp || argc != 2) return -1; fread(pwfile.pass, 1, 20, fp); // 구조체의 1부터 20만큼의 데이터를 읽음 pwfile.pass[19] = 0; ptr[atoi(argv[1])] = 0; // 입력된 argv[1] 을 정수형태로 변경하여 ptr에서 argv[1]만큼 떨어진 위치의 값을 0으로 변경 fread(pwfile.msg_err, 1, 19, fp); // 구조체의 21부터 19만큼을 읽음 fclose(fp); if(!strcmp(pwfile.pass, argv[1])) execl("/bin/sh", "sh", 0); else puts(pwfile.msg_err); return 0; } | cs |
>> 문제분석
문제에서는 구조체에 저장된 패스워드와 에러메세지를 파일포인터를 이용하여 읽는다.
그리고 파일을 읽는 fread 함수 중간에 ptr(atoi(argv[1])=0; 을 통해 1byte를 0으로 변경할 수 있도록 하고 있다.
이 문제는 특정 위치의 값을 0으로 변경 또는 덮어써서 pwfile.pass의 값을 알아내고
파라미터로 pwfile.pass의 값을 전달하여 shell을 얻는 문제이다.
>> 알아야할 개념
파일포인터와 파일포인터 구조
File 구조체는 /usr/include/libio.h (which is included in stdio.h) and struct defintion 에 정의되어 있다.
아래 명령어를 치면 Command 창에서 확인 할 수 있다. File은 _IO_FILE 구조체의 형태를 가지고 있다.
위 그림을 보면 File 은 _IO_read_base를 기준으로 _IO_read_ptr 위치의 값을 읽는 것을 알 수 있고
Library Buffer를 사용함을 알 수 있다.
>> 풀이
ASLR이 적용되어 있는 않은 경우 함수의 주소가 거의 변경되지 않음을 이용하여
/tmp 에 프로그램을 복사하여 ptr과 fp 구조체에서 _IO_read_base, _IO_read_ptr의 주소를 확인해보자.
파일구조체와 동일한 형태의 파일도 생성하고 파일의 경로도 수정한다.
A : 20개, C : 19개
주소 확인을 위해 아래 라인을 추가하였다.
printf("%p %p %p %p\n\n", fp, ptr, fp->_IO_read_base, fp->_IO_read_ptr);
Fp의 주소는 0x804a008 이고, library buffer의 base는 0xb7fde000 이다.
level10_alt.pass의 내용이 39자리로 0x27이기 때문에 현재 ptr의 주소는 0xb7fde027 임을 알 수 있다.
라이브러리 버퍼의 base에 내용 확인을 위해 ptr을 조작하는 라인을 추가하여 실행하면
아래와 같이 라이브러리 버퍼의 base라인부터 출력이 된다.
즉 _IO_read_ptr의 마지막 자리를 0으로 변경하여 password를 알아내야 한다.
브레이크를 걸어 Fp의 구조체를 확인해볼 수 있다.
Gdb를 이용하여 파일포인터의 위치에서 우리가 변경할 값이 4byte 떨어진 _IO_read_ptr 임을 확인할 수 있다.
0으로 변경해야할 위치는 0x804a008 + 4 = 0x804a00c 이고 fp 시작위치에서의 거리는
ptr[atoi(argv[1])] = 0;
prt[0] + argv[1] = 0x804a00c
gdb를 이용하여 ptr의 주소를 찾아보자.
위 구문은 atoi 된 결과가 저장된 eax의 값만큼 ptr의 주소가 저장된 ebp-0x58에서 떨어진 1 바이트를 0으로 바꾸는 명령이다.
여기에서 ptr은 ebp-0x58 위치에 저장되어 있음을 확인할 수 있다.
0xbffffc86 + argv[1] = 0x804a00c
argv[1] = 0x804a00c-0xbffffc86
FP는 heap영역을 사용하기 때문에 Bash shell을 이용하여 bruteforce로 근사치의 값을 적용해보자
for((i=1208262558;i<1208263558;i++)); do ./level10 $i | grep -v ACCESS ; done;
결과값을 넣어서 실행하면 여전히 ACCESS DENY가 나타난다.
!! 가 shell 명령의 특수기호이기 때문에 파일에 넣어 읽거나, python으로 전달해야 한다.
참고 : http://stackoverflow.com/questions/16424349/where-to-find-struct-io-file