FTZ_level20
해킹/FTZ 11~20 2016. 1. 14. 04:10
level20의 힌트.
이 문제는 여태까지와 다르게 bof 문제가 아니다.
딱보면 알 수 있는 이유는 fgets 함수를 사용하여 크기를 체크하고 있기 때문이다.
그럼 이 프로그램은 어디서 취약점이 발생하냐 하면
printf(bleh);바로 이부분이다.
printf와 같이 %s, %c %x등 '포맷스트링'을 사용하는 함수들은 저렇게 포인터만으로 구성했을 때 포맷스트링 버그 취약점이 생긴다.
반드시 printf("포맷스트링", 변수);와 같이 사용해야 안전하다.
자 그럼 먼저 포맷스트링의 원리를 간단히 알아보자.
printf 함수가 포맷스트링을 찾는 방식을 설명하기 위한 그림이다.
맨 왼쪽 SFP의 더 좌측부터가 printf의 스텍이고 우측은 main의 스텍이다.
위와같이 printf 함수가 호출되면 ebp+8부분에 포맷스트링을 포함한 문장의 주소를 가지게 된다.
printf는 해당 주소의 내용을 "I'm "까지 출력하고 나서 %d를 감지하고는 문장주소의 4바이트 뒤에 들어있는 25라는 숫자를 찾아낸다.
그럼 저 문장 주소라고 되어있는 자리에 그냥 입력받은 문자열을 가르키는 포인터가 있는 경우 어떤 문제가 생길까?
일반적으로는 별 문제가 생기지 않지만 입력받은 문자열에 포맷스트링이 있는 경우에 문제가 생긴다.
실제로 attckme 프로그램에 포맷스트링 %x를 몇개 집어넣어 보자.
내가 입력한 aaaa가 0x61616161이라는 형태로 다시 튀어나왔다.
어째서 이런 현상이 발생하냐하면
이런 그림으로 설명할 수 있다.
printf는 bleh의 주소에서 %x를 만날때 마다 뒷쪽 4바이트를 읽어서 출력했고 네번째에 bleh의 주소까지 내려가서 aaaa까지 읽은 것이다.
그럼 이것만으로 어떻게 공격을 하느냐... 이것만으로는 불가능하고 %n이라는 놈에 대해서 알아야 한다.
이 %n이라는 포맷스트링은 정말 특이한 역할을 한다.
%n은 자신 앞까지 문장의 자릿수를 센 다음 위의 과정과 마찬가지로 4바이트 뒤에서 주소를 찾는다.
그리고 해당 주소에 그 자릿수를 집어넣는다.
그러니까 예컨대 저 마지막 네번째 %x 대신 %n이 들어갔다면 0x61616161이라는 주소에 %n까지의 자리수를 적어넣을 것이다.
그럼 aaaa대신에 내가 원하는 주소를 넣어주면 해당주소의 값을 변조할 수 있는 것이다.
그렇다면 그 주소에 들어가는 숫자, 즉 자릿수는 어떻게 맞춰줄 것이냐? %123c 이런식으로 하면 123자리를 인위적으로 만들 수 있다.
어떤 주소를 덮어야 할까?? 만약 main함수가 좀더 길어서 뒤에 함수가 더 있다면 GOT 부분을 덮어써도 좋겠지만... 이 프로그램은 printf 이후에 바로 종료되어 버리니 main함수가 종료되고 나서 실행되는 .dtors부분을 덮어쓰면 되겠다.
.dtors에 대해서는 추가적으로 공부할 필요를 느끼는데... 일단은 objdump -h로 알아낼 수 있다.
여기서 두번째에 쓰여있는 0x08049594부분이 dtors의 주소이다.
해당 부분을 gdb로 보면..
이렇게 나온다. 이 attackme 프로그램에선 심볼을 다 지워놔서 setreuid+4764 이렇게 나오지만 실제로는 .dtors 부분이다.
보이는것 처럼 0xffffffff 뒤에 0x00000000이 나온다. 이게 심볼과 함께 보면 __DTOR_LIST__ 라고 나오는데 아마 리스트로 구성되고 0xffffffff부분이 구분선의 역할을 하는 것 같다.
그래서 실제 덮어써야 할 주소는 0x08049594가 아닌 그 4바이트 뒤에 널로 채워져 있는 0x08049598주소가 되겠다.
그럼 쉘코드의 주소를 넣기 위해서 환경변수에 쉘코드를 올려놓고 주소를 확인해보자.
10000개의 nop+ 41바이트 쉘코드를 올려놓고 주소가 0xbfffd50a인 것을 확인했다.
+5000정도를 해서 nop의 중간지점인 0xbfffe892를 넣도록 하자.
자 그럼 목적이 확실해졌다.
포맷스트링 버그를 이용하여 .dtors+4주소인 0x08049598에 쉘코드 주소인 0xbfffe892를 집어넣는다.
이제 방법론, 즉 포맷스트링 버그를 어떻게 이용할까의 문제다.
먼저 작은 문제가 하나 있다. 집어넣을 숫자인 0xbfffe892가 int 자료형의 범위보다 커지는 것이다. 결국 2바이트씩 2번에 걸쳐서 넣는 방법을 쓰는데..
뭐 %n을 사용해서 뒷쪽 2바이트가 삐져나오지만 4바이트씩 2바이트 겹치게 두번 쓰는 방법도 있고... %hn을 사용해서 2바이트씩 쓰는 방법도 있다.
%hn을 이용해서 2바이트씩 두번 넣는 방법을 사용하기로 했다. %hn은 %n과 똑같은 기능을 하면서 short 자료형으로 써서 2바이트만 쓰는것이 가능하다.
조금 복잡해보이지만... 0xbfff 보다 0xe892가 크므로 먼저 작은 0xbfff를 0x0804959a에 집어넣고 그다음에 0x08049598에 0xe892를 집어넣으면 최종적으로
0x92 0xe8 0xff 0xbf 로 메모리에 들어가서 0xbfffe892가 만들어질 것이다. 작은수를 먼저 넣는 이유는 계산을 편하게 하기 위해서다. 뒤에 구성을 보면 이해가 간다.
0x0804959a||더미 4바이트||0x08049598||%8x||%8x||%49123c||%hn||%10387c||%hn
구성한 페이로드는 위와 같다.
아까 %x를 attackme에 넣어봤을 때 네번째 %x에 첫 입력값인 0x61616161이 나왔었다.
그러므로 위 페이로드에서는 네번째 포맷스트링인 %hn이 첫 입력값인 0x0804959a의 주소에 앞의 자리수 4+4+4+8+8+49123=49151=0xbfff를 입력할 것이다.
마찬가지로 여섯번째 포맷스트링인 두번째 %hn은 거기에서 4*2=8바이트 뒤에 쓰여진 0x08049598라는 주소에 앞의 자리수를 더한 0xbfff+10387=0xe892를 입력할 것이다.
설명은 이해가 쉽게 역순으로 했지만 사실 0xbfff를 만들기 위해서 0xbfff-(4+4+4+8+8)=49123 이런식으로 %c에 들어갈 숫자를 구하는 것이다.
마찬가지로 두번째로 %c에 들어갈 숫자는 0xe892-0xbfff=10387 로 구해줬다.
이게 대체 왜 이렇게 되는지 처음엔 이해가 잘 안갔지만... 천천히 들여다보면 조금씩 이해가 된다. %n은 출력이 없으므로 자리수를 먹지 않는다.
위 구성대로 페이로드를 짜봤다.
(python -c 'print "\x9a\x95\x04\x08"+"aaaa"+"\x98\x95\x04\x08"+"%8x"*2+"%49123c"+"%hn"+"%10387c"+"%hn"';cat)|./attackme
......
공격에 성공했다!!
이로써 기나긴 FTZ 포스팅을 마친다... 몰아쳐서 하느라 고생이 많았던 나에게 영광을 바친다.
끝-
'해킹 > FTZ 11~20' 카테고리의 다른 글
FTZ_level19 (0) | 2016.01.13 |
---|---|
FTZ_level18 (0) | 2016.01.13 |
FTZ_level17 (0) | 2016.01.13 |
FTZ_level16 (0) | 2016.01.13 |
FTZ_level15 (0) | 2016.01.13 |