今日表示しない

トップへ戻る

トップへ戻る

脅威インテリジェンス

脅威インテリジェンス

脅威インテリジェンス

LazarusComebacker

LazarusComebacker

LazarusComebacker

エンキホワイトハット

エンキホワイトハット

コンテンツ

コンテンツ

コンテンツ

概要

  • XにLazarusに分類されたドメインが報告され、該当ドメインに存在するマルウェアを確保した。

  • 確保したマルウェアを分析した結果、Lazarusが使用するComebackerマルウェアの新規変種であることが確認された。

  • C&Cサーバーのインフラを分析する過程で、追加のC&CサーバードメインとComebackerマルウェアを確保して分析した。

1. 概要

XのThreatBookLabs投稿からLazarusに分類されたドメインが報告(25.06.19)され、該当ドメインに存在するdocxファイルに偽装したマルウェアを確保して分析した。

分析の結果、該当のマルウェアはLazarusが使用するComebackerマルウェアの新規変種であることが確認され、C&Cサーバーのインフラ分析過程で2025年3月に流布されたと推定されるComebackerマルウェアも追加で確保することができた。

本稿では、新規変種であるComebackerマルウェアに対する分析内容と攻撃の変遷過程について言及する。

1.1. Comebacker

Comebackerマルウェアは、2021年のGoogle Threat Analysis Groupのブログ記事で最初に報告されたマルウェアであり、C&CサーバーからDLLファイルをダウンロードして実行する方式のダウンローダー/バックドアである。Microsoftによって最初にComebackerと命名され、現在までLazarusマルウェアとして知られている。

2024年には、PyPIパッケージにComebackerマルウェアが挿入されて配布されたことが確認されており、2021年から継続的に流布されている。

2. マルウェア解析

2.1. C&Cサーバーのオープンディレクトリ

マルウェアが発見されたC&Cサーバーのアドレスはoffice-theme[.]comである。dat、tmp、binなど複数の拡張子ファイルが存在していたが、datまたはtmpが拡張子のファイルはダウンロードできなかった。

악성코드가 발견된 office-theme[.]com 접속 화면

caption - マルウェアが発見されたoffice-theme[.]comのアクセス画面

ダウンロードできるファイルのうち、bin拡張子のファイルはdocxファイルで、VBAマクロが挿入されたマルウェアである。計4つのdocxファイルが存在し、おとりファイルは異なるが、すべて同一のマルウェアを生成する。

2.2. Comebackerドロッパー

docxファイルは、追加のマルウェアとおとりファイルを生成して実行するドロッパーである。

docxファイルに挿入されたVBAマクロはolevbaを使用して抽出した。

olevba를 사용하여 추출한 VBA 매크로 코드

caption - olevbaを使用して抽出したVBAマクロコード

VBAマクロはdocxファイルが実行された際、16進文字列として保存されたローダーマルウェアとおとり文書を復号して保存する。ローダーマルウェアとおとり文書のファイルパスは以下の通りである。

  • ローダーマルウェア: C:\ProgramData\WPSOffice\wpsoffice_aam.ocx

  • おとり文書: C:\ProgramData\Document\EDGE_Group_Interview_NDA.docx

復号プロセスはxorとbitswapで構成されており、復号スクリプトは「付録C. Comebackerドロッパー復号スクリプト」を参照されたい。

その後、ローダーマルウェアをLoadLibraryA関数で呼び出し、おとり文書を実行する。確認されたおとり文書の内容は計4件で、Edge Groupを詐称した文書1件、インド工科大学であるIIT Kanpurを詐称した文書2件、エアバスを詐称した文書1件が存在する。

詐称した団体がいずれも宇宙航空分野に関連する団体であり、おとり文書の内容がすべて宇宙航空分野に関連していることから、攻撃者の目的は宇宙航空分野に関する情報の確保であることが推測される。

Guest_Lecture_Invitation_Format_IITK.docx의 내용

caption - Guest_Lecture_Invitation_Format_IITK.docxの内容

Airbus_C295_Integration_Document_for_TASL.docx의 내용

caption - Airbus_C295_Integration_Document_for_TASL.docxの内容

2.3. Comebacker 1次ローダー – wpsoffice_aam.cox

自身に存在する暗号化されたマルウェアを復号して実行するローダーマルウェアである。暗号化されたマルウェアは復号後、zlibで圧縮解凍される。

まず、chacha20アルゴリズムで暗号化されたマルウェアを復号し、初期カウンター値としては1を使用する。この他に使用されるkeyとivは以下の通りである。

  • key: ad9c5aca9977d04c73be579199a827049b6dd9840091ffe8e23acc05e1d4a657

  • iv: edc9ce049daeba35b8687740

  • counter: 1

復号スクリプトは「付録C. Comebacker 1次ローダー復号スクリプト」を参照されたい。

復号結果をzlibで圧縮解凍すると、最終的にマルウェアデータを確認することができ、マルウェアのパスは以下の通りである。

  • マルウェアのパス: C:\ProgramData\USOShared\USOPrivate.dll

악성코드 복호화 및 USOPrivate.dll 생성 루틴

caption - マルウェアの復号およびUSOPrivate.dll生成ルーチン

「C:\ProgramData\USOShared\USOPrivate.dll」を正常に生成すると、USOPrivate.dllのLoadMimi関数を呼び出すLNKファイルを生成する。LNKファイルを生成するために実行されるコマンドは以下の通りである。

  • cmd.exe /C powershell -Command "$s = (New-Object -COMWScript.Shell).CreateShortcut('C:\\ProgramData\\USOShared\\Micro.lnk'); $s.TargetPath = 'C:\\Windows\\System32\\rundll32.exe'; $s.Arguments = '\"[USOPrivate.dll パス]\" LoadMimi \"C:\\Windows\\System32\\cmd.exe\"'; $s.Save()"

その後、生成されたLNKファイルをスタートアップフォルダにコピーして永続性を確保し、rundll32を利用してUSOPrivate.dllからLoadMimi関数を呼び出す。

2.4. Comebacker 2次ローダー – USOPrivate.dll

自身に存在する暗号化されたComebackerマルウェアを復号してメモリ上で実行するローダーマルウェアである。

復号アルゴリズムはwpsoffice_aam.ocxと同一のchacha20で、使用されるkeyとiv、カウンター値も同一である。

  • key: ad9c5aca9977d04c73be579199a827049b6dd9840091ffe8e23acc05e1d4a657

  • iv: edc9ce049daeba35b8687740

  • カウンター値: 1

Comebacker 악성코드 복호화 루틴

caption - Comebackerマルウェア復号ルーチン

復号後にzlib圧縮を解凍する手順まで、wpsoffice_aam.ocxと同一である。Comebackerマルウェアはメモリにロードされ、GetWindowSizedW関数に「1282」を渡して呼び出す。

Comebacker GetWindowSizedW 함수 호출 루틴

caption - Comebacker GetWindowSizedW関数呼び出しルーチン

2.5. Comebacker

GetWindowSizedW関数が呼び出されると、まず「[ランダムな英数字10文字][引数値(1282)]64」の形式でIDを生成する。

ID 생성 루틴

caption - ID生成ルーチン

その後、C&Cサーバーへの初期接続を試みる。C&Cサーバーのアドレスは以下の通りである。

  • hxxps://hiremployee[.]com

