소스를 잃어버린 MFC 프로그램을 REA 소식을 보며 다시 떠올렸습니다

예전에 MFC로 만든 프로그램이 하나 있습니다. 실행 파일은 지금도 어딘가 뒤지면 나옵니다. 더블클릭하면 아마 뜰 겁니다. 소스는 없습니다. 언제 어떻게 사라졌는지도 이제는 정확히 모르겠고, 그냥 어느 순간부터 없었습니다. 그래서 그 프로그램은 고칠 수가 없는 상태로 몇 년째 그대로 있습니다. 쓰려면 쓸 수는 있는데, 손을 댈 수는 없는 물건이요.
이번 주에 REA라는 오픈소스가 계속 눈에 띄었습니다. 이름이 Reverse Engineer Anything입니다. GitHub 저장소를 열어 보니 스타가 34만 개를 넘어 있었습니다. 이 정도면 화제라는 말로도 좀 부족한 것 같습니다.
하는 일을 한 줄로 줄이면, AI 에이전트가 리버스엔지니어링 도구를 직접 다루게 해 주는 MCP 서버입니다. Hopper, Ghidra, IDA 같은 디스어셈블러와 안드로이드용 JADX를 에이전트가 직접 부르고, 네이티브 바이너리든 Electron 앱이든 .NET 어셈블리든 웹사이트든 소스 없이 들여다봅니다. README가 내세우는 문장은 대략 이런 겁니다. 마음에 드는 기능을 보면 그걸 바이너리 수준까지 이해하고 내 프로젝트용으로 다시 만든다.
이 문장을 읽고 처음 든 생각은 솔직히 남의 앱이 아니라 제 MFC 프로그램이었습니다.
소스를 잃어버린 프로그램은 생각보다 많을 것 같습니다
저만 그런 게 아닐 거라고 봅니다. 개발을 좀 오래 한 사람이면 하나쯤은 갖고 있지 않을까요. 학생 때 과제로 만든 것, 첫 회사에서 혼자 만든 사내 도구, 친구 부탁으로 만들어 준 프로그램. 하드디스크를 바꾸면서, 백업을 안 해서, 혹은 그때는 버전 관리라는 걸 제대로 안 써서 소스만 사라지고 실행 파일만 남은 것들이요. CD에 구워 뒀는데 그 CD가 어디 갔는지 모르는 경우도 있을 거고, 회사 메일에 첨부해 둔 채 퇴사해서 다시는 못 여는 경우도 있을 겁니다. 실행 파일은 이메일로 여기저기 보내 두니까 오히려 살아남는데, 소스는 보통 한 군데에만 있으니까요.
개인 물건만 그런 것도 아닌 것 같습니다. 회사에서 오래 돌던 업무 프로그램 중에도 만든 사람은 나갔고 소스는 어디 있는지 아무도 모르는데 매일 아침 누군가 그걸 켜는 경우가 있다고 들었습니다. 고장 나면 큰일인데 아무도 열어 볼 수 없으니, 다들 안 고장 나기만 바라는 상태로 쓰는 거죠.
이런 프로그램은 지금까지 두 가지 결말밖에 없었습니다. 그냥 그대로 쓰다가 운영체제가 바뀌는 날 같이 죽거나, 누가 처음부터 다시 만들거나. 다시 만드는 쪽은 대개 원래 프로그램이 뭘 하고 있었는지 화면을 보면서 추측하는 일이었고, 추측이 틀린 부분은 누군가 "예전엔 이거 됐는데요"라고 말할 때까지 아무도 모릅니다.
이번에 든 생각은, 여기에 세 번째 길이 생긴 것 같다는 겁니다.
그 회사 업무 프로그램 같은 경우를 조금 더 생각해 보면, 이게 마냥 반가운 소식만은 아닐 것 같습니다. 아무도 열어 볼 수 없던 프로그램을 이제 열어 볼 수 있게 됐다는 건, 누군가 "그럼 이참에 다시 짜죠"라고 말하기 쉬워졌다는 뜻이기도 하니까요. 에이전트가 바이너리를 읽고 동작을 설명해 주고, 그걸 바탕으로 새 코드를 짜고, 테스트도 원본과 똑같이 나오게 맞춰 주면 그럴듯해 보입니다. 그런데 원본을 끄고 새것을 켜는 날은 되돌리기 어려운 날입니다. 원본이 아무도 모르는 이유로 처리하던 예외가 하나라도 빠져 있으면, 그건 그날 아침 누군가 그 프로그램을 켰을 때에야 드러납니다. 테스트 케이스는 우리가 알고 있는 동작만 검사하고, 문제는 늘 우리가 모르던 동작에서 나니까요.
그래서 이 도구가 열어 준 길에서 제일 먼저 하고 싶은 건 다시 만들기가 아니라 읽기입니다. 이 프로그램이 지금 정확히 뭘 하고 있는지를 아는 것만으로도, 고장 나기만을 바라던 상태에서는 한 발 나오는 거니까요. 다시 만들지 말지는 그다음 문제이고, 그걸 정하는 건 에이전트가 아니라 그 프로그램을 매일 켜는 사람들 쪽일 겁니다.
리버스엔지니어링 자체는 원래 있던 일입니다
사실 처음 든 생각은 이거 원래 있던 거 아닌가, 였습니다.
게임 쪽에서는 이미 사람 손으로 끝까지 간 사례가 있습니다. 슈퍼 마리오 64를 바이너리에서 C 코드로 되돌린 n64decomp/sm64 프로젝트가 대표적이고, 젤다의 전설 시간의 오카리나도 팬 커뮤니티가 게임 코드를 전부 C로 되돌렸다는 보도가 있었습니다. 이 사람들은 함수 하나하나를 컴파일했을 때 원래 바이너리와 바이트 단위로 똑같이 나오도록 맞춰 가면서 작업했다고 합니다. 몇 년이 걸린 일입니다.
이 프로젝트들 README를 보면 재미있는 대목이 있습니다. 코드는 다 되돌렸는데 이름 붙이기와 문서화는 아직 진행 중이라고 적혀 있습니다. 그리고 에셋은 저장소에 없어서 원본 롬을 갖고 있어야 빌드가 됩니다. 그러니까 이 사람들이 되살린 건 닌텐도 개발자가 쓴 그 소스가 아닙니다. 같은 바이너리가 나오는 새로운 소스입니다. 변수 이름도, 주석도, 왜 이 숫자가 여기 있는지도 원래 것은 하나도 안 돌아왔고, 그건 지금도 사람들이 추측해서 채우고 있습니다.
그 사람들이 왜 몇 년을 거기에 썼을까도 좀 생각해 봤습니다. 돈이 되는 일은 아니었을 겁니다. 대부분은 좋아하는 게임을 오래 남기고 싶어서, 다른 기계에서도 돌아가게 하고 싶어서였을 거라고 봅니다. 원래 만든 회사가 소스를 공개할 리 없고, 공개하지 않는 사이에 그 게임을 돌릴 수 있는 기계는 점점 줄어드니까요. 소스가 없는 프로그램을 되살리는 일은 그동안 이렇게 애정이 아주 큰 사람들이 몇 년을 들여야 하는 일이었습니다. 제 MFC 프로그램 같은 건 거기에 끼지도 못했습니다. 몇 년을 쓸 만큼 아끼는 물건은 아니었으니까요.
이번에 오래 붙잡고 있던 건 이 차이입니다. 몇 년짜리 애정이 있어야 되살아나던 프로그램들이, 이제는 그 정도 애정이 없어도 되살아날 수 있게 됐습니다. 세상에 실행 파일만 남은 프로그램이 얼마나 많은지는 모르겠지만, 그중 대부분은 슈퍼 마리오가 아니라 제 MFC 프로그램 쪽일 겁니다. 아무도 몇 년을 쓰지 않을 프로그램들이요.
이걸 AI가 대신하기 시작한 것도 REA가 처음은 아닙니다. 작년에 Quesma라는 곳에서 Claude와 Ghidra를 MCP로 연결해서 아타리 게임 River Raid의 롬을 뜯어본 글을 올린 적이 있습니다. 목숨이 줄어드는 코드를 찾아서 무한 목숨으로 바꾸는 이야기였는데, 글쓴이가 설정 과정에서 실패할 수 있는 단계가 꽤 많았다고 적어 둔 게 기억에 남습니다. 그때만 해도 이건 해 볼 줄 아는 사람이 해 보는 실험이었습니다.
REA가 바꾼 건 그 설정 과정인 것 같습니다. npx 한 줄로 에이전트에 등록하고 작업 지침까지 깔아 준다고 되어 있습니다. 해 볼 줄 아는 사람만 하던 일이 명령어 한 줄짜리 일이 됐습니다. 기술이 새로 생긴 게 아니라 문턱이 낮아진 쪽에 가깝다고 봅니다. 34만이라는 숫자도 아마 그 문턱 쪽에서 나온 숫자일 거고요.
문턱이 낮아진다는 건 하는 사람이 바뀐다는 뜻이기도 합니다. 예전에 바이너리를 열어 보던 사람들은 대개 보안 쪽이나 게임 보존 쪽처럼 그걸 오래 해 온 사람들이었고, 그 사람들 사이에는 어디까지 해도 되는지에 대한 감각 같은 게 어느 정도 있었을 겁니다. 이제는 그런 감각 없이 명령어 한 줄로 들어오는 사람이 훨씬 많아질 텐데, 그게 좋은 일인지는 아직 잘 모르겠습니다.
REA가 실제로 보여 준 건 기능 하나씩입니다
rea.tools에 올라온 사례들을 하나씩 봤습니다. 이건 직접 돌려 본 게 아니라 사이트에 적힌 걸 읽은 겁니다.
1996년 Windows 게임 DX-Ball에서 공 위치가 좌우 스피커 소리로 바뀌는 계산을 복원했다는 사례가 있습니다. README에는 이 복원한 함수가 원래 x86 바이너리의 테스트 케이스 3,205개를 통과하고 컴파일된 함수 바이트 63개를 그대로 재현한다고 적혀 있습니다. Notion에서는 복사한 블록이 서식을 유지한 채 클립보드로 넘어가는 경로를 렌더러부터 메인 프로세스까지 따라갔고, 안드로이드 인증 앱 Aegis에서는 30초마다 바뀌는 로그인 코드가 어떻게 만들어지는지를 추적했습니다. Windows 계산기의 % 버튼이 + 다음에 누를 때와 × 다음에 누를 때 다르게 동작한다는 걸 찾아낸 예도 있었습니다.
다 재밌습니다. 그리고 다 기능 하나입니다.
이 사례들이 정확히 뭘 보여 주는지 따져 보고 싶었습니다. 사운드 팬 계산 하나, 클립보드 경로 하나, 버튼 하나의 규칙. 이건 어떤 프로그램이 "무엇을 하는지"를 바이너리에서 꺼낸 겁니다. 그것도 정확하게, 사람보다 훨씬 빨리요. 3,205개 케이스를 통과했다는 건 적어도 그 함수에 대해서는 동작이 원본과 같다는 뜻이니까 꽤 강한 주장입니다.
사이트 쪽 설명도 의외로 정직했습니다. 공룡 게임 예시는 구글의 크롬 빌드가 아니라 누가 크로미움에서 떼어 낸 브라우저판을 본 거고, 미니게임의 그리기와 충돌 코드는 원본이 아니라 새로 쓴 거라고 밝혀 뒀습니다. 계산기 쪽은 특정 버전 하나를 본 거고, 설명에 쓴 이름들은 마이크로소프트가 공개한 소스에서 가져왔다고 적었습니다. README도 결과에 근거와 한계를 같이 낸다는 걸 장점으로 내세웁니다. 다 됐다고만 말하는 도구보다는 이쪽이 훨씬 믿음이 갑니다.
제 MFC 프로그램에 대입해 보면
이제 제 프로그램 얘기로 돌아오면, 아마 꽤 많은 게 될 것 같습니다. MFC는 메시지 맵이 있어서 기본적으로 뜯어보기 쉬운 편입니다. 어떤 메시지가 어떤 함수로 가는지가 표처럼 박혀 있으니까요. 에이전트가 Ghidra를 붙잡고 몇 시간 돌면 창이 몇 개고 버튼을 누르면 어떤 함수가 불리고 파일을 어떤 형식으로 저장하는지까지는 꺼내 줄 거라고 봅니다. 그걸 요즘 프레임워크로 다시 짜 달라고 하면 그것도 해 줄 거고요.
근데 그렇게 돌아온 것이 제가 잃어버린 그 소스인가, 하면 좀 망설여집니다.
소스를 잃어버렸을 때 제가 아쉬웠던 게 정확히 뭐였는지를 다시 생각해 봤습니다. 코드 줄 자체였을까요. 그건 아닌 것 같습니다. 지금 그 프로그램을 고치려고 든다면 막히는 건, 이 부분을 바꾸면 저 부분이 왜 안 되는지 알려면 제가 그때 왜 이렇게 짰는지를 알아야 하는데 그게 남아 있지 않다는 점일 겁니다. 그리고 소스가 있었다고 해도 그 이유는 상당 부분 코드에 안 적혀 있었을 것 같습니다. 대부분의 코드가 그렇듯이요.
바이너리에서 복원한 코드는 "이 함수는 이 값을 받아서 이렇게 계산한다"까지는 말해 줍니다. "이 값이 왜 하필 이 숫자인지"는 말해 주지 않습니다. 그 숫자가 원래 요구사항에서 온 건지, 어떤 버그를 피하려고 맞춰 둔 건지, 그냥 그때 대충 넣은 건지. 디컴파일 결과 안에서는 셋 다 똑같이 생겼습니다. SM64 프로젝트가 코드를 다 되돌리고도 몇 년째 이름을 붙이고 있는 이유가 아마 이거라고 봅니다. 동작은 바이트 단위로 맞출 수 있는데, 의도는 맞춰 볼 원본이 없으니까요.
그래서 저한테 REA가 열어 주는 길은 "잃어버린 소스를 되찾는 길"이라기보다는 "잃어버린 프로그램과 똑같이 동작하는 새 프로그램을 얻는 길"에 가깝다고 보고 있습니다. 이 둘은 생각보다 많이 다릅니다. 똑같이 동작하는 새 프로그램은 오늘은 원본과 같지만, 내일 뭔가를 고쳐야 할 때 원본이 왜 그렇게 생겼는지 모르는 상태에서 고쳐야 합니다. 원본을 만든 사람이 저인데도요.
그래서 만약 제 프로그램을 넣어 본다면, 코드보다 먼저 받고 싶은 건 설명서 같은 겁니다. 이 프로그램이 어떤 입력을 받아서 어떤 파일을 만들고, 어떤 경우에 어떤 메시지를 띄우는지. 그걸 읽으면서 "아 맞다, 이거 그때 이래서 이렇게 했지" 하고 기억이 돌아오는 부분이 있을 거고, 끝까지 기억이 안 나는 부분도 있을 겁니다. 기억이 돌아오는 부분은 제가 아직 원작자로 남아 있는 부분이고, 안 돌아오는 부분은 이제 저도 남인 부분입니다. 그 경계를 알게 되는 것만으로도 꽤 의미가 있을 것 같습니다. 코드는 그다음에 받아도 늦지 않고요.
여기서 REA가 결과에 근거와 한계를 같이 내준다는 점이 저한테는 제일 크게 보입니다. 에이전트가 "복원 완료했습니다"라고 말하는 순간을 저는 별로 믿지 않습니다. 실행은 했는데 확인은 못 한 걸 완료라고 부르는 경우를 너무 많이 봤으니까요. 이 함수는 원본 바이너리와 몇 개 케이스에서 같은 결과를 냈고, 이 부분은 추정이고, 이 부분은 못 봤다. 이렇게 나눠서 말해 주는 도구라면 적어도 제가 어디를 의심해야 할지는 알 수 있습니다. 소스를 잃어버린 프로그램을 다룰 때는 그게 제일 필요한 정보입니다.
그럼 상용 앱은
여기까지는 제 물건 얘기라 마음이 편한데, README의 그 문장은 남의 물건 쪽을 향해 있습니다. 마음에 드는 기능을 보면 바이너리 수준까지 이해하고 내 프로젝트용으로 다시 만든다.
법은 잘 모르는데, 찾아보니 한국 저작권법에는 프로그램 코드 역분석을 다루는 조항(제101조의4)이 있는데, 정당한 권한으로 프로그램을 쓰는 사람이 호환에 필요한 정보를 다른 방법으로 얻기 어려울 때 필요한 부분만 역분석할 수 있게 한 구조로 알고 있습니다. 그렇게 얻은 정보를 호환 말고 다른 목적에 쓰거나 비슷한 프로그램을 만드는 데 쓰는 건 막혀 있다고 읽었습니다. 앱마다 사용권 계약(EULA)에 역분석 금지 조항이 들어가 있는 경우도 흔하고요. 기능을 보고 다시 만드는 건 괜찮지만 코드를 그대로 옮기는 건 다르다는 해석도 봤습니다. 실제 제품에 쓰려면 법률 검토를 받으라는 말이 어느 자료에나 붙어 있었습니다.
REA README도 이걸 모르는 척하지 않습니다. 합법적인 연구, 교육, 상호운용, 소프트웨어 보존 용도이고 내가 소유하거나 분석 권한이 있는 소프트웨어를 대상으로 하라고 적혀 있습니다. IDA나 Hopper 같은 상용 도구는 정식으로 구해야 하고 크랙이나 훔친 키는 안 된다는 말도 있습니다. 그러니까 도구를 만든 쪽은 선을 그어 놨습니다. 그 선을 지킬지는 쓰는 사람이 정하고요.
제 MFC 프로그램은 이 선 안쪽에 확실하게 들어가는 경우라서 마음이 편합니다. 제가 만들었고 제가 갖고 있는 물건이니까요. 다만 회사 업무 프로그램 쪽은 조금 애매할 수 있겠다는 생각이 듭니다. 외주로 만든 프로그램이면 저작권이 누구한테 있는지, 계약서에 소스 인도 조항이 있었는지부터 봐야 할 것 같고, 그 계약서도 소스와 함께 어디로 갔는지 모르는 경우가 많을 것 같습니다. 도구가 쉬워졌다고 그 확인까지 쉬워지지는 않았습니다.
솔직히 법적인 선보다 제가 더 신경 쓰이는 건 다른 쪽입니다. 선을 넘지 않는 범위에서도, 내 앱의 기능 하나를 누군가 며칠 만에 똑같이 동작하게 만들 수 있다는 건 이제 거의 기정사실 같습니다. 예전에는 경쟁사가 우리 기능을 따라 하려면 화면을 보고 추측하고, 추측이 틀려서 버그가 나고, 그걸 고치는 데 몇 달이 걸렸습니다. 그 몇 달이 사실상 진입장벽이었습니다. 그 몇 달이 이제 며칠이 되는 것 같습니다.
그런데 그 몇 달 동안 따라 하는 쪽이 한 일이 정말 쓸모없는 일이었나 생각해 보면, 꼭 그렇지도 않았던 것 같습니다. 화면을 보고 추측하고 틀리고 고치는 동안, 따라 하는 팀은 그 기능이 왜 그렇게 생겼는지를 자기 식으로 조금씩 이해하게 됩니다. 틀린 추측을 하나 고칠 때마다 원본 팀이 했던 판단 하나를 뒤늦게 따라가는 셈이니까요. 몇 달이 걸린 대신 몇 달 뒤에는 그 기능을 자기 것처럼 고칠 수 있게 됩니다.
바이너리에서 정확하게 뽑아낸 기능은 그 과정을 건너뜁니다. 처음부터 맞게 동작하니까 틀릴 일이 없고, 틀릴 일이 없으니 왜 그런지 물을 일도 없습니다. 이건 가상의 얘기지만, 어떤 팀이 경쟁 앱의 정산 화면을 바이너리 수준에서 그대로 다시 만들었다고 해 보겠습니다. 소수점 처리가 묘하게 특이한데 테스트는 다 통과하니 그대로 갑니다. 몇 달 뒤 원본 쪽은 그 특이한 처리를 바꿉니다. 원래 어떤 결제사 하나 때문에 넣었던 거였고, 그 결제사와 계약이 끝났거든요. 따라 한 팀은 그 소식을 모릅니다. 자기 코드에 왜 그 처리가 있는지도 모르니, 지울 수도 남길 수도 없는 코드를 하나 안고 가게 됩니다. 제 MFC 프로그램이랑 똑같은 처지가 되는 거죠.
README의 그 문장을 개발자 입장에서 다시 읽어 보면, 내 프로젝트용으로 다시 만든다는 부분이 계속 걸립니다. 남의 앱에서 마음에 드는 기능을 보고 그걸 정확하게 내 프로젝트에 옮겨 올 수 있다면, 그다음에 남는 질문은 이 기능이 내 프로젝트에 정말 필요한가 하는 겁니다. 예전에는 따라 만드는 데 몇 달이 들었으니 그 질문을 저절로 오래 하게 됐습니다. 몇 달을 쓸 만한 기능인지 따져야 했으니까요. 이제 그 비용이 거의 없어지면 그 질문을 건너뛰기 쉬워집니다. 좋아 보이는 기능을 하나씩 다 옮겨 오다 보면, 남의 앱 조각을 이어 붙인 프로그램이 되고, 그 조각마다 원래 있던 맥락은 하나도 같이 안 옵니다.
이 정도 일에 이걸 쓸 일인가, 하는 질문을 저는 도구를 볼 때마다 하는 편인데, REA 앞에서는 그 질문이 조금 다른 모양이 됩니다. 도구를 쓰는 비용은 낮아졌는데, 도구로 가져온 걸 계속 들고 가는 비용은 그대로입니다. 남의 기능을 가져오는 건 한 번이고, 그 기능을 유지하는 건 앱이 살아 있는 동안 계속이니까요.
코드가 복제되면 개발자한테 남는 건 뭘까
이 질문을 계속 붙들고 있었습니다. 코드는 소스가 없어도 복제되는 세상에서 개발자는 뭘로 먹고사나.
제가 지금 보고 있는 쪽은 이렇습니다. 바이너리에 들어 있는 건 결정의 결과이고, 결정 그 자체는 안 들어 있습니다.
예를 들면 이런 겁니다. 어떤 앱에 버튼이 하나 있고, REA로 그 버튼이 누르면 무슨 일을 하는지 정확하게 복원했다고 해 보겠습니다. 바이너리에서 나오는 건 그 버튼이 지금 하는 일입니다. 안 나오는 건 이런 것들입니다. 원래는 버튼이 두 개였는데 사용자들이 계속 헷갈려해서 하나로 합쳤다는 것. 합치면서 한쪽 기능은 일부러 뺐다는 것. 뺀 기능을 쓰던 사용자가 소수 있었고 그 사람들한테는 다른 경로를 안내했다는 것. 다음 분기에는 이 버튼을 아예 없앨지 고민 중이라는 것. 이건 전부 그 버튼의 일부인데, 바이너리 어디에도 없습니다.
이전에 AI 에이전트가 코드 바깥의 맥락을 못 본다는 글을 쓴 적이 있는데, 이번에는 같은 얘기를 반대쪽에서 보게 된 것 같습니다. 그때는 AI가 내 코드를 볼 때 코드 바깥을 못 본다는 얘기였다면, 이번에는 누가 내 앱을 통째로 뜯어봐도 코드 바깥은 같이 안 따라간다는 얘기입니다. 같은 구멍인데 이번에는 그게 오히려 위안이 됩니다.
복제한 쪽은 오늘의 버튼을 갖게 됩니다. 원본을 만든 쪽은 그 버튼이 어제 왜 바뀌었고 내일 어떻게 될지를 갖고 있습니다. 소프트웨어는 출시한 날부터 계속 바뀌는 물건이라서, 오늘 상태를 똑같이 베낀 사람은 내일부터 다시 혼자 판단해야 합니다. 그리고 그 판단의 근거, 그러니까 어떤 사용자가 뭘 불편해했고 어떤 장애가 왜 났고 어떤 기능은 왜 안 쓰였는지는 운영을 해 본 쪽에만 쌓여 있습니다.
운영하면서 쌓인 맥락이라는 게 사실 겉으로 보면 되게 사소한 모양을 하고 있습니다. 재시도 횟수가 세 번이 아니라 두 번인 것, 어떤 요청은 타임아웃이 유독 긴 것, 특정 시간대에만 꺼지는 기능 같은 것들이요. 바이너리를 뜯어보면 이런 값들은 그냥 상수로 보입니다. 하지만 운영해 본 쪽에서는 그 상수 하나하나에 장애가 하나씩 붙어 있는 경우가 많습니다. 예를 들면 세 번 재시도했더니 뒤쪽 서버가 같이 넘어간 적이 있어서 두 번이 된 거고, 그 시간대에는 정산 배치가 돌아서 꺼 둔 거고. 이 이유를 모르는 사람이 보기에 그 숫자들은 정리하고 싶은 지저분한 값이고, 이유를 아는 사람이 보기에는 건드리면 안 되는 값입니다.
거슬리는 값을 발견해도 바로 지우지 않는 습관이 저한테는 있는데, 그게 이런 이유에서입니다. 누가 일부러 남겨 둔 것일 수 있으니까요. 그런데 바이너리에서 복원한 코드를 받은 사람한테는 물어볼 누군가가 없습니다. 이 값을 남겨 둔 사람은 원본 회사에 있고, 그 사람의 기억은 바이너리에 안 들어갔습니다. 그러니 복제한 쪽은 그 값을 그대로 두거나 지우거나 둘 중 하나를 골라야 하는데, 어느 쪽이 맞는지 알 방법이 없습니다.
제 MFC 프로그램이 정확히 이 상태라는 게 좀 웃깁니다. 저는 그 프로그램의 원작자인데, 소스와 함께 그때의 판단도 같이 잃어버려서 이제는 저도 복제하는 사람 쪽에 서 있습니다. REA로 동작을 되돌려도 저는 오늘의 그 프로그램만 갖게 되고, 왜 그렇게 만들었는지는 기억나는 만큼만 갖게 됩니다. 원작자라는 게 바이너리 앞에서는 별 의미가 없더라고요.
34만이라는 숫자를 보면서
스타 숫자는 좀 따로 생각해 보고 싶었습니다. 34만은 개발 도구로서는 거의 본 적 없는 숫자입니다. 이 숫자를 보면 다들 이제 리버스엔지니어링이 일상이 된다고 말하는데, 저는 그 말이 맞는지 아직 잘 모르겠습니다.
스타를 누르는 일과 실제로 바이너리를 열어 보는 일 사이에는 거리가 꽤 있습니다. README에도 적혀 있듯이 깊은 네이티브 분석을 하려면 Hopper나 Ghidra나 IDA가 있어야 하고, 상용 도구는 정식으로 구해야 하고, Node 버전도 맞춰야 하고, macOS에서는 Hopper의 첫 실행 라이선스 창도 직접 넘겨야 한다고 되어 있습니다. 큰 바이너리는 타임아웃을 늘려야 할 수도 있고요. 명령어 한 줄로 시작되는 건 맞는데, 그 한 줄 뒤에 있는 것들이 다 사라진 건 아닙니다. Quesma 글에서 실패할 수 있는 단계가 많다고 했던 그 부분이 줄었을 뿐 없어지지는 않은 것 같습니다.
그리고 이게 원래 있던 걸 크게 포장한 건 아닌가 하는 의심도 조금은 있습니다. Ghidra를 MCP로 붙이는 프로젝트는 작년에도 여럿 있었고, 디컴파일러는 그 전부터 있었습니다. REA가 한 일은 그걸 하나로 묶고 대상 범위를 넓히고 결과에 근거를 붙이는 쪽이었다고 봅니다. 새 능력이 생긴 것과 이미 있던 능력이 쓰기 편해진 건 구분해서 보고 싶습니다. 근데 또 생각해 보면, 할 줄 아는 사람이 드물어서 일어나지 않던 일이 이제 하고 싶은 사람이면 누구나 하는 일이 됐으니, 세상 입장에서는 그게 새 능력이 생긴 거나 별로 다르지 않을 수도 있겠습니다. 제 MFC 프로그램 입장에서도 그렇고요.
그래서 지금 뭘 남기고 있나
이런 생각을 하다 보니 결국 제 습관 쪽으로 돌아오게 됩니다.
코드는 이제 남길 필요가 점점 줄어드는 것 같습니다. 극단적으로 말하면 실행 파일만 있어도 동작은 되돌릴 수 있게 됐으니까요. 반대로 남겨야 할 건 늘어난 것 같습니다. 왜 이렇게 했는지, 왜 다른 방법은 안 썼는지, 무엇을 일부러 뺐는지. 규칙 하나를 바꿀 때 예전 규칙이 왜 생겼고 왜 바꿨는지를 짧게 적어 두는 버릇이 있는데, 이게 예전에는 다음 사람이 같은 함정에 빠지지 않게 하려는 거였다면 이제는 그게 거의 유일하게 복제가 안 되는 기록이라는 생각도 듭니다.
커밋 메시지도 비슷합니다. 무엇을 바꿨는지는 diff가 이미 말해 줍니다. 디컴파일러도 말해 주고, 이제는 에이전트도 말해 줍니다. 왜 바꿨는지는 그 사람만 압니다. 제 MFC 프로그램에 커밋 메시지 같은 게 하나라도 남아 있었다면, 바이너리에서 코드를 되돌린 다음 그걸 옆에 펴 놓고 이름을 붙이는 일이 훨씬 쉬웠을 겁니다. SM64 프로젝트 사람들이 몇 년째 하고 있는 그 일을 저는 제 프로그램에 대해서도 해야 하는 상황인 거죠.
판단 쪽도 마찬가지입니다. 무엇을 만들지 정하는 일은 바이너리에 안 들어갑니다. 누가 내 기능을 며칠 만에 베낄 수 있게 됐다면, 그 기능을 만들기로 정한 근거는 베낀 사람한테 없습니다. 근거가 없는 쪽은 다음 기능을 정할 때 다시 베낄 대상을 찾아야 하고, 근거가 있는 쪽은 그걸 바탕으로 다음 걸 정합니다. 개발자가 살아남는 방법을 묻는다면 저는 지금 이쪽을 보고 있습니다. 코드를 짜는 속도로는 에이전트를 못 이기는데, 왜 이걸 짜는지에 대한 답은 아직 사람 쪽에 남아 있는 것 같습니다.
그리고 그 답은 혼자 갖고 있으면 별 의미가 없는 것 같습니다. 옆 사람이 이어받을 수 없는 판단은 그 사람이 나가는 날 같이 나갑니다. 소스를 잃어버린 회사 업무 프로그램들이 대개 그렇게 생겼을 겁니다. 코드는 서버 어딘가에 있었을 수도 있는데, 그걸 왜 그렇게 만들었는지 아는 사람이 나가면서 사실상 소스를 잃어버린 것과 같은 상태가 된 경우도 꽤 있지 않을까 싶습니다. 소스를 잃어버리는 방법은 파일을 지우는 것만 있는 게 아닙니다.
이것도 언제까지 그럴지는 모르겠습니다. 운영 로그나 사용자 피드백을 통째로 학습한 에이전트가 판단까지 흉내 내는 날이 올 수도 있을 것 같고요. 다만 그 판단의 재료는 바이너리가 아니라 운영하는 쪽 서버와 회의록과 사람 머릿속에 있으니, 적어도 REA 같은 도구로 바깥에서 꺼낼 수 있는 종류는 아니라고 봅니다.
아직 정리가 안 된 부분
제 MFC 프로그램을 실제로 REA에 넣어 볼지는 아직 모르겠습니다. 넣으면 아마 돌아오긴 할 겁니다. 동작하는 무언가가요. 그걸 받아 들었을 때 반가울지, 아니면 그때 왜 그렇게 짰는지를 하나도 모르는 코드를 보면서 남의 프로그램 같다고 느낄지가 좀 궁금합니다.
한편으로는 넣어 보는 게 좀 무섭기도 합니다. 지금은 그 프로그램이 제 기억 속에서 꽤 괜찮은 물건으로 남아 있는데, 동작을 하나하나 풀어 놓고 보면 그때의 제가 얼마나 대충 짰는지가 다 드러날 테니까요. 소스를 잃어버린 덕분에 오히려 지켜지고 있던 체면 같은 게 있었던 건가 싶기도 합니다. 반쯤 농담이지만 반쯤은 진심입니다. 디컴파일한 코드는 변명을 안 해 주니까요. 원래 소스였다면 주석 한 줄로라도 "임시로 이렇게 함" 정도는 적혀 있었을 텐데, 복원한 코드에는 그 임시가 영원한 것처럼 찍혀 나올 겁니다.
그리고 넣어서 동작을 다 되찾는다 해도, 그걸로 뭘 할지는 또 따로 정해야 합니다. 요즘 환경으로 다시 만들어서 계속 쓸지, 아니면 그냥 이런 걸 만들었었다는 기록으로만 둘지. 그건 REA가 정해 줄 수 없는 문제이고, 제가 그 프로그램을 지금도 필요로 하는지에 달린 문제입니다. 결국 여기서도 판단은 바이너리 바깥에 있네요.
어쩌면 소스를 잃어버린 프로그램이 많은 세상이라 이런 도구가 이렇게 빨리 퍼진 걸 수도 있겠다는 생각도 들었습니다. 34만 명이 전부 남의 앱을 뜯어보려고 스타를 누른 건 아닐 거고, 그중 꽤 많은 사람이 저처럼 어딘가 실행 파일만 남은 자기 물건을 떠올렸을 것 같습니다. 그 사람들이 받아 들게 되는 건 잃어버린 소스가 아니라 새로 쓴 소스일 텐데, 그 차이를 다들 언제쯤 느끼게 될지도 좀 궁금합니다.
예전에 쓴 글에서 결과물은 이제 아무것도 증명하지 못한다고 적은 적이 있습니다. 이번에는 그게 한 단계 더 간 것 같습니다. 결과물은 증명도 못 하고, 이제는 내 것이라고 하기도 애매한 물건이 됐습니다. 실행 파일만 남은 제 프로그램은 지금 누구 거라고 해야 할지, 저도 잘 모르겠습니다.
댓글
댓글 쓰기