/var/log/Sawada.log

SAINO中毒患者の備忘録。

CUIでお洒落な開発環境を整えよう

はじめに

みなさん年の瀬いかがお過ごしでしょうか.こたつスウィングバイが止まらないさわだです.
工大アドベントカレンダー(一日目)の空枠を拝借して,自分も最近の事を書こうと思います.

adventar.org

今回の記事はこれです.

きっかけ

我が愛機であるThinkpad X220にArch LinuxをホストOSとしてインストールしましたが,chrome君とslack君に加え,Intellijの子供たちを使うとメモリが逼迫するのでCUIで開発環境を整えたいなと思ったのがきっかけです.

整備する

以前はneovim周辺の設定を弄って満足出来ましたが,あらゆる事をCUIで完結させたい欲が高まるにつれ,複数のターミナルを起動するのも億劫になってきました. 更にCUIでも毎日触るのが楽しくなるような見た目が欲しくなりました. となると...自分にとって必要な環境整備は以下の項目が挙げられます.

  • CUIツールの導入

  • ターミナルマルチプレクサの導入(且つ,ばぁぁーーんといい感じに起動したい)

  • 諸々の見た目の整備

では順を追って解説に入ります.

CUIツール

必要なものはすべて偉大なるArch Wikiが教えてくれました.アプリケーション一覧にあるコンソールに様々な紹介があります*1

システム監視として見た目がいい感じかつGo製のgotop,ファイルマネージャとして操作感がvimに似たranger,作業BGMを聴くためにプレイリストが管理できるmps-youtubeIRCクライアントとしてweechatを導入しました.

Archではyay -S <pkg>でこれら全てが楽にインストール出来ます.他OSもドキュメントに従えば楽にツールを導入できます.

gotop

vimに似た操作感でプロセス管理も可能です.詳しくはリポジトリのREADMEを読めばいいと思います.nodejs製のgtopはモニタリングツールとして有名ですが,CUIで使うにはちょっと軽量とは言い難い部分があります.私の環境ではgotopによりメモリ使用量はgtop 85MB から gotop 7.3MBへ節約できました.gotop -c solarizedでカラースキーム指定起動するとこんな画面です.tmux上ではminimal表示を利用しています.

f:id:takuzoo3868:20181228003551p:plain

ranger

vimに似た操作感でファイル操作ができます.設定ファイルは.config/ranger配下に作られるので,dotfilesによる設定管理も容易です.自分は見た目のカスタマイズにranger_deviconsを使っています.カラースキームはsnowを採用.

github.com

f:id:takuzoo3868:20181228004601p:plain

mps-youtube

作業用にyoutubeにあるnightcoreを聴きたいけれど,ブラウザは使いたくない自分がたどり着いたのはこのツールでした.Python製です.操作キーは上記2つのツールに比べると若干特殊なため覚える必要があります.起動はmpsyt.操作方法は以下の記事を見ればよいかと.自分はキーワード検索/hogefugaして全曲再生all,またはプレイリスト再生pl <playlist ID>を使うことが多いです.

www.blky.me

f:id:takuzoo3868:20181228010252p:plainf:id:takuzoo3868:20181228010305p:plain

weechat

IRCチャット用に軽量なweechatは様々なプラグインで拡張が出来ます.ただし,プラグインは言語依存があるので注意が必要です.入りたいIRCサーバを追加したり,バッファにチャンネルリストを表示したり,色々設定すればGUIに負けないチャットツールとして重宝できるでしょう.今は開発者コミュニティで情報収集やPKhack界隈のチャットとして使っていますが,できればtwitterやslackもweechatで表示したいと格闘中...

f:id:takuzoo3868:20181228011416p:plain

ターミナルマルチプレクサ

日本語の記事も多いtmuxを導入しました.sshを多用する場面でも何かと重宝するので以前から入っていましたが,本格的に設定したのは今回が初めてです. tmuxをいい感じに起動するためにセッションマネージャとしてtmuxpを導入します.

github.com

これは,競合であるtmuxinatorがRuby依存だったのに対して,tmuxpがpython依存だったからです(すでにpyenv+pipenvで環境構築していたため).この辺は好きなセッションマネージャを導入すればいいと思います.設定にはyamlを使います.

tmux_options: -2
session_name: work
root: ~/CTF/

windows:
- window_name: main
  layout: 86f2,151x37,0,0{84x37,0,0[84x22,0,0,2,84x14,0,23,6],66x37,85,0[66x10,85,0,3,66x26,85,11,4]}
  panes:
    - screenfetch
    - gotop -c solarized -m
    - ranger
    - mpsyt pl PLBFICLTzW2LckbdM1Ri6BBfpeSPwRYzXe
- window_name: chat
  panes:
    - weechat
- window_name: work
  focus: true
  layout: cab4,151x37,0,0{92x37,0,0,6,58x37,93,0,7}
  panes:
    - pane
    - pane

layoutの項目はtmux起動中にtmux list-windowsで調べることが出来ます.上記のような設定を書いて.tmux配下などに保存しておきtmuxp load path/to/config.yamlで起動するとウィンドウが複数立ち上がり,分割されたペイン上でそれぞれのCUIツールを走らせることが出来ます.大変便利.

諸々の見た目

Nerd Fonts

ターミナル上にもアイコン系ロゴ系フォントを導入します.理由はascii artのような見た目の楽しさと,neovimで既にairline用としてpowerline系のフォントを使っていたからです. 有名所をまとめた素晴らしいロゴフォントとしてNerd Fontsがあります.

github.com

このフォントを普段使うターミナル用フォントにfontforgeで合成し設定することで,ターミナルの表示はガラリと変化するはず.手っ取り早く導入したい方は日本語プログラミング環境用等幅フォントCica fontをおすすめします.ライセンス関係で一部表示できないため,どうしてもアイコン類を表示したい方はfontforgeを使って上書きすれば良いかと.日本語表示には影響がないので...

ターミナルでの表示確認用にシェルスクリプトを書きました.もともとNerd Fontsに同封されていた使いづらいテストコードをコマンドツールとして改良したものになります.

#!/usr/bin/env bash

# Author: takuzoo3868
# Last Modified: 07 Dec 2018.

# Check Nerd fonts drawing on terminal.
# Nerd Fonts Version: 2.0.0
# Script Version: 1.0.0

PROGNAME=$(basename $0)
VERSION="1.0.0"


