3 ポイント 投稿者 GN⁺ 2024-08-30 | 1件のコメント | WhatsAppで共有
  • ある開発者がWebで見かけたASCIIアートコンポーネントの実装を理解しようと、縮小されたJavaScriptをChatGPTに入力したところ、難読化された流れを読みやすい構造に展開した
  • 元の実装は、Reactコンポーネントがウィンドウサイズに合わせて列・行を計算し、setInterval60fpsの文字列グリッドを更新する方式だった
  • ChatGPTは文字セットの選択、座標・時間ベースの文字計算、div.textContentの更新フローを説明したうえで、同等のTypeScript/Reactコードとして書き直した
  • 当初は░▒▓█のようなブロック文字が抜けており実装が異なると見られたが、元のスクリプトを再確認するとコピー&ペースト時のエンコーディング問題だった
  • 正しい文字セットに更新した結果、出力は元のコンポーネントと同じになり、縮小コードの理解と再実装にChatGPTを実用的に使えることを示した

縮小されたJavaScriptからASCIIアート実装を見つける

  • reactive.network hackathonページのASCIIアートブロックがどのように実装されているか確認するため、ソースコードを調べた
  • 関連コードは縮小されており、人間がすぐに読むのは難しく、通常は自力で解読するか.mapファイルを探して元のコードを復元する必要がある状況だった
  • 今回は縮小されたコード全体をChatGPTに貼り付け、コードの動作を説明してほしいと依頼した

縮小コードから見えた構造

  • コードはJavaScriptとReactを使って動的なテキストコンテンツを生成する構造だった
  • 主な要素は次の通り
    • Math.floorMath.absMath.minのような数学関数に短いエイリアスを付けて使用している
    • "reactive.network REACTIVE.NETWORK"と特殊文字・ブロック文字で構成された文字セットを持つ
    • Date.now() % 3の結果に応じて使用する文字セットを選択する
    • 選択された文字セットの長さを基準に、各座標に表示する文字インデックスを計算する
  • 中核関数であるeTは、位置座標と時間値を受け取り、表示する文字を1つ返す
    • 時間値はt.time * 8e-5に変換される
    • 列・行のうち小さい値と画面比率を使って座標を補正する
    • Math.cos(position.x * position.x - position.y * position.y)の計算結果と時間値を組み合わせて文字インデックスを決める

Reactコンポーネントのレンダリング方式

  • コンポーネントはuseRefでテキストを入れるdivを参照し、useStateでウィンドウのheightwidthを保存する
  • 1つ目のuseEffectwindow.innerHeightwindow.innerWidthで初期サイズを設定し、resizeイベントが発生したときにサイズを更新する
  • 2つ目の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に貼り付けた文字とは別の文字が入っていた
  • スクリプトを直接ダウンロードすると正しい文字を取得でき、問題の原因はエンコーディングの違いだと確認された
  • 文字セットを修正した後、出力は元のコンポーネントと同一になった

1件のコメント

 
GN⁺ 2024-08-30
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年代前半の話だ

    • まもなく、この手の用途と軍拡競争が起きそうだ
      難読化されたクライアントサイドコードに一部依存するコンテストをWebサイトが提供し、賢い参加者はそれをChatGPTに入れて勝率を高められるようになる
      まだ始まったばかりだ
  • これは人間より優れた知的遂行能力の例だ
    否定できない。この作業は知的であり、単純な暗記とは関係がない。Web上には、LLMが学習できる圧縮コード/展開コードのペアが膨大にあるわけでもない
    LLMは自分が展開している対象を理解しており、この特定のテーマに限っては概して人間より優れている

    • これは単なるテキスト変換
      Web上に圧縮コードと非圧縮コードのペアが多くないと確信できるのか?既存のJSから簡単に生成して学習セットに使えるし、すでに学習に使われた非圧縮JSも膨大にある
    • AIには楽観的だが、これがあなたの言う現象の例だとは確信できない
      人間が圧縮されたコードを理解しにくい理由は、不透明な変数名、不自然なループ、最小化された空白などにある。コンピューターはこうしたものを難しいとは感じないし、そもそも私たちが圧縮する理由もそこにある。アテンション構造はこうした作業を得意とするはずだ
      圧縮/非圧縮コードも非常に多いと思う。ソースマップファイルの目的がまさにそれだ。OpenAIがWebブラウジングとソフトウェア開発に投資していることを考えると、学習に圧縮/非圧縮データを含めていても驚かない
    • 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はUnicode文字の貼り付けを許していないように見える。元の版では見えるが、ChatGPT生成版では見えない。
      ただし、私が必要な文脈をすべて含めていなかったためである可能性も十分ある。
      文字が数個抜けているという理由で出力全体を貶めるのは、細かすぎる揚げ足取りに近い。それ以外は、私には出力は同一に見える。
    • しかもJavaScriptからTypeScriptへ再実装するようにも頼んでいた。
      変数名とメソッド名だけを変えるよう頼んでいれば、元のコードにもっと近かったと思う。