2.5.1. C&Cサーバー通信

C&Cサーバーとの通信はHTTPSを使用し、送信データはAES-128-CBCで暗号化された後、base64でエンコードされる。このとき使用される暗号化keyとivは同一である。

  • 暗号化keyおよびiv: x!P<&}mjH2YHRQ',

AES 암호화 루틴

caption - AES暗号化ルーチン

C&Cサーバーの受信データもまたbase64デコードの後、AES-128-CBCで復号される。復号に使用されるkeyとivは、暗号化に使用されたものと同一である。

復号スクリプトは「付録C. Comebacker C&Cサーバー送受信データ復号スクリプト」を参照されたい。

2.5.2. 初期接続

C&Cサーバーへの初期接続時、まず以下の形式のメッセージを生成して送信する。視認性向上のため、&を基準に区切っている。

  • [ランダムな英字2文字]=[ランダムな英字10文字]&

    [ランダムな英字5文字]=[base64エンコードされたID値]&

    [ランダムな英字4文字]=&

    [ランダムな英字6文字]=0&

    [ランダムな英字6文字]=[base64エンコードされた現在時刻の長さ]&

    [ランダムな英字6文字]=[base64エンコードされた現在時刻]&

    [10文字以内のランダムな英字]=[20文字以内のランダムな英字]

これに対するC&Cサーバーの応答は以下の通りである。

  • [16進数4文字] [16進数1文字] [base64エンコードされたメッセージ長] [base64エンコードされたメッセージ]

C&Cサーバーの応答データのうち、数値とメッセージに応じて以下の行動を実行する。

| C&Cサーバー応答データ | 行動 |
| --- | --- |
| 最初の数値が13の場合 | 現在実行中のプロセスを終了する。 |
| デコードされたメッセージが0の場合 | 0を受信した回数が20回未満であれば1分後に再通信を試み、多ければ20分後に再通信を試みる。 |
| デコードされたメッセージが1の場合 | 10秒後に再通信を試みる。

| C&Cサーバー応答データ | 行動 |
| --- | --- |
| 最初の数値が13の場合 | 現在実行中のプロセスを終了する。 |
| デコードされたメッセージが0の場合 | 0を受信した回数が20回未満であれば1分後に再通信を試み、多ければ20分後に再通信を試みる。 |
| デコードされたメッセージが1の場合 | 10秒後に再通信を試みる。

| C&Cサーバー応答データ | 行動 |
| --- | --- |
| 最初の数値が13の場合 | 現在実行中のプロセスを終了する。 |
| デコードされたメッセージが0の場合 | 0を受信した回数が20回未満であれば1分後に再通信を試み、多ければ20分後に再通信を試みる。 |
| デコードされたメッセージが1の場合 | 10秒後に再通信を試みる。

caption - C&C response info

上記の表にリストされている場合を除き、メッセージは以下のように定められた形式に従う。

  • [コマンドコード]|[暗号化されたファイルサイズ]|[実行関数名]|[実行引数]|[暗号化されたファイルのMD5ハッシュ]

上記の条件を満たすメッセージを受信した場合、C&Cサーバーから暗号化されたファイルをダウンロードして実行する。

2.5.3. ファイルのダウンロードおよび実行

C&Cサーバーから暗号化されたファイルをダウンロードすると、まず暗号化されたファイルのMD5ハッシュが先に受信したハッシュと同一であるかを検証する。もしハッシュが同一でない場合、初期接続で「デコードされたメッセージが0の場合」と同様の動作を行う。

md5 해시 비교 루틴

caption - md5ハッシュ比較ルーチン

ハッシュが同一であれば、暗号化されたファイルを復号する。復号アルゴリズムはwpsoffice_aam.ocxと同一のchacha20で、使用されるkeyとiv、カウンター値も同一である。

  • key: ad9c5aca9977d04c73be579199a827049b6dd9840091ffe8e23acc05e1d4a657

  • iv: edc9ce049daeba35b8687740

  • カウンター値: 1

chacha20 복호화 루틴

caption - chacha20復号ルーチン

復号されたファイルはメモリにロードされ、初期接続時に受信した実行関数名と実行引数を渡して実行される。その後、実行結果をC&Cサーバーに送信し、再び初期接続を試みる。

分析当時、C&Cサーバーから追加ファイルが配布されていなかったため、ダウンロードされるファイルの機能を確認することはできなかった。

3. 追加マルウェアの確保および分析