usage() {
  echo "usage: ${PROGNAME} [--font_option]"
  echo ""
  echo "Check Nerd fonts v.2.0.0 drawing on terminal."
  echo ""
  echo "optional arguments:"
  echo "  -h, --help            show this help message and exit"
  echo "  --dev                 Font Devicon"
  echo "  --fa                  Font Awesome"
  echo "  --fae                 Font Awesome Extension"
  echo "  --iec                 IEC Power Symbols"
  echo "  --linux, --fontlogos  Font Linux and other open source Glyphs"
  echo "  --material, --mdi     Material Design Icon"
  echo "  --oct                 Octicons"
  echo "  --pl                  Powerline"
  echo "  --ple                 Powerline Extra"
  echo "  --pom                 Pomicon"
  echo "  --seti                Seti-UI + Custom"
  echo "  --weather             Weather Icons"
  exit 1
}

# Given an array of decimal numbers print all unicode codepoint.
function print-decimal-unicode-range() {
  local originalSequence=("$@")
  local counter=0
  # Use alternating colors to see which symbols extend out of the bounding
  # box.
  local bgColorBorder='\033[48;5;8m'
  local bgColorCode='\033[48;5;246m'
  local alternateBgColorCode='\033[48;5;240m'
  local bgColorChar='\033[48;5;66m'
  local alternateBgColorChar='\033[48;5;60m'
  local underline='\033[4m'
  local currentColorCode="${bgColorCode}"
  local currentColorChar="${bgColorChar}"
  local reset_color='\033[0m'
  local allChars=""
  local allCodes=""
  local wrapAt=5
  local topLine="${bgColorBorder}╔══════╦══════╦══════╦══════╦══════╗${reset_color}"
  local bottomLine="${bgColorBorder}╚══════╩══════╩══════╩══════╩══════╝${reset_color}"
  local line="${bgColorBorder}╠══════╬══════╬══════╬══════╬══════╣${reset_color}"
  local bar="${bgColorBorder}${reset_color}"
  local originalSequenceLength=${#originalSequence[@]}
  local leftoverSpaces=$((wrapAt - (originalSequenceLength % wrapAt)))

  # add fillers to array to maintain table:
  if [[ "$leftoverSpaces" < "$wrapAt" ]]; then
    # shellcheck disable=SC2034
    # needs rework without 'i' var?
    for i in $(seq 1 $leftoverSpaces); do
      originalSequence+=(0)
    done
  fi

  local sequenceLength=${#originalSequence[@]}

  printf "%b\\n" "$topLine"

  for decimalCode in "${originalSequence[@]}"; do
    local hexCode
    hexCode=$(printf '%x' "${decimalCode}")
    local code="${hexCode}"
    local char="\\u${hexCode}"

    # fill in placeholder cells properly formatted:
    if [ "${char}" = "\\u0" ]; then
      char=" "
      code="    "
    fi

    allCodes+="${currentColorCode} ${underline}${code}${reset_color}${currentColorCode} ${reset_color}$bar"
    allChars+="${currentColorChar}  ${char}   ${reset_color}$bar"
    counter=$((counter + 1))
    count=$(( (count + 1) % wrapAt))

    if [[ $count -eq 0 ]]; then

      if [[ "${currentColorCode}" = "${alternateBgColorCode}" ]]; then
        currentColorCode="${bgColorCode}"
        currentColorChar="${bgColorChar}"
      else
        currentColorCode="${alternateBgColorCode}"
        currentColorChar="${alternateBgColorChar}"
      fi

      printf "%b%b%b" "$bar" "$allCodes" "$reset_color"
      printf "\\n"
      printf "%b%b%b" "$bar" "$allChars" "$reset_color"
      printf "\\n"

      if [ "$counter" != "$sequenceLength" ]; then
        printf "%b\\n" "$line"
      fi

      allCodes=""
      allChars=""
    fi

  done

  printf "%b\\n" "$bottomLine"

}

function print-unicode-ranges() {
  echo ''

  local arr=($@)
  local len=$#
  local combinedRanges=()

  for ((j=0; j<len; j+=2)); do
    local start="${arr[$j]}"
    local end="${arr[(($j+1))]}"
    local startDecimal=$((16#$start))
    local endDecimal=$((16#$end))

    combinedRanges+=($(seq "${startDecimal}" "${endDecimal}"))

  done

  print-decimal-unicode-range "${combinedRanges[@]}"

}


for OPT in "$@"
do
  case $OPT in
    '-h' | '--help' )
      usage
      ;;

    '--dev' )
      echo "Nerd Fonts - Devicons"
      print-unicode-ranges e700 e7c5
      echo; echo
      ;;

    '--fa' )
      echo "Nerd Fonts - Font awesome" 
      print-unicode-ranges f000 f2e0
      echo; echo
      ;;

    '--fae' )
      echo "Nerd Fonts - Font awesome extension" 
      print-unicode-ranges e200 e2a9
      echo; echo
      ;;

    '--ice' )
      echo "Nerd Fonts - Font Power Symbols"
      print-unicode-ranges 23fb 23fe 2b58 2b58
      echo; echo
      ;;

    '--linux' | '--fontlogos')
      echo "Nerd Fonts - Font Linux"
      print-unicode-ranges f300 f31c
      echo; echo
      ;;

    '--material' | '--mdi')
      echo "Nerd Fonts - Material Design Icons"
      print-unicode-ranges f500 fd46
      echo; echo
      ;;

    '--oct' )
      echo "Nerd Fonts - Octicons"
      print-unicode-ranges 2665 2665 26A1 26A1 f400 f4a8 f67c f67c
      echo; echo
      ;;

    '--pl' )
      echo "Nerd Fonts - Powerline"
      print-unicode-ranges e0a0 e0a2 e0b0 e0b3
      echo; echo
      ;;

    '--ple' )
      echo "Nerd Fonts - Powerline Extra"
      print-unicode-ranges e0a3 e0a3 e0b4 e0c8 e0cc e0d2 e0d4 e0d4
      echo; echo
      ;;

    '--pom' )
      echo "Nerd Fonts - Pomicons"
      print-unicode-ranges e000 e00a
      echo; echo
      ;;

    '--seti' )
      echo "Nerd Fonts - Symbols original"
      print-unicode-ranges e5fa e62e
      echo; echo
      ;;

    '--weather' )
      echo "Nerd Fonts - Weather Icons"
      print-unicode-ranges e300 e3eb
      echo; echo
      ;;

    -*)
      echo "$PROGNAME: illegal option -- '$(echo $1 | sed 's/^-*//')'" 1>&2
      exit 1
      ;;

    *)
      usage
      ;;
  esac
