해커스쿨 FTZ - 20단계

20번의 힌트이다. 기분 나쁘게 생긴 printf가 하나 있다. 그렇다. 이번 문제는 마지막 문제 답게 포맷스트링 버그를 활용해야 하는 문제이다!

printf는 print format이라는 뜻을 갖고있는데, 그말인즉 인자로 받은 문자를 포맷과 함께 '\0'이 나오기 전까지 계속 출력하는 기능을 갖고있다.
그런데 "%s"와 같은 형식이 아니라 배열의 주소를 생으로 줘버리니까, 우리가 마음대로 포맷을 넣을 수 있는 문제가 발생한다.


그런데 이게 무엇이 문제가 될까?

printf에서 포맷(%s나 %d)을 출력한다고 해보자.
그 포맷을 출력하기 위해서 인자 값을 참조할것이다.

함수의 경우에는 함수 호출 전 스택에 값을 푸시해두는것으로 인자를 전달한다.
그러니까, 함수 호출때 기준으로 스택의 제일 상단에 있는것이 printf함수가 인자로 간주하는 값이라는것이다.
(가끔 c언어 프로그래밍 초보들이 printf를 사용할때 포맷을 넣어두고 인자값을 넘기지 않았을때 이상한 값이 뜨곤 한다. 그게 printf호출기준으로 스택의 최 상단에 있던 값이다.)

그런데, 우리는 포맷스트링버그를 활용하기 위해 %n이라는 포맷을 알아야한다.
%n은 %n이 사용되기 직전에 사용된 포맷에 의해 출력된 문자들의 개수를 다음변수에 저장 시킬 수 있는 포맷이다.

main 스택의 상단에 위치해있던 값부터 하나하나 pop되면서 출력이 될텐데, printf의 인자값으로 받은 주소는 pop을 세번하면 있는곳(4*3=12)에 위치해있나보다.

FSB는 보통 .dtors 영역을 수정하는것으로 공격이 이루어진다. 이는 gcc 컴파일러의 특징이다.
gcc 컴파일러는 .ctors(constructor), .dtors(destructor)을 생성하고, 이것들은 각각 main 실행전에 실행된다는것, 그리고 main종료 후에 실행이 된다는 특징을 갖고있다. (우리가 그동안 BOF를 통해 건드렸던 RET영역이 이 .dtors를 호출하는 부분이다!)

아무튼 BOF를 못하니까 .dtors를 수정하도록 하자.
objdump라는 명령어를 통해 이 .dtors의 시작주소를 알아 볼 수 있다.




08049594. 이게 dtors의 주소이다. 그런데 이 값이 0이 아닐경우 dtors+4를 실행한다고 한다!
그래서 이 공간을 셸코드로 덮어씌워야한다.

그런데 주소의 숫자가 너무 커서 메모리를 덮어 쓸 수 없다. 그래서 2byte씩 나눠서 반씩 넣어야한다.
그래서 두개를 나눠 넣어야한다.

%n은 인자값으로 받은 주소를 덮어 씌운다고 했다. 그러니, 페이로드는 다음과 같아야 한다.
(아무값)*4+.dtors의 주소1+(아무값)*4+.dtors의 주소2+(%8x)*3+"%(주소1-앞에서 출력된 바이트 수)c%n"+"%1(주소2-앞에서 출력된 바이트 수)%n"

각각을 설명해주자면,
처음의 (아무값)*4: 뒤의 주소1c를 출력하기 위해서 필요한것이다.(포맷 하나 출력할때마다 pop되니까, %c를 하고 나서 메인 스택의 최상단이 .dtors의 주소여야 하니까.)
그리고 그 뒤에 %n을 통해서 .dtors의 주소1이 들어있는곳에 그동안 출력된 값의 크기가 들어가게 된다.

같은 이유로 아무값 네개를 넣고, .dtors의 두번째 주소를 넣는다.
%1(주소2-앞에서 출력된 바이트 수)%n"는주소를 두개씩 나눠 넣어야 하는 만큼, 주소2를 입력해주고 %n을 통해 넣어주는 역할을 한다. %n은 앞에서 출력된 바이트수를 인자값으로 받은 주소에 넣는 역할을 하니까, 내가 미리 알아낸 주소에 앞에 출력된 만큼을 빼주어야 한다.


(python -c 'print "AAAA\x98\x95\x04\x08AAAA\x9a\x95\x04\x08"+"%8x%8x%8x"+"%주소1c%n"+"%주소2c%n"'; cat)|./attackme​

이런식으로 말이다.

그래서 eggshell을 넣고 주소를 얻은 뒤,


굳굳.

Comments

Popular Posts