ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [02-14 공부정리(FSB 정리)]
    공부정리 2018. 2. 15. 04:07

    오늘은 장장 5시간만에 FSB를 이해했다..

    시스템 공부를 어느정도 한 후에 FTZ를 풀이를 진행했다.

    12번까지 푸는데 뭔가 너무 허무한 것이다..

    11번과 12번을 둘 다 RTL을 이용해서 푸는데 RPG로 비교하면

    200제 무기로 슬라임잡는 기분이었다..


    FTZ를 다시 시작한 이유는 RTL과 ROP기법말고 다른 공격기법을 익히기 위해서 였는데

    정작 사용하는 공격기법을 보면 RTL만 사용하고 있는 나를 보고서

    " 이렇게 FTZ를 풀어봤자 무의미하겠구나.. " 라는 생각이 들었다.

    그래서 Level11번을 FSB로 다시 한번 풀어봤다.


    무식한 BOF만 익혀오다가 좀 계산적인 부분이 있는 FSB를 익히려니까 처음엔 이해가 정말 안됐다...ㅠ

    뭐 지금와서 보면 계산적이라고 할 것도 없지만,,

    정리가 굉장히 잘해주신 블로거분들이 많아서 이해하는데 굉장한 도움이 됐다.


    #참고한 자료#

    Eunice Story님 블로그 : [바로가기]

    단국대 SecureSW 김동진님 정리자료 :  Format String 공격.pdf


    나는 이 자료들에서 내가 이해하기 어려웠던 부분을 보완해서 정리해볼 생각이다.
    물론 내 기준이기때문에 저 문서들이 더 이해하기 편할수도 있다.
    자 그럼 정리를 시작해보자!




    [Format String Bug]


    우선 FSB취약점 소스코드를 보자


    [FSB_printf Code]

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #include <unistd.h>
    #include <string.h>
    int main(int argc,char **argv)
    {
            char str[64];
     
            strcpy(str,argv[1]);
     
            printf("%s\n",str);
            printf(str);
            printf("\n");
     
            return 0;
    }
    cs



    [FSB_printf 실행]

    보는 것처럼 두 printf( )의 결과는 같다.

    하지만 2번째 printf( )는 FSB의 취약점이 존재한다.


    [Format String]

     포맷 인자

     입력 타입

     출력타입

     %d

     값

     10진수

     %x

    값 

    16진수 

    %s

    포인터 

    문자열 

    %n 

    포인터 

    지금까지 출력된 바이트 수  [4Byte]

     %hn

    포인터 

    지금까지 출력된 바이트 수 [2Byte] 


    위에 표를 보면 문자열의 입력타입은 포인터이다.

    따라서, 저렇게 printf(str)로 인자를 넘겨주면, str의 시작주소가 넘어가게된다.

    우리가 Input값에 일반 문자가 아닌 [%d, %x]와 같은 포맷 인자들을 넘겨주면 실제로 printf( )에서는

    " printf("Hello World! %x %d %x"); " 이렇게 실행이 되는 것이다.

    직접 확인을 해보면

    첫 번째 포맷스트링을 사용한 printf( )는 %x를 일반 문자로 인식했지만

    두 번째로 str을 인자로 넘긴 printf( )는 %x를 포맷인자로 인식하고 그대로 실행한 모습을 볼 수 있다.


    [출력된 값 분석]

    AAAA : 입력한 Input값

    [bffff100] / [28] / [40015a38] : printf( )인자값공간과 str배열시작공간 사이의 dummy값

    41414141 : str배열에 저장된 "AAAA"의 16진수형태(%x)


    자 여기까지 FSB의 기본적인 설명은 끝났다.

    이제 FSB에서 공격용도로 사용하는 [%n / %hn]에 대해서 알아보도록 하자





    [formatN Code]

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #include <unistd.h>
     
    int main(int agrc, char **argv)
    {
            int x = 279;
            int y = 77;
            int pos;
            printf("%d %n%d\n",x,&pos,y);
            printf("Pos:%d\n",pos);
     
            return 0;
    }
     
    cs



    [formatN 실행]

    위 표에서도 말했다싶이 %n의 입력타입은 포인터이다.

    따라서, %n을 사용하면 pos주소에 반환값인 현재까지의 출력된 바이트 수(4)를 저장한다.

    여기서 4는 x의279(정수 3자리) + 공백 1자리를 더한 값이다.


    좀 더 심화된 예제를 보면


    [formatN_2 Code]

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #include <unistd.h>
     
    int main(int agrc, char **argv)
    {
            int pos_1,pos_2;
     
            printf("Hello! I'm py0zz1%n FSB is very simple!%n\n",&pos_1,&pos_2);
     
            printf("Pos_1:%d\nPos_2:%d\n",pos_1,pos_2);
     
            return 0;
    }
     
    cs


    [formatN_2 실행]

    pos_1에는 "Hello! I'm py0zz1"까지의 출력 바이트 수인 17이 저장되었고

    pos_2에는 앞의 문장의 문자수(17) + " FSB is very simple!"(20)이 더해진 37이 저장되었다.


    여기까지 내용을 정리해보면

    1. %n의 입력타입은 포인터로, 공간에 바이트수를 저장한다.


    2. %n이 여러개 있다고해서 중간중간 바이트수가 끊기는게 아니라 계속해서 중첩된다.


    %n 사용법도 익혔으니 본격적인 FSB공격에 대해서 알아보도록 하자 




    [FSB_Attack Code]

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #include <unistd.h>
    #include <string.h>
    int main(int argc, char **argv)
    {
            char buf[64];
            static int val=77;
     
            strcpy(buf,argv[1]);
            printf(buf);
     
            printf("\nval:%d\nval_p:%p / val_vp:%p\n",val,&val,val);
            return 0;
    }
     
    cs

    [FSB_Attack 실행]

    첫 째줄에는 입력한 값을 출력한다.

    둘째줄에는 변수val의 값을 출력한다.

    셋째줄에는 val의 주소값 / val의 16진수값을 출력한다.

    FSB공격으로 val의 값을 변조해보도록 하겠다.


    우선 FSB공격을 하려면 AAAA와 buf배열의 사이의 dummy값이 얼마인지 확인 해야한다.

    [dummy값 확인]

    총 4Byte * 3 = 12Byte만큼의 dummy값이 있음을 확인할 수 있다.


    그렇다면, 어떻게 val주소에 접근해서 안에 있는 값을 변조할 수 있을까?

    위에서 %n에 대응되는 입력값은 포인터. 즉, 주소값이라고 했다.

    따라서, %n 대응되는 값에 val의 주소값을 대응시키면 val주소에 현재까지 출력된 바이트수가 저장될 것이다.

    한번 확인 해보도록 하자


    [Val값 변조]

    자 보면 val의 값이 28로 바뀐 것을 확인할 수 있다.

    Input값 4Byte + %08x*3(24) = 28이 %n에 대응되는 [0x08049494] val의 주소에 저장된 것이다.


    여기서 이해가 잘 안되는 부분이 있을 것이다.

    왜 [%24x]이렇게 한번에 하지 않고, [%08x]*3 이렇게 따로 하는지에 대해 말이다.

    나 또한 여기서 이해하는데 좀 오래 걸렸는데 그림을 보면서 이해하니까 좀 더 쉽게 이해할 수 있었다.

    그림으로 이해를 돕자면


    [포맷 인자 대응표]

     %08x

    %08x 

    %08x 

    %n

    ④ 


                


    dummy 

    dummy 

    dummy 

    [0x08049494] 


    printf( )인자공간과 우리가 입력한 Input값 사이에는 dummy값들이 존재했다.

    따라서, 우리가 입력한 %n에 대응시키려면 앞에 dummy값들을 다른 포맷인자 대응시켜서 인자카운트를 올려줘야 한다.

    스택에 비유해서 말하자면 

    앞에 dummy값들을 POP(%x)해서 ESP(%n)가 변조할 주소값을 가리키도록 해야한다. 라고 말할 수 있다.


    그럼 이제 val값을 우리의 입맛대로 바꿀 수 있으니 이번에는 [804bbaa]로 변조해보도록 하자

    아까처럼 val주소에 3721182122(804bbaa)를 넣으면 될까?

    결과는 NO

    x앞에 범위 값은 4byte(-2,147,483,647~2,147,483,646)가 한계다.

    또, 4byte범위 안에 값이여도 cpu는 저렇게 큰값을 바로 인식하지 못한다.

    따라서, 저 큰값을 넣으려면 따로 조각을 내서 넣어야 한다.


    값을 2Byte씩 조각내서 넣기 위해선 중간중간에 %x와 대응되는 dummy값들이 필요하다.

    이 dummy값은 4Byte의 크기로 값은 아무거나 상관없다.

    나는 AAAA의 4Byte의 dummy값을 사용하겠다.


    [%x Default Size]

    170(aa)을 넣고, 187(bb)를 넣은 후, 04를 넣기위해선 그 260(104)를 넣었다.

    (앞에 1값은 다음 08부분이 덮어쓰기 때문에 상관없다.)

    그 후, 108을 넣기위해서 4를 더해서 넣어줬다. 과연 우리가 원하는 [0x0804bbaa]가 들어갔을까?


    [%x Default Size] 

    결과는 8이 더해진 [0x0c04bbaa]이다.

    그 이유는 %x의 기본 Byte가 8Byte이기 때문이다.

    따라서, 우리는 108이 아닌 208을 넣어야 한다.


    [0x0804bbaa Success]

    자 이렇게 넣으면 우리가 원하는 값인 [0x0804bbaa]가 들어간 것을 확인할 수 있다.




    [FSB Attack #단순화(1)] 

    우리는 위에서 Input값에 위치로 이동하기 위해 %x 포맷인자를 여러번 사용했었다.

    여기서 $기호를 사용하면 한번에 Input값으로 이동할 수 있다.

    사용법은 %[N]$n 이며, N만큼 위치를 이동한다.


    [FSB_Attack $사용]


    위와같이 주소값 중간중간 [AAAA]dummy값도 사용하지 않아도 되며, 바로바로 원하는 위치로 이동할 수 있다.





    [FSB Attack #단순화(2)]

    위 포맷스트링 표를 보면 %n아래에 %hn이라고 %n속성 포맷인자가 하나 더 존재했었다.


    [%hn]

    %hn은 값을 4Byte씩 저장하지 않고, 절반인 2Byte씩 저장한다.

    따라서, 우리가 [0x0804bbaa]를 저장하고 싶을 때, [0804] / [bbaa] 이렇게 2등분만 해서 저장하면 되는 것이다.

    바로 실습을 통해 알아보도록 하자


    [FSB_Attack 쇼트쓰기 단순화]

    [0x08049494]에 2Byte(bbaa)를 넣고, 2Byte 다음 주소인 [0x08049496]에 2Byte(19546)을 넣어서

    [ 48042(bbaa) + 19546 = 67588(10804) ]10804가 들어가도록 했다.

    결과를 보면, [0x0804bbaa]가 잘들어간것을 확인할 수 있다.

    또한, 페이로드의 길이도 처음과 비교 했을때 훨씬 짧아진것을 볼 수 있다.




    [FSB 보안대책]

    가장 기본적이고 궁극적인 보안 대책은 printf(str)같은 형태를 사용하지 않으면 된다.

    이런 프로그래머의 실수를 대비해서 OS자체에서도 보안을 강화했는데

    우선 지금 FSB공격 환경은 Redhat9버전이다.

    버전이 올라갈수록 printf( )와 str배열사이의 더미값의 갯수가 증가하며

    스택에 환경변수를 이용하여 쉘코드 공격을 하는 경우에 대비해

    스택의 환경변수를 랜덤하게 변하도록 패치를 해놓았다.

    언제나 그렇듯이 저렇게 패치를 해도 뚫린다. 아니 이미 뚫렸다.


    따라서, printf(str)과 같은 형태의 프로그래밍은 절대 하지말자


    '공부정리' 카테고리의 다른 글

    Return-to-Csu 기법 정리  (8) 2019.03.05
    메모리 보호기법 - PIE  (1) 2019.01.12
    [02-11 공부정리(Chaining RTL 정리)]  (0) 2018.02.11
    [02-10 공부정리]  (0) 2018.02.10
    [02-08 공부정리(RTL정리)]  (1) 2018.02.08

    댓글

Designed by Tistory.