hiziriAI’s blog

自分用雑記

CBCTF2017の供養

参加したけど1問しか解けてない。 writeup書いても仕方ないような問題(Common modulus1)なので他の人に任せよう。 なので解くのに使った自分のスクリプト紹介をする。 lispで書いているのでroswellとemacsとslimeがあれば基本動く。 sbclしか想定していないけど、それ以外でも動くかもしれない。

ここにある。

基本的には暗号系の問題を解くのに使っている。 lispで書いていて、いくつかはquicklispのlocal-projectsに入れるなりリンク張るなりすれば簡単にロードできるようになる。 今回はrsaの中のhackrsaを使った。 いまのところ、common modulus attackとwiener attackはまともに動くはず。 今回の問題では計算に時間がかかり過ぎて解けなかったので、改善してあるのでそこそこ使えるはずだと思う。

基本的にはあちこちにpythonスクリプトがあるのでわざわざlispを憶えてまでこれを使う必要はない気がするが...

利点としては、組み込みで分数があり多倍長整数を扱えるため整数の演算では精度は落ちないこと、REPLでの評価がインデント崩れると死ぬpythonよりやりやすい気がすること、sbclならデフォルトでGMP、MPFRを使えたこと(終わってから気付いた)だろうか。 あと、コンパイルしたり型を指定してチェックしないようにしたりもできるので、pythonより高速に動かせる(とはいえ、速度が必要な場面でpythonは使わない気もするが)。

ここで紹介したし、もう少し使いやすくしようかな。READMEとか書いてテストも書いて。

Dockerまとめ

環境はCentOS7のはず。

インストール方法

curl -fsSL get.docker.com -o get-docker.sh
sh ./get-docker.sh

あるいは単にcurl -fsSL get.docker.com | shでもいいかもしれない。

Dockerの起動

systemctl start docker

Dockerの状態を確認するにはsystemctl status docker

サーバ起動時にDockerも起動する場合は systemctl enable docker

使い方

基本的にはgitやlinuxのコマンドを踏襲した名前のサブコマンドを使って管理する。 例えば、imageの一覧はdocker image lsのような感じになる。

コンテナの動かし方

イメージとコンテナがある。 イメージはコンテナの設計図のようなもので、何をインストールしてどのコマンドを実行するべきかなどの情報を持っているらしい。 一方コンテナは実際に稼動しているサービスのファイルシステムのようである。 イメージに従いコンテナを作りあげ、Dockerはそのコンテナの中でサービスを動かすといったような感じだろうか? 実際のコマンドは以下のような感じになる。

docker container run hello-world

hello-worldというのはリポジトリにある、Helloと表示するだけのDockerイメージである。 これを使ってDockerが使えるかどうか試せる。 他のイメージでもだいたい同じようにして動かせる。

Dockerではコンテナの実行が終了するとそのコンテナを破棄するらしい(実際はしばらくの間/var以下にあるっぽい)。 なので、データを保存したり次の実行時に使う場合には-vを使って共有する領域に書き出すようにするなどの工夫が必要になる。

イメージのダウンロード

どこかが用意したリポジトリから既に設定されたイメージを使うことができる。 基本的に動かそうとしたときにローカルにないなら自動的にダウンロードされることになる。

Dockerイメージの作り方

  • docker container commit
  • コンテナから作る方法、知らない
  • docker image build
  • Dockerfileから作る方法、教わった

Dockerfileはこんな感じになった。 FROMには元となるイメージ、このイメージの上に新しい皮を被せていく感じで構成していくことになる。 RUNはコンテナを構成するときに実行するコマンド、ソフトウェアのインストールとか。 CMDはコンテナとして実行されたときにデフォルトで実行するコマンド、hello-worldと自動で表示されたのはこれによるものだと思われる。

FROM base image
RUN running-command(like apt install something)
CMD defalut-running-command

ビルドするには以下のようにする。

docker image bulid -t image-name path"

dockerhubへのログイン

githubのようなサービスとして、イメージを共有できるdockerhubがある。 ログインしておくとイメージのアップロード、ダウンロードができるようになる。 ログインからアップロードまでの流れ。

docker login
docker image tag image-name:tag <user-name>/image-name:latest
docker image push <user-name>/image-name
docker logout

まとめ

