3P by GN⁺ | ★ favorite | 댓글 1개
  • 한 개발자가 웹에서 본 ASCII 아트 컴포넌트의 구현을 이해하려고 축소된 JavaScript를 ChatGPT에 넣었고, 난독화된 흐름을 읽을 수 있는 구조로 풀어냄
  • 원본은 React 컴포넌트가 창 크기에 맞춰 열·행을 계산하고, setInterval60fps 문자열 그리드를 갱신하는 방식이었음
  • ChatGPT는 문자 집합 선택, 좌표·시간 기반 문자 계산, div.textContent 갱신 흐름을 설명한 뒤 동등한 TypeScript/React 코드로 재작성함
  • 처음에는 ░▒▓█ 같은 블록 문자가 빠져 구현이 다르다고 봤지만, 원본 스크립트를 다시 확인하자 복사-붙여넣기 인코딩 문제였음
  • 올바른 문자 집합으로 갱신한 결과 출력이 원본 컴포넌트와 같아졌고, 축소 코드 이해와 재구현에 ChatGPT를 실용적으로 쓸 수 있음을 보여줌

축소된 JavaScript에서 ASCII 아트 구현 찾기

  • reactive.network hackathon 페이지의 ASCII 아트 블록이 어떻게 구현됐는지 확인하기 위해 소스 코드를 살펴봄
  • 관련 코드는 축소되어 있어 사람이 바로 읽기 어려웠고, 일반적으로는 직접 해독하거나 .map 파일을 찾아 원본을 복원해야 하는 상황이었음
  • 이번에는 축소된 코드 전체를 ChatGPT에 붙여 넣고 코드의 동작을 설명해 달라고 요청함

축소 코드에서 드러난 구조

  • 코드는 JavaScript와 React를 사용해 동적 텍스트 콘텐츠를 생성하는 구조였음
  • 주요 요소는 다음과 같았음
    • Math.floor, Math.abs, Math.min 같은 수학 함수에 짧은 별칭을 붙여 사용함
    • "reactive.network REACTIVE.NETWORK"와 특수문자·블록 문자로 구성된 문자 집합을 둠
    • Date.now() % 3 결과에 따라 사용할 문자 집합을 선택함
    • 선택된 문자 집합 길이를 기준으로 각 좌표에 표시할 문자 인덱스를 계산함
  • 핵심 함수인 eT는 위치 좌표와 시간 값을 받아 표시할 문자를 하나 반환함
    • 시간 값은 t.time * 8e-5로 변환됨
    • 열·행 중 작은 값과 화면 비율을 이용해 좌표를 보정함
    • Math.cos(position.x * position.x - position.y * position.y) 계산 결과와 시간 값을 조합해 문자 인덱스를 정함

React 컴포넌트의 렌더링 방식

  • 컴포넌트는 useRef로 텍스트를 넣을 div를 참조하고, useState로 창의 heightwidth를 저장함
  • 첫 번째 useEffectwindow.innerHeight, window.innerWidth로 초기 크기를 설정하고, resize 이벤트가 발생할 때 크기를 갱신함
  • 두 번째 useEffect는 화면 크기를 바탕으로 ASCII 그리드 크기를 계산함
    • colsMath.floor(width / 12) * 1.6
    • rowsMath.floor(height / 12)
    • aspectcols / rows
  • setInterval1_000 / 60 간격으로 실행되어 60fps에 맞춰 문자열을 다시 생성함
  • 각 프레임에서는 모든 행과 열을 순회해 계산된 문자를 이어 붙이고, 최종 문자열을 element.textContent에 넣음