VirusTotalのRelation機能を通じて、C&Cサーバー(hxxps://hiremployee[.]com)の応答とHTMLハッシュが同一の応答を送信するURLが存在するか確認した。その結果、新しいC&Cサーバーのドメインであるbirancearea[.]comを発見した。

새로운 C&C 서버 도메인

caption - 新しいC&Cサーバーのドメイン

birancearea[.]comは2025年3月に活性化されたことが確認されており、該当するC&Cサーバーと通信するComebackerローダーのマルウェアハッシュは以下の通りである。

  • f2b3867aa06fb38d1505b3c2b9e523d83f906995dcdd1bb384a1087b385bfc50

3.1. Comebacker 1次ローダー

上記プロセスで確認したComebackerローダーマルウェアはDLLファイルであり、2025年3月に初めてVirusTotalにアップロードされた。これは、2024年にPyPIパッケージに挿入されて配布されたComebackerローダーマルウェアのHC256実装および使用方式、復号されたマルウェア実行コードと類似している。

マルウェアの実行引数値が「9Ez6THDirL6Zye4」でない場合に実行を終了するコードが存在することから、該当するマルウェアを実行する上位段階のマルウェアが存在することが推測できた。しかし、上位段階のマルウェアを確保できなかったため、どのように実行されるかは確認できなかった。

실행 인자 검사 루틴

caption - 実行引数検査ルーチン

実行引数が同一であれば、自身に存在する暗号化されたマルウェアを復号し、圧縮を解凍して「C:\ProgramData\USOShared\USOInfo.dat」に保存する。

復号に使用されるアルゴリズムはHC256であり、keyとivは同一である。2024年にPyPIパッケージに挿入されて配布されたComebackerローダーマルウェアでも、keyとivが同一のデータであり、HC256を使用していることが確認できた。

  • key, iv: LH*x239udC<*sd_Sej%lOa0$&ujHl(.R

새로 확인한 Comebacker 로더 악성코드 HC256 루틴

caption - 新たに確認されたComebackerローダーマルウェアのHC256ルーチン

2024년 PyPI 패키지에 삽입되어 배포된 Comebacker 로더 악성코드 HC256 루틴

caption - 2024年にPyPIパッケージに挿入されて配布されたComebackerローダーマルウェアのHC256ルーチン

復号スクリプトについては、「HC256復号スクリプト」を参照されたい。

復号および圧縮解凍が完了すると、rundll32を利用してGetSysStartTime関数を呼び出す。この際、実行引数として「dfgdfg」と「G3z!X97k7QrwG」を渡す。

USOInfo.dat 실행 루틴

caption - USOInfo.dat 実行ルーチン

3.2. Comebacker 2次ローダー - USOInfo.dat

まず、2番目の実行引数値が「G3z!X97k7QrwG」であるかを検査し、同一でない場合は実行を終了する。

同一であれば、自身に存在する暗号化されたマルウェアを復号し、圧縮を解凍する。復号に使用されるアルゴリズムはHC256であり、keyとivは同一である。

  • key, iv: 6w6ZT9|a-0}s$@;(@&#jPVC4o+V?1IU%

실행 인자 확인 및 복호화 루틴

caption - 実行引数の確認および復号ルーチン

復号および圧縮解凍が完了すると、メモリにロードしてGetWindowSizedW関数に「3718」を渡して実行する。このように実行されるマルウェアはComebackerであり、その挙動は「マルウェア分析 - Comebacker」で説明したものと同様である。

4. 攻撃の変化

4.1. 復号プロセス

2021年のGoogle Threat Analysis Groupの記事で報告されたComebackerマルウェアは、RC4またはkeyとivが同一のHC256を介して暗号化されたマルウェアを復号していました。

Google, Microsoft 보고서 a75886b016d84c3eaacaf01a3c61e04953a7a3adf38acf77a4a2e3a8f544f855 복호화 루틴

caption - Google、Microsoftの報告書 a75886b016d84c3eaacaf01a3c61e04953a7a3adf38acf77a4a2e3a8f544f855 復号ルーチン

2024年のPyPIパッケージに挿入されて配布されたComebackerマルウェアと、2025年3月に流布したと推定されるComebackerマルウェアは、keyとivが同一のHC256を介して暗号化されたマルウェアを復号します。

caption - jpcertの報告書 pycryptoenvで流布されたcomebackerマルウェア復号ルーチン

今回新たに確認されたComebackerマルウェアは、xorとbit swapを利用したカスタム暗号と、keyとivが同一のchacha20を介して暗号化されたマルウェアを復号します。

신규 Comebacker 악성코드 복호화 루틴

caption - 新規Comebackerマルウェア復号ルーチン

4.2. 通信の暗号化

2021年のGoogle Threat Analysis Groupの記事で報告されたComebackerマルウェアと、2024年のPyPIパッケージに挿入されて配布されたComebackerマルウェアは、C&Cサーバーの送受信データが平文です。

jpcert - PyPI 패키지에 삽입되어 배포된 Comebacker 악성코드

caption - jpcert - PyPIパッケージに挿入されて配布されたComebackerマルウェア

2025年3月以降に確認されたComebackerマルウェアは、AES-CBC-128を利用して送受信データを暗号化・復号化します。

신규 Comebacker 악성코드 AES 루틴

caption - 新規ComebackerマルウェアのAESルーチン

4.3. 排布プロセス(感染経路)

最初にGoogle Threat Analysis Groupの記事で報告されたComebackerマルウェアは、セキュリティ研究者を対象とした脆弱性分析の協力要請を装ったメッセージを通じて配布されました。攻撃者はマルウェアが挿入されたVisual Studioプロジェクトを送信し、マルウェアを実行するように誘導しました。この他にも、Internet Explorerの0dayを利用した攻撃を実行しました。0day攻撃に関する情報はエンキホワイトハット(ENKI Whitehat)の研究者が確認し、ブログ記事に公開しました。

공격자 활동 (출처 : google - New campaign targeting security researchers)

caption - 攻撃者の活動(出典:google - New campaign targeting security researchers

2024年のPyPIパッケージに挿入されて配布されたComebackerマルウェアは、タイポスクワッティング手法を用いて、不特定多数がマルウェアをダウンロードして実行するように誘導しました。

Comebacker 악성코드가 삽입된 pycryptoconf (출처 : jpcert - New Malicious PyPI Packages used by Lazarus)

caption - Comebackerマルウェアが挿入されたpycryptoconf(出典:jpcert - New Malicious PyPI Packages used by Lazarus

2025年3月に流布したと推定されるComebackerマルウェアは、現在どのような方式で配布されたのか確認されていません。

オープンディレクトリで確認された最新のマルウェアについても、攻撃事例や配布プロセスは公開されていませんが、おとり文書の内容から配布プロセスを推測することができました。確認されたおとり文書は、Edge Groupを詐称した文書1件、インド工科大学であるIIT Kanpurを詐称した文書2件、エアバスを詐称した文書1件の計4件で、おとり文書に共通して確認された情報は、ファイルパス、本文に組織名などの明確なキーワードが存在することです。これは特定の機関、人物を対象に作成されたおとり文書と見なすことができ、少数の標的を狙ったスピアフィッシングの配布状況と合致します。

5. おわりに

本稿では、Lazarusが使用するComebackerマルウェアの新規バリアントを分析し、攻撃手法と変化の様相を整理した。攻撃者はdocx文書に悪意のあるVBAマクロを挿入し、ユーザーが文書を開くと囮文書が表示されると同時に、ローダーマルウェアがペイロードを復号・展開した上で、メモリ上で実行するように構成している。

特に囮文書については、Edge Groupを騙る文書が1件、インド工科大学(IIT Kanpur)を騙る文書が2件、エアバスを騙る文書が1件の計4件であり、それぞれ異なるキーワードが含まれていることがわかる。また、ファイルパスや本文に組織名などの明確なキーワードが存在することから、特定の機関や人物を標的として作成された囮文書であると推測される。この状況から、流布の手法はスピアフィッシングである可能性が高い。

現在まで攻撃の事例は報告されていないが、C&Cサーバーが活性化している状況を考慮すると、攻撃者がいつでも攻撃を試みる可能性があり、格別の注意が必要である。

6. 付録

付録 A. MITRE ATT&CK

| Tactics | Techniques |
| --- | --- |
| Resource Development | T1583.001: Acquire Infrastructure: Domains |
| Initial Access | T1566.001: Phishing: Spearphighing Attachment |
| Execution | T1204.002:User Execution: Malicious File-nT1204.005: User Execution: Malicious Library-nT1059.001: Command and Scripting Interpreter: PowerShell-nT1059.003: Command and Scripting Interpreter: Windows Command Shell-nT1059.005: Command and Scripting Interpreter: Visual Basic |
| Persistence | T1547.001: Boot or Logon Autostart Execution: Registry Run Keys / Startup Folder-nT1547.000: Boot or Logon Autostart Execution: Shortcut Modification |
| Defense Evasion | T1140: Deobfuscate/Decode Files or Information-nT1027.013: Obfuscated Files or Information: Encrypted/Encoded File-nT1027.015: Obfuscated Files or Information: Compression-nT1218.011: System Binary Proxy Execution: Rundll32-nT1620: Reflective Code Loading |
| Command and Control | T1132.001: DataEncoding: Standard Encoding-nT1573.001: Encrypted Channel: Symmetric Cryptography-nT1071.001: Application Layer Protocol: Web Protocols-nT1102: Web Service

| Tactics | Techniques |
| --- | --- |
| Resource Development | T1583.001: Acquire Infrastructure: Domains |
| Initial Access | T1566.001: Phishing: Spearphighing Attachment |
| Execution | T1204.002:User Execution: Malicious File-nT1204.005: User Execution: Malicious Library-nT1059.001: Command and Scripting Interpreter: PowerShell-nT1059.003: Command and Scripting Interpreter: Windows Command Shell-nT1059.005: Command and Scripting Interpreter: Visual Basic |
| Persistence | T1547.001: Boot or Logon Autostart Execution: Registry Run Keys / Startup Folder-nT1547.000: Boot or Logon Autostart Execution: Shortcut Modification |
| Defense Evasion | T1140: Deobfuscate/Decode Files or Information-nT1027.013: Obfuscated Files or Information: Encrypted/Encoded File-nT1027.015: Obfuscated Files or Information: Compression-nT1218.011: System Binary Proxy Execution: Rundll32-nT1620: Reflective Code Loading |
| Command and Control | T1132.001: DataEncoding: Standard Encoding-nT1573.001: Encrypted Channel: Symmetric Cryptography-nT1071.001: Application Layer Protocol: Web Protocols-nT1102: Web Service

| Tactics | Techniques |
| --- | --- |
| Resource Development | T1583.001: Acquire Infrastructure: Domains |
| Initial Access | T1566.001: Phishing: Spearphighing Attachment |
| Execution | T1204.002:User Execution: Malicious File-nT1204.005: User Execution: Malicious Library-nT1059.001: Command and Scripting Interpreter: PowerShell-nT1059.003: Command and Scripting Interpreter: Windows Command Shell-nT1059.005: Command and Scripting Interpreter: Visual Basic |
| Persistence | T1547.001: Boot or Logon Autostart Execution: Registry Run Keys / Startup Folder-nT1547.000: Boot or Logon Autostart Execution: Shortcut Modification |
| Defense Evasion | T1140: Deobfuscate/Decode Files or Information-nT1027.013: Obfuscated Files or Information: Encrypted/Encoded File-nT1027.015: Obfuscated Files or Information: Compression-nT1218.011: System Binary Proxy Execution: Rundll32-nT1620: Reflective Code Loading |
| Command and Control | T1132.001: DataEncoding: Standard Encoding-nT1573.001: Encrypted Channel: Symmetric Cryptography-nT1071.001: Application Layer Protocol: Web Protocols-nT1102: Web Service

caption - MITRE ATT&CK

付録 B. IOCs

sha256

  • b7d625679fbcc86510119920ffdd6d21005427bf49c015697c69ae1ee27e6bab - docx file

  • 046caa2db6cd14509741890e971ddc8c64ef4cc0e369bd5ba039c40c907d1a1f - docx file

  • 14213c013d79ea4bc8309f730e26d52ff23c10654197b8d2d10c82bbbcd88382 - docx file

  • b357b3882cf8107b1cb59015c4be3e0b8b4de80fd7b80ce3cd05081cd3f6a8ff - docx file

  • 7e61c884ce5207839e0df7a22f08f0ab7d483bfa1828090aa260a2f14a0c942c - wpsoffice_aam.cox

  • c4a5179a42d9ff2774f7f1f937086c88c4bc7c098963b82cc28a2d41c4449f9e - USOPrivate.dll

  • f2b3867aa06fb38d1505b3c2b9e523d83f906995dcdd1bb384a1087b385bfc50 - Comebacker Loader

  • 96b973e577458e5b912715171070c0a0171a3e02154eff487a2dcea4da9fb149 - USOInfo.dat

C&C

  • hxxps://birancearea[.]com/adminv2

  • hxxps://hiremployee[.]com

Open Directory C&C

  • Office-theme[.]com

aes key

  • x!P<&}mjH2YHRQ',

chacha20 key

  • ad9c5aca9977d04c73be579199a827049b6dd9840091ffe8e23acc05e1d4a657

HC256 key

  • LH*x239udC<*sd_Sej%lOa0$&ujHl(.R

  • 6w6ZT9|a-0}s$@;(@&#jPVC4o+V?1IU%

付録 C. 復号スクリプト

Comebacker ドロッパー復号スクリプト

def decrypt(enc):
    dec = ''
    for i in enc:
        dec += bin(i ^ 25)[2:].rjust(8, '0')
    enc = dec[:]
    dec = ''
    for i in range(0, len(enc), 4):
        dec += (enc[i + 2] + enc[i + 3] + enc[i] + enc[i + 1])
    enc = dec[:]
    dec = ''
    for i in enc[::-1]:
        dec += '1' if i == '0' else '0'
    final = b''
    for i in range(0, len(dec), 8):
        final += bytes([int(dec[i:i + 8], 2)])
    return final

enc = bytes.fromhex(open('loader_enc', 'r').read())
loader = decrypt(enc)
open('wpoffice_aam.ocx', 'wb').write(loader)

enc = bytes.fromhex(open('lurefile_enc', 'r').read())
lurefile = decrypt(enc)
open('EDGE_Group_Interview_NDA.docx', 'wb').write(lurefile)
def decrypt(enc):
    dec = ''
    for i in enc:
        dec += bin(i ^ 25)[2:].rjust(8, '0')
    enc = dec[:]
    dec = ''
    for i in range(0, len(enc), 4):
        dec += (enc[i + 2] + enc[i + 3] + enc[i] + enc[i + 1])
    enc = dec[:]
    dec = ''
    for i in enc[::-1]:
        dec += '1' if i == '0' else '0'
    final = b''
    for i in range(0, len(dec), 8):
        final += bytes([int(dec[i:i + 8], 2)])
    return final

enc = bytes.fromhex(open('loader_enc', 'r').read())
loader = decrypt(enc)
open('wpoffice_aam.ocx', 'wb').write(loader)

enc = bytes.fromhex(open('lurefile_enc', 'r').read())
lurefile = decrypt(enc)
open('EDGE_Group_Interview_NDA.docx', 'wb').write(lurefile)
def decrypt(enc):
    dec = ''
    for i in enc:
        dec += bin(i ^ 25)[2:].rjust(8, '0')
    enc = dec[:]
    dec = ''
    for i in range(0, len(enc), 4):
        dec += (enc[i + 2] + enc[i + 3] + enc[i] + enc[i + 1])
    enc = dec[:]
    dec = ''
    for i in enc[::-1]:
        dec += '1' if i == '0' else '0'
    final = b''
    for i in range(0, len(dec), 8):
        final += bytes([int(dec[i:i + 8], 2)])
    return final

enc = bytes.fromhex(open('loader_enc', 'r').read())
loader = decrypt(enc)
open('wpoffice_aam.ocx', 'wb').write(loader)

enc = bytes.fromhex(open('lurefile_enc', 'r').read())
lurefile = decrypt(enc)
open('EDGE_Group_Interview_NDA.docx', 'wb').write(lurefile)

Comebacker 一次ローダー復号スクリプト

import struct

def rotl32(x, n):
    return ((x << n) & 0xffffffff) | (x >> (32 - n))

def pack4(b):
    return struct.unpack('<I', bytes(b))[0]

def unpack4(x):
    return list(struct.pack('<I', x))

class ChaCha20:
    def __init__(self, key: bytes, nonce: bytes, counter: int = 0):
        assert len(key) == 32
        assert len(nonce) == 12
        self.state = [0] * 16
        self.keystream32 = [0] * 16
        self.counter = counter
        self.position = 64
        self.key = key
        self.nonce = nonce
        self._init_block()
        self._set_counter(counter)

    def _init_block(self):
        constants = b"expand 32-byte k"
        key = self.key
        nonce = self.nonce
        self.state[0] = pack4(constants[0:4])
        self.state[1] = pack4(constants[4:8])
        self.state[2] = pack4(constants[8:12])
        self.state[3] = pack4(constants[12:16])
        for i in range(8):
            self.state[4 + i] = pack4(key[i * 4:(i + 1) * 4])
        self.state[12] = 0
        for i in range(3):
            self.state[13 + i] = pack4(nonce[i * 4:(i + 1) * 4])

    def _set_counter(self, counter):
        self.state[12] = counter & 0xffffffff
        self.state[13] = pack4(self.nonce[:4]) + ((counter >> 32) & 0xffffffff)

    def _quarterround(self, x, a, b, c, d):
        x[a] = (x[a] + x[b]) & 0xffffffff
        x[d] = rotl32(x[d] ^ x[a], 16)
        x[c] = (x[c] + x[d]) & 0xffffffff
        x[b] = rotl32(x[b] ^ x[c], 12)
        x[a] = (x[a] + x[b]) & 0xffffffff
        x[d] = rotl32(x[d] ^ x[a], 8)
        x[c] = (x[c] + x[d]) & 0xffffffff
        x[b] = rotl32(x[b] ^ x[c], 7)

    def _block_next(self):
        x = self.state[:]
        for _ in range(10):
            self._quarterround(x, 0, 4, 8, 12)
            self._quarterround(x, 1, 5, 9, 13)
            self._quarterround(x, 2, 6, 10, 14)
            self._quarterround(x, 3, 7, 11, 15)
            self._quarterround(x, 0, 5, 10, 15)
            self._quarterround(x, 1, 6, 11, 12)
            self._quarterround(x, 2, 7, 8, 13)
            self._quarterround(x, 3, 4, 9, 14)
        for i in range(16):
            self.keystream32[i] = (x[i] + self.state[i]) & 0xffffffff
        self.state[12] = (self.state[12] + 1) & 0xffffffff
        if self.state[12] == 0:
            self.state[13] = (self.state[13] + 1) & 0xffffffff
            assert self.state[13] != 0

    def xor(self, data: bytes) -> bytes:
        output = bytearray()
        keystream = bytearray()
        for word in self.keystream32:
            keystream.extend(struct.pack('<I', word))
        for byte in data:
            if self.position >= 64:
                self._block_next()
                keystream = bytearray()
                for word in self.keystream32:
                    keystream.extend(struct.pack('<I', word))
                self.position = 0
            output.append(byte ^ keystream[self.position])
            self.position += 1
        return bytes(output)

key = bytes.fromhex("ad9c5aca9977d04c73be579199a827049b6dd9840091ffe8e23acc05e1d4a657")
iv = bytes.fromhex("edc9ce049daeba35b8687740")

enc = open('USOPrivate_enc', 'rb').read()
dec = b''
cipher = ChaCha20(key, iv, counter=1)
for i in range(0, 0x2D903, 16):
    dec += cipher.xor(enc[i:i+16])
    cipher.state[12] = cipher.state[12] + 1 & 0xffffffff
open('USOPrivate_dec', 'wb').write(dec)
import struct

def rotl32(x, n):
    return ((x << n) & 0xffffffff) | (x >> (32 - n))

def pack4(b):
    return struct.unpack('<I', bytes(b))[0]

def unpack4(x):
    return list(struct.pack('<I', x))

class ChaCha20:
    def __init__(self, key: bytes, nonce: bytes, counter: int = 0):
        assert len(key) == 32
        assert len(nonce) == 12
        self.state = [0] * 16
        self.keystream32 = [0] * 16
        self.counter = counter
        self.position = 64
        self.key = key
        self.nonce = nonce
        self._init_block()
        self._set_counter(counter)

    def _init_block(self):
        constants = b"expand 32-byte k"
        key = self.key
        nonce = self.nonce
        self.state[0] = pack4(constants[0:4])
        self.state[1] = pack4(constants[4:8])
        self.state[2] = pack4(constants[8:12])
        self.state[3] = pack4(constants[12:16])
        for i in range(8):
            self.state[4 + i] = pack4(key[i * 4:(i + 1) * 4])
        self.state[12] = 0
        for i in range(3):
            self.state[13 + i] = pack4(nonce[i * 4:(i + 1) * 4])

    def _set_counter(self, counter):
        self.state[12] = counter & 0xffffffff
        self.state[13] = pack4(self.nonce[:4]) + ((counter >> 32) & 0xffffffff)

    def _quarterround(self, x, a, b, c, d):
        x[a] = (x[a] + x[b]) & 0xffffffff
        x[d] = rotl32(x[d] ^ x[a], 16)
        x[c] = (x[c] + x[d]) & 0xffffffff
        x[b] = rotl32(x[b] ^ x[c], 12)
        x[a] = (x[a] + x[b]) & 0xffffffff
        x[d] = rotl32(x[d] ^ x[a], 8)
        x[c] = (x[c] + x[d]) & 0xffffffff
        x[b] = rotl32(x[b] ^ x[c], 7)

    def _block_next(self):
        x = self.state[:]
        for _ in range(10):
            self._quarterround(x, 0, 4, 8, 12)
            self._quarterround(x, 1, 5, 9, 13)
            self._quarterround(x, 2, 6, 10, 14)
            self._quarterround(x, 3, 7, 11, 15)
            self._quarterround(x, 0, 5, 10, 15)
            self._quarterround(x, 1, 6, 11, 12)
            self._quarterround(x, 2, 7, 8, 13)
            self._quarterround(x, 3, 4, 9, 14)
        for i in range(16):
            self.keystream32[i] = (x[i] + self.state[i]) & 0xffffffff
        self.state[12] = (self.state[12] + 1) & 0xffffffff
        if self.state[12] == 0:
            self.state[13] = (self.state[13] + 1) & 0xffffffff
            assert self.state[13] != 0

    def xor(self, data: bytes) -> bytes:
        output = bytearray()
        keystream = bytearray()
        for word in self.keystream32:
            keystream.extend(struct.pack('<I', word))
        for byte in data:
            if self.position >= 64:
                self._block_next()
                keystream = bytearray()
                for word in self.keystream32:
                    keystream.extend(struct.pack('<I', word))
                self.position = 0
            output.append(byte ^ keystream[self.position])
            self.position += 1
        return bytes(output)

key = bytes.fromhex("ad9c5aca9977d04c73be579199a827049b6dd9840091ffe8e23acc05e1d4a657")
iv = bytes.fromhex("edc9ce049daeba35b8687740")

enc = open('USOPrivate_enc', 'rb').read()
dec = b''
cipher = ChaCha20(key, iv, counter=1)
for i in range(0, 0x2D903, 16):
    dec += cipher.xor(enc[i:i+16])
    cipher.state[12] = cipher.state[12] + 1 & 0xffffffff
open('USOPrivate_dec', 'wb').write(dec)
import struct

def rotl32(x, n):
    return ((x << n) & 0xffffffff) | (x >> (32 - n))

def pack4(b):
    return struct.unpack('<I', bytes(b))[0]

def unpack4(x):
    return list(struct.pack('<I', x))

class ChaCha20:
    def __init__(self, key: bytes, nonce: bytes, counter: int = 0):
        assert len(key) == 32
        assert len(nonce) == 12
        self.state = [0] * 16
        self.keystream32 = [0] * 16
        self.counter = counter
        self.position = 64
        self.key = key
        self.nonce = nonce
        self._init_block()
        self._set_counter(counter)

    def _init_block(self):
        constants = b"expand 32-byte k"
        key = self.key
        nonce = self.nonce
        self.state[0] = pack4(constants[0:4])
        self.state[1] = pack4(constants[4:8])
        self.state[2] = pack4(constants[8:12])
        self.state[3] = pack4(constants[12:16])
        for i in range(8):
            self.state[4 + i] = pack4(key[i * 4:(i + 1) * 4])
        self.state[12] = 0
        for i in range(3):
            self.state[13 + i] = pack4(nonce[i * 4:(i + 1) * 4])

    def _set_counter(self, counter):
        self.state[12] = counter & 0xffffffff
        self.state[13] = pack4(self.nonce[:4]) + ((counter >> 32) & 0xffffffff)

    def _quarterround(self, x, a, b, c, d):
        x[a] = (x[a] + x[b]) & 0xffffffff
        x[d] = rotl32(x[d] ^ x[a], 16)
        x[c] = (x[c] + x[d]) & 0xffffffff
        x[b] = rotl32(x[b] ^ x[c], 12)
        x[a] = (x[a] + x[b]) & 0xffffffff
        x[d] = rotl32(x[d] ^ x[a], 8)
        x[c] = (x[c] + x[d]) & 0xffffffff
        x[b] = rotl32(x[b] ^ x[c], 7)

    def _block_next(self):
        x = self.state[:]
        for _ in range(10):
            self._quarterround(x, 0, 4, 8, 12)
            self._quarterround(x, 1, 5, 9, 13)
            self._quarterround(x, 2, 6, 10, 14)
            self._quarterround(x, 3, 7, 11, 15)
            self._quarterround(x, 0, 5, 10, 15)
            self._quarterround(x, 1, 6, 11, 12)
            self._quarterround(x, 2, 7, 8, 13)
            self._quarterround(x, 3, 4, 9, 14)
        for i in range(16):
            self.keystream32[i] = (x[i] + self.state[i]) & 0xffffffff
        self.state[12] = (self.state[12] + 1) & 0xffffffff
        if self.state[12] == 0:
            self.state[13] = (self.state[13] + 1) & 0xffffffff
            assert self.state[13] != 0

    def xor(self, data: bytes) -> bytes:
        output = bytearray()
        keystream = bytearray()
        for word in self.keystream32:
            keystream.extend(struct.pack('<I', word))
        for byte in data:
            if self.position >= 64:
                self._block_next()
                keystream = bytearray()
                for word in self.keystream32:
                    keystream.extend(struct.pack('<I', word))
                self.position = 0
            output.append(byte ^ keystream[self.position])
            self.position += 1
        return bytes(output)

key = bytes.fromhex("ad9c5aca9977d04c73be579199a827049b6dd9840091ffe8e23acc05e1d4a657")
iv = bytes.fromhex("edc9ce049daeba35b8687740")

enc = open('USOPrivate_enc', 'rb').read()
dec = b''
cipher = ChaCha20(key, iv, counter=1)
for i in range(0, 0x2D903, 16):
    dec += cipher.xor(enc[i:i+16])
    cipher.state[12] = cipher.state[12] + 1 & 0xffffffff
open('USOPrivate_dec', 'wb').write(dec)

Comebacker C&C サーバー送受信データの復号スクリプト

from base64 import b64decode
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

enc = b64decode("zTJFPv1/59Of+S7AlhXPufXxy5abaigGREwmiXSvF9Q=")
key = b"x!P<&}mjH2YHRQ',"

cipher = AES.new(key, AES.MODE_CBC, key)
dec = unpad(cipher.decrypt(enc), AES.block_size)
print(dec)
from base64 import b64decode
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

enc = b64decode("zTJFPv1/59Of+S7AlhXPufXxy5abaigGREwmiXSvF9Q=")
key = b"x!P<&}mjH2YHRQ',"

cipher = AES.new(key, AES.MODE_CBC, key)
dec = unpad(cipher.decrypt(enc), AES.block_size)
print(dec)
from base64 import b64decode
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

enc = b64decode("zTJFPv1/59Of+S7AlhXPufXxy5abaigGREwmiXSvF9Q=")
key = b"x!P<&}mjH2YHRQ',"

cipher = AES.new(key, AES.MODE_CBC, key)
dec = unpad(cipher.decrypt(enc), AES.block_size)
print(dec)

HC256 復号スクリプト

from typing import Optional
import logging
import zipfile

def bit_check(n: bytes) -> int:
    return len(n) * 8

def rotr32(x: int, n: int) -> int:
    return (x >> n) + ((x << (32 - n))) & 0xffffffff

def rotl32(x: int, n: int) -> int:
    return (x << n) + ((x >> (32 - n))) & 0xffffffff

class HC256():
    def __init__(
        self,
        key: bytes,
        iv: bytes,
        log_level: Optional[int] = logging.WARN
    ):
        logging.basicConfig()
        self.logger = logging.getLogger(__name__)
        self.logger.setLevel(log_level)

        if bit_check(key) != 256:
            self.logger.warning("keylen != 256 bits, null-padding")

        if bit_check(iv) != 256:
            self.logger.warning("ivlen != 256 bits, null-padding")

        key = key + b"\x00" * (32 - len(key))
        iv = iv + b"\x00" * (32 - len(iv))
        # Convert to list of DWORD
        self.key = [
            int.from_bytes(key[i:i+4], byteorder="little")
            for i in range(len(key))[::4]
        ]

        self.iv = [
            int.from_bytes(iv[i:i+4], byteorder="little")
            for i in range(len(iv))[::4]
        ]

        self.logger.debug(f"key: {self.key}")
        self.logger.debug(f"iv: {self.iv}")
        self.__init_cipher()
        self.ctr = 0

    def __init_cipher(self):
        def f1(x):
            return (rotr32(x, 7) ^ rotr32(x, 18) ^ (x >> 3)) & 0xffffffff

        def f2(x):
            return (rotr32(x, 17) ^ rotr32(x, 19) ^ (x >> 10)) & 0xffffffff

        if len(self.key) != 8:
            raise ValueError("Invalid key len!")

        if len(self.iv) != 8:
            raise ValueError("Invalid IV len!")

        self.logger.debug("Setting key/IV")
        self.ctr = 0
        w = [0] * 2560
        self.P = None
        self.Q = None

        for i in range(len(self.key)):
            w[i] = self.key[i]
            w[i+8] = self.iv[i]

        for i in range(16, 2560):
            w[i] = ((
                ((f2(w[i-2]) + w[i-7]) & 0xffffffff) +
                (f1(w[i-15]) + w[i-16]) & 0xffffffffff
            ) + i) & 0xffffffff
        self.P = w[512:1536]
        self.Q = w[1536:]
        if len(self.P) != 1024 or len(self.Q) != 1024:
            raise ValueError(f"P/Q invalid len: P: {len(self.P)}, Q: {len(self.Q)}")
        # Run 4096 iters.
        self.logger.debug("Running keystream iterations before cipher")
        for i in range(4096):
            self.keystream()

    def keystream(self) -> int:
        r = None

        def g1(x: int, y: int) -> int:
            return ((rotr32(x, 10) ^ rotr32(y, 23)) + self.Q[(x ^ y) % 1024]) & 0xffffffff

        def g2(x: int, y: int) -> int:
            return ((rotr32(x, 10) ^ rotr32(y, 23)) + self.P[(x ^ y) % 1024]) & 0xffffffff

        def h1(x: int) -> int:
            r = self.Q[x & 0xff]
            r = (r + self.Q[256 + ((x >> 8) & 0xff)]) & 0xffffffff
            r = (r + self.Q[512 + ((x >> 16) & 0xff)]) & 0xffffffff
            r = (r + self.Q[768 + ((x >> 24) & 0xff)]) & 0xffffffff
            return r & 0xffffffff

        def h2(x: int) -> int:
            r = self.P[x & 0xff]
            r = (r + self.P[256 + ((x >> 8) & 0xff)]) & 0xffffffff
            r = (r + self.P[512 + ((x >> 16) & 0xff)]) & 0xffffffff
            r = (r + self.P[768 + ((x >> 24) & 0xff)]) & 0xffffffff
            return r & 0xffffffff

        j = self.ctr % 1024
        j3 = (j - 3) % 1024
        j10 = (j - 10) % 1024
        j12 = (j - 12) % 1024
        j1023 = (j - 1023) % 1024
        if self.ctr < 1024:
            self.P[j] = (
                self.P[j] +
                self.P[j10] +
                g1(self.P[j3], self.P[j1023])
            ) & 0xffffffff
            r = h1(self.P[j12])
            r = (r ^ self.P[j]) & 0xffffffff
        else:
            self.Q[j] = (
                self.Q[j] +
                self.Q[j10] +
                g2(self.Q[j3], self.Q[j1023])
            ) & 0xffffffff
            r = h2(self.Q[j12])
            r = (r ^ self.Q[j]) & 0xffffffff

        self.ctr = (self.ctr + 1) & 0x7ff
        return r

    def crypt(self, cipher: bytes) -> bytes:
        uncipher = list(cipher)
        i = 0
        while i < len(cipher):
            k = self.keystream()
            self.logger.debug(f"keystream[{i}]: {k}")
            j = 0
            while (j < 4 and i < len(cipher)):
                uncipher[i] ^= (k & 0xff)
                i += 1
                j += 1
                k = (k >> 8)

        return bytes(uncipher)

key = b'LH*x239udC<*sd_Sej%lOa0$&ujHl(.R'
encdat = open('enc_dat','rb').read()
cipher = HC256(key, key)
decdat = cipher.crypt(encdat)
open('PK_dat','wb').write(decdat)
zipfile.ZipFile('PK_dat', 'r').extractall('.')
from typing import Optional
import logging
import zipfile

def bit_check(n: bytes) -> int:
    return len(n) * 8

def rotr32(x: int, n: int) -> int:
    return (x >> n) + ((x << (32 - n))) & 0xffffffff

def rotl32(x: int, n: int) -> int:
    return (x << n) + ((x >> (32 - n))) & 0xffffffff

class HC256():
    def __init__(
        self,
        key: bytes,
        iv: bytes,
        log_level: Optional[int] = logging.WARN
    ):
        logging.basicConfig()
        self.logger = logging.getLogger(__name__)
        self.logger.setLevel(log_level)

        if bit_check(key) != 256:
            self.logger.warning("keylen != 256 bits, null-padding")

        if bit_check(iv) != 256:
            self.logger.warning("ivlen != 256 bits, null-padding")

        key = key + b"\x00" * (32 - len(key))
        iv = iv + b"\x00" * (32 - len(iv))
        # Convert to list of DWORD
        self.key = [
            int.from_bytes(key[i:i+4], byteorder="little")
            for i in range(len(key))[::4]
        ]

        self.iv = [
            int.from_bytes(iv[i:i+4], byteorder="little")
            for i in range(len(iv))[::4]
        ]

        self.logger.debug(f"key: {self.key}")
        self.logger.debug(f"iv: {self.iv}")
        self.__init_cipher()
        self.ctr = 0

    def __init_cipher(self):
        def f1(x):
            return (rotr32(x, 7) ^ rotr32(x, 18) ^ (x >> 3)) & 0xffffffff

        def f2(x):
            return (rotr32(x, 17) ^ rotr32(x, 19) ^ (x >> 10)) & 0xffffffff

        if len(self.key) != 8:
            raise ValueError("Invalid key len!")

        if len(self.iv) != 8:
            raise ValueError("Invalid IV len!")

        self.logger.debug("Setting key/IV")
        self.ctr = 0
        w = [0] * 2560
        self.P = None
        self.Q = None

        for i in range(len(self.key)):
            w[i] = self.key[i]
            w[i+8] = self.iv[i]

        for i in range(16, 2560):
            w[i] = ((
                ((f2(w[i-2]) + w[i-7]) & 0xffffffff) +
                (f1(w[i-15]) + w[i-16]) & 0xffffffffff
            ) + i) & 0xffffffff
        self.P = w[512:1536]
        self.Q = w[1536:]
        if len(self.P) != 1024 or len(self.Q) != 1024:
            raise ValueError(f"P/Q invalid len: P: {len(self.P)}, Q: {len(self.Q)}")
        # Run 4096 iters.
        self.logger.debug("Running keystream iterations before cipher")
        for i in range(4096):
            self.keystream()

    def keystream(self) -> int:
        r = None

        def g1(x: int, y: int) -> int:
            return ((rotr32(x, 10) ^ rotr32(y, 23)) + self.Q[(x ^ y) % 1024]) & 0xffffffff

        def g2(x: int, y: int) -> int:
            return ((rotr32(x, 10) ^ rotr32(y, 23)) + self.P[(x ^ y) % 1024]) & 0xffffffff

        def h1(x: int) -> int:
            r = self.Q[x & 0xff]
            r = (r + self.Q[256 + ((x >> 8) & 0xff)]) & 0xffffffff
            r = (r + self.Q[512 + ((x >> 16) & 0xff)]) & 0xffffffff
            r = (r + self.Q[768 + ((x >> 24) & 0xff)]) & 0xffffffff
            return r & 0xffffffff

        def h2(x: int) -> int:
            r = self.P[x & 0xff]
            r = (r + self.P[256 + ((x >> 8) & 0xff)]) & 0xffffffff
            r = (r + self.P[512 + ((x >> 16) & 0xff)]) & 0xffffffff
            r = (r + self.P[768 + ((x >> 24) & 0xff)]) & 0xffffffff
            return r & 0xffffffff

        j = self.ctr % 1024
        j3 = (j - 3) % 1024
        j10 = (j - 10) % 1024
        j12 = (j - 12) % 1024
        j1023 = (j - 1023) % 1024
        if self.ctr < 1024:
            self.P[j] = (
                self.P[j] +
                self.P[j10] +
                g1(self.P[j3], self.P[j1023])
            ) & 0xffffffff
            r = h1(self.P[j12])
            r = (r ^ self.P[j]) & 0xffffffff
        else:
            self.Q[j] = (
                self.Q[j] +
                self.Q[j10] +
                g2(self.Q[j3], self.Q[j1023])
            ) & 0xffffffff
            r = h2(self.Q[j12])
            r = (r ^ self.Q[j]) & 0xffffffff

        self.ctr = (self.ctr + 1) & 0x7ff
        return r

    def crypt(self, cipher: bytes) -> bytes:
        uncipher = list(cipher)
        i = 0
        while i < len(cipher):
            k = self.keystream()
            self.logger.debug(f"keystream[{i}]: {k}")
            j = 0
            while (j < 4 and i < len(cipher)):
                uncipher[i] ^= (k & 0xff)
                i += 1
                j += 1
                k = (k >> 8)

        return bytes(uncipher)

key = b'LH*x239udC<*sd_Sej%lOa0$&ujHl(.R'
encdat = open('enc_dat','rb').read()
cipher = HC256(key, key)
decdat = cipher.crypt(encdat)
open('PK_dat','wb').write(decdat)
zipfile.ZipFile('PK_dat', 'r').extractall('.')
from typing import Optional
import logging
import zipfile

def bit_check(n: bytes) -> int:
    return len(n) * 8

def rotr32(x: int, n: int) -> int:
    return (x >> n) + ((x << (32 - n))) & 0xffffffff

def rotl32(x: int, n: int) -> int:
    return (x << n) + ((x >> (32 - n))) & 0xffffffff

class HC256():
    def __init__(
        self,
        key: bytes,
        iv: bytes,
        log_level: Optional[int] = logging.WARN
    ):
        logging.basicConfig()
        self.logger = logging.getLogger(__name__)
        self.logger.setLevel(log_level)

        if bit_check(key) != 256:
            self.logger.warning("keylen != 256 bits, null-padding")

        if bit_check(iv) != 256:
            self.logger.warning("ivlen != 256 bits, null-padding")

        key = key + b"\x00" * (32 - len(key))
        iv = iv + b"\x00" * (32 - len(iv))
        # Convert to list of DWORD
        self.key = [
            int.from_bytes(key[i:i+4], byteorder="little")
            for i in range(len(key))[::4]
        ]

        self.iv = [
            int.from_bytes(iv[i:i+4], byteorder="little")
            for i in range(len(iv))[::4]
        ]

        self.logger.debug(f"key: {self.key}")
        self.logger.debug(f"iv: {self.iv}")
        self.__init_cipher()
        self.ctr = 0

    def __init_cipher(self):
        def f1(x):
            return (rotr32(x, 7) ^ rotr32(x, 18) ^ (x >> 3)) & 0xffffffff

        def f2(x):
            return (rotr32(x, 17) ^ rotr32(x, 19) ^ (x >> 10)) & 0xffffffff

        if len(self.key) != 8:
            raise ValueError("Invalid key len!")

        if len(self.iv) != 8:
            raise ValueError("Invalid IV len!")

        self.logger.debug("Setting key/IV")
        self.ctr = 0
        w = [0] * 2560
        self.P = None
        self.Q = None

        for i in range(len(self.key)):
            w[i] = self.key[i]
            w[i+8] = self.iv[i]

        for i in range(16, 2560):
            w[i] = ((
                ((f2(w[i-2]) + w[i-7]) & 0xffffffff) +
                (f1(w[i-15]) + w[i-16]) & 0xffffffffff
            ) + i) & 0xffffffff
        self.P = w[512:1536]
        self.Q = w[1536:]
        if len(self.P) != 1024 or len(self.Q) != 1024:
            raise ValueError(f"P/Q invalid len: P: {len(self.P)}, Q: {len(self.Q)}")
        # Run 4096 iters.
        self.logger.debug("Running keystream iterations before cipher")
        for i in range(4096):
            self.keystream()

    def keystream(self) -> int:
        r = None

        def g1(x: int, y: int) -> int:
            return ((rotr32(x, 10) ^ rotr32(y, 23)) + self.Q[(x ^ y) % 1024]) & 0xffffffff

        def g2(x: int, y: int) -> int:
            return ((rotr32(x, 10) ^ rotr32(y, 23)) + self.P[(x ^ y) % 1024]) & 0xffffffff

        def h1(x: int) -> int:
            r = self.Q[x & 0xff]
            r = (r + self.Q[256 + ((x >> 8) & 0xff)]) & 0xffffffff
            r = (r + self.Q[512 + ((x >> 16) & 0xff)]) & 0xffffffff
            r = (r + self.Q[768 + ((x >> 24) & 0xff)]) & 0xffffffff
            return r & 0xffffffff

        def h2(x: int) -> int:
            r = self.P[x & 0xff]
            r = (r + self.P[256 + ((x >> 8) & 0xff)]) & 0xffffffff
            r = (r + self.P[512 + ((x >> 16) & 0xff)]) & 0xffffffff
            r = (r + self.P[768 + ((x >> 24) & 0xff)]) & 0xffffffff
            return r & 0xffffffff

        j = self.ctr % 1024
        j3 = (j - 3) % 1024
        j10 = (j - 10) % 1024
        j12 = (j - 12) % 1024
        j1023 = (j - 1023) % 1024
        if self.ctr < 1024:
            self.P[j] = (
                self.P[j] +
                self.P[j10] +
                g1(self.P[j3], self.P[j1023])
            ) & 0xffffffff
            r = h1(self.P[j12])
            r = (r ^ self.P[j]) & 0xffffffff
        else:
            self.Q[j] = (
                self.Q[j] +
                self.Q[j10] +
                g2(self.Q[j3], self.Q[j1023])
            ) & 0xffffffff
            r = h2(self.Q[j12])
            r = (r ^ self.Q[j]) & 0xffffffff

        self.ctr = (self.ctr + 1) & 0x7ff
        return r

    def crypt(self, cipher: bytes) -> bytes:
        uncipher = list(cipher)
        i = 0
        while i < len(cipher):
            k = self.keystream()
            self.logger.debug(f"keystream[{i}]: {k}")
            j = 0
            while (j < 4 and i < len(cipher)):
                uncipher[i] ^= (k & 0xff)
                i += 1
                j += 1
                k = (k >> 8)

        return bytes(uncipher)

key = b'LH*x239udC<*sd_Sej%lOa0$&ujHl(.R'
encdat = open('enc_dat','rb').read()
cipher = HC256(key, key)
decdat = cipher.crypt(encdat)
open('PK_dat','wb').write(decdat)
zipfile.ZipFile('PK_dat', 'r').extractall('.')

エンキホワイトハット

エンキホワイトハット

ENKI ホワイトハット
ENKI ホワイトハット

オフェンシブセキュリティの専門企業として、攻撃者の視点から次元の異なるセキュリティを提示します。

オフェンシブセキュリティの専門企業として、攻撃者の視点から次元の異なるセキュリティを提示します。

隙のないセキュリティ設計の始まり、NO.1ホワイトハッカーのノウハウから

インシデント発生前、
今すぐ備えましょう

隙のないセキュリティ設計の始まり、
No.1ホワイトハッカーのノウハウから

インシデント発生前、
今すぐ備えましょう

隙のないセキュリティ設計の始まり、
No.1ホワイトハッカーのノウハウから

インシデント発生前、
今すぐ備えましょう

購読する

コンテンツが役に立ったら?
エンキーレターを購読しましょう!

Copyright © 2025. ENKI WhiteHat Co., Ltd. All rights reserved.

Copyright © 2025. ENKI WhiteHat Co., Ltd. All rights reserved.

Copyright © 2025. ENKI WhiteHat Co., Ltd. All rights reserved.