後でもっと詳しく書く。 ほかにもいくつかのイメージをまとめたものをまとめて扱うといったようなこともできる。

Dockerまとめ

環境はCentOS7のはず。

インストール方法

# curl -fsSL get.docker.com -o get-docker.sh
# sh ./get-docker.sh

あるいは単にcurl -fsSL get.docker.com | shでもいいかもしれない。

Dockerの起動

# systemctl start docker

Dockerの状態を確認するには# systemctl status docker

サーバ起動時にDockerも起動する場合は systemctl enable docker

使い方

基本的にはgitやlinuxのコマンドを踏襲した名前のサブコマンドを使って管理する。 例えば、imageの一覧はdocker image lsのような感じになる。

コンテナの動かし方

イメージとコンテナがある。 イメージはコンテナの設計図のようなもので、何をインストールしてどのコマンドを実行するべきかなどの情報を持っているらしい。 一方コンテナは実際に稼動しているサービスのファイルシステムのようである。 イメージに従いコンテナを作りあげ、Dockerはそのコンテナの中でサービスを動かすといったような感じだろうか? 実際のコマンドは以下のような感じになる。

# docker container run hello-world

hello-worldというのはリポジトリにある、Helloと表示するだけのDockerイメージである。 これを使ってDockerが使えるかどうか試せる。 他のイメージでもだいたい同じようにして動かせる。

Dockerではコンテナの実行が終了するとそのコンテナを破棄するらしい(実際はしばらくの間/var以下にあるっぽい)。 なので、データを保存したり次の実行時に使う場合には-vを使って共有する領域に書き出すようにするなどの工夫が必要になる。

イメージのダウンロード

どこかが用意したリポジトリから既に設定されたイメージを使うことができる。 基本的に動かそうとしたときにローカルにないなら自動的にダウンロードされることになる。

Dockerイメージの作り方

+ docker container commit

  • docker image build
FROM base image
RUN running-command(like apt install something)
CMD defalut-running-command
docker image bulid -t image-name path"

dockerhub login docker login docker image tag image-name:tag <user-name>/mywhale:latest make space for container docker image push <user-name>/mywhale docker logout

nginx docker image pull nginx:alpine docker contianer run -d nginx:alpine -d is detach-mode

docker container run -d -P nginx:alpine -P is port mapping docker container run -d -p 8080:80 nginx:alpine host 8080 -> docker 80

docker network inspect bridge | less

network setting docker network create --driver bridge --subnet 192.168.10.0/24 --gateway 192.168.10.1 mynet

run docker run -it alpine /bin/sh -i accept stdin -t pseudo-tty --net network-name

make volume docker volume create myvolune docker run -it -v myvolume:/data alpine /bin/sh -v volume, this can mount local-directory

swarm

ps auxfw [0] tkgsy@tkgsy-HP-Pr0B00k ~/m/s/docker ➞

Othlotechさん主催のDocker勉強会に参加した

参加することに決めたきっかけ

そもそも去年あたりに一度勧誘を受けていてOthlotechの存在を知っていたし参加したいとも思っていたけど、なかなか興味のあるテーマがなかったり院試があったりして伸び伸びになっていた。 院試が終わりFEも合格したので、余裕ができたので色々なことをやってみたくなった。 なので何かしらに参加しようと思っていたところふとこの勉強会が目に留まり、Docker勉強するいい機会だと思って参加を決めた。 最初のうちは人数が一杯だったのでキャンセル待ちだったが、台風のおかげかキャンセルが出て参加することができた。

参加してみて

見事に遅刻した、まあ滅多に出歩かないのでしかたない。 内容自体はとても面白かったし勉強になった、参加して良かった。 講義はDockerがどのようなものでどのような使い方をされているのか、どのようにして実現されているのかといった基本的な背景の解説から始まった。 そして休憩を挟んでハンズオンが始まった、実際にさくらインターネットクラウドサービスを借りてそこでいちからDockerの環境を作った。 そしてDockerhubにあるイメージを利用して、Dockerを使ってアプリケーションを稼動させた。 Dockerが生まれることになった背景から、実際にどれほどの効果があったのかを実際にやってみて体験することで、その有効性を体感できた。 やっぱり新しい技術なんかを勉強するときは何故それが必要なのかを理解していると、すんなりと解説を飲み込むことができる。

