이번에는 H만 3큽니다. 플래그는 DH로 시작하는군요. 김날먹은 이를 이용하여 불과 몇 분 만에 전체 플래그를 얻어냈습니다.
무슨 일이 일어났을까요?
간단합니다. VM 위에서 동작하는 프로그램은 입력값을 암호화 하고, 마지막 부분에서 그 값을 반복문을 이용하여 검사했던 겁니다. 만약 암호화된 입력값의 첫 번째 바이트가 암호화된 플래그와 일치하지 않으면 즉시 Wrong을 출력하고 반복문을 탈출합니다. 그렇다면, 바이트가 일치하면 명령어의 실행횟수가 늘어날 겁니다.
고급 언어로 보면 이런 식입니다:
for (int i = 0; i < 32; i++) {
if (encrypted_input[i] != encrypted_flag[i]) {
puts("Wrong!");
exit(1);
}
}
puts("Correct!");
김날먹은 이 점을 이용하여 출제자가 열심히 만든 VM 문제를 사실상 전혀 분석하지 않고 해결했습니다. 물론 다음 전제에 대한 사전 증거는 전혀 없었습니다.
평문의 i번째 바이트는 암호문의 i번째 바이트에만 영향을 미침
암호문을 비교하기 전에 실행되는 명령어의 횟수는 항상 동일함
그러나 이 방법은 시도에 드는 비용이 매우 적습니다. 한번 템플릿 스크립트만 만들어두면 VM 문제를 만날 때 마다 루프 주소값만 바꿔서 돌릴 수 있으니까요. 틀렸다면 약간 아쉬운 정도입니다.
VM 문제가 아닌 경우에도 비슷하게 memcmp 혹은 암호문 비교 루프를 공략하면 문제를 쉽게 무력화시킬 수 있습니다.
날먹 기술 2 - Compare Oracle
리버싱 출제자들은 김날먹의 Breakpoint Oracle을 막기 위해 새로운 암호문 비교 방식을 도입했습니다. 이번에는 바이트가 일치하지 않으면 바로 Wrong을 출력하는 것이 아닌, 전체 바이트를 비교한 다음 Correct나 Wrong을 출력하게 만들었습니다. 이러면 명령어의 실행횟수에 차이가 없어지겠네요!
고급 언어로 보면 이런 식입니다:
int is_correct = 1;
for (int i = 0; i < 32; i++) {
is_correct &= (encrypted_input[i] == encrypted_flag[i]);
}
if (is_correct) {
puts("Correct!");
} else {
puts("Wrong!");
}
하지만 이 정도로는 김날먹을 막을 수 없습니다. 김날먹은 IDA에서 비교 연산에 사용되는 CMP(==) 명령어를 처리하는 부분만 찾아서 비교되는 두 값이 일치한 횟수를 측정하는 GDB Script를 작성했습니다. 암호화된 바이트가 정답과 일치하면 CMP에서 두 값이 일치한 횟수가 더 많아질 것입니다. 명령어가 실행된 횟수가 아닌 비교연산이 옳았던 횟수를 이용하는 것입니다. 출제자들의 대책은 결국 미봉책에 불과했습니다.
비교연산을 CMP가 아닌 AND나 XOR로 수행하는 경우는 어떨까요? 비교가 아닌 일반 연산에도 많이 사용되는 명령어이기 때문에 노이즈가 생겨서 어려울 수 있지만, 디버깅을 좀 하고 "마지막 n개의 XOR의 결과가 0이 된 횟수를 측정" 같은 식으로 얼마든지 풀어낼 수 있습니다.
암호화 자체를 개선하지 않는 이상 김날먹은 패턴을 파악하고 유연하게 대응할 수 있습니다.
대응방안
앞서 알아본 2개의 기술은 공격자가 시스템의 반응(오라클)을 이용해 비밀 정보를 추측하거나 유출하는 공격 기법인 Oracle attack에 해당합니다.
리버싱 문제에서 이를 방지하기 위한 방법은 대표적으로 혼돈(Confusion)과 확산(Diffusion)을 충분히 잘 구현한 암호화를 이용하는 것입니다.
혼돈이란 키와 암호문(또는 평문) 사이의 관계를 복잡하게 만들어서, 키가 조금만 바뀌어도 암호문이 전혀 다른 형태가 되도록 하는 것을 말합니다.
확산이란 평문의 각 비트가 암호문 전체에 고르게 퍼지도록 하여, 평문의 구조를 숨기는 것입니다.
쉽게 말하면, 평문과 암호문의 관계를 눈으로는 도저히 파악할 수 없게 만드는 것입니다. 앞서 김날먹이 푼 문제들은 단순히 평문의 첫 번째 바이트가 암호문의 첫 번째 바이트에만 영향을 미쳤습니다. 만약 평문이 1비트만 바뀌어도 암호문이 완전히 달라진다면 김날먹이 사용한 기술들은 완벽히 무력화될 것입니다.
대표적으로는 AES, DES, Blowfish 같은 블록 암호나 RC4, RC5, ChaCha20 같은 스트림 암호가 있습니다. 혹은 직접 만들거나 변형해도 좋습니다. 이러한 암호는 김날먹이 사용한 것과 같은 단순한 Oracle Attack을 효과적으로 방지할 수 있습니다.
현황
현재 많은 워게임 문제 및 CTF 문제가 Breakpoint Oracle 및 Compare Oracle에 취약한 상태입니다. 아무리 복잡한 난독화를 적용해도, 암호화 자체가 취약하면 그 문제는 매우 쉬운 문제가 되어버릴 수 있습니다. 앞으로 이런 설계 이슈가 줄어들어서 리버싱 문제들이 더 재미있어졌으면 좋겠습니다.