done

Bash prompt

Nerd Fontsの導入で改善したい見た目その①はシェルのプロンプトでした.zshをお使いの方はPowerlevel9kを使えばイケイケなプロンプトになります. 自分はbashのままかfishが多いので,プロンプトをPowerlevel9kっぽく変更したいと思います.試行錯誤の末,完成したプロンプトがこちら.

f:id:takuzoo3868:20181228015533p:plain

  • お決まりのユーザー名・コンピュータ名

  • ディレクトリの種類で表示アイコンを切替

  • git管理ディレクトリ配下ではブランチ名,コミットID,各種ステータス

  • shell err code ステータス

といった表示を行っています.現時刻やipアドレスといった別ツールでも参照しやすい情報は後述するtmuxのステータスラインに表示することにしました. Nerd Fontsを中てた上でbashrcPS1を下記のように上書きすれば同様の表示となるはずです.

#!/usr/bin/env bash

if [[ $COLORTERM = gnome-* && $TERM = xterm ]] && infocmp gnome-256color >/dev/null 2>&1; then
    export TERM='gnome-256color';
elif infocmp xterm-256color >/dev/null 2>&1; then
    export TERM='xterm-256color';
fi;

if tput setaf 1 &> /dev/null; then
    tput sgr0; # reset colors
    bold=$(tput bold);
    reset=$(tput sgr0);

    black=$(tput setaf 234);
    blue=$(tput setaf 27);
    cyan=$(tput setaf 39);
    green=$(tput setaf 76);
    orange=$(tput setaf 166);
    purple=$(tput setaf 125);
    red=$(tput setaf 124);
    violet=$(tput setaf 61);
    white=$(tput setaf 15);
    yellow=$(tput setaf 154);
else
    bold='';
    reset="\e[00m";
    black="\e[1;30m";
    blue="\e[1;34m";
    cyan="\e[1;36m";
    green="\e[1;32m";
    orange="\e[1;33m";
    purple="\e[1;35m";
    red="\e[1;31m";
    violet="\e[1;35m";
    white="\e[1;37m";
    yellow="\e[1;33m";
fi;

export PROMPT_DIRTRIM=2

# icons set
ICON_HOME=""
ICON_DIR=""
ICON_ETC=""
ICON_USER=""
ICON_HOST=""

ICON_OK=""
ICON_FAIL=""
ICON_LOCK=""
ICON_NOT_FOUND=""
ICON_STOP=""

ICON_OCTOCAT=""
ICON_GIT_BITBUCKET=""
ICON_GIT_GITLAB=""

ICON_GIT_BRANCH=""
ICON_GIT_COMMIT=""
ICON_GIT_REMOTE_BRANCH=""
ICON_GIT_UNTRACKED=""
ICON_GIT_UNSTAGED=""
ICON_GIT_STAGED=""
ICON_GIT_STASH=""
ICON_GIT_INCOMING_CHANGES=""
ICON_GIT_OUTGOING_CHANGES=""
ICON_GIT_TAG=""

# git status veiw
prompt_git() {
    local s='';
    local branchName='';
    local gitHash='';

    GIT_DIR="$(git rev-parse --git-dir 2>/dev/null)"

    # Check if the current directory is in a Git repository.
    if [ $(git rev-parse --is-inside-work-tree &>/dev/null; echo "${?}") == '0' ]; then

        # check if the current directory is in .git before running git checks
        if [ "$(git rev-parse --is-inside-git-dir 2> /dev/null)" == 'false' ]; then

            # Ensure the index is up to date.
            git update-index --really-refresh -q &>/dev/null;

            # Check for uncommitted changes in the index.
            if ! $(git diff --quiet --ignore-submodules --cached); then
                s+=${ICON_GIT_STAGED};
            fi;

            # Check for unstaged changes.
            if ! $(git diff-files --quiet --ignore-submodules --); then
                s+=${ICON_GIT_UNSTAGED};
            fi;

            # Check for untracked files.
            if [ -n "$(git ls-files --others --exclude-standard)" ]; then
                s+=${ICON_GIT_UNTRACKED};
            fi;

            # Check for stashed files.
            if $(git rev-parse --verify refs/stash &>/dev/null); then
                s+=${ICON_GIT_STASH};
            fi;

        fi;

        # Get the short symbolic ref.
        # If HEAD isn’t a symbolic ref, get the short SHA for the latest commit
        # Otherwise, just give up.
        branchName=" ${ICON_GIT_BRANCH} $(git symbolic-ref --quiet --short HEAD 2> /dev/null || \
           git rev-parse --short HEAD 2> /dev/null || \
           echo '(unknown)')";

        [ -n "${s}" ] && s=" ${s}";

        # Get commit hash
        gitHash=" ${ICON_GIT_COMMIT} $(git rev-parse --short HEAD)";


        echo -e "${ICON_OCTOCAT}${1}${branchName}${gitHash}${2}${s}";
    else
        return;
    fi;
}

prompt_dir_icon(){
    case $PWD in
        $HOME)
            echo ${ICON_HOME}
            ;;
        "/etc")
            echo ${ICON_ETC}
            ;;
        *) 
            echo ${ICON_DIR}
            ;;
    esac
}

prompt_user(){
    if [[ "${USER}" == "root" ]]; then
        user_state="${orange}";
    else
        user_state="${blue}";
    fi;

    # the hostname when connected via SSH.
    if [[ "${SSH_TTY}" ]]; then
        hostStyle="${bold}${red}";
    else
        hostStyle="${yellow}";
    fi;

    echo -e "${user_state}${ICON_USER}"
}

prompt_host(){
    echo -e "${cyan}${ICON_HOST}"
}

prompt_result() {
  code=$?
  if [ ${code} == 0 ]; then
    echo -e "${ICON_OK}";
  elif [ ${code} == 126 ]; then
    echo -e "${ICON_LOCK}";            # Command invoked cannot execute
  elif [ ${code} == 127 ]; then
    echo -e "${ICON_NOT_FOUND}";       # Command not found
  elif [ ${code} == 130 ]; then
    echo -e "${ICON_STOP}";            # Script terminated by Control-C
  else
    echo -e "${ICON_FAIL} ${bold}${code}${reset}";
  fi;
}