感想

勉強会というものに始めて参加したけど、やっぱりひとりで勉強するより知ってる人に教わる方が確実に素早く学べるし、積極的に参加した方がいいと思った。 できれば同じことをやれる友達が欲しいんだけど、できなかった、残念。

libnet

libnet

RDNSS付きのルータ広告を投げる必要があったのでlibnetについて調べてみたが情報がなかったため残しておく。

やり方

初期化

libnetにはIPv6に対応したパケットを作成する機能があるため、普通のパケットならば問題なく作成できる。 今回は近隣探索のパケットを投げるため、ICMPv6のパケットの作成もしている。 libnetを使うときは、最初にinit関数を呼ぶ必要がある。 この返り値に作成するパケットの情報が保持されているようなので最後まで引き回すことになる。

// interfaceはパケットを投げる先のインターフェースを指定する。 exp. "enp4s0"
libnet_t* l = libnet_init(LIBNET_RAW6, interface, errbuf);

パケット作成

buildと付いた関数がたくさんあるので、その中からそれっぽいのを探してそれを使う。 ICMPv6などのパケットならば、連続してbuild関数を呼び出すとそれっぽく処理してくれる。 今回はICMPv6, IPv6のパケットのbuild関数を呼び出している。 checksumなどの値も自動で計算されるので楽。もちろん間違った値をわざと入れることもできる。

今回使おうとしたRDNSSはうまくオプションを設定できなかったため、直接値を入れるようにしている。 多分もっと普通の方法があると思うけど、なぜか近隣探索のオプション設定がうまくできなかったので力技でやっている。

追記 libnet_ptag_tでそれぞれのプロトコルのブロックを管理しているらしいので試してみる。

送信

最後のlibnet_writeで送信している。 送信先アドレスや送信元アドレスによって失敗することもある。 あまり適当すぎるアドレスだと不正になってしまうようだ。

code

#include <stdio.h>
#include <libnet.h>

#define ND_RA_MANAGED_CONFIG_FLAG 0x0800000
#define ND_RA_OTHER_CONFIG_FLAG   0x0400000
#define ND_RA_HOP_LIMIT           0x1000000
#define ND_OPT_RDNSS              0x19
#define LIFETIME_INF              0xffffffff

typedef struct libnet_in6_addr libnet_in6_addr;

void build_icmpv6_rdnss_opt(libnet_t* l,
                libnet_in6_addr *header,
                uint8_t *payload,
                uint32_t lifetime,
                const char* dns_addr);

int main(int argc, char** argv){
  if (argc != 5) {
    fprintf(stderr, "%s <interface> <src addr> <dist addr> <dns addr>\n", argv[0]);
    exit(1);
  }

  // set argv
  char *interface = argv[1];
  char *dist_addr = argv[2];
  char *src_addr = argv[3];
  char *dns_addr = argv[4];
  
  libnet_t *l;
  libnet_in6_addr sip, dip, trg;
  char errbuf[LIBNET_ERRBUF_SIZE];

  /***************************************************************
    initialize libnet, this must be called before other functionsn
   ***************************************************************/
  l = libnet_init(LIBNET_RAW6, interface, errbuf);
  if(l == NULL) {
    printf("libnet_init: %s\n", errbuf);
    exit(1);
  }

  // get ipv6-addr struct
  sip = libnet_name2addr6(l, src_addr, LIBNET_DONT_RESOLVE);
  dip = libnet_name2addr6(l, dist_addr, LIBNET_DONT_RESOLVE);

  /********************************* 
   *   build router advertisement  *
   *********************************/  
  uint32_t lt = LIFETIME_INF;
  uint8_t payload[16];
  build_icmpv6_rdnss_opt(l, &trg, payload, lt, dns_addr);
  
  libnet_build_icmpv6_ndp_nadv(
                   ND_ROUTER_ADVERT,                               // uint8_t type
                   0,                                              // uint8_t code
                   0,                                              // uint16_t check_sum
                   64 * ND_RA_HOP_LIMIT + ND_RA_OTHER_CONFIG_FLAG, // uint32_t flags
                   trg,                                            // libnet_in6_addr target
                   payload,                                        // uint8_t* payload
                   16,                                             // uint32_t payload size
                   l,                                              // libnet_t* context
                   0                                               // libnet_ptag_t ptag, 0 means create new one
                   );
  
  // build ipv6 packet
  libnet_build_ipv6(
            0,                                        // uint8_t traffic class
            0,                                        // uint32_t flow label
            LIBNET_IPV6_H + LIBNET_ICMPV6_NDP_NADV_H, //uint16_t len
            IPPROTO_ICMP6,                            //uint8_t nh -> next header
            64,                                       //uint8_t hl -> hop limit
            sip,                                      //libnet_in6_addr src
            dip,                                      //libnet_in6_addr dst
            NULL,                                     //uint8_t* payload
            0,                                        //uint32_t payload_s
            l,                                        //libnet_t* l
            0                                         //libnet_ptag_t ptag
            );
    
  if(libnet_write(l) == -1) {
    printf("libnet_write: %s\n", libnet_geterror(l));
    exit(1);
  }

  libnet_destroy(l);
 
  return 0;
}

