본문 바로가기
System Hacking/해커스쿨 F.T.Z Hacking Zone

해커스쿨 F.T.Z Level 20

by En_Geon 2020. 3. 2.

level 20 로그인 후 hint를 본다.

 

level 20 hint

 

bleh 배열 80byte, fgets에서 79byte를 받는다. BOF를 할 수 없는 조건이다.

다른 방법을 찾으면 level 11에서 링크로 대체했던 포맷 스트링 버그(FSB)를 발생시킬 수 있다.

 

 

포맷 스트링 버그(Format String Bug, FSB)

 

포맷 스트링 버그는 printf 함수에서 발생하는데 printf 함수를 사용할 때 포맷 스트링을 사용하지 않고 위 hint와 같이

print(bleh)를 사용하면 취약점이 일어난다.

이때 입력 값을 포맷 스트링으로 넣으면 입력 값을 문자로 취급하는 게 아닌, 서식문자로 취급한다.

 

포맷 스트링(Format String)

 

parameter 변수 형식
%d 정수형 10진수 상수 (int)
%f 실수형 상수 (float)
%lf 실수형 상수 (double)
%c 문자 값 (char)
%s 문자열 (string)
%u 양의 정수 (10진수)
%o 양의 정수 (8진수)
%x 양의 정수 (16진수)
%n *int(쓰인 총 바이트 수) 4byte
%hn %n의 반인 2byte 단위

 

 

포맷 스트링 입력

 

입력 값을 포맷 스트링으로 넣으면 메모리의 다음 4바이트 위치를 참조하여 포맷 스트링의 기능대로 출력하는 걸 볼 수 있다. 그런데 포맷 스트링을 4번 넣었더니 A의 16진수가 나왔다. bleh[80]이 읽힌 것이다.

 

여기서 필요한 포맷 스트링은 "%n"이다. "%n"은 "%n"이 나오기 전에 출력된 자릿수를 계산하여 스택의 다음 4바이트에 있는 내용을 주소로 여기고 그 주소에 계산한 자릿수 즉 숫자를 입력한다.

 

"%n" 앞에 다른 포맷 스트링을 이용하여 "%n"이 인식하는 자릿수를 지정할 수도 있다.

예를 들어 "%100c%n" 한다면 100이라는 숫자를 넣는다.

이것을 보면 우리가 원하는 메모리 주소에 원하는 내용을 쓸 수 있다는 것이다.

 

이제 "%n"을 통해 쉘코드를 ret에 넣으면 되는데 "%n"은 자릿수를 10진수로 넣기 때문에 쉘코드 주소를 10진수로 바꿔서 넣어야 한다.

 

gdb로 attackme를 열어본다.

 

gdb attackme

 

gdb로 열어봤지만, main symbol이 없다며 gdb로 분석을 할 수가 없다.

이렇게 되면 ret의 주솟값을 찾기가 힘들다. 이럴때는 .dtors를 쓴다.

 

 

.dtors

 

gcc에는 constructors와 destructors 속성이 있다. 

constructor(.ctors) 속성의 함수는 main() 함수 전에 실행되고, destructor(.dtors) 속성의 함수는 main() 함수 종료 후에 실행된다.

destructor 속성 함수는 exit()에 의한 종료 시에도 실행된다. 

그러므로 .dtors의 주소를 가지고 쉘코드를 입력한다.

 

.dtors 주소

 

.dtors의 주소는 08049594가 되고, .dtors 주소 +4는 리턴 값의 주소가 된다.

그래서 08049598 주소에 쉘코드를 넣어야 한다.

 

쉘코드의 주소를 알아온다.

 

쉘코드 주소

 

위에서 말했듯이 %c와 %n을 써야 하는데 10진수로 받기 때문에 쉘코드의 주소를 10진수로 바꿔야 한다.

bffff4d0을 10진수로 바꾸면 3,221,222,840이 되는데 이 숫자는 int형을 벗어난 숫자다. 32bit PC에서는 이와 같은 큰 수는 입력할 수 없다. 따라서 여기서는 하위 주소와 상위 주소를 나눠서 사용하게 된다.

 

bfff4d0 4byte를 2byte씩 나눠서 사용한다.

.dtors 주소인 08049598에 "f4d0" 2byte를 넣고 .dtros 주소보다 2byte 큰 주소인 0804959a에 "bfff" 2byte를 넣어주면 된다.

 

"f4d0"을 10진수로 바꾸면 62672이 되고, "bfff"를 10진수‬로 바꾸면 49151‬이 된다.

 

위 설명 들을 반영한 코드를 입력하면 다음과 같다.

 

 

더보기

 

"AAAA\x98\x95\x04\x08AAAA\x9a\x95\x04\x08%x%x%x%62672c%n%49151c%n"

 

그러나 이 코드는 덜 완벽하다. 

 

"AAAA\x98\x95\x04\x08AAAA\x9a\x95\x04\x08%x%x%x%62672c%n%49151c%n"

 

빨간 부분에서 완벽하지 않다.

"%x"는 메모리에 어떤 값이 있느냐에 따라서 출력 결과가 다르게 나오기 때문에 출력 값을 정해줘야 한다.

"%8x"로 8byte로 정해주면 작은 byte가 들어있어도 8byte 모두를 출력해준다.

 

그 뒤에 나오는 %n은 이전까지의 입력 용량을 저장하므로 62672에 맞춰주기 위해서는 %n 이전에 입력되는 값을 빼야 한다.

 

AAAA (4byte) + \x90\x95\x04\x08 (4byte) + AAAA (4byte) + \x9a\x95\x04\x08 (4byte) + %8x (8byte) + %8x (8byte) + %8x (8byte)

즉, "4 + 4 + 4 + 4 + 8 + 8 + 8" 40을 빼고 넣어줘야 한다. 그래서 첫 번째 %n에는 62632이 들어간다.

마찬가지로 뒤에 49151도 변경해야 한다. 이전의 출력된 양이 62672이므로 49151에서 62672를 빼야 하는데 음수가 나오기 때문에 1bfff에서 f4d0을 뺀 값인 52015를 넣어주면 된다.

 

최종적으로 나온 코드 다음과 같다.

 

"AAAA\x98\x95\x04\x08AAAA\x9a\x95\x04\x08%8x%8x%8x%62632c%n%52015c%n"

 

공격한다.

 

공격 실행

 

공격하면 몇 글자가 나오고 입력한 크기의 공백이 나오고 끝난다.

 

실행 후 공백

댓글