# Set the terminal title and prompt.
PS1=" ";
PS1+="\$(prompt_user) \[${bold}\]\u ";
PS1+="\[${reset}\]";
PS1+="\$(prompt_host) \[${bold}\]\h ";
PS1+="\[${reset}\]";
PS1+="\[${green}\]\$(prompt_dir_icon) \[${bold}\]\w ";
PS1+="\[${reset}\]";
PS1+="\[${yellow}\]\$(prompt_git) ";
PS1+="\[${reset}\]";                                        
PS1+="\[${yellow}\]\$(prompt_result)";
PS1+="\[${reset}\]";                                    
PS1+="\n";
PS1+="\$ ";                            
export PS1;

Tmux status line

いい感じの見た目とすべく,昔は先人の情報をもとにtmux-powerlineを使っていました. しかし,dotfilesで管理しにくい点,及びpyenv環境ではpowerline設定がややこしい点から廃止し,ステータスラインをフルスクラッチすることにしました. 自分でも割とお気に入りの見た目を作ることが出来たと思っています.

f:id:takuzoo3868:20181228022707p:plain

メンテナンスしやすいようtmux.conftmux_local.confへ分離したり,色々やってますが長くなるので割愛します.dotfilesにあるこいつを見てくれ!独自にステータスへスクリプトを組み込むことも可能です.自分の場合は例として,local IP / global IP / 天気情報をスクリプトで追加しています.

#!/usr/bin/env bash
#
# Author: takuzoo3868
# Last Modified: 25 Nov 2018.

# Check OS
ostype() { echo $OSTYPE | tr '[A-Z]' '[a-z]'; }

export SHELL_PLATFORM='unknown'

case "$(ostype)" in
    *'linux'*  ) SHELL_PLATFORM='linux'  ;;
    *'darwin'* ) SHELL_PLATFORM='osx'        ;;
    *'bsd'*        ) SHELL_PLATFORM='bsd'        ;;
esac

shell_is_linux() { [[ $SHELL_PLATFORM == 'linux' || $SHELL_PLATFORM == 'bsd' ]]; }
shell_is_osx()   { [[ $SHELL_PLATFORM == 'osx' ]]; }
shell_is_bsd()   { [[ $SHELL_PLATFORM == 'bsd' || $SHELL_PLATFORM == 'osx' ]]; }

export -f shell_is_linux
export -f shell_is_osx
export -f shell_is_bsd

__run_lan() {
    if shell_is_bsd || shell_is_osx ; then
        all_nics=$(ifconfig 2>/dev/null | awk -F':' '/^[a-z]/ && !/^lo/ { print $1 }')
        for nic in ${all_nics[@]}; do
            ipv4s_on_nic=$(ifconfig ${nic} 2>/dev/null | awk '$1 == "inet" { print $2 }')
            for lan_ip in ${ipv4s_on_nic[@]}; do
                [[ -n "${lan_ip}" ]] && break
            done
            [[ -n "${lan_ip}" ]] && break
        done
    else
        # Get the names of all attached NICs.
        all_nics="$(ip addr show | cut -d ' ' -f2 | tr -d :)"
        all_nics=(${all_nics[@]//lo/})    # Remove lo interface.

        for nic in "${all_nics[@]}"; do
            # Parse IP address for the NIC.
            lan_ip="$(ip addr show ${nic} | grep '\<inet\>' | tr -s ' ' | cut -d ' ' -f3)"
            # Trim the CIDR suffix.
            lan_ip="${lan_ip%/*}"
            # Only display the last entry
            lan_ip="$(echo "$lan_ip" | tail -1)"

            [ -n "$lan_ip" ] && break
        done
    fi

    echo "${lan_ip-N/a}"
    return 0
}

__run_lan
#!/usr/bin/env bash
#
# Author: takuzoo3868
# Last Modified: 25 Nov 2018.

# Check OS
ostype() { echo $OSTYPE | tr '[A-Z]' '[a-z]'; }

export SHELL_PLATFORM='unknown'

case "$(ostype)" in
    *'linux'*  ) SHELL_PLATFORM='linux'  ;;
    *'darwin'* ) SHELL_PLATFORM='osx'        ;;
    *'bsd'*        ) SHELL_PLATFORM='bsd'        ;;
esac

shell_is_linux() { [[ $SHELL_PLATFORM == 'linux' || $SHELL_PLATFORM == 'bsd' ]]; }
shell_is_osx()   { [[ $SHELL_PLATFORM == 'osx' ]]; }
shell_is_bsd()   { [[ $SHELL_PLATFORM == 'bsd' || $SHELL_PLATFORM == 'osx' ]]; }

export -f shell_is_linux
export -f shell_is_osx
export -f shell_is_bsd

# Path tmp file
export DIR_TEMPORARY="/tmp/tmux-weather_${USER}"
if [ ! -d "$DIR_TEMPORARY" ]; then
    mkdir -p "$DIR_TEMPORARY"
fi

__run_wan() {
    local tmp_file="${DIR_TEMPORARY}/wan_ip.txt"
    local wan_ip

    if [ -f "$tmp_file" ]; then
        if shell_is_osx || shell_is_bsd; then
            stat >/dev/null 2>&1 && is_gnu_stat=false || is_gnu_stat=true
            if [ "$is_gnu_stat" == "true" ];then
                last_update=$(stat -c "%Y" ${tmp_file})
            else
                last_update=$(stat -f "%m" ${tmp_file})
            fi
        elif shell_is_linux || [ -z $is_gnu_stat]; then
            last_update=$(stat -c "%Y" ${tmp_file})
        fi

        time_now=$(date +%s)
        update_period=900
        up_to_date=$(echo "(${time_now}-${last_update}) < ${update_period}" | bc)

        if [ "$up_to_date" -eq 1 ]; then
            wan_ip=$(cat ${tmp_file})
        fi
    fi

    if [ -z "$wan_ip" ]; then
        wan_ip=$(curl --max-time 2 -s http://whatismyip.akamai.com/)

        if [ "$?" -eq "0" ]; then
            echo "${wan_ip}" > $tmp_file
        elif [ -f "${tmp_file}" ]; then
            wan_ip=$(cat "$tmp_file")
        fi
    fi

    if [ -n "$wan_ip" ]; then
        echo "${wan_ip}"
    fi

    return 0
}

__run_wan
#!/usr/bin/env bash
#
# Author: takuzoo3868
# Last Modified: 25 Nov 2018.
# API: http://developer.yahoo.com/weather
#
# osx need coreutils

# Check OS
ostype() { echo $OSTYPE | tr '[A-Z]' '[a-z]'; }

export SHELL_PLATFORM='unknown'

case "$(ostype)" in
  *'linux'*    ) SHELL_PLATFORM='linux'  ;;
  *'darwin'*   ) SHELL_PLATFORM='osx'        ;;
  *'bsd'*      ) SHELL_PLATFORM='bsd'        ;;
