본문 바로가기

🔐 Security/System

[시스템보안] 포맷 스트링

위 글은 2022년도 목포대학교 학점교류 '시스템보안' 수업에서 수행한 과제를 작성한 내용입니다.
교재 : 시스템 해킹과 보안: 정보 보안 개론과 실습 3판, 양대일 (2018)

 

1. 포맷 스트링 공격 원리 이해하기

1) test1.c 컴파일 진행하기

vi 에디터로 test1.c 작성

컴파일 및 실행해 보면 동작에는 문제가 없으나 보안에는 치명적인 함수 사용법이다.

 

2) test2.c 컴파일 실행하기

char *buffer에 문자열을 입력할 때 %x 포맷 스트링 문자를 추가하여 test2.c를 작성한다.

printf(buffer) 행에 브레이크 포인트 설정 후, wishfree 문자열 저장된 다음 메모리에 존재한 값인 0x8048440 값을 확인한다.

buffer 포인터 (*buffer), 값(buffer), 주솟값(&buffer) 확인한다.
결과 확인을 해보면 *buffer : 119, buffer : 0x8048440, &buffer : 0xbffffba4


주소 값(&buffer) 기준으로 스택 내용과 &buffer 주솟값에 저장된 내용 결과 확인을 해보면 &buffer : 0x08048440이고, 0x08048440의 주솟값 : “wishfree\n%x\n” (printf의 인수)이다.

 

3) test3.c 컴파일과 실행하기

test2.c 파일에 %x 여러 개 사용하여 포맷 스트링의 역할 확인 및 스택 값을 확인한다.


컴파일 후, printf(buffer)에 브레이크 포인트 설정 후, 실행한다.

buffer 값을 기준으로 스택 내용을 확인해 보면 해보면 출력값 : 0x8048440, 0xbffffbc8, 0x400329cb이다.

이 값은 test3 실행 결과 wishfree 다음으로 출력되는 값과 일치함을 알 수 있다. 문자열과 자기 자신 뒤로 이어지는 스택에 저장된 주솟값을 출력한 것이다.

 

4) test4.c 컴파일과 실행하기

포맷 스트링 이용하여 메모리를 읽는다.

컴파일하고 결과를 확인해보면 i 값이 0에서 4로 변경됨을 확인할 수 있다. %n 자리에 int 포인터를 넣어주었으므로 바로 전까지 프린트한 문자 개수를 출력한 것이다.

gdb 명령을 이용하여 실제 메모리에서 동작을 확인한다. 6번째 줄과 9번째 줄에 브레이크 포 인트를 설정한 후 실행한다.

포맷 스트링 공격 전의 I와 d값과 관련된 스택 값 확인해 보면 i의 주소에 저장된 값은 0이고, d의 주소에 저장된 값은 1234 임을 확인할 수 있다.

다음 브레이크 포인트까지 실행해 보면 i의 주소에 저장된 값이 0에서 4로 변경되어 있다.

 

5) test5.c 컴파일과 실행하기

I에 임의의 숫자를 넣어 test5 코드를 작성한다.

결과를 확인해보면 I값은 3039(12345의 HEX 값)가 나온다.

 

6) test6.c 컴파일과 실행하기

test6.c 코드

dumpcode.h 코드

 프로그램을 실행할 때 메모리 내용을 조회할 수 있는 파일이다.

test6을 컴파일 후 A 8개 입력한 결과이다.

 

7) 포맷 스트링 문자를 이용한 메모리 값 변조하기

printf 함수는 ASCII 코드값을 HEX로 입력하여 해당 문자열을 출력한다. cat 문과 파이프를 사용하여 실행할 인수를 전달한다.

/x88 넣은 값

/x98 넣은 값

%%n을 %%hn으로 입력한다. 88번째 스택에 있는 메모리 값이 09 00으로 변경되었다.

 

8) 포맷 스트링 문자를 이용한 메모리 값을 특정 값으로 변조하기

메모리값은 변조가 가능하나 메모리 주소를 변경할 수 없다. 따라서 %%c 대신에 %%64d를 입력하여 해결한다.

그러면 88번째 스택에 있는 주소가 48 00으로 변경된다.

 

9) 포맷 스트링 문자를 이용한 메모리 값을 특정 주솟값으로 변조하기

eggshell을 이용하여 주소를 확인한다. 0xbffffb58
ret 주솟값을 0xbffffb58로 변경하여 셸이 실행되면 공격이 성공한 것이다.

프로그램이 실행이 정상적으로 되지 않는다.

0xbffff168 뒤의 2바이트 0xfb68 입력한다.
0xfb59의 십진수는 64360, 입력 문자열 8개를 제외하여 입력한다.
입력한 문자열 8개를 제외하여 %%64352를 입력(64360-8)한다.

0xbffff1a8을 변경 시도했으므로, 68 fb로 2바이트가 변경되어 공격이 성공하는 것을 확인할 수 있다. 결과적으로 스택에 0x1bfff(114,687)를 입력하는 경우, 64352를 뺀 50335를 입력한다.

a8의 네 바이트가 변경되었다.
공격 결과는 64352d 앞에 총 16개가 존재하기 때문에 원하는 주소로 변경이 되지 않았다. 원래 0xfb59의 표현은 64360-16=64344.

합이 0x1bfff(114,687)이어야 하므로, 나머지 값 114,687-(16+64344)로 50327

 

2. 포맷 스트링 공격 수행하기

format_bugfile.c 코드를 작성한다.

1) format_bugfile.c, eggshell.c 컴파일과 권한 부여 및 실행하기

관리자 계정으로 format_bugfile.c와 eggshell.c을 컴파일한다. 컴파일된 format_bugfile.c에 SetUID를 설정한다.

 

2) ret 주소 확인하기

 

AAAAAA(A문자 6개)와 함께 적당한 길이의 %x를 입력한다. (19개)

결과를 확인해 보면 A 문자열 6개가 출력되고, 스택에 입력된 A 문자열 ASCII 코드값 41414141이 출력되었다. int I=0에서 isms 스택 위치 확인을 위한 값이고, 저장된 EBP값(0xbffff1a8)을 확인한다. main 함수의 ret 값은 0x400329cd 임을 확인할 수 있다. 공격을 시도했을 때 main 함수의 ret 값을 eggshell 주소로 변경하여 셸이 실행되면 공격에 성공한 것이다.

 

3) gdb에서 주소 확인하기

format_bugfile을 gdb로 실행한다.

포멧 스트링 공격으로 ret 주소를 변경한 후, 스택에서 메모리 주소를 확인한다.

결과를 확인해 보면 앞에서 확인한 값인 0x400329cd와 같음을 알 수 있다.

 

4) eggshell 실행하기

chapter8에 eggshell을 복사한 후, eggshell을 실행하면 목표 주소인 0xbffff178을 확인할 수 있다.

 

5) ret 주소 확인하기

sfb는 0xbffff1ab
sfp와 ret 주소의 차이는 0x1c
ret 주소 0xbffff18f

 

6) 포맷 스트링 공격 수행하기

ret 주소 (0xbffff18f-0xbffff191)에 eggshell에서 확보한 0xbffff178(3221221720)를 입력한다.

0xbffff178를 0x1bfff, 0xf178 두 개로 나눈 후,
0xbffff18f : 52903(114687-61816=52871)을 입력한다.
0xbffff191 : 61800(61816-16) 입력한다.

공격 결과이다. 주솟값에 해당하는 스택 내용이 화면에 나오지 않아 오류처리 하였으나
id 명령어를 통해 uid가 root 권한으로 변경되어 공격이 성공했음을 알 수 있다.