TypeScript로 읽기 쉬운 코드 재작성

  • 개발자는 이어서 동등한 코드를 TypeScript로 구현하되 사람이 읽기 쉽게 만들어 달라고 요청함
  • ChatGPT가 만든 코드는 축소 코드의 역할을 다음 이름들로 풀어냄
    • selectedCharacterSet
    • characterSetLength
    • calculateCharacter
    • Size 타입
    • AsciiArtGenerator React 컴포넌트
  • 난해한 변수명은 구현 의도를 드러내는 이름으로 바뀜
    • calculateCharacter는 좌표, 열·행, 화면 비율, 시간 값을 받아 표시할 문자를 계산함
    • textRef는 실제 텍스트가 들어갈 div를 가리킴
    • sizesetSize는 창 크기 상태를 나타냄
  • 재작성된 코드는 원래 축소 코드보다 훨씬 읽기 쉬웠고, 컴포넌트 구현 방식을 학습하기에 충분한 수준이었음

문자 누락 오해와 인코딩 문제

  • 초기 비교에서는 LLM이 몇 가지 구현 세부 사항을 놓친 것처럼 보였음
  • 특히 원본 컴포넌트에 보이던 ░▒▓█ 같은 문자가 ChatGPT 결과에 빠진 것으로 판단함
  • 이후 HN 포럼 사용자가 복사-붙여넣기 문제일 가능성을 짚었고, 추가 확인 결과 원본 코드에는 ChatGPT에 붙여 넣은 문자와 다른 문자가 들어 있었음
  • 스크립트를 직접 다운로드하자 올바른 문자를 얻을 수 있었고, 문제의 원인은 인코딩 차이로 확인됨
  • 문자 집합을 바로잡은 뒤 출력은 원본 컴포넌트와 동일해졌음

댓글과 토론