esac

shell_is_linux() { [[ $SHELL_PLATFORM == 'linux' || $SHELL_PLATFORM == 'bsd' ]]; }
shell_is_osx()   { [[ $SHELL_PLATFORM == 'osx' ]]; }
shell_is_bsd()   { [[ $SHELL_PLATFORM == 'bsd' ]]; }

export -f shell_is_linux
export -f shell_is_osx
export -f shell_is_bsd

# Path tmp file
export DIR_TEMPORARY="/tmp/tmux-weather_${USER}"
if [ ! -d "$DIR_TEMPORARY" ]; then
  mkdir -p "$DIR_TEMPORARY"
fi

# DEFAULT
WEATHER_DATA_PROVIDER_DEFAULT="yahoo"
WEATHER_UNIT_DEFAULT="c"
WEATHER_UPDATE_PERIOD_DEFAULT="600"
# input your woeid https://lab.syncer.jp/Tool/WOEID-Lookup/
WEATHER_LOCATION_DEFAULT="1118108"

export WEATHER_DATA_PROVIDER="${WEATHER_DATA_PROVIDER_DEFAULT}"
# What unit to use. Can be any of {c,f,k}.
export WEATHER_UNIT="${WEATHER_UNIT_DEFAULT}"
# How often to update the weather in seconds.
export WEATHER_UPDATE_PERIOD="${WEATHER_UPDATE_PERIOD_DEFAULT}"
# Name of GNU grep binary if in PATH, or path to it.
export WEATHER_GREP="${WEATHER_GREP_DEFAULT}"
# Your location. Find a code that works for you:
export WEATHER_LOCATION="${WEATHER_LOCATION_DEFAULT}"

# Setting grep command
if shell_is_bsd  && [ -f /user/local/bin/grep  ]; then
  WEATHER_GREP_DEFAULT="/usr/local/bin/grep"
else
  WEATHER_GREP_DEFAULT="grep"
fi

__default_settings() {
  if [ -z "$WEATHER_DATA_PROVIDER" ]; then
    export WEATHER_DATA_PROVIDER="${WEATHER_DATA_PROVIDER_DEFAULT}"
  fi
  if [ -z "$WEATHER_UNIT" ]; then
    export WEATHER_UNIT="${WEATHER_UNIT_DEFAULT}"
  fi
  if [ -z "$WEATHER_UPDATE_PERIOD" ]; then
    export WEATHER_UPDATE_PERIOD="${WEATHER_UPDATE_PERIOD_DEFAULT}"
  fi
  if [ -z "$WEATHER_GREP" ]; then
    export WEATHER_GREP="${WEATHER_GREP_DEFAULT}"
  fi
  if [ -z "$WEATHER_LOCATION" ]; then
    echo "No weather location specified.";
    exit 8
  fi
}

# Run status line in tmux
__run_weather() {
  __default_settings
  local tmp_file="${DIR_TEMPORARY}/weather_yahoo.txt"
  local weather
  case "$WEATHER_DATA_PROVIDER" in
    "yahoo") weather=$(__yahoo_weather) ;;
    *)
      echo "Unknown weather provider [${$WEATHER_DATA_PROVIDER}]";
      return 1
  esac
  if [ -n "$weather" ]; then
    echo "$weather"
  fi
}

# Get the weather from Yahoo!
__yahoo_weather() {
  degree=""
  if [ -f "$tmp_file" ]; then
    if shell_is_bsd; then
      last_update=$(stat -f "%m" ${tmp_file})
    elif shell_is_linux || shell_is_osx; then
      last_update=$(stat -c "%Y" ${tmp_file})
    fi
    time_now=$(date +%s)

    up_to_date=$(echo "(${time_now}-${last_update}) < ${WEATHER_UPDATE_PERIOD}" | bc)
    if [ "$up_to_date" -eq 1 ]; then
      __read_tmp_file
    fi
  fi

  if [ -z "$degree" ]; then
    weather_data=$(curl --max-time 4 -s "https://query.yahooapis.com/v1/public/yql?format=xml&q=SELECT%20*%20FROM%20weather.forecast%20WHERE%20u=%27${WEATHER_UNIT}%27%20AND%20woeid%20=%20%27${WEATHER_LOCATION}%27")
    if [ "$?" -eq "0" ]; then
      error=$(echo "$weather_data" | grep "problem_cause\|DOCTYPE");
      if [ -n "$error" ]; then
        echo "error"
        exit 1
      fi

      # Assume latest grep is in PATH
      gnugrep="${WEATHER_GREP}"

      # <yweather:units temperature="F" distance="mi" pressure="in" speed="mph"/>
      unit=$(echo "$weather_data" | "$gnugrep" -Zo "<yweather:units [^<>]*/>" | sed 's/.*temperature="\([^"]*\)".*/\1/')
      condition=$(echo "$weather_data" | "$gnugrep" -Zo "<yweather:condition [^<>]*/>")
      # <yweather:condition  text="Clear"  code="31"  temp="66"  date="Mon, 01 Oct 2012 8:00 pm CST" />
      degree=$(echo "$condition" | sed 's/.*temp="\([^"]*\)".*/\1/')
      condition=$(echo "$condition" | sed 's/.*text="\([^"]*\)".*/\1/')
      # Pull the times for sunrise and sunset so we know when to change the day/night indicator
      # <yweather:astronomy sunrise="6:56 am"   sunset="6:21 pm"/>
      if shell_is_bsd; then
        date_arg='-j -f "%H:%M %p "'
      else
        date_arg='-d'
      fi
      sunrise=$(date ${date_arg}"$(echo "$weather_data" | "$gnugrep" "yweather:astronomy" | sed 's/^\(.*\)sunset.*/\1/' | sed 's/^.*sunrise="\(.*m\)".*/\1/')" +%H%M)
      sunset=$(date ${date_arg}"$(echo "$weather_data" | "$gnugrep" "yweather:astronomy" | sed 's/^.*sunset="\(.*m\)".*/\1/')" +%H%M)
    elif [ -f "${tmp_file}" ]; then
      __read_tmp_file
    fi
  fi

  if [ -n "$degree" ]; then
    if [ "$WEATHER_UNIT" == "k" ]; then
      degree=$(echo "${degree} + 273.15" | bc)
    fi
    condition_symbol=$(__get_weather_image "$condition" "$sunrise" "$sunset") 
    echo "${condition_symbol} ${degree}°$(echo "$WEATHER_UNIT" | tr '[:lower:]' '[:upper:]')" | tee "${tmp_file}"
  fi
}