/**
 * set config value to header and payload.
 * @param l libnet context
 * @param header header of RDNSS, set some value into this
 * @param payload dns address is set here
 * @param lifetime lifetime of dns server
 * @param dns_addr address of dns server, like "2001:db8::1"
 */
void build_icmpv6_rdnss_opt(libnet_t* l,
                libnet_in6_addr *header,
                uint8_t *payload,
                uint32_t lifetime,
                const char* dns_addr){
  // copy address, builder funciton accepts only uint8_t*
  for (int i = 0; i < 16; i++)
    payload[i] = libnet_name2addr6(l, dns_addr, LIBNET_DONT_RESOLVE).__u6_addr.__u6_addr8[i];
  
  header->__u6_addr.__u6_addr8[8] = 0x19; // type num RDNSS
  header->__u6_addr.__u6_addr8[9] = 0x2 + 0x1; // 0x2 + number_of_dns_addr
  header->__u6_addr.__u6_addr32[3] = lifetime;
  
  return;
}

MacBookリカバリー

MacBookが壊れて起動しなくなったのでリカバリーの手順をメモっておく。

症状

起動するとログインアカウントが表示されず、?マークのあるフォルダのアイコンが表示されていた。 正確に言うとデュアルブートできるようにしていたので、linuxの方しか起動しなかった。 パーティション削除したらフォルダアイコンでたので多分同じ原因だと思う。

修復方法

まず一旦電源を落とし、再起動する。 起動したタイミングでCmd+Rを押し、地球のマークがでてくるまで待機する。 するとリカバリメニューに入るためにネットワーク接続が必要になるので、無線LANの設定をするか有線を繋ぐかしてオンラインにする。 5分くらい待つ、多分回線速度次第で速くなったり遅くなったりすると思う。

するとリカバリメニュー一覧みたいなのが表示されるので、ディスクの修復メニューに入る。 自分の場合はパーティションの設定をミスってディスクがおかしくなっていたらしいので、ディスクをまるごとフォーマットした。 ここからバックアップも取れるっぽいので、必要なら取っておく。 タイムマシンにデータがあったので今回はフォーマットだけにした。

フォーマットなり修復なりが終了したら、ディスクメニューを終了し、タイムマシンからの復元かOSの再インストールを選択する。 起動しない場合は多分入れなおさないと直らないんじゃないかと思う。バックアップもあることだし再インストールした。

これで直る(かもしれない)。

pwntools 使い方

pwntools 使い方

忘れないようにメモする。 公式のDocsとか、関数のdescriptionが優秀なのでそっちを読んだ方が正確だと思う。 でも日本語じゃないと読むのに時間がかかってしまうので日本語でメモする。

基本

基本的な機能の使い方。 プログラムへの入出力。

from pwn import *

# プログラムを実行するprocessを作る
# cwdキーワードで現在のワーキングディレクトリが変更できる
p = process('test_program')
# p = remote('127.0.0.1', 12345) # 127.0.0.1の12345ポートに接続する、APIが同じなのでそっくりそのまま同じように動作する

ret = p.recv() # test_programの出力をEOFまでを受けとる
ret = p.recvline() # 改行までを受けとる、改行が送られないとここで止まるので注意
ret = p.recvline(timeout=0.01) # recv系はtimeoutを設定できる、単位は秒。ハングするのが嫌なら設定しておくといい
ret = p.recvuntil('some output') # 引数の文字列までを受けとる。