Hacker News 의견들
  • HumanifyJS 만든 사람인데, 이 용도로 LLM 기반 도구를 따로 만들었음
    LLM을 AST 수준에서 사용해서, 난독화 해제 이후에도 코드가 계속 동작하도록 보장함: https://github.com/jehna/humanify

    • 처음부터 이름 다시 짓기 기능을 추가하기 어려울지 궁금함
      일반 코드에서 사용자의 의미 있는 이름을 모두 지운 뒤, 알고리즘과 남은 이름들, 예를 들면 내장 이름을 바탕으로 새 이름을 고르는 기능을 말함
      리팩터링할 때 가끔 LLM으로 수동으로 하는데, parallel_line_opposite_direction 대신 antiparallel_line 같은 더 표준적인 용어를 찾거나, find_animal_instance_in_animals 대신 find_instance_in_list처럼 더 일반화 가능한 이름을 발견하는 데 유용함
    • 더 많은 도구가 AST 기반으로 만들어져야 함, 훌륭한 작업임
      솔직히 AST 수준의 버전 관리를 아직도 기다리고 있음
    • LLM에 어떤 질문을 던지는지 궁금함
      함수 전체를 주고 “무엇을 이름 바꿔야 하나?”를 전부 바뀔 때까지 반복해서 묻는 방식인지, 아니면 전체를 한 번에 시킨 뒤 출력을 파싱하고 AST가 여전히 일치하는지 확인하는 방식인지 궁금함
    • 거대한 파일에서도 동작하는지 궁금함
      5만 줄 정도를 말하는 것임
      수정: 지금 겨우 1.2천 줄짜리 JS 파일을 openai 모드로 돌려보는 중인데 20분이 지나도 70%만 끝났음. 이론적으로 5만 줄 파일에서 동작하더라도 시도하지 않는 편이 좋을 것 같음
  • JS 압축은 꽤 기계적이고 단순해서 되돌리는 작업도 비교적 쉬워야 함
    물론 일반적으로 사람이 직접 하기엔 충분히 지루하지만, 변환 자체는 상당히 제한적이라 난독화된 식별자를 추적하는 메모만 있으면 읽는 것이 가능함
    더 일반적인 압축 해제나 난독화 해제는 아직 열린 문제처럼 보임. 예전에 일부러 난독화한 프로그램을 몇 개 작성했는데, 내 경험상 ChatGPT는 표면적인 수준에서도 이해하지 못했음
    예를 들어 160바이트짜리 C로 작성한 Brainfuck 인터프리터 gist에 GPT-4로 코드를 설명하려 한 댓글이 있었는데 [1], 그 “명확해진 버전”은 원본 코드와 전혀 닮지 않았음
    [1] https://gist.github.com/lifthrasiir/596667#gistcomment-47512...

    • 어떤 작업이 단순하다고 해서 그 역방향 작업도 단순해야 하는 것은 아님
      예를 들면 곱셈/소인수분해, 미분/적분, 과거 기억/미래 예측이 있음
      코드 난독화 해제는 분명 이런 어려운 역문제 중 하나이고, 버그, 사용되지 않거나 무관한 루틴, 우연히 맞는 결과를 내는 잘못된 구현 때문에 쉽게 더 어려워질 수 있음
      그런 의미에서 ChatGPT가 난독화 해제에서 괜찮은 결과를 낸다면 다행인 일이지, 선험적으로 그럴 수 있어야 한다는 기대는 없음. 더 어려운 문제로 ChatGPT의 한계를 확인한 건 좋지만, 발견한 것은 상한선일 뿐이라고 봄. 원문의 예제를 사소하다고 보진 않음
    • let userSignedInTimestamp = new Date()let x = new Date()로 바꾸는 건 사소하지만, 반대로 가려면 x가 어떤 맥락에서 쓰이는지 보려고 주변 코드를 읽고 이해해야 할 가능성이 큼
      게다가 나머지 코드도 압축되어 있으니 더 어렵고, 다 맞게 해도 결국 손실 변환이라 변수명이 코드에 명시되지 않은 특성을 담고 있었을 수도 있음
    • 컬러 사진을 흑백으로 바꾸는 건 꽤 단순하지만, 원래 색으로 되돌리는 건 쉽지 않음
      이건 과정에서 데이터가 사라졌기 때문임
      압축도 같은 방식으로 작동함. 코드를 이해하는 데 필요한 정보가 많이 사라지고, 그 정보를 되찾는 일은 매우 까다로울 수 있음
    • 설명적인 변수명 timeFactor를 훨씬 짧은 i로 바꾸는 행위는 기계적이고 단순할 수 있지만, 그 과정은 파괴적이고 되돌리기는 비교적 쉽지 않음
      실제로 코드가 무엇을 하는지 꽤 정교하게 이해하지 않고는 불가능함. 여기서 LLM이 한 일이 바로 그 부분이고, 놀랍진 않지만 멋진 일임. 너무 즉각적으로 깎아내리는 태도는 별로임
    • Claude 3.5 Sonnet으로 첫 번째 무작위 시도를 해봄: https://claude.site/artifacts/246c1b1a-3088-447a-a526-b1e716...
      PC가 아니라 테스트는 못 해봤음
  • 제목에는 회사명인 OpenAI 대신 ChatGPT나 모델명인 GPT-4를 써야 하는 것 아닌가?

    • OpenAI가 일반 기술 용어였던 GPT를 브랜드로 만들어버린 걸 생각하면, OpenAI를 제품명처럼 쓰는 데 어느 정도 아이러니한 공정함이 있음
  • LLM은 텍스트 변환에 탁월함
    이게 핵심 강점인데 충분히 활용되지 않는 것 같음

    • 핵심 강점일 뿐 아니라, 트랜스포머가 하도록 설계된 일이 바로 그것이고, 논쟁적으로는 그것밖에 못 한다고 볼 수도 있음
      추론하거나 지식을 보존한다는 다른 능력들은, 의도된 의미를 “이해”하지 않고 텍스트를 재생산하는 이 초인적인 능력의 부작용일 뿐임
    • 특히 순서가 크게 바뀌지 않고 항목 간 관계를 깊게 고려하지 않아도 되는, 거의 선형적인 변환에서 강함
      예를 들어 목록 정렬은 못 하지만 언어 번역은 할 수 있음. 거의 맞게 정렬된 목록은 틀린 것이지만, 거의 맞는 번역은 우리가 종종 받아들이기 때문임
    • 적절한 도구가 붙으면 코드베이스를 다른 언어와 프레임워크로 거의 버튼 한 번에 옮길 수 있어야 함
      팀이 Python 코드의 큰 덩어리를 Elixir로 바꿀 가치가 있는지 고민한다면 더 이상 추측만 할 필요가 없음
      얼마 전 Python 스크립트를 JavaScript로 번역해봤는데 흠잡을 데 없었고, 약간의 가드레일만 있으면 규모를 키울 수 있을 것 같음
    • 이미 비즈니스에서의 주된 사용처가 이쪽 아닌가?
      우리는 주로 다른 형식에서 구조화된 데이터를 추출하는 데 씀
    • 문제는 환각 위험을 신경 쓰지 않아도 되거나, 이미 유용한 형식의 데이터가 없어도 출력을 검증할 수 있는 경우여야 한다는 것임
      게다가 awk, Python, Perl 등으로 더 빨리 처리할 지식이나 기술도 부족해야 함
  • 압축을 난독화 수단으로 쓰던 개발자들 중 일부는 분명 화가 날 것 같음
    예전 ColdFusion 버전에 코드 “암호화” 도구가 있었던 일이 떠오름. 알고리즘이 매우 약해서 누군가 복호화 도구를 만드는 데 오래 걸리지 않았음
    그래도 어떤 사람들은 그 도구가 안전하다고 믿고, 소스를 공개하지 않고 코드를 팔 때 쓰고 있었기 때문에 싫어했음. 오픈소스가 압도적인 기본값이 되기 전인 90년대 후반/2000년대 초반 이야기임

    • 곧 이런 식의 사용처와 군비 경쟁이 생길 것 같음
      웹사이트가 난독화된 클라이언트 측 코드에 일부 의존하는 대회를 제공하고, 영리한 참가자들은 이제 ChatGPT에 넣어 승률을 높일 수 있음
      이제 시작임
  • 이건 인간보다 우월한 지적 수행 능력의 예임
    부정할 수 없음. 이 작업은 지적이고, 단순 암기와 관련이 없음. 웹에는 LLM이 학습할 수 있는 압축 코드/압축 해제 코드 쌍이 엄청나게 많지도 않음
    LLM은 자신이 압축 해제하는 대상을 이해하고 있으며, 이 특정 주제에 한해서는 대체로 인간보다 우월함

    • 이건 그냥 텍스트 변환
      웹에 압축 코드와 비압축 코드 쌍이 많지 않다고 확신할 수 있나? 기존 JS에서 쉽게 생성해 학습 세트로 쓸 수 있고, 이미 학습에 쓰인 비압축 JS도 엄청나게 많음
    • AI에는 낙관적이지만, 이게 말한 현상의 예라고는 확신이 안 듦
      사람이 압축된 코드를 이해하기 어려운 이유는 불투명한 변수명, 어색한 반복문, 최소화된 공백 등 때문임. 컴퓨터는 이런 것들을 어려워하지 않으며, 애초에 우리가 압축을 하는 이유도 그것임. 어텐션 구조는 이런 작업을 잘해야 함
      압축/비압축 코드도 엄청나게 많다고 봄. 소스 맵 파일의 목적이 바로 그것임. OpenAI가 웹 브라우징과 소프트웨어 개발에 투자한 걸 보면, 학습에 압축/비압축 데이터를 포함했어도 놀랍지 않음
    • AI나 지능 이해에 전문성은 없지만, 휴대용 계산기가 산술을 정말 이해하고 인간보다 우월한 지적 수행 능력을 갖췄다고도 말할 수 있지 않을까?
    • 인간이 압축 해제를 돕는 도구를 만들었다는 사실을 인간 점수에 포함하면 안 되는 이유가 뭔가?
      컴퓨터가 1000자리 숫자를 즉시 곱하는 건 인간이 그런 도구를 먼저 만들어 곱셈에 성공한 예임. 거기서 지적으로 성공한 것이 또 무엇인가? 컴퓨터가 스스로를 만든 것도 아님
      인간 지능의 경계를 두개골에서 끊고, 그 지능이 만들고 쓰는 도구를 문제 해결 과정의 단계로 치지 않는다면, 인간은 우주로 날아가거나 수술하거나 대부분의 식사를 조리할 만큼 지능적이지 않다는 것도 받아들여야 함
    • GPT-4는 당신의 모든 조상이 평생 볼 코드보다 더 많은 코드를 소비했고, 코드와 압축된 버전 사이의 내재적 패턴을 이해함
      변수명이 빠진 코드의 추상적인 형태를 인식하고, 거대한 인터넷에서 본 비슷한 패턴에 사람이 읽을 수 있는 변수명을 매핑하는 일이 터무니없어 보이진 않음
  • 흥미롭고, 많이 좋아진 것 같음
    1년 조금 넘게 전에 해킹된 WordPress 사이트의 커스텀 플러그인을 도와달라는 메일을 받고, 악성 코드 난독화 해제에 GPT를 써보려 했음. 그때는 직접 코드를 단계별로 따라가는 편이 훨씬 더 진도가 잘 나갔음
    이 글을 읽고 다시 시도해봤음 [0]. 이해할 만한 내용을 주긴 했지만, Window 객체를 통해 읽을 수 없는 문자열을 사실상 eval하는 정도로 난독화되어 있어서 그것만으로는 충분하지 않았음
    당시 보낸 보고서 일부는 이랬음: 심하게 난독화된 JavaScript 코드를 파고들어, 페이지 로드를 감지하고, 이론상 상수인 계산의 허울을 호출한 뒤, 페이지를 악성 사이트로 리다이렉트하는 로직을 해독할 수 있었다는 내용이었음
    [0] https://chatgpt.com/share/f51fbd50-8df0-49e9-86ef-fc972bca6b...

  • 디컴파일러 LLM을 작업 중인 사람이 있나?
    모든 코드를 오픈소스처럼 만들 수 있을 것 같음
    이 경우 학습 데이터는 만들기 쉬움. 무료 GitHub 코드를 여러 컴파일러로 대량 빌드하고, 컴파일을 되돌리도록 학습시키면 됨. 합성 학습 데이터가 적절하고 꽤 쉽게 생성 가능한 경우임
    디컴파일러는 컴파일을 되돌리는 데만 학습시키고, 주석 추가 같은 작업은 기존의 더 큰 코드 LLM을 쓰면 됨

    • 잠재적 파급력이 엄청남
      오픈소스화뿐 아니라, 독점 앱을 쉽게 디컴파일하고 수정해서 버그를 고치거나 기능을 추가하는 상황을 상상해볼 수 있음. 특히 오래전에 죽은 프로그램에는 큰 해방구가 될 수 있음
      법적인 이유로 주요 모델에서는 이런 동작이 차단된 기능이 될 가능성이 큼
    • 소스를 갖는 것은 첫 단계일 뿐이고, 그 코드를 읽고, 수정하고, 실행하고, 수정본을 재배포할 권리도 필요함
      그런 권리는 코드 작성자만 부여할 수 있음
    • 동의함. “AI가 소스 코드를 생성/이해한다”는 건 큰 주의 분산용 미끼라고 봄
      AI가 코드를 정말 잘 이해한다면 그냥 바이너리를 만들거나 고칠 것임
      진짜 AI 프로그래머가 나오면 사람이 읽을 수 있는 코드는 신경 쓰지 않고 기계어로 전부 코딩할 것이라 믿음. 기존 시스템을 유지보수하라는 과제를 받으면 소프트웨어와 하드웨어 스택 전체를 통째로 이해할 것임
      이미지 생성 확산 모델이 실제 드로잉 기법을 배우는 데 신경 쓰지 않는 것과 비슷함
    • x86에서 C로 디컴파일하는 LLM이 있음: https://github.com/albertan017/LLM4Decompile
    • 압축 해제는 디컴파일이 아님
      변수와 함수 이름을 바꾸고 줄바꿈을 넣는 것에 가까움
  • 내가 LLM을 자주 쓰는 방식과 매우 비슷함 [0]
    직접 힘으로 코드를 읽어야 할 때, 해독의 첫 단계로 씀
    스파게티 같은 엉망진창 코드를 넣고 더 읽기 쉽게 풀어달라고 한 뒤 거기서 시작할 수 있는 건 엄청나게 해방감이 있음
    저자도 발견했듯 LLM은 가끔 세부사항을 놓치지만, 그런 부분은 내가 직접 잡으면 됨
    또 다른 사용처는 코드가 무엇을 하는지는 이해하지만 왜 그런 방식으로 되어 있는지 머리에 잘 들어오지 않을 때임. 특히 그 코드를 작성한 사람이 더 이상 회사에 없을 때 그렇다. 그때는 메서드를 LLM 채팅에 넣고 무엇을 하는지 설명한 다음, 왜 어떤 것들이 특정 방식으로 되어 있을 수 있는지 묻음
    항상 완벽하진 않지만, 꽤 자주 실제로 말이 되고 검토해도 버티며 새 통찰을 주는 설명을 내놓음. 한두 번은 나중에 골치 아팠을 방식으로 리팩터링하는 걸 막아주기도 했음
    [0] chatGPT와 최근에는 여러 다른 모델, 주로 Claude 변형들을 프런트엔드로 쓰는 openwebUI를 사용해 차이를 봄. 서로 다른 모델이 서로의 답을 검토하게 하는 재미있는 구성도 가능함

  • 압축 해제된 코드가 압축된 코드와 일치하지 않는다면, 끝부분의 “LLM 응답이 몇몇 구현 세부사항을 놓친 것 같다”는 내용처럼, 유용성이 크게 줄어듦
    특히 많은 경우 글처럼 코드를 간단히 실행해서 차이를 확인할 수 없기 때문임
    수정: 아래 스레드를 보니 인코딩 문제였던 것 같음. 그래도 정확성은 여전히 조금 걱정됨

    • 실제 이름 변경은 HumanifyJS처럼 다른 도구를 써야 함: https://github.com/jehna/humanify
    • 압축 해제된 코드는 원본과 꽤 가까워 보임
      어떤 경우에는 ChatGPT가 압축 해제에 더해 자체 리팩터링까지 했음
      원본 코드는 handleResize를 즉시 호출하지 않고 그 내용을 인라인으로 갖고 있음. 아마 압축기가 실제 인라이닝을 했을 것임. 여기서 유일한 실제 차이는 if (typeof window < "u") 조건이 빠진 것임
    • 그 부분은 ChatGPT가 생성한 버전에 원본 예제에서 쓰인 일부 문자가 빠졌다는 뜻임
      구체적으로 블록 문자들을 말하는데, HN이 유니코드 문자를 붙여넣게 해주지 않는 것 같음. 원본 버전에서는 보이지만 ChatGPT 생성 버전에서는 보이지 않음
      다만 내가 필요한 맥락을 모두 포함하지 않았기 때문일 가능성도 충분함
      문자 몇 개가 빠졌다는 이유로 전체 출력을 깎아내리는 건 너무 꼼꼼한 트집에 가까움. 그 외에는 내가 보기엔 출력이 동일함
    • 게다가 JavaScript에서 TypeScript로 재구현하라고도 시켰음
      변수와 메서드 이름부터 바꾸라고만 했다면 원본에 더 가까웠을 것 같음