# Get symbol for condition. 
# Available conditions: http://developer.yahoo.com/weather/#codes
__get_weather_image() {
  local condition=$(echo "$1" | tr '[:upper:]' '[:lower:]')
  local sunrise="$2"
  local sunset="$3"
  case "$condition" in
    "tornado" | "tropical storm" | "hurricane")
      echo "" # weather_hurricane
      ;;
    "sunny" | "fair")
      time_forecast=$(date +%H%M)
      if [ "$time_forecast" -ge "$sunset" -o "$time_forecast" -le "$sunrise" ]; then
        echo ""
      else
        echo "" # mdi_weather_sunny
      fi
      ;;
    "hot")
      time_forecast=$(date +%H%M)
      if [ "$time_forecast" -ge "$sunset" -o "$time_forecast" -le "$sunrise" ]; then
        echo ""
      else
        echo "" # weather_hot
      fi
      ;;
    "rain" | "light rain" | "drizzle" | "light drizzle")
      echo ""
      ;;
    "showers" | "scattered showers")
      echo "" # weather_showers
      ;;
    "mixed rain and snow" | "mixed rain and sleet" | "freezing drizzle" | "freezing rain" | "mixed rain and hail" | "rain and snow")
      echo "" # weather_rain_mix
      ;;
    "light rain with thunder")
      echo "" # mdi_weather_lightning
      ;;
    "snow" | "mixed snow and sleet" | "snow flurries" | "light snow showers" | "blowing snow" | "sleet" | "heavy snow" | "scattered snow showers" | "snow showers" | "light snow" | "snow grains")
      echo ""
      ;;
    "hail")
      echo "" # mdi_weather_hail
      ;;
    "cloudy" | "mostly cloudy")
      echo "" # fa_cloud
      ;;
    "partly cloudy")
      echo "" # mdi_weather_partlycloudy
      ;;
    "severe thunderstorms" | "thunderstorms" | "isolated thunderstorms" | "scattered thunderstorms" | "isolated thundershowers" | "thundershowers")
      echo "" # weather_lightning
      ;;
    "dust" | "fog" | "haze" | "smoky")
      echo "" # weather_dust
      ;;
    "fog" | "foggy" | "mist")
      echo "" # weather_fog
      ;;
    "windy" | "blustery" | "breezy")
      echo "" # mdi_weather_windy
      ;;
    "clear" | "cold")
      time_forecast=$(date +%H%M)
      if [ "$time_forecast" -ge "$sunset" -o "$time_forecast" -le "$sunrise" ]; then
        echo "" # mdi_weather_night
      else
        echo ""
      fi
      ;;
    "not available")
      echo "" 
      ;;
    *)
      echo "" # unknown
      ;;
  esac
}

__read_tmp_file() {
  if [ ! -f "$tmp_file" ]; then
    return
  fi
  cat "${tmp_file}"
  exit
}

# exec
__run_weather

特に天気情報を表示するスクリプトは更新の止まっているプラグインが多かったので,書いてみて勉強にもなりました.

おわりに

今までの一連の設定を終えるとこんな感じにCUI作業環境が整います.

これらの環境で必要な設定ファイルは全て自分のdotfilesに入っていますので良かったら使ってみてください.

github.com

今後はタスク管理やメール管理もCUIで整備したいと考えています.ちなみにIRCtwitter環境やslackのTUI版であるsclackの導入も考えましたが,うまく出来なかったので知見ある方はコメントで教えていただけると助かります!それでは良きCUIライフを!

*1:筆者はこの他にMOONGIFTさんの記事をチェックしています.

radare2 覚書

最近何かとCTFでお世話になっているradare2だが,どうもコマンドの物忘れがひどいので自分用の覚書としてここに残しておく.随時更新する.

redare2って何

OSSとして開発が進んでいるReverse Engineering Framework.時々動作に難はあるものの,対応しているアーキテクチャやファイルフォーマットの多さから重宝している*1.元々はフォレンジックツールとして開発が始まったそうだが,今ではバイナリ解析用に逆アセンブルデバッグ機能も追加され,有用な機能を持ったツール群として radare2 に統合された.操作性はvim,gdbに近く馴染みやすい.素晴らしいね!CTFで利用する人も増えてきており,最近は標的型Malwareの解析BlackHatの成果発表でも見かけるなど実用性も上がってきている模様.詳細は以下.

github.com

やりたいこと

基本的には図のように表層解析や静的解析で使えれば嬉しい.

f:id:takuzoo3868:20181105230522p:plain
マルウェア解析の概略

具体的には,

  • 表層解析

    • ファイルサイズやファイルタイプの確認
    • プログラムの保護機能の有無を確認(RELRO/SSP/Nxbit/ASLR/PIE)
    • string情報の表示
  • 静的解析

    • 特定命令からの逆アセンブル
    • メモリアドレスに格納されている値の確認やデータの参照表示
    • ブレークポイントの設置とステップ実行によるデバッグ
    • IDAのようなグラフ機能で制御文などの確認

起動と初期動作

引数に解析したいプログラムのファイル名を指定して,radare2を起動する.

$ r2 <target_file>
$ r2 -d <target_file>   # デバッグ時にはオプションにdをつける

解析を終了する場合はqコマンドでradare2を終了できる.例に使ったのはksnctfの村人A. デバッグ時のradare shell起動はこんな感じ.尚,MacOSの場合はデバッグを有効化するためにコード署名が必要

f:id:takuzoo3868:20181109085616p:plain
ほのぼのとしたデバッグモードの起動画面

表層解析

対象プログラムの基本的な情報を表示

i: Information command

checksecのような情報や,import,export,string情報はi: Information command*2を使う.radare2ではメインとなるコマンドに続いて,「何がしたいのか」をサブコマンド・オプションで指定することで1つの実行としている.例えば,バイナリの情報を調べたい場合はiIコマンド(Binary info)で表示できる.

