본문 바로가기

Pwnable/개념

[Pwnable] Dreamhack STAGE 10

https://dreamhack.io/lecture/roadmaps/2

 

System Hacking

시스템 해킹을 공부하기 위한 로드맵입니다.

dreamhack.io


[ Pwnable: stage 10 Format String Bug ]

 

* 포맷 스트링

 - 예: printf, scanf, fprintf, fscanf, sprint, sscanf 등

 - 위의 함수들은 포맷 스트링을 채울 값들을 레지스터나 스택에서 가져옴

→ 그러나 해당 함수 내부에는 포맷 스트링이 필요로 하는 인자의 개수와 함수에 전달된 인자의 개수를 비교하는 루틴이 없음.

 따라서 사용자가 포맷 스트링을 입력할 수 있다면, 악의적으로 다수의 인자를 요청하여 레지스터나 스택의 값을 읽어낼 수 있음

 다양한 형식지정자를 활용하여 원하는 위치의 스택 값을 읽거나, 스택에 임의 값을 쓰는 것도 가능

포맷 스트링 버그(Format String Bug, FSB)

 

 

* 포맷 스트링의 구조

%[parameter][flags][width][.precision][length]type

형식 지정자(specifier)

   : 인자를 어떻게 사용할지 지정

형식 지정자 설명
d 부호 있는 10진수 정수
s 문자열
x 부호 없는 16진수 정수
n 인자에 현재까지 사용된 문자열의 길이를 저장
p void형 포인터

 

 최소 너비(width)

   : 최소 너비를 지정

   : 치환되는 문자열이 이 값보다 짧을 경우, 공백문자를 패딩 해줌

 

너비 지정자 설명
정수 정수의 값 만큼을 최소 너비로 지정
* 인자의 값 만큼을 최소 너비로 지정

 

 parameter

   : 참조할 인자의 인덱스 지정

   : 이 필드의 끝은 $로 표기

   : 인덱스의 범위를 전달된 인자의 개수와 비교하지 않음

 

 

* 포맷 스트링 버그(Format String Bug, FSB)

 : 포맷 스트링 함수의 잘못된 사용으로 발생하는 버그

 : 포맷 스트링을 사용자가 입력할 수 있을 때, 공격자는 레지스터와 스택을 읽을 수 있고, 임의 주소 읽기 및 쓰기를 할 수 있음.

 


[figure 4. 포맷 스트링 버그 실습]

 

일단 %p를 이용해서 auth의 주소를 확인해보았다. Fsb_auth.c에서 auth에 0x42424242를 넣었으므로, Rsp+12에 위치해 있는 것을 알 수 있다.

Auth의 주소 = 7FFF FFFF DDDC

 

 

 

[exploit tech: format string bug]

 포맷 스트링 버그는 포맷스트링을 사용하는 모든 함수에서 발생할 수 있음.

먼저


 강의에서 나온 코드를 복사해서 checksec을 돌려보았다.

 

그런데 강의에서는 canary found라고 표시되었는데 나는 canary가 없다고 한다

우분투 gcc는 기본적으로 카나리 적용한다면서 왜 안 해줄까?

계속해봐도 카나리 설정이 안 되길래 그냥 진행했다.

 

이제 코드를 확인해보자.

main함수 while 문에서 get_string으로 입력받은 0x20의 buf 값을

아래에서 printf의 인자로 사용하고 있으며, 해당 부분에서 포맷 스트링 취약점이 생길 것이다.

그리고 코드를 보니 changeme 변수가 1337이면 쉘을 탈취할 수 있다고 되어있다.

 

결국 changeme의 주소를 구하고, 포맷스트링 취약점을 사용해서 changeme를 1337로 설정해주면 쉘을 탈취할 수 있을 것 같다.

 

그럼 이제 changeme의 주소를 구해야 하는데, 일단 강의를 따라가 보았다.

 

위에서 printf 부분에 bp를 걸고, 강의랑 똑같이 12345678을 입력값으로 줬다.

 


그럼 위와 같은 위치에서 멈추는데

Stack 부분에서 rsp+16에 저장된 0x555555554940이(강의랑 다르다) 코드 영역에 포함되니까 이 주소를 사용해서 PIE 베이스 주소를 구할 수 있다고 한다.

 

이어서 진행을 하면, printf 함수는 “rdi”에 포맷 스트링을 전달하고, “rsi, rdx, rcx, r8, r9, 스택”에 포맷 스트링의 인자를 전달한다고 한다.

나도 똑같이 정리해보면

 

RSI        0x7fffffffdfb0 ◂— '12345678'
RDX        0x8
RCX        0x7ffff7af2031 (read+17) ◂— cmp    rax, -0x1000 /* 'H=' */
R8         0x7ffff7dcf8c0 (_IO_stdfile_1_lock) ◂— 0x0
R9         0x7ffff7fe14c0 ◂— 0x7ffff7fe14c0
[RSP]      ‘12345678’
[RSP+8]    0X0
[RSP+16]   0x555555554940

이렇게 된다.

 

 

이제 vmap으로 rsp+16에 저장된 값과 PIE 베이스 주소의 기준 주소와의 차이가 0X940인 것을 알 수 있다.

이제 changeme의 주소를 구해야 하는데

[%8$p로 출력한 주소] – [0x940] + [changeme의 오프셋] = [changeme의 주소]

라고 한다.

 


Changeme의 오프셋은 다음과 같고

 

이렇게 되니까 계산을 해보면

말도 안되는 값이 나온다...?

 

그냥 이어서 pwntools 스크립트를 작성해보자!!

 

진짜 하나도 안 맞는다. 왜 이럴까?

 

 

일단 changeme의 주소를 구했으니까, changeme에 1337을 넣어주면 될 것 같다.

 

1337바이트 길이의 문자열을 출력하고 %n을 사용해서 출력된 문자열의 길이를 인자에 저장하는 방식으로 익스플로잇을 진행한다.

그런데 위의 c 코드에서 buf 값을 0x20만큼 입력받으니 1337개를 입력할 수 없다. 이럴 때 포맷스트링의 width 속성을 사용할 수 있다고 한다.

 

Width 속성은 출력의 최소 길이를 지정하고, 출력할 문자의 길이가 최소 길이보다 작으면 그만큼 패딩 문자를 추가해준다고 한다.

 

그래서 총 페이로드를 정리해보면 먼저

1. %1337c: 1337을 최소 길이로 지정하는 문자를 출력

2. %8$n: 현재까지 사용된 문자열의 길이(1337)를 8번째 인자(changeme의 주소)에 작성

3. AAAAAA: 8의 배수를 위한 패딩

4. payload.encode()+p64(changeme): 페이로드 뒤에 changeme 변수의 주소 작성

 

이렇게 된다고 한다.

 

이렇게 된다!

'Pwnable > 개념' 카테고리의 다른 글

[Pwnable] Dreamhack STAGE 11  (0) 2022.08.26
[Pwnable] Dreamhack STAGE 8  (0) 2022.08.26
[Pwnable] Dreamhack STAGE 8  (0) 2022.08.26
[Pwnable] Dreamhack STAGE 7  (0) 2022.08.26
[Pwnable] Dreamhack STAGE 6  (0) 2022.08.25