포맷 스트링(FS) - 2
포맷 스트링으로 특정 위치 변조(오염)하기
우선 본격적으로 들어가기 전에 서론 조금만 얘기할게요..
분명 나는 웹 해킹 기법 공부하려다가 배보다 배꼽이 더 크게 공부해버림. 주말을 제외하고 하루죙일 포맷 스트링만 한 듯. 그러다가 갑자기 리눅스가 뻗어버려서 살리는데도 하루 넘게 걸리기도 했고.... (CentOS 7 금쪽아 왜 그러는거야 ㅠㅠ) 지금은 해결
또 얘기하고 싶은게 있지만 그건 중간에 FSB 설명하면서 넣도록 하겠다.!
#include <stdio.h>
int main(){
int i = 0;
printf("press any key!\n");
getchar(); // 브레이크 포인트 여따 검
printf("done: %d\n", i);
return 0;
}
사용한 코드이다. 원래는 read 코드를 사용하여 계속 진행하려 했지만 특정한 이유(후술)로 실패 후 조금 더 가볍게 변경한 코드.
gdb로 실행해보자!
gdb로 main 함수 disassemble후 결과다. 여기서 printf에 브레이크를 잡아주자.
브레이크 걸고 실행시켜준 후에 현재 i 값을 확인했다. i = 0으로 초기화했으니 0의 값이 나온다. 중간에 warning은 신경 안 써도 된다. 코드 변경 후에 컴파일을 안 시켜줘서 그렇다.
이제 포맷 스트링으로 변조를 하기 위해서 사용되는 것이 %n이다!
포맷 스트링(FS)
주요정보통신기밥 웹 취약 분석 평가 방법 두 번째 항목인 포맷 스트링(FS)이다.버퍼 오버플로우도 그렇고 포맷 스트링도 그렇고 웹 보안 취약점으로 나오기도 하지만 실상은 시스템 보안 쪽에
ragdo11.tistory.com
여기서 설명했지만 %n은 쓰였던 총 바이트 수를 인자해 저장해준다.
만약 i에 11을 저장하고 싶다면?
이런 식으로 11c, 즉 11개의 문자(null 한 개 + 공백 10개)를 받아서 11이라는 값을 i에 넣게 되는 것이다.
뭐 그렇다면 저번에 했던 대로 해서 접근하면 안되는 변수 같은 곳의 내용을 %n을 이용해 망쳐버리면 되겠다!!라고 생각할 수 있다. 물론 무결성을 망쳐버리는 괜찮은 공격이나 더 엄청난 공격을 할 수 있다.
[함수 호출 규약 참조] 함수가 어찌 됐든 실행이 됬으면 실행이 된 후 사용한 자원들을 넘겨주고 원래 주소로 돌아가야 한다. 원래 주소로 돌아가기 위해서 ret 주소에 그 자리를 저장하게 되는데 만약 이 ret 주소를 fsb를 이용해서 공격자가 원하는 주소로 변조할 수 있다면 어떻게 될까? 함수가 끝나고 나면 원래 주소로 돌아가야 하는데 공격자가 ret 주소를 변조해놨으니 변조된 주소로 이동하여 악의적인 프로그램이나 쉘 실행 등이 가능해진다.
그렇기 때문에 내가 해 볼 것은 main 함수의 ret 주소를 오염시켜 쉘 실행을 해보고 싶었다. (<< 저 생각 때문에 적당히 하고 넘어가야 할 FSB가 일주일이 넘어가도록 공부하게 만듬)
뭔가 대충 저렇게 i 변조했고 와 이렇게 변조하면 됩니다!! 라고 대충 이론적으로 넘어가기 싫어하는 괴상한 성격이라 무조건 해봐야 됬다.
우선 main의 return 주소부터 알아봐야겠다. 위의 disassemble main 결과를 보면
0x000..04005f8 <+56>: retq
이렇게 나와있다.
ret에 브레이크 포인트를 한 번 더 설정해주고 마저 continue 한 후에 스택 포인터 값을 확인하면 0x7fffffffdf68: 0x00007ffff7a3caf5라고 보인다. 여기서 알 수 있는 것은 ret 주소는 0x7ffff7a3caf5이고 이 주소가 저장되어 있는 메모리는 0x7fffffffdf68이라는 점을 알 수가 있다.
그럼 본격적인 변조를 하기 전에! ret 주소를 그럼 뭘로 변조할 것이냐!? 쉘을 획득해보는 시나리오로 가보자. 악의적인 파일이 있으면 그런걸로 변조해봐도 좋을 것 같다.
쉘 주소를 먼저 확인해 봐야겠지요??
0x7ffff7a5d850 주소로 변조하면 되겠다.
시험 삼아서 50만 변조해봤다. 성공적으로 변조되었다.(16진수 32 = 50) 그럼 7ffff7a5d850이면 10진수로 변환해서 140737348229200 -> ?? 이니까 140737348229200c로 페이로드를 작성하면 되겠네!! 는 당연히 안되고
%n은 4바이트를 사용하므로 저렇게 큰 수를 곧장 변조할 수가 없다. 위의 사진을 보면 알겠지만 %50c%n으로 변조를 했는데 완전히 다 변조가 되지 않고 앞의 7fff가 남은 것을 보고 %n이 몇 바이트를 사용하는지 추정할 수도 있다.
그래도 다행히 앞의 7fff 부분은 같으니까 나머지 f7a5d850으로만 변조하면 되는거 아니야? 그러면 4바이트만 사용해도 되고 되겠는데?? 그럼 f7a5d850은 10진수로 얼마일까. 4154841168. 애초에 딱 봐도 솔직히 말이 안됨을 알 수 있다. 저 큰 숫자만큼의 공간을 먼저 받고 값으로 넘겨야 되는데 오류가 발생할 수 밖에 없다. 아래는 직접 실행 결과
그럼 어떻게 변조하지? 여기서 %n보다 작은 바이트를 사용하는 %hn, %hhn이 있다. %n 앞에 길이 지정자가 붙음으로서 변수 크기를 지정하는 것이다. h는 half인 절반을 의미하는 것이므로 hn은 2바이트, hhn은 1바이트를 사용한다.
hn을 사용한다면 7fff / f7a5 / d850 으로 나누어서 변조를 하면 되고, hhn을 사용한다면 7f / ff / f7 / a5 / d8 / 50 으로 나누어서 사용해야한다.
가장 안전하게 공격이 성공하려면 hhn을 사용하는게 좋다. 크기가 작아서 성공하기 좋음. 페이로드 작성은 귀찮을 수도 있다.
변조는 50부터 시작해야 한다. 이유를 모르겠다면 리틀 엔디안을 참조하면 된다. 빅 엔디안이면 7f 부터 해도 되겠지만..
50은 10진수로 80이다. 먼저 뒤 1바이트를 변조한다.
그 다음 d8은 10진수로 216. 그 다음 1바이트를 변조한다.
변조는 했는데 뭔가 조금 이상하다. 아하... %hhn 쓰겠다고 해놓고서 %n을 쓰는 바람에 ff 사라짐... 이러면 수고를 한 번 더 해야된다. 이런 ㅠ
a5는 10진수로 165. 이번엔 hhn 빼먹지 말고. 그렇게 나머지도 다 채워주면 된다.
f7 > 247
ff > 255
여기선 7f가 이미 있으니 굳이 변조를 시도하지 않았지만 끝까지 시도해봐도 된다.
이로서 ret 주소가 system@plt(shell addr)로 변조가 되었다. 이제 마저 실행시키면 main 함수가 종료되면서 오염된 ret 주소로 돌아가려고 할 것이고 그럼 쉘을 획득할 수 있을 것이다!!
?
이것은 지금 gdb 디버깅 중에 내가 fork를 시도해서 뜨는 것이다. GDB가 특정 프로세스를 디버깅할 때 해당 프로세스가 자식 프로세스를 fork하면 GDB는 두 프로세스 중 하나만 따라갈 수 있으므로 다른 프로세스의 진행을 중단해야 한다.
set follow-fork-mode parent
그러므로 gdb에서 위 명령어를 입력해주어 fork된 후에도 원본 프로세스에 붙어있게 만들어주자.
캬 FSB로 bash 쉘을 실행시켰다. 이 얼마나 이쁜
gdb가 종료되고 다시 쉘로 돌아온 것처럼 보이지만 ctrl+Q로 프로세스를 종료하면 다시 gdb로 돌아오는 것을 볼 수 있다.
이게 FSB로 ret 주소 등을 변조하여 원하는 공격을 하는 방법이다. 저번에는 수동적인 훔쳐보기 식의 공격이었다면 이번엔 직접 변조하고 공격할 수 있는 능동적인 공격.
그런데 잠깐
Q: 오 근데 이게 주통기반 웹 기반 보안 취약점에 있다는거 맞죠? 웹 해킹 같지 않은데;;
A : 나도 있으니까 정리는 하려고 했던게 제대로 보여주려는 것이 그냥 시스템 해킹 공부나 열심히 해버린 것 같다. 배보다 배꼽이 더 크게 공부한게 이것. 아무래도 모의해킹을 하게 되면 진짜 공격을 해서 파멸적인 결과를 만들어내는 것이 아니라 여기 취약점이 있다!! 라는 것만 찾다보니 이렇게 까진 할 필요가 없지 않나 싶으면서도.. 쉘을 획득했을 때 기쁨 + 갑자기 정리하자니 드는 허탈함 ㅠ
참고로 버퍼 오버플로우도 깊게 들어갔으면 무조건 이래 됬을 듯
그럼 주통기반에서는 FSB 공격에 대해 웹 점검을 어떤 식으로 하는가...
1. %n%n%n%n%n%n%n%n%n%n%n%n
2. %s%s%s%s%s%s%s%s%s%s%s%s
3. %1!n!%2!n!%3!n!%4!n!%5!n!%6!n!%7!n!%8!n!%9!n!%10!n!
4. %1!s!%2!s!%3!s!%4!s!%5!s!%6!s!%7!s!%8!s!%9!s!%10!s!
...
결국은 사용자의 입력이 포맷 스트링으로서 해석될 여지가 있는가. 이것을 검사하는 것이다. 참고로 3,4번은 리눅스가 아닌 윈도우에서 사용되는 형태임.
자 이제 드디어 주통기반에서 제시한 보안설정방법을 살펴보면(놀랍게도 본론임.)
- 컴파일러에서 문자열 입력 포맷에 대한 자체적인 검사를 내장하고 있으므로 문자열 입력 포맷 검증 후 소스 코드에 적용
- 웹 서버 프로그램 최신 보안패치 적용
- 웹 사이트에서 사용자가 입력한 파라미터 값 처리 중에 발생한 경우 사용자 입력 값의 유효성에 대한 검증 로직을 구현
이라고 되어 있다. 요새 컴파일러는 이런 포맷 스트링 공격에 대해 자체적으로 막거나 경고를 출력한다.
개발자 입장에서 FSB 방어하는 방법 역시 매우 쉽다.
printf(buf) X --> FSB 공격 가능!!
printf(buf, %s) O --> 형태를 지정해줌으로서 공격자의 FSB 공격이 지정된 형태로만 해석됨
사용자의 입력을 직접 받는 행위는 FSB가 아니더라도 항상 어디서나 위험하다는 것을 잊지 말자.