[0x08048500]> iI
arch     x86
baddr    0x8048000
binsz    5857
bintype  elf
bits     32
canary   false
sanitiz  false
class    ELF32
crypto   false
endian   little
- 省略 -

radare2は1文字 <-> 1命令の一対一対応で,各々の文字に(基本的には)意味がある. また,iコマンドに限らず,各コマンドの末尾に?を付けることでコマンドのヘルプを参照できる. 先程述べた,import情報はii,export情報はiEなど,string情報はizなどで確認できる.

[0x08048500]> ii
[Imports]
Num  Vaddr       Bind      Type Name
   1 0x08048464    WEAK  NOTYPE __gmon_start__
   2 0x00000000    WEAK  NOTYPE _Jv_RegisterClasses
   3 0x08048474  GLOBAL    FUNC putchar
   4 0x08048484  GLOBAL    FUNC fgets
   5 0x08048494  GLOBAL    FUNC __libc_start_main
   6 0x080484a4  GLOBAL    FUNC fopen
   7 0x080484b4  GLOBAL    FUNC printf
   8 0x080484c4  GLOBAL    FUNC puts
   9 0x080484e4  GLOBAL    FUNC strcmp
  12 0x080484d4  GLOBAL    FUNC __gxx_personality_v0
   2 0x00000000    WEAK  NOTYPE _Jv_RegisterClasses
[0x08048500]> iE
[Exports]
Num Paddr      Vaddr      Bind     Type Size Name
046 0x000006e0 0x080486e0 GLOBAL   FUNC    5 __libc_csu_fini
047 0x00000500 0x08048500 GLOBAL   FUNC    0 _start
050 0x00000798 0x08048798 GLOBAL    OBJ    4 _fp_hw
051 0x0000077c 0x0804877c GLOBAL   FUNC    0 _fini
055 0x0000079c 0x0804879c GLOBAL    OBJ    4 _IO_stdin_used
056 0x00000a00 0x08049a00 GLOBAL NOTYPE    0 __data_start
058 0x000007a0 0x080487a0 GLOBAL    OBJ    0 __dso_handle
059 0x000008e4 0x080498e4 GLOBAL    OBJ    0 __DTOR_END__
060 0x000006f0 0x080486f0 GLOBAL   FUNC   90 __libc_csu_init
062 ---------- 0x08049a04 GLOBAL NOTYPE    0 __bss_start
063 ---------- 0x08049a04 GLOBAL    OBJ    4 stdin@@GLIBC_2.0
064 ---------- 0x08049a10 GLOBAL NOTYPE    0 _end
066 ---------- 0x08049a04 GLOBAL NOTYPE    0 _edata
069 0x0000074a 0x0804874a GLOBAL   FUNC    0 __i686.get_pc_thunk.bx
070 0x000005b4 0x080485b4 GLOBAL   FUNC  298 main
071 0x00000424 0x08048424 GLOBAL   FUNC    0 _init
[0x08048500]> iz
[Strings]
Num Paddr      Vaddr      Len Size Section  Type  String
000 0x000007a4 0x080487a4  17  18 (.rodata) ascii What's your name?
001 0x000007b6 0x080487b6   4   5 (.rodata) ascii Hi, 
002 0x000007bb 0x080487bb  21  22 (.rodata) ascii Do you want the flag?
003 0x000007d5 0x080487d5  16  17 (.rodata) ascii I see. Good bye.
004 0x000007e8 0x080487e8   8   9 (.rodata) ascii flag.txt

静的解析

コード解析のための初歩

a: Analyze command

radare2ではIDAと違い,関数解析や相互参照など高度な解析を自動で行ってくれないで,a: Analyze commandを実行する必要がある. 少しづつ改善されているが,難読化されたプログラムなどに対して高度な解析を自動化してしまうと,処理に相当な時間がかかるためこのような仕様になっている(?). どうしても自動解析したい場合はradare2起動時に引数として-Aあるいは-AAを追加すればよい.

[0x08048500]> aaaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
[x] Type matching analysis for all functions (afta)
[x] Emulate code to find computed references (aae)
[x] Analyze consecutive function (aat)
[0x08048500]> aaaaaaa
An r2 developer is coming to your place to manually analyze this program. Please wait for it

--press any key--

解析の程度はa,aa(analyze all),aaa,aaaaとオプションを増やすことで対象を広げている.しかし,iIコマンドの結果にstripped trueなどがある場合は,時間がかかるため最低限のaaコマンドが良い.この辺は表層解析の結果をみて臨機応変に使い分けすればOK.上の例ではaa,aac,aar,aan,afta,aae,aatまで実行している.それぞれの内容はヘルプを参照しておくれ.ちなみにaaaaaaa以上はジョークコマンドである.

この他にradare shell上でよく使うaコマンドには以下の通り.

コマンド 具体例 具体例の機能
ax: analyze ref/Xref axt コードやデータの参照先を表示
af: analyze Functions afl 自動解析(af,aa,aaaなど)で取得した関数のリストを表示

カレントアドレスの移動と逆アセンブル

s: Seek command

自動解析で得られた関数や,特定のアドレスへカレントアドレスを移動する場合にはs: Seek commandを使う.カレントアドレスはradare shellのプロンプトに表示されている.カレントアドレスを移動せずに特定のコマンドを実行したい場合は<cmd> @ <address>とすれば良い.

[0x08048500]> afl
0x08048424    3 48           sym._init
0x08048464    1 6            loc.imp.__gmon_start
0x08048474    1 6            sym.imp.putchar
0x08048484    1 6            sym.imp.fgets
0x08048494    1 6            sym.imp.__libc_start_main
0x080484a4    1 6            sym.imp.fopen
0x080484b4    1 6            sym.imp.printf
0x080484c4    1 6            sym.imp.puts
0x080484d4    1 6            sym.imp.__gxx_personality_v0
0x080484e4    1 6            sym.imp.strcmp
0x08048500    1 33           entry0
0x08048530    6 86           sym.__do_global_dtors_aux
0x08048590    4 36           sym.frame_dummy
0x080485b4    8 298          sym.main
0x080486e0    1 5            sym.__libc_csu_fini
0x080486f0    4 90           sym.__libc_csu_init
0x0804874a    1 4            sym.__i686.get_pc_thunk.bx
0x08048750    4 43           sym.__do_global_ctors_aux
0x0804877c    1 28           sym._fini
[0x08048500]> s sym.main
[0x080485b4]> 

上に例は,自動解析でわかったmain関数(0x080485b4 8 298 sym.main)へカレントアドレス([0x08048500]>)の移動(s sym.main)を行っている([0x080485b4]>).

pd: print disassemble command

アセンブルについてはpdコマンドを用いる.デフォルトは数十命令ぶんを逆アセンブルしてくれる.引数で命令数の指定もできる.pdfコマンドは関数全体を逆アセンブルしてshellに表示してくれる.先程main関数へ移動したので実行してみるとこんな感じ*3.一部のスクショ.

f:id:takuzoo3868:20181109205109p:plain
main関数を逆アセンブルした画面

f:id:takuzoo3868:20181109205122p:plain
左側でgotoの宛先が可視化されていたり,コメント内容も色々変更できるらしい

また,disassembleより高級なdecompileを使用したい場合は,radare2のパッケージマネージャであるr2pmを使ってr2decをインストールできる.このデコンパイラは多数のアーキテクチャに対応しており,IDA買えず人権低めの自分は重宝している*4.標準でもpdcコマンドでclangっぽくデコンパイル可能だが,このプラグインを導入したほうが可読性も高めゆえおすすめ.自分はradare2rcにe cmd.pdc = pdd存在を無かったことにしている.r2decを導入するとpddコマンドでデコンパイル結果がshell上に表示される.

f:id:takuzoo3868:20181109230626p:plain
pdcコマンドによる何かそれっぽいデコンパイル
f:id:takuzoo3868:20181109230641p:plain
r2decによる見やすいデコンパイル

グラフ機能などを備えたヴィジュアルモード

V: Visual mode command

IDAのようなグラフで見たい場合やTUIとして扱いたい場合はV: Visual mode commandを使う.操作の雰囲気はvimに近く,hjklで画面を移動したり,qキーでradare shellに戻ったり,:キーで一時的にradare shellが使えるなどなど.

  • Vコマンド
    この表示方法で起動すると,Hexdump, disassemble, debugger, word-hexidecimal, etc...と様々な表示ができる.それぞれコマンドにより描画できる表示だがヴィジュアルモードを使うと切替が便利.pまたはPキーで表示切替,数字キーで対応するcall/jumpへ移動,oで指定のオフセットへ移動,xキーでaxtコマンドのようにxref表示,vキーで関数/変数の解析用UI表示,uキーで操作した数字またはoキーからUndoする.

    f:id:takuzoo3868:20181110001308p:plain
    Visual modeでHexdumpの表示
    f:id:takuzoo3868:20181110001738p:plain
    Visual modeでDubuggerの表示
    f:id:takuzoo3868:20181110002825p:plain
    Visual modeで関数/変数の解析用UI表示

  • VVコマンド
    こちらはIDAのようなグラフビュー関連の表示ができる.またVコマンドからはSpaceキーによってこちらへ切替も可能.制御文による遷移の確認に役立つ.基本的な操作は上のモードと同じ.shift+dキーでアセンブリも同時に表示が可能.

    f:id:takuzoo3868:20181110013056p:plain
    Visual modeでグラフの表示
    f:id:takuzoo3868:20181110013104p:plain
    Visual modeでミニグラフの表示

  • V!コマンド
    radareでTUIのような操作ができる.それぞれのパネルへの移動はtabキーやw-->hjkl,メニューの利用はmキーを使う.パネルの分割やその他の操作方法は同様に?で確認できる.

    f:id:takuzoo3868:20181110020600p:plain
    Visual modeでTUIの表示

デバッグ

d: debug command

デバッグモードで起動した際には,デバッグが可能となる(それはそう).ブレークポイント置いて,continueして,ステップ実行...とかプログラムの挙動調査で使うやつ.その際はd: debug commandを使う.また具体例として,プログラムがパッキングされている場合*5などは,OEP(Original Entry Point)を探し出しメモリ上に展開されたソースを解析したりすることになる*6*7.そんなときにデバッグは有効.使えそうなコマンドを表にまとめておく.

  • 実行フロー関連
コマンド 機能
dc Breakpointの設置箇所までプログラムの実行(gdbのcontinue)
dcu <address> 指定したアドレスまで実行.e.g. dcu sym.main
ds ステップ実行(gdbのsi)
dcr 関数がstack frame returnになる,つまり抜けるまで実行
dr <register> レジストリ内部の格納情報を表示
  • Breakpoint関連
コマンド 機能
db <address> 指定したアドレスにBreakpointを設置
dbc <address> <r2_cmd> BP設置かつ実行されたときに使うr2コマンドの登録
drx <num> <addr> <len> <rwx> 読み/書き/実行で指定アドレス範囲にアクセスしたときHBP*8設置
db- <address> BPの削除
drx- <address> HBPの削除
  • メモリマップ関連
コマンド 機能
dm プロセスのメモリマップを表示(gdbのvmmap)
dmi <address | libname>
読み込みのあったDLL symbolのリストを表示
  • メモリダンプ関連
コマンド 機能
wtf <filename> <size> @<starting_address>
指定アドレスから指定サイズ分の情報をダンプ
dmd <filename> @<address>
メモリマップの情報をダンプ

そんなunpackingとかしない場合は,VppコマンドでVisual debugger modeを使ったほうがサッとできる. F2でtoggle breakpoint,F4でrun to cursor,F7でsingle step,F8でstep over,F9でcontinueが使える.大変便利.

おわりに

とりあえず自分がよく使うコマンドを中心にまとめた.記事を書く過程で知らなかった機能や使い方の勉強にもなった.CTFやcrackme,降ってきた検体で精進する. radare2は自前のカンファレンスも開催しており各年のリポジトリに豊富な資料が残っている.radare2 bookの冊子体やswagを配布しているそうなので一度は行ってみたい...

github.com

github.com

*1:x86にも対応しており無料で使えるので有り難い

*2:rabin2でも同様の機能.

*3:radare2は265のカラーテーマが使える.自分は机に植物がないのでlimaを使っている.ecoコマンドで色々と変更できる.

*4:この他に自分がよく使うデコンパイラはavast社のretdec

*5:https://www.packerinspector.com/oakland-2015-dpi.pdf

*6:radare2に自動のunpackerパッケージがあるのかはわからない.あればとても便利だね.

*7:radare2でのunpack関連はここが大変よくまとまってる.

*8:Hardware Break Point