# ログとして出力する。context.log_levelに値を設定することで、debug, infoなど出力するログも操作できるので便利
log.info(ret) 

payload = ''
payload += p32(64) # 数値をリトルエンディアンで32ビット長の文字列として変換する。
# これなら'@\x00\x00\x00'になる。
# ほかにもp64なら64ビット長に変換する。
# 逆変換はu32()、符号無し整数としてデコードしてくれる。
payload += 'some shellcodes'

# test_programにshellcodeを送る。文字列ならなんでも。
p.send(shellcode) # 末尾になにもなし、多分EOFが付く
p.sendline(shellcode) # これなら末尾に改行が付く

# 実行すると入出力を直接表示、送信するようになる。
# 相手側でシェルを奪ったときに起動する
p.interactive()

基本的にはこれらを使う。他にも便利機能がたくさんあるけど、これだけでもpwnはできると思う。 人によっては他のコマンドの方を好むかもしれない。

実際の流れとしては、processでプログラムをローカルで起動させる。 場合によってはローカル環境でもremoteを使う。server型とか。
send系で情報をリークさせるかしてrecv系で情報を取得し、payloadを構築していく。 最後にシェルが取れるような場合にはinteractiveで直接シェルとやりとりする。

elf

人によってはIDAとかobjdumpとかradare2を使ってすませることもあるが、pwntoolsからでもelfの解析ができる。

from pwn import *

elf = ELF('program') # 解析、ログにセキュリティ機構などの解析結果も表示される。
# 多分pwntoolsに付いてくるchecksecと同じ出力

elf.plt['printf'] # plt領域にある関数のアドレスを辞書型で保持してる。
# ハードコーディングせずにすむのでこっちの方が好き。
# ただし間違ってることがあるっぽい?理解が足りなくて勘違いしてるだけかもしれない
# 心配ならobjdumpとかreadelfとかでも調べよう

elf.got['printf'] # 同じようにgot領域も調べられる

他にも色々な情報が参照できるので最初に解析用のスクリプト作っておくのもいいかもしれない。 dir()でメンバの名前見ればだいたい何の情報か想像も付くので忘れても安心。

context

contextはconfigみたいな感じに解析のための情報を渡すことができる。

from pwn import *

context.arch = 'amd64' # 解析するプログラムのアーキテクチャを設定できる、初期値は多分i386
context.log_level = 'debug' # ログのレベルを設定できる。普段は多分infoぐらいが表示される
# pwntoolsのバグっぽいのに遭遇したらdebugにして見てみよう

ちょっと変わったバイナリを解析するときは、一度こっちの設定を確認して、適切かどうか見るといい。

shellcode

shellstormでいいけど、pwntoolsにも簡単なものはあるので楽になるかもしれない。 自分で書くという選択肢はない。

from pwn import *

asm(shellcraft.sh()) # shellcraft.sh()はシェルを開くアセンブリコードを返す、asm()はそれをアセンブルする

asmを使えば自作shellcodeを使うこともできるので、shellcodeを書く練習にももってこい。 shellcraftのサブモジュールにはアーキテクチャやOSに対応したモジュールがあり、様々シェルコードが収録されているみたいなので一度探してみると役に立つかもしれない。

debug

自分の環境では動かなかったが、gdbデバッグもできるらしい。

from pwn import *

context.terminal = ['terminator', '-e']
p = process('program')
gdb.attach(p)
gdb.debug('program')

追記 どうも元のプログラムでプロセスIDを直接引数としてgdbに渡してるのが原因っぽい。 gdbはプロセスにアタッチするときは-pオプションに渡さないとデバッグできない。 そのことでIssueも立ってたし、このこと聞いてみたけど問題として認識してないっぽいのでどうしよう?

おわり

使ったことのある機能はこれぐらい。 他にも色々便利そうな、書式文字列攻撃用のモジュールだったりflag管理用のモジュールだったりがあるので、公式を見ておくとワクワクできる。 使い方は分かりません。使われてない機能だとバグっぽいのもしばしばあったりするけれど、そういうときは他のコマンドで補おう。