Arch Linuxの有線LANが繋がらなかったしょうもない話
あるときしばらく有線LANを使わずにWifiでのみネットを使っていた.
久しぶりに有線LANを差したら問題なさそうに見えるのにネットにつながらない(IPが取れてない).
結論を言えばdhcp
が動いていなかった.
そんなしょうもない話を戒めに書き残す.
さっさと解決したい人は$ sudo systemctl status dhcpcd
する.
原因が同じならそれが答えだ.
環境
いつもの儀式.
$ uname -a Linux poppycompass 5.4.8-arch1-1 #1 SMP PREEMPT Sat, 04 Jan 2020 23:46:18 +0000 x86_64 GNU/Linux
ただのArch Linux 64bit
のようだ.
現象
- PCにLANケーブルをつなぐ
- LANケーブルコネクタのリンクランプ点灯(緑・オレンジ共)
- ネットにつながらない
調査
まずはdmesg
$ dmesg [26247.169774] e1000e: enp0s31f6 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: Rx/Tx
インターフェイスは立ち上がっている.
$ ip a 2: enp0s31f6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000 link/ether <MAC addr> brd ff:ff:ff:ff:ff:ff inet6 <addr>/64 scope link valid_lft forever preferred_lft forever
おや,IPv4
のアドレスがない.
世界はIPv6
に移行したんだっけ?(してない)
思いつく原因と対応(復旧しなかったやつ)
- そういえば最近アップデートしたからまさか?
→$ sudo pacman -Syu
- 原因不明にはこれが効く!
→$ sudo reboot
- つないだまましばらくしたらつながるだろう
→ つないだまま放置
解決編
うーん,原因はIPv4
が取れていないことにありそうだ.
Googleでarch linux cannot get ip addr
で検索してそれっぽいのがあった.
最初の方の"systemctl status dhcpcd", the service is active (running).
を見て一応確認
$ systemctl status dhcpcd Unit enable.service could not be found. ● dhcpcd.service - dhcpcd on all interfaces Loaded: loaded (/usr/lib/systemd/system/dhcpcd.service; disabled; vendor preset: disabled) Active: inactive (dead)
犯人はこいつのようだ.
$ sudo systemctl start dhcpcd ● dhcpcd.service - dhcpcd on all interfaces Loaded: loaded (/usr/lib/systemd/system/dhcpcd.service; disabled; vendor preset: disabled) Active: active (running) since Wed 2020-01-08 22:16:53 JST; 3s ago ...(snip)...
これで解決.
所感
最初の調査段階でIPが取れていない時点でネットワーク周りのソフトが全部動いているかどうかを確認するべきだったと反省.
参考
プログラム実行時の命令数(関数単位)を可視化してみた
Intel Pin
で任意のプログラム実行時の実行命令数を関数単位で計測し,d3.js
に食わせたという話.
背景
プログラム解析時に複雑で命令数が多いところはなるだけ読みたくない.
読むなら心構えがしたい.
一目でわかるようにできないかと思って可視化に手を出してみた,第一弾.
処理の流れ
まとめてしまうとこれだけ.大したことはしていない.
しかしd3.js
が曲者で時間を取られた.
実行環境
お決まりのやつ.
$ uname -a Linux poppycompass 5.3.11-arch1-1 #1 SMP PREEMPT Tue, 12 Nov 2019 22:19:48 +0000 x86_64 GNU/Linux
$ gcc -v (snip) gcc version 9.2.0 (GCC)
Intel Pinで実行命令数の計測
まずはIntel Pin
を準備.
$ wget https://software.intel.com/sites/landingpage/pintool/downloads/pin-3.11-97998-g7ecce2dac-gcc-linux.tar.gz $ tar xvf pin-3.11-97998-g7ecce2dac-gcc-linux.tar.gz $ cd pin-3.11-97998-g7ecce2dac-gcc-linux.tar.gz $ cd source/tools/ManualExamples/ $ make all TARGET=intel64 $ ../../../pin -t obj-intel64/inscount0.so -- /bin/ls # inscount0は全体で何命令実行されたかのカウントするプログラム $ cat insount0.out Count 650939
カウント数が出ればOK.
関数単位での実行命令数を出力するプログラムを組もうと思いきや,proccount.cpp
がまさにそれだった.
流石インテル入ってる.
そのままだと目に優しいテキストとして出力されるから,CSV出力にいじった.
全部を載せると長いからいじったFini
関数のところのみ抜粋.空白を消して,
が入るようにしただけ.
// This function is called when the application exits // It prints the name and count for each procedure VOID Fini(INT32 code, VOID *v) { outFile << "Procedure" << "," << "Image" << "," << "Address" << "," << "Calls" << "," << "Instructions" << endl; for (RTN_COUNT * rc = RtnList; rc; rc = rc->_next) { if (rc->_icount > 0) outFile << rc->_name << "," << rc->_image << "," << hex << rc->_address << dec <<"," << rc->_rtnCount << "," << rc->_icount << endl; } }
計測対象は簡単のためにHello Worldとした.
以下を適当なファイルに保存する.
// hello.c #include <stdio.h> int main(void) { puts("hello"); return 0; }
$ gcc hello.c $ ./a.out hello
これで準備は整った. データを取る.
$ ../../../pin -t obj-intel64/proccount.so -- ./a.out hello $ cat proccount.out Procedure,Image,Address,Calls,Instructions __strlen_avx2,libc.so.6,7fe200f906e0,1,16 __strrchr_avx2,libc.so.6,7fe200f90510,1,28 _dl_addr,libc.so.6,7fe200f67de0,1,66742 __init_misc,libc.so.6,7fe200f2e130,1,25 sbrk,libc.so.6,7fe200f25190,2,51 (snip)
Intel Pin
でやることはここまで.
CSVをJSONに変換
可視化で見たいものは実行命令数の大小.
棒グラフでも良かったがここではバブルチャートを使うことにした.
d3.js
に食わせるにはJSON
形式のほうが都合が良さそうだったから変換する.
スクリプトを書いただけ.
#!/usr/bin/env python # -*- coding: utf-8 -*- # file: csv2json.py import json import csv CSV_FILE = "proccount.csv" JSON_FILE = "proccount.json" def all_in_one(): json_contents = {} children = [] with open(CSV_FILE, 'r') as f: for row in csv.DictReader(f): children.append( {"name": row["Procedure"], "size": row["Instructions"]}) with open(JSON_FILE, 'w') as f: json_contents["name"] = "execute" json_contents["children"] = children json.dump(json_contents, f) def each_image(): json_contents = {} children = {} with open(CSV_FILE, 'r') as f: for row in csv.DictReader(f): if not row["Image"] in children: children[row["Image"]] = [] children[row["Image"]].append( {"name": row["Procedure"], "size": row["Instructions"]}) with open(JSON_FILE, 'w') as f: for (key,val) in zip(children.keys(), children.values()): json_contents = {} json_contents["Image"] = key json_contents["children"] = val json.dump(json_contents, f) f.write('\n') if __name__ == '__main__': all_in_one()
Intel Pin
の出力はどのファイル内にある関数かも記録してある.それぞれを取り出すための関数がeach_image
,ファイル関係なくまとめたのがall_in_one
.
$ python csv2json.py $ cat proccount.json {"name": "execute", "children": [{"name": "__strlen_avx2", "size": "16"}, {"name": "__strrchr_avx2", "size": "28"}, {"name": "_dl_addr", "size": "66742"}, {"name": "__init_misc", "size": "25"} (snip)
データの準備もこれで完了
加工したデータをバブルチャートで表示
バブルチャートのサンプルを取ってきたらほぼそのまま使えた.
以下はindex.html
.
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>D3 v5 hierarchy pack v4/v5</title> </head> <body> <svg width="800" height="600"></svg> <script src="https://d3js.org/d3.v4.min.js"></script> <script src="graph.js"></script> </body> </html>
// graph.js var diameter = 960, format = d3.format(",d"), color = d3.scaleOrdinal(d3.schemeCategory20c); var bubble = d3.pack() .size([diameter, diameter]) .padding(1.5); var svg = d3.select("body").append("svg") .attr("width", diameter) .attr("height", diameter) .attr("class", "bubble"); d3.json("proccount.json", function(error, data) { if (error) throw error; var root = d3.hierarchy(classes(data)) .sum(function(d) { return d.value; }) .sort(function(a, b) { return b.value - a.value; }); bubble(root); var node = svg.selectAll(".node") .data(root.children) .enter().append("g") .attr("class", "node") .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }); node.append("title") .text(function(d) { return d.data.className + ": " + format(d.value); }); node.append("circle") .attr("r", function(d) { return d.r; }) .style("fill", function(d) { return color(d.data.packageName); }); node.append("text") .attr("dy", ".3em") .style("text-anchor", "middle") .text(function(d) { return d.data.className.substring(0, d.r / 3); }); }); // Returns a flattened hierarchy containing all leaf nodes under the root. function classes(root) { var classes = []; function recurse(name, node) { if (node.children) node.children.forEach(function(child) { recurse(node.name, child); }); else classes.push({packageName: name, className: node.name, value: node.size}); } recurse(null, root); return {children: classes}; } d3.select(self.frameElement).style("height", diameter + "px");
今回はローカルにあるproccount.json
を読み込むため,通常のブラウザだとCORS
関連で弾かれる.
これはブラウザ起動時にオプションをつければ良い.
$ google-chrome-stable --allow-file-access-from-files --disable-web-security --user-data-dir --disable-features=CrossSiteDocumentBlockingIfIsolating
開いたブラウザからfile://パス/index.html
へアクセスする.
結果
メイン処理と全く関係ないところが非常に大きな比重を占めていることがひと目でわかる.
そりゃmain
部なんてprintf
してるだけだからこの図じゃわかるわけなかった.
これだけだともやもやするからファイル毎の関数で可視化してみた(前述のeach_image
を利用).
each_image
の出力は各ファイル単位で改行されるから,見たいやつ以外の行を消すこと.
以下,libc.so.6
,ld-linux-x86-64.so.2
,a.out
の結果.
バブルの大きさは各ファイルの命令数の平均に影響されている.a.out
のmain
は7命令だけど結構大きくなってしまっている.
所感
命令数が多い=重要な関数かと言われると実行準備段階の命令も入っているから一概に言えない.
処理を追う上では流れの関係を示さなければ役には立たなさそうだ.
d3.js
ってエクセルの3Dマップのように球体表示し,ドラッグドロップで後ろまで見えるかと思っていたが,そこまでの機能はないようだ.
可視化はあくまでネットワーク図のように繋がりを一目で表現するためのものでCADみたいな3Dツールとは別物なんだと知った.
ごちゃごちゃ書いたが結局これだけだと解析にはイマイチ使えない.
次の方法を考えないといけない.
参考
Bubble Chart d3 v4 - bl.ocks.org
yaourtがいつの間にか消えていた話
AUR使うときに世話になっているyaourt
がいつの間にか開発終了していて,yay
という代替プログラムに乗り換えた.
経緯
3年ぶりくらいに Arch Linux を最初からインストールして環境を構築した.
AURを使うために
$ pacman --sync --refresh yaourt
とかでyaourt
をインストールしようとしても見つからない.
なんでだろうと調べていたら開発が終了していた.
日本語の記事が少なかったのと英語の記事タイトルがYaourt is dead!
(unsupported
とかで検索していた)となっていたから調べるのに苦労した.
kill
といいやたら物騒な表現が好きな世界だ.
代替プログラム(yay
)
yay
以外にpakku
やaurutils
もあるが,参考サイトが綺麗に書いているからそっちに譲る.
今回は使い勝手が同じそうなyay
を使うことにした.
Go言語で書かれているらしい.
以下,導入方法.
yaourt
がインストールされているシステム
今使っているやつから乗り換えるときは
$ yaourt -S yay
で簡単にできる.
最初から導入
git clone https://aur.archlinux.org/yay.git cd yay makepkg -si
blackarchレポジトリを追加しているとき
流石の品揃え.
$ pacman -Ss yay blackarch/yay 9.0.0-1 (blackarch blackarch-misc) Yet another yogurt. Pacman wrapper and AUR helper written in go. $ pacman -S yay
使い方
以前と同じ.
検索: $ yay -Ss <package> インストール: $ yay -S <package>
尚,他の代替プログラムも検索・インストールに限って言えばオプションは変わらない.
最後に
今までありがとう,yaourt
.
参考サイト
Don't Install Yaourt! Use These Alternatives for AUR in Arch Linux
Yaourt is Dead! - General ArchLabs Discussion - ArchLabs Linux
CiscoルータからSCPでIOSを抽出する方法メモ
CiscoのルータからIOSを抜き出すときは,FTP経由での方法をよく聞く.
そこでFTPを試してみたが,上手く行かない.
デバッグする気が起きなかったから,SCPで抜き出してみることにした.
コピー先のWindows上でサーバを立てるのは手間だから,ルータ上でSSHサーバを立てて,SCP接続する.
コマンドメモがうろ覚えだから,上手く行かなかったら訂正してくれると嬉しい.
なにがしたかったか
Cisco機器からIOSのバイナリファイルを抜き出すと,それを使ってGNS3でネットワーク構成を仮想でいじることができる.
というわけでバイナリが欲しかった.
準備
最初はいつも通りにシリアルコンソールで接続する.
加えて,ルータのLANポートのどれかとPCをLANケーブルで接続しておく.
挿したらPCからTelnetでログインしておく.
OSがWindowsならputtyから,putty.zip
をダウンロード・適当なところに解凍する.
今回はデスクトップに解凍した前提で進めていく.
dir
コマンドで一応自分が抜き出そうとしているIOSのバージョンを確認しておいたほうが良い.
GNS3でサポートしていないバージョンの可能性がある.
ルータ側での設定
CiscoルータからWindowsがインストールされた端末にコピーする.
ただ打ち込んでいくだけ.
大きな流れは以下の通り.
- IPを割り振る
- ユーザを作成
- SSH鍵の作成(今回は1024bit)・有効化
- SCPを許可
$ enable $ configure terminal $ interface vlan 1 $ ip address 192.168.1.100 255.255.255.0 $ username cisco privilege 15 password cisco $ line vty 0 4 $ login local $ hostname R1 # ここは適当で良い $ ip domain-name r1.com # ここは適当で良い $ crypto key generate rsa modulus 1024 $ ip ssh version 2 $ ip ssh time-out 120 $ ip ssh authentication-retries 3 $ ip scp server enable $ aaa new-model $ aaa session-id unique $ aaa authentication login default local # 認証にローカルDBを利用 $ aaa authorization exec default local none # ログイン認可方式として,全ての回線にローカルDBを使用して認可する. $ enable secret cisco
ルータ側の設定はこれで終了.
ポイントはaaa
認証部分.
aaa
はAuthentication/Authorization/Accounting
に関する制御コマンド.
認証方式を全てローカルのDBに設定しないと,認証が上手く行かない.
IOSを抜き出す
ルータ側で
$ dir flash
でIOSのファイル名を確認する.
Windowsでプロンプトを開く.
$ cd C:\Users\Desktop\putty # puttyを解凍したフォルダに移動 $ pscp.exe -scp cisco@192.168.1.100:???.bin C:\Users\poppycompass\Desktop\IOS
これでダウンロードできる.
Linuxならscp
を打つだけで良い.
失敗談
失敗した事例をメモしておく.
SCP: [22 -> 192.168.1.100:?????] send Privilege denied.
が出る.- 多分,ユーザの権限が十分じゃない.
username cisco privilege 15 password cisco
などで権限を最高にしていないか(show privilege
で確認),aaa
を設定していない
- 多分,ユーザの権限が十分じゃない.
WinSCPでSCPできない
スイッチで同じ設定してみたが,上手く行かない
- 原因不明.知りたい.
おまけ
抜き出してから,IOSのバージョンがGNS3のサポート対象外であることに気づいた.
呆然としつつ,file
コマンドすると
ELF 32-bit MSB executable, Sunplus S+core7 RISC, version 1 (SYSV), statically linked, stripped
と出て驚いた.
どうやらIOSの正体はELF形式のプログラムのようだ.Wikiにも碌に情報が載っていない.
これはまたマニアックなアーキテクチャを使っているな・・・.
OSがELFってことは,BIOSかそれに準ずるプログラムがELFを実行できるということだろうか.
OSを実行するためのOSでも存在するのかな.
謎だけ残ってしまった.
所感
SCPだとファイアウォールの無効化とかがいらないから,躓きが少なくて良い.
参考
Memo.html[Cisco]IOSイメージの抽出(追記2017/4/3)
pycを読む(スクリプト味)
前回はpycを低レベルな命令に変換して読む方法を説明した.
あれはあれで読めはするが,アセンブリ言語と同様の読みづらさはある.
今回は直接pyc
をpythonのスクリプトまで変換する方法を紹介する.
変換するライブラリは複数あるが,ここでは uncompyle
を使った.
結論として,精度はいい感じ.pycにソース保護を期待するのはやめましょう.
uncompyle
python byte-code decompiler.
uncompyle2
とuncompyle6
がある.
前者は2系サポート,後者は2・3系両方で使える.
環境
各種バージョン
以下のとおり.
$ uname -a Linux poppycompass 4.18.6-arch1-1-ARCH #1 SMP PREEMPT Wed Sep 5 11:54:09 UTC 2018 x86_64 GNU/Linux $ python2 Python 2.7.15 (default, Jun 27 2018, 13:05:28) [GCC 8.1.1 20180531] on linux2
Arch Linuxの64ビット版.
つまりただのLinux,pythonは2系.
環境作成
$ virtualenv2 ENV $ . ENV/bin/activate $ pip install uncompyle
サンプル
単純なやつ
適当に組んだ.
# sample.py import string def add(a, b): return a+b a = "abc" b = "def" c = "ghi" d = a + b + c sum = 0 for i in range(100): sum += 0 add(1,2)
pyc
を生成して,戻してみる.
$ python2 -m compileall sample.py # pyc生成 Compiling sample.py ... $ ENV/bin/uncompyle6 ./sample.pyc # uncompyleで変換 # uncompyle6 version 3.2.3 # Python bytecode 2.7 (62211) # Decompiled from: Python 2.7.15 (default, Jun 27 2018, 13:05:28) # [GCC 8.1.1 20180531] # Embedded file name: sample.py # Compiled at: 2018-09-26 06:49:02 import string def add(a, b): return a + b a = 'abc' b = 'def' c = 'ghi' d = a + b + c sum = 0 for i in range(100): sum += 0 add(1, 2) # okay decompiling ./sample.pyc
見た感じ,完璧に戻されている.
まともなやつ
簡単なやつだけじゃつまらないから,Oxyry Python Obfuscatorから取ってきたNQueensで試してみる.
オリジナルコードは以下.
__all__ = [] class NQueens: """Generate all valid solutions for the n queens puzzle""" def __init__(self, size): # Store the puzzle (problem) size and the number of valid solutions self.__size = size self.__solutions = 0 self.__solve() def __solve(self): """Solve the n queens puzzle and print the number of solutions""" positions = [-1] * self.__size self.__put_queen(positions, 0) print("Found", self.__solutions, "solutions.") def __put_queen(self, positions, target_row): """ Try to place a queen on target_row by checking all N possible cases. If a valid place is found the function calls itself trying to place a queen on the next row until all N queens are placed on the NxN board. """ # Base (stop) case - all N rows are occupied if target_row == self.__size: self.__show_full_board(positions) self.__solutions += 1 else: # For all N columns positions try to place a queen for column in range(self.__size): # Reject all invalid positions if self.__check_place(positions, target_row, column): positions[target_row] = column self.__put_queen(positions, target_row + 1) def __check_place(self, positions, ocuppied_rows, column): """ Check if a given position is under attack from any of the previously placed queens (check column and diagonal positions) """ for i in range(ocuppied_rows): if positions[i] == column or \ positions[i] - i == column - ocuppied_rows or \ positions[i] + i == column + ocuppied_rows: return False return True def __show_full_board(self, positions): """Show the full NxN board""" for row in range(self.__size): line = "" for column in range(self.__size): if positions[row] == column: line += "Q " else: line += ". " print(line) print("\n") def __show_short_board(self, positions): """ Show the queens positions on the board in compressed form, each number represent the occupied column position in the corresponding row. """ line = "" for i in range(self.__size): line += str(positions[i]) + " " print(line) def main(): """Initialize and solve the n queens puzzle""" NQueens(8) if __name__ == "__main__": # execute only if run as a script main()
戻した結果
__all__ = [] class NQueens: """Generate all valid solutions for the n queens puzzle""" def __init__(self, size): self.__size = size self.__solutions = 0 self.__solve() def __solve(self): """Solve the n queens puzzle and print the number of solutions""" positions = [ -1] * self.__size self.__put_queen(positions, 0) print ('Found', self.__solutions, 'solutions.') def __put_queen(self, positions, target_row): """ Try to place a queen on target_row by checking all N possible cases. If a valid place is found the function calls itself trying to place a queen on the next row until all N queens are placed on the NxN board. """ if target_row == self.__size: self.__show_full_board(positions) self.__solutions += 1 else: for column in range(self.__size): if self.__check_place(positions, target_row, column): positions[target_row] = column self.__put_queen(positions, target_row + 1) def __check_place(self, positions, ocuppied_rows, column): """ Check if a given position is under attack from any of the previously placed queens (check column and diagonal positions) """ for i in range(ocuppied_rows): if positions[i] == column or positions[i] - i == column - ocuppied_rows or positions[i] + i == column + ocuppied_rows: return False return True def __show_full_board(self, positions): """Show the full NxN board""" for row in range(self.__size): line = '' for column in range(self.__size): if positions[row] == column: line += 'Q ' else: line += '. ' print line print '\n' def __show_short_board(self, positions): """ Show the queens positions on the board in compressed form, each number represent the occupied column position in the corresponding row. """ line = '' for i in range(self.__size): line += str(positions[i]) + ' ' print line def main(): """Initialize and solve the n queens puzzle""" for i in range(self.__size): line += str(positions[i]) + ' ' print line def main(): NQueens(8) if __name__ == '__main__': main()
やはり,記述はほぼ完璧に戻されている.
面白いのは,"""
で付けたコメントアウトは復元でき,#
で付けたものは復元できなかったこと.
可読性のためにつける\
も消える.そりゃそんな情報残すわけ無いか.
難読化すると・・・?
さっきのやつをOxyry Python Obfuscatorで難読化した結果も試してみる.
""#line:4 __all__ =[]#line:6 class O0000OO0000OOO0OO :#line:8 ""#line:9 def __init__ (O00OO0O0O0O00OO00 ,O00OO0OO00OOO00OO ):#line:11 O00OO0O0O0O00OO00 .__OO00OO0OO00OOO00O =O00OO0OO00OOO00OO #line:13 O00OO0O0O0O00OO00 .__O00OOO00O0O000000 =0 #line:14 O00OO0O0O0O00OO00 .__O0OO00000O0O0O0O0 ()#line:15 def __O0OO00000O0O0O0O0 (OOOOO0O00OO0O0O0O ):#line:17 ""#line:18 O0OO0000OO0OO0000 =[-1 ]*OOOOO0O00OO0O0O0O .__OO00OO0OO00OOO00O #line:19 OOOOO0O00OO0O0O0O .__OO0OOOO0000O0O00O (O0OO0000OO0OO0000 ,0 )#line:20 print ("Found",OOOOO0O00OO0O0O0O .__O00OOO00O0O000000 ,"solutions.")#line:21 def __OO0OOOO0000O0O00O (O0OO00O0000O000O0 ,O0OOOOO0OOOOO000O ,OO0OOOO00OO00OOO0 ):#line:23 ""#line:28 if OO0OOOO00OO00OOO0 ==O0OO00O0000O000O0 .__OO00OO0OO00OOO00O :#line:30 O0OO00O0000O000O0 .__OOO0OO0O0000O00OO (O0OOOOO0OOOOO000O )#line:31 O0OO00O0000O000O0 .__O00OOO00O0O000000 +=1 #line:32 else :#line:33 for OO000O0OOOOO0O0OO in range (O0OO00O0000O000O0 .__OO00OO0OO00OOO00O ):#line:35 if O0OO00O0000O000O0 .__O00O0O00000O0O00O (O0OOOOO0OOOOO000O ,OO0OOOO00OO00OOO0 ,OO000O0OOOOO0O0OO ):#line:37 O0OOOOO0OOOOO000O [OO0OOOO00OO00OOO0 ]=OO000O0OOOOO0O0OO #line:38 O0OO00O0000O000O0 .__OO0OOOO0000O0O00O (O0OOOOO0OOOOO000O ,OO0OOOO00OO00OOO0 +1 )#line:39 def __O00O0O00000O0O00O (OO0OOO0OOO0O0OO0O ,O00000O0O00OOOOOO ,O00O0O000000OOOOO ,OOOO00OOO000OO0OO ):#line:42 ""#line:46 for OOO000O0OO0O00000 in range (O00O0O000000OOOOO ):#line:47 if O00000O0O00OOOOOO [OOO000O0OO0O00000 ]==OOOO00OOO000OO0OO or O00000O0O00OOOOOO [OOO000O0OO0O00000 ]-OOO000O0OO0O00000 ==OOOO00OOO000OO0OO -O00O0O000000OOOOO or O00000O0O00OOOOOO [OOO000O0OO0O00000 ]+OOO000O0OO0O00000 ==OOOO00OOO000OO0OO +O00O0O000000OOOOO :#line:50 return False #line:52 return True #line:53 def __OOO0OO0O0000O00OO (OOOO0OOO0OOO0OO00 ,OO0OO0O00OOO00OO0 ):#line:55 ""#line:56 for O0OOO0O0O0OO00O0O in range (OOOO0OOO0OOO0OO00 .__OO00OO0OO00OOO00O ):#line:57 OOO00OO0OOOOOOOOO =""#line:58 for OO00O0O0OOO0O0000 in range (OOOO0OOO0OOO0OO00 .__OO00OO0OO00OOO00O ):#line:59 if OO0OO0O00OOO00OO0 [O0OOO0O0O0OO00O0O ]==OO00O0O0OOO0O0000 :#line:60 OOO00OO0OOOOOOOOO +="Q "#line:61 else :#line:62 OOO00OO0OOOOOOOOO +=". "#line:63 print (OOO00OO0OOOOOOOOO )#line:64 print ("\n")#line:65 def __OO0O00O0O0OOO0OOO (OO0OO000O00O0OO00 ,O00O00O00OO0O0OOO ):#line:67 ""#line:71 OOO000OOO00OO0OOO =""#line:72 for O00OOO000OO0O00OO in range (OO0OO000O00O0OO00 .__OO00OO0OO00OOO00O ):#line:73 OOO000OOO00OO0OOO +=str (O00O00O00OO0O0OOO [O00OOO000OO0O00OO ])+" "#line:74 print (OOO000OOO00OO0OOO )#line:75 def OOO0O0OO0OOO000OO ():#line:77 ""#line:78 O0000OO0000OOO0OO (8 )#line:79 if __name__ =="__main__":#line:81 OOO0O0OO0OOO000OO ()#line:83
戻す
__all__ = [] class O0000OO0000OOO0OO: """""" def __init__(O00OO0O0O0O00OO00, O00OO0OO00OOO00OO): O00OO0O0O0O00OO00.__OO00OO0OO00OOO00O = O00OO0OO00OOO00OO O00OO0O0O0O00OO00.__O00OOO00O0O000000 = 0 O00OO0O0O0O00OO00.__O0OO00000O0O0O0O0() def __O0OO00000O0O0O0O0(OOOOO0O00OO0O0O0O): """""" O0OO0000OO0OO0000 = [ -1] * OOOOO0O00OO0O0O0O.__OO00OO0OO00OOO00O OOOOO0O00OO0O0O0O.__OO0OOOO0000O0O00O(O0OO0000OO0OO0000, 0) print ('Found', OOOOO0O00OO0O0O0O.__O00OOO00O0O000000, 'solutions.') def __OO0OOOO0000O0O00O(O0OO00O0000O000O0, O0OOOOO0OOOOO000O, OO0OOOO00OO00OOO0): """""" if OO0OOOO00OO00OOO0 == O0OO00O0000O000O0.__OO00OO0OO00OOO00O: O0OO00O0000O000O0.__OOO0OO0O0000O00OO(O0OOOOO0OOOOO000O) O0OO00O0000O000O0.__O00OOO00O0O000000 += 1 else: for OO000O0OOOOO0O0OO in range(O0OO00O0000O000O0.__OO00OO0OO00OOO00O): if O0OO00O0000O000O0.__O00O0O00000O0O00O(O0OOOOO0OOOOO000O, OO0OOOO00OO00OOO0, OO000O0OOOOO0O0OO): O0OOOOO0OOOOO000O[OO0OOOO00OO00OOO0] = OO000O0OOOOO0O0OO O0OO00O0000O000O0.__OO0OOOO0000O0O00O(O0OOOOO0OOOOO000O, OO0OOOO00OO00OOO0 + 1) def __O00O0O00000O0O00O(OO0OOO0OOO0O0OO0O, O00000O0O00OOOOOO, O00O0O000000OOOOO, OOOO00OOO000OO0OO): """""" for OOO000O0OO0O00000 in range(O00O0O000000OOOOO): if O00000O0O00OOOOOO[OOO000O0OO0O00000] == OOOO00OOO000OO0OO or O00000O0O00OOOOOO[OOO000O0OO0O00000] - OOO000O0OO0O00000 == OOOO00OOO000OO0OO - O00O0O000000OOOOO or O00000O0O00OOOOOO[OOO000O0OO0O00000] + OOO000O0OO0O00000 == OOOO00OOO000OO0OO + O00O0O000000OOO OO: return False return True def __OOO0OO0O0000O00OO(OOOO0OOO0OOO0OO00, OO0OO0O00OOO00OO0): """""" for O0OOO0O0O0OO00O0O in range(OOOO0OOO0OOO0OO00.__OO00OO0OO00OOO00O): OOO00OO0OOOOOOOOO = '' for OO00O0O0OOO0O0000 in range(OOOO0OOO0OOO0OO00.__OO00OO0OO00OOO00O): if OO0OO0O00OOO00OO0[O0OOO0O0O0OO00O0O] == OO00O0O0OOO0O0000: OOO00OO0OOOOOOOOO += 'Q ' else: OOO00OO0OOOOOOOOO += '. ' print OOO00OO0OOOOOOOOO print '\n' def __OO0O00O0O0OOO0OOO(OO0OO000O00O0OO00, O00O00O00OO0O0OOO): """""" OOO000OOO00OO0OOO = '' for O00OOO000OO0O00OO in range(OO0OO000O00O0OO00.__OO00OO0OO00OOO00O): OOO000OOO00OO0OOO += str(O00O00O00OO0O0OOO[O00OOO000OO0O00OO]) + ' ' print OOO000OOO00OO0OOO def OOO0O0OO0OOO000OO(): """""" def OOO0O0OO0OOO000OO(): """""" O0000OO0000OOO0OO(8) if __name__ == '__main__': OOO0O0OO0OOO000OO()
まあ,やっぱりしっかりと戻された.
おわりに
pythonのソース保護としてpycで配布するのが良いという意見があるが,バイトコードにするだけではほぼ無意味なようだ.
本気で保護したいのなら,難読化,言語を変える,クラウドとして提供して一切ソースが手元に渡らないようにするくらいだろうか.
今回紹介したuncompyle
以外にも,unpyclib
というものもあるようだ.
試してみたらエラーが出て上手く行かなかったから,こちらはまた今度.
リバースエンジニアリング対策にpycをstripする方法はあるのだろうか?
参考
python - Is it possible to decompile a compiled .pyc file into a .py file? - Stack Overflow
GitHub - wibiti/uncompyle2: Python 2.7 decompiler
Oxyry Python Obfuscator - The most reliable python obfuscator in the world
どうやったらPythonアプリのソースコードを保護できますか? - Quora
Oxyry Python Obfuscator - The most reliable python obfuscator in the world
pycを読む(アセンブリ味)
pycファイルだけが手元にあり,なにが書いてあるか読みたいときがある.
pycのバイトコードは逆アセンブルして読める.
やり方と例文をまとめる.
ただし,これは低レベルな命令になるから可読性は良くない.
直接pythonコードにまで戻す方法があるから,そっちを使ったほうが良い.
厳密な仕様までは調べてないから,間違いはあるかも.指摘歓迎.
環境
各種バージョン
以下のとおり.
$ uname -a Linux poppycompass 4.18.6-arch1-1-ARCH #1 SMP PREEMPT Wed Sep 5 11:54:09 UTC 2018 x86_64 GNU/Linux $ python2 Python 2.7.15 (default, Jun 27 2018, 13:05:28) [GCC 8.1.1 20180531] on linux2
Arch Linuxの64ビット版.
つまりただのLinux,pythonは2系.
環境作成
環境はローカルに作成した.
$ virtualenv2 ENV $ . ENV/bin/activate $ pip install dis
pycを逆アセンブルするスクリプト
以下のスクリプトで変換できる.
# pyc_disasm.py import dis, marshal code = open("sample.pyc", "rb").read()[8:] code = marshal.loads(code) dis.dis(code)
ここでは,sample.pyc
というファイルを逆アセンブルする.
sample.py
に変換したいコードを書けばよい.
使い方は簡単.
$ python2 -m compileall sample.py # pyc生成 $ python2 ./pyc_disasm.py
変換一覧
逆アセンブルしたものはアセンブリ味になっている.
pythonによる記述と逆アセンブルしたものの比較をいくつか挙げておく.
各命令の左にある数値はアドレスのようだ.
ライブラリのインポート
普通にインポート
before
import random
after
0 LOAD_CONST 0 (-1) 3 LOAD_CONST 1 (None) 6 IMPORT_NAME 0 (random) 9 STORE_NAME 0 (random)
ちょっと書き方を変えたインポート
before
from random import *
after
0 LOAD_CONST 0 (-1) 3 LOAD_CONST 1 (('*',)) 6 IMPORT_NAME 0 (random) 9 IMPORT_STAR
変数定義
before
a = ''
after
0 LOAD_CONST 0 ('') 3 STORE_NAME 0 (a)
関数呼び出し
before
def hoge(a,b): return a+b hoge(1,2)
after
最初の部分が関数定義,後半が呼び出したときの部分.
関数呼び出しは,関数名・引数・CALL・戻り値格納先の順になる
0 LOAD_CONST 0 (<code object hoge at 0x7fc3dec66b30, file "./sample.py", line 1>) 3 MAKE_FUNCTION 0 6 STORE_NAME 0 (hoge) 9 LOAD_NAME 0 (hoge) 12 LOAD_CONST 1 (1) 15 LOAD_CONST 2 (2) 18 CALL_FUNCTION 2 21 POP_TOP 22 LOAD_CONST 3 (None) 25 RETURN_VALUE
演算
before
2以上の変数による演算.
d = a + b + c
after
a
, b
, c
には,文字列と数値のどちらをいれてもコード自体は変わらない.
直前のLOAD_NAME
で入れる値が変わるだけ.
BINARY_ADD
は相手が文字列だと連結,数値だと数値演算を行う.
3つの変数が相手だと,2回BINARY_ADD
する.
以下のコードは数値版.
18 LOAD_NAME 0 (a) 21 LOAD_NAME 1 (b) 24 BINARY_ADD 25 LOAD_NAME 2 (c) 28 BINARY_ADD 29 STORE_NAME 3 (d) 32 LOAD_CONST 1 (None) 35 RETURN_VALUE
文字列連結だと以下の通り
before
allchar = string.ascii_letters + string.punctuation + string.digits # import string後
after
80 LOAD_NAME 0 (string) 83 LOAD_ATTR 9 (ascii_letters) 86 LOAD_NAME 0 (string) 89 LOAD_ATTR 10 (punctuation) 92 BINARY_ADD 93 LOAD_NAME 0 (string) 96 LOAD_ATTR 11 (digits) 99 BINARY_ADD 100 STORE_NAME 12 (allchar)
ループ
before
sum = 0 for i in range(100): sum += i
after
FOR_ITER
にジャンプしてループを回しているようだ.
0 LOAD_CONST 0 (0) 3 STORE_NAME 0 (sum) 6 SETUP_LOOP 30 (to 39) 9 LOAD_NAME 1 (range) 12 LOAD_CONST 1 (100) 15 CALL_FUNCTION 1 18 GET_ITER >> 19 FOR_ITER 16 (to 38) 22 STORE_NAME 2 (i) 25 LOAD_NAME 0 (sum) 28 LOAD_NAME 2 (i) 31 INPLACE_ADD 32 STORE_NAME 0 (sum) 35 JUMP_ABSOLUTE 19 >> 38 POP_BLOCK >> 39 LOAD_CONST 2 (None) 42 RETURN_VALUE
ありがちなコード
before
とあるCTF問題にあった一文.
password = ('').join((choice(allchar) for a in range(randint(60, 60))))
after
繰り返し部はGET_ITER
が入るようだ.
103 LOAD_CONST 6 ('') 106 LOAD_ATTR 13 (join) 109 LOAD_CONST 7 (<code object <genexpr> at 0x7f2fb42d9330, file "ransomware.py", line 11>) 112 MAKE_FUNCTION 0 115 LOAD_NAME 14 (range) 118 LOAD_NAME 15 (randint) 121 LOAD_CONST 8 (60) 124 LOAD_CONST 8 (60) 127 CALL_FUNCTION 2 130 CALL_FUNCTION 1 133 GET_ITER 134 CALL_FUNCTION 1 137 CALL_FUNCTION 1 140 STORE_NAME 16 (password)
おわりに
正直,いざpyc読むときにはこんな低レベルなやつを読むのは労力が要る.正にドM.
pythonコードに直に変換する方法がある.
そっちを使ったほうが余程楽.
参考
volatilityにSANSのプラグインを追加する方法
volatilityでmimikatzを使いたくなった.
セットアップ方法が見つからなかったから書き残す.
mimikatzとは
Windowsのパスワードなどの各種認証情報をメモリから抜き出すツール.
管理者権限の取得やなりすましにも使える.
CTFではメモリダンプからアカウントの平文パスワードを抜き出すのによく使う.
デフォルトではvolatilityと連携して使うことができない.
SANSがプラグインを書いて公開しているから,それを追加してやれば良い.
やり方
基本はプラグインをコピーするだけ.
$ git clone https://github.com/volatilityfoundation/volati $ git clone https://github.com/sans-dfir/sift-files $ cp sift-files/volatility/* volatility/volatility/plugins
これでmimikatz
コマンド自体は使えるよう.しかし・・・
$ python2 volatility/vol.py -f ./memdump.mem mimikatz Volatility Foundation Volatility Framework 2.6 *** Failed to import volatility.plugins.openioc_scan (ImportError: No module named ioc_writer) ERROR : volatility.debug : You must specify something to do (try -h)
pythonモジュール不足でまだ動かない.
ERROR : volatility.debug : You must specify something to do (try -h)
はコマンドが無いときにも出る.わかりにくい.
モジュールについてはvirtualenv2
でローカル環境を作成して,整えていく.
$ virtualenv2 ENV && . ENV/bin/activate $ pip install distorm3 pycrypto lxml ioc_writer colorama construct==2.5.5-reupload
Crypt.Hash
モジュールのためにpycrypto
.
AttributeError: 'module' object has no attribute 'ULInt32'
はconstruct==2.5.5-reupload
で対処.
$ python2 volatility/vol.py -f <mem file> --profile=WinXPSP2x86 mimikatz
これで動く.
参考
error in attribute with construct module · Issue #15 · volatilityfoundation/community · GitHub
python - ImportError: No module named Crypto.Cipher - Stack Overflow
lsass.exe のダンプからユーザーパスワードを抽出 :mimikatz
https://www.iij.ad.jp/dev/tech/techweek/pdf/171108_02.pdf
GitHub - gentilkiwi/mimikatz: A little tool to play with Windows security