
약 일주일 정도 리버싱 기초를 공부하면서 이제 막 CTF 문제 풀이에 들어가기 직전 단계까지 왔다. 너무 기초적인 내용들은 따로 정리하기 애매해서, 실제 문제를 풀기 시작하는 시점부터 블로그에 기록을 남기기로 했다. 아직 어셈블리어가 완벽하게 읽히는 수준은 아니지만, 반복해서 보다 보니 코드의 전체 흐름은 어느 정도 눈에 들어오기 시작했다.
오늘 공부하면서 느낀 가장 큰 특징은, 리버싱 문제들이 겉보기에는 복잡해 보이지만 실제 구조는 상당히 단순하다는 점이었다. 대부분의 문제는 입력값을 받아서 특정 연산을 거친 뒤, 조건을 통과하면 최종적으로 key와 비교하는 방식으로 구성되어 있었다. 이 흐름을 정리하면 “input → helper → 조건 → key 비교”라는 하나의 틀로 묶을 수 있고, 실제로 풀어본 문제들도 전부 이 구조에서 크게 벗어나지 않았다.
여기서 가장 중요한 개념이 바로 helper였다. helper는 특별한 함수라기보다는 입력값에 적용되는 연산들의 묶음이라고 보면 이해가 쉬웠다. 예를 들어 (input[i] + 1) ^ 0x20 같은 형태가 나오는데, 이 한 줄이 실제로는 입력값을 변형해서 새로운 값으로 만드는 핵심 과정이었다. 결국 리버싱은 이 helper 과정을 식으로 정리하고, 그 식을 거꾸로 풀어서 원래의 input을 찾아내는 작업이라고 볼 수 있다.
이 과정에서 자연스럽게 역산이라는 개념을 사용하게 된다. 예를 들어 (input[i] + 1) ^ 0x20 = key[i]라는 식이 나오면, 이를 뒤집어서 input[i] = (key[i] ^ 0x20) - 1 형태로 바꿔주면 된다. 여기서 중요한 규칙은 생각보다 단순하다. 더하기는 빼기로, 빼기는 더하기로 바꾸고, XOR 연산은 그대로 유지하면 된다. 이 규칙만 정확하게 기억하면 대부분의 기본 문제는 해결이 가능하다.
또 하나 인상 깊었던 부분은 XOR 연산이었다. ^ 0x20은 대문자와 소문자를 서로 변환하는 역할을 하는데, 실제 문제에서도 이 패턴이 굉장히 자주 등장했다. 처음에는 단순한 연산처럼 보였지만, 실제로는 문자열을 변형하는 중요한 포인트였고, 이걸 알아보는 순간 코드 해석이 훨씬 쉬워졌다.
오늘 공부하면서 가장 크게 깨달은 부분은 조건문의 위치였다. 처음에는 단순히 “입력값이 조건을 만족해야 한다”고 생각했는데, 문제를 풀다 보니 조건이 input 자체가 아니라 helper 연산을 거친 결과값에 걸리는 경우도 많았다. 즉 같은 조건문이라도 어떤 변수에 적용되는지에 따라 완전히 다른 결과가 나올 수 있다는 의미였다. 이걸 놓치면 정답을 구해도 틀리는 상황이 생기기 때문에 반드시 확인해야 하는 포인트였다.
또한 같은 변수 t라도 연산이 여러 번 적용되면서 의미가 계속 바뀐다는 점도 중요했다. 어떤 시점에서는 조건 검사용 값으로 사용되고, 그 이후에는 다시 변형되어 key 비교에 사용되기도 한다. 그래서 단순히 변수 이름만 보는 것이 아니라, “지금 이 값이 어떤 단계의 결과인지”를 구분해서 보는 것이 필요하다는 걸 느꼈다.
실제로 간단한 문제를 하나 풀어보면, key가 mnop일 때 (input[i] + 1) ^ 0x20 = key[i]라는 식을 세울 수 있었고, 이를 역산해서 LMNO라는 입력값을 구할 수 있었다. 그리고 이 값이 조건을 만족하는지까지 확인하면 최종적으로 정답이 된다. 처음에는 하나하나 계산해야 해서 시간이 걸렸지만, 몇 번 반복하다 보니 패턴이 눈에 익기 시작했다.
오늘 공부를 정리해보면, 리버싱은 복잡한 기술이라기보다는 일정한 구조를 반복해서 익히는 과정에 가깝다는 느낌이 들었다. 특히 helper를 식으로 바꾸고, 그 식을 역산해서 input을 구하는 흐름은 거의 모든 문제에 공통적으로 적용되는 핵심 패턴이었다. 앞으로는 문제를 더 많이 풀면서 이 과정을 더 빠르게 처리하는 연습이 필요할 것 같다.
아직 완전히 자유롭게 해석하는 수준은 아니지만, 최소한 코드의 흐름을 보고 어떤 구조인지 파악할 수 있게 된 것만으로도 큰 진전이라고 느껴진다. 다음 단계에서는 좀 더 복잡한 구조의 문제를 풀면서, 조건과 연산이 섞여 있는 상황에서도 흔들리지 않도록 연습해볼 생각이다.