level20

해커스쿨 FTZ 2018. 4. 4. 14:19

FTZ의 마지막. LEVEL20의 hint이다. 보면 bleh의 크기가 80이고 fgets에서 79바이트만 읽고 있기 때문에 BOF를 하는것은 불가능 해 보인다.


대신 그 아래 printf(bleh);를 자세히 보자.


평소에 c언어로 코딩을 할 때, 우리는 보통





처럼 사용해왔다. 여기서 %d, %s등의 표기를 포맷스트링이라고 한다.


%d : 정수형 10진수 상수(integer)

 

%f : 실수형 상수(float)

%lf : 실수형 상수(double)

 

%c : 문자 값(char)

%s : 문자 스트링((const)(unsigned) char *)

 

%u : 양의 정수(10진수)

%o : 양의 정수(8진수)

%x : 양의 정수(16진수)

 

%n : int *(쓰인 총 바이트 수를 저장할 정수형 포인터)

%hn : %n의 반인 2바이트 단위




왜 이런 형태로 코딩을 하여야 할까?


이유는 attackme의 코드에서 확실히 볼 수 있다.


한번 attackme에다가 포맷스트링을 넘겨보자.



보다시피 AAAA는 그대로 출력되지만 %x는 이상한 값이 나온다,


이는 %x에 해당하는 값이 전달되지 않았기 때문이다. %x는 값이 전달되지 않으면 알아서 현재주소에 4byte(1워드)만큼 스택에서 POP한다.


따라서 스택의 상태를 해커가 볼 수 있게 되는것이다.


한번 %x를 여러번 넣어보았다.



출력 4번쨰에서 41414141이 보인다. 0x41은 아스키코드로 'A'를 의미하므로 내가 지금 입력한 AAAA가 12바이트 떨어져 있다는 것을 알 수 있다.


이런 오류를 포맷스트링버그(Format String Bug,FSB)라고 부른다.



그럼 이것들을 가지고 어떻게 쉘 권한을 얻을 수 있을까?




이 버퍼에 값을 넣고 ret값을 수정하여도 가능하긴 하지만, 

Level20의 attackme는 gdb에서 disas main으로 열어볼 수가 없다.

따라서 기존 ret값을 구하기는 어렵다.


이런 부분에서 쓰이는 것이 있는데 .dtors라는 것이다.


리눅스에서 프로그램을 실행하면 자동으로 실행되는 것이 있다.

프로그램이 켜질때 .ctors ; 프로그램이 종료될 때 .dtors



따라서 우리는 .dtors를 덮어써서 쉘코드를 실행시킬 것이다.



우선 환경변수를 만든다.

'export SHELL=`python -c 'print "A"*20 + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"

그리고 환경변수의 주소를 얻는다.



컴파일 후 실행 해 보면



환경변수가 0xbffffc2a인것을 알았다.


그러면 이번엔 .dtors의 주소를 알아보자.

알아보는 명령어는 다음과 같다.


objdump -h [파일명] | grep .dtors



결과를 보면 두번째와 세번째가 같은것을 볼 수 있다. 이것이 .dtors의 주소이다.

(커널프로그램의 경우 두개가 다른 경우도 있다고 한다. 하지만 이번 문제와는 관련이 없으니 패스.)


그럼 저 .dtors에 쉘코드를 넣어보자.


공격하는 코드는

(python -c 'print "AAAA" + "[.dtors주소 + 4]" + "AAAA" + "[.dtors 주소 + 6]" + "%8x"*3 + "%[정수]c%n" + "%[정수]c%n"'; cat) | ./attackme

이다.


여기서 [정수]에 뭘 넣어야 할지 설명하기 전에 %[정수]c 뒤에 붙은 %n부터 알아보자


%n은 포맷스트링 중에 하나다. 위쪽 표를 잘 읽어본 사람은 이미 알겠지만 


%n : int *(쓰인 총 바이트 수를 저장할 정수형 포인터)


%n은 쓰인 총 바이트 수를 저장하는 역할을 한다. 즉



를 실행하면 count에 hackerschool의 글자수가 저장된다는 것이다.

이를 포맷스트링버그에 응용하면 %n이 앞의 입력된 바이트수를 현재 주소 다음 4byte에 저장하게 할 수 있다.


따라서 우리는 이 %n을 가지고 우리가 설정한 환경변수의 주소를 입력할 것이다.


환경변수의 주소가 0xbffffc2a인데 이는 십진수로 바꾸면3221224490로 4바이트로 표현할 범위를 넘어가 버린다. 이를 해결하기위해 우리는 상단의 반과 하단의 반을 나눠서 넣을 것이다.


뒤의 fc2a는 십진수로 64554이다.


위쪽에서 입력한대로 

(python -c 'print "AAAA" + "[.dtors주소 + 4]" + "AAAA" + "[.dtors 주소 + 6]" + "%8x"*3 + "%[정수]c%n" + "%[정수]c%n"'; cat) | ./attackme


를 입력하면

"AAAA" 4바이트 + .dtors 주소 4바이트 + "AAAA"4바이트 + ".dtors 주소 4바이트" + %8x *3 24바이트 

= 4 + 4 + 4 + 4 + 24 = 40이다


즉 앞쪽에 이미 40바이트가 입력되어 있었으므로 우리는 64554에서 40만큼을 빼줘야 한다. 즉 첫번째 [정수]는 64514가 될 것이다.


그렇다면 두번째 정수는 어떨까 0xBFFF는 49151로 이미 입력된 64554보다 작다. 

이를 해결하기위해 보수방식 계산을 한다.

BFFF앞에 1을 붙혀 1BFFF에서 64554를 빼는것이다.


계산결과 50133이 나온다.

즉 두번째 정수는 50133이 된다.

해당코드를 실행해 보면



처럼 클리어한 것이 나온다!


끄으읏!!!

(나중에 선배들에게 질문할게 많다)

'해커스쿨 FTZ' 카테고리의 다른 글

level19  (0) 2018.04.03
level18  (0) 2018.04.02
level17  (0) 2018.04.02
level16  (0) 2018.04.02
LEVEL15  (0) 2018.03.29
블로그 이미지

천재보다는 범재

,