拾い物のコンパス

まともに書いたメモ

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のようだ.

現象

  1. PCにLANケーブルをつなぐ
  2. LANケーブルコネクタのリンクランプ点灯(緑・オレンジ共)
  3. ネットにつながらない

調査

まずは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が取れていないことにありそうだ. Googlearch 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が取れていない時点でネットワーク周りのソフトが全部動いているかどうかを確認するべきだったと反省.

参考

https://bbs.archlinux.org/viewtopic.php?id=214111

プログラム実行時の命令数(関数単位)を可視化してみた

Intel Pinで任意のプログラム実行時の実行命令数を関数単位で計測し,d3.jsに食わせたという話.

背景

プログラム解析時に複雑で命令数が多いところはなるだけ読みたくない.
読むなら心構えがしたい.
一目でわかるようにできないかと思って可視化に手を出してみた,第一弾.

処理の流れ

  1. Intel Pinで実行命令数を計測・CSVで出力(サンプルをほぼ流用できた)
  2. 1.の出力をJSONに変換
  3. 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でやることはここまで.

CSVJSONに変換

可視化で見たいものは実行命令数の大小.
棒グラフでも良かったがここではバブルチャートを使うことにした.
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へアクセスする.

結果

f:id:poppycompass:20191118135543j:plain メイン処理と全く関係ないところが非常に大きな比重を占めていることがひと目でわかる.
そりゃmain部なんてprintfしてるだけだからこの図じゃわかるわけなかった.
これだけだともやもやするからファイル毎の関数で可視化してみた(前述のeach_imageを利用).
each_imageの出力は各ファイル単位で改行されるから,見たいやつ以外の行を消すこと.
以下,libc.so.6,ld-linux-x86-64.so.2,a.outの結果.

f:id:poppycompass:20191118142029j:plainf:id:poppycompass:20191118142037j:plainf:id:poppycompass:20191118142101j:plain

バブルの大きさは各ファイルの命令数の平均に影響されている.a.outmainは7命令だけど結構大きくなってしまっている.

所感

命令数が多い=重要な関数かと言われると実行準備段階の命令も入っているから一概に言えない.
処理を追う上では流れの関係を示さなければ役には立たなさそうだ.

d3.jsってエクセルの3Dマップのように球体表示し,ドラッグドロップで後ろまで見えるかと思っていたが,そこまでの機能はないようだ.
可視化はあくまでネットワーク図のように繋がりを一目で表現するためのものでCADみたいな3Dツールとは別物なんだと知った.

ごちゃごちゃ書いたが結局これだけだと解析にはイマイチ使えない.
次の方法を考えないといけない.

参考

Intel Pinを使ってみる - ももいろテクノロジー

Bubble Chart d3 v4 - bl.ocks.org

json --- JSON エンコーダおよびデコーダ — Python 3.8.0 ドキュメント

JsonからPythonで複数のキー値を持つ入れ子になったJson構造を作成する - コードログ

yaourtがいつの間にか消えていた話

AUR使うときに世話になっているyaourtがいつの間にか開発終了していて,yayという代替プログラムに乗り換えた.

経緯

3年ぶりくらいに Arch Linux を最初からインストールして環境を構築した.
AURを使うために

$ pacman --sync --refresh yaourt

とかでyaourtをインストールしようとしても見つからない.
なんでだろうと調べていたら開発が終了していた.
日本語の記事が少なかったのと英語の記事タイトルがYaourt is dead!(unsupportedとかで検索していた)となっていたから調べるのに苦労した.
killといいやたら物騒な表現が好きな世界だ.

代替プログラム(yay)

yay以外にpakkuaurutilsもあるが,参考サイトが綺麗に書いているからそっちに譲る.
今回は使い勝手が同じそうな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

Trizenも良いけどyayも使おう - Qiita

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がインストールされた端末にコピーする.
ただ打ち込んでいくだけ.
大きな流れは以下の通り.

  1. IPを割り振る
  2. ユーザを作成
  3. SSH鍵の作成(今回は1024bit)・有効化
  4. 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 認証部分.
aaaAuthentication/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できない

    • ログインターミナルの関係でエラーが出て上手く行かない.Linuxのターミナル使うか,puttyCUI(pscp.exe)を使おう.
  • スイッチで同じ設定してみたが,上手く行かない

    • 原因不明.知りたい.

おまけ

抜き出してから,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)

Cisco IOS セキュリティ コマンド リファレンス:コマンド D ~ L、Cisco IOS XE Release 3SE(Catalyst 3850 スイッチ) - ip scp server enable [Cisco Catalyst 3850 シリーズ スイッチ] - Cisco

Solved: SCP error - Cisco Community

IOSでSCPを利用する : TEMPEST社長ブログ

AAAとは

pycを読む(スクリプト味)

前回はpycを低レベルな命令に変換して読む方法を説明した.
あれはあれで読めはするが,アセンブリ言語と同様の読みづらさはある.
今回は直接pycpythonスクリプトまで変換する方法を紹介する.
変換するライブラリは複数あるが,ここでは uncompyleを使った.
結論として,精度はいい感じ.pycにソース保護を期待するのはやめましょう.

uncompyle

python byte-code decompiler.
uncompyle2uncompyle6がある.
前者は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ビット版.
つまりただのLinuxpythonは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

uncompyle6 · PyPI

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ビット版.
つまりただのLinuxpythonは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コードに直に変換する方法がある.
そっちを使ったほうが余程楽.

参考

pycファイルの逆アセンブル - one day in summer

.pycファイルを作成する方法メモ - かせきのうさぎさん

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モジュールのためにpycryptoAttributeError: '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