fr33f0r4ll

自分用雑記

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の使い方

tags: ctf pwn pwntools howtouse

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

基本

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

from pwn import *

# プログラムを実行するprocessを作る
# cwdキーワードで現在のワーキングディレクトリが変更できる
p = process('test_program')
# p = remote('127.0.0.1', 12345) # 127.0.0.1の12345ポートに接続する、processと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ビット長の文字列として変換する。
# 64なら'@\x00\x00\x00'になる。
# ほかにもp64という関数があり、こっちは64ビット長として変換する。
# 逆変換はu32()、符号無し整数としてデコードしてくれる。
payload += 'some shellcodes'

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

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

基本的にはrecvsend系を使っていく。他にも便利機能がたくさんあるけど、これだけでもpwnを始められる。

実際のpwnの流れとしては、processでプログラムを実行して解析しながらsendrecvで送受信を行なって、シェルが取れたらinteractiveで直接操作する、というような感じになる

場合によってはローカル環境でプログラムを実行できるときもremoteを使うことがある、プログラムが直接ポートにバインドされてフォークするようないわゆるfork型のときとか。

ELFの解析

elfファイルの解析はIDAとかreadelfとかradare2でやったりするが、なんとpwntoolsからでもある程度解析ができる。 ちょっと関数のアドレスとかpltやgotのアドレスを使いたいときにはハードコーディングしなくて済むので、後から確認するとき"あれ、これなんだっけ?"とならずに済む。 ROP関連の機能にも使える。

from pwn import *

elf = ELF('program') # 解析、ログにセキュリティ機構などの解析結果も表示される。
# 多分pwntoolsに付いてくるchecksecと同じ出力
# libcなどの共有ライブラリも解析できて、その場合はオフセットが分かる
# ローカルとリモートでglibcが違うときとかに読み込むライブラリを変更すると便利

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

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

elf.symbols['local_variable'] # stripされてなかったりするとシンボルのアドレスも参照できる

elf.bss() # セクションのアドレスなんかもある

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

設定

contextは解析のための情報を渡すことができる、ようはconfig。

from pwn import *

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

context(arch='amd64') # 関数から設定することもできる

ちょっと変わったバイナリを解析するときは、一度こっちの設定を確認して、適切かどうか見るといいかもしれない。 シェルコードの生成とか関数の挙動に影響するので、間違った設定になっていると思った通りにエクスプロイトが動かなかったりする。 お行儀良く最初に設定しておこう。

shellcode

shellstormでいいけど、pwntoolsにも簡単なものはあるので多少は楽にエクスプロイトが書けるかも。 スクリプト中でアセンブルもできる。書けるかどうかはともかく。

from pwn import *

asm(shellcraft.sh()) # shellcraft.sh()はシェルを開くアセンブリコードを返す、asm()はそれをアセンブルする
# shellcraftの返り値は単なるアセンブリコードなので、シェルコードの勉強に持ってこいだったりする
# デフォルトが使えなかったときは攻撃対象に合わせて書き換えてみよう

# アセンブリコードを文字列として渡すとアセンブルしてくれるので、必要に応じて書くこともできる
asm('''
xor ebx, ebx
lea eax, [ebx + 4]
mov ecx, esp
lea edx, [ebx + 0xff]
int 0x80
''')

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

書式文字列攻撃

書式文字列攻撃のための機能もある。 攻撃を自動でやるようにする使い方もできるらしいが、関数のセッティングが面倒そうだったので、ここではペイロードを作成する機能の紹介だけにする。 基本的には書き込みたい値と書き込み先アドレスを指定すると、書式文字列攻撃で書き込みをするペイロードを返すといった感じ。

from pwn import *

p = process('target_program')

trg = ELF('target_program')
trg_addr = trg.symbols['target_symbol']

# trg_addrに0x12345678と書き込む
writes = {trg_addr : 0x12345678}
offset = 11 # printfの引数があるオフセット、書式文字列攻撃ができるなら多分オフセットのリークもできる
payload = fmtstr_payload(offset, writes, numbwritten=0)

p.send(payload)

offsetには書式文字列バグのある箇所で、入力した文字列が出現する位置を指定する。 適当に%xとか入力して調べる必要がある。

numbwrittenは既に書き込んだ文字数を指定する、デフォルトで0なのでこの例では本当はいらない。 書式文字列攻撃はprintfのこれまで出力したバイト数を変数へ書き込むパラメータを利用して任意のアドレスへの書き込みをするので、これまでの出力数が正しくないとペイロードを組み立てることができない。

writes{書き込みたいアドレス : 書き込みたい値}という形式で書き込んでいく値などを指定する。

fmtstr_payloadにそれらを渡すとペイロードを作成して返すようになっている。

ROP

ropチェインを組み立てることができる機能がある。 elfの解析結果を使えば自動的に呼び出しのフレームを作成したりできるので便利。

from pwn import *

elf = ELF('test_program')
rop = ROP(elf)

# 直接値をスタックに積む
rop.raw(0)

# 関数などの呼び出しをする、シンボルがないと無理?
# 引数も渡せる
rop.call('read', [0, 4, 10])

# 横着なwrite call
rop.write(1, 4, 10)

# ropガジェットを勝手に探してきてスタックフレームの調節をしてくれるらしいので気にせず呼び出せる

# 本当にちゃんと出来てるか知りたいときはダンプすることもできる
# シンボルなどがあるときはその内容まで表示してくれるので分かりやすい
print(rop.dump())

# ropガジェットの検索もできる
# 返り値はGadgetクラスで直接rop.raw()に渡したりできる、むしろ直接渡した方が情報が表示されるので良い
rop.find_gadget(['pop eax', 'ret']) # pop eax; ret

# ガジェット一覧
rop.gadgets()

# ropチェインの取得、文字列として
rop.chain()
str(rop)

callがとても便利そう。ガジェットの検索はrp++とかの専用ツールほどフレキシブルな検索はやってくれないっぽいのでそういうのはやっぱり自分で探す必要がある(残念)。

デバッガ

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

from pwn import *

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

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

定数値

エクスプロイトを書くときや解析しているときは様々な定数値(システムコール番号とかマクロとか)がしょっちゅうでてくる。 でも全部憶えるのはまず無理なのでその都度調べるのだけど、pwntoolsにそのような定数値を調べられる機能がある。 一々ブラウザやman開いてスクリプトにメモするのも面倒なので積極的に使いたい機能の1つ。

from pwn import *

# 名前で調べる、`execve`のシステムコール番号が返る
constants.eval('SYS_execve')

# 直接定数値をロード
constants.SYS_execve

# 他にもシグナル番号などもある
constants.SIGKILL

# どんな定数値があるかの一覧
help(constants)

定数値はしょっちゅう忘れるので地味に便利。 スクリプト中でマジックナンバーになって意味が分からなくなったりしないし。 contextの設定に従ってアーキテクチャに対して適切な値を返すので、設定してから使わないと変な値になったりする。

その他

pwntoolsはライブラリとしてだけではなく、コマンドラインから使うこともできる。 pwn --helpで使い方が表示される。 checksecとかちょっとしたアセンブラとか便利な機能も多いので一度覗いてみるといいかもしれない。 定数値検索のできるconstgrepがとても便利。

おわり

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

X64 pwn

x64でのpwn

Cでの話、goとかだと違う。 基本はx86と同じだが、いくつかの点で違いがある。
https://blog.techorganic.com/2015/04/10/64-bit-linux-stack-smashing-tutorial-part-1/を参考にしている。

引数の渡し方

x86では関数呼び出しするときにスタックに値をpushして引数を渡していたため、スタックの値を書き変えれば引数を操作できた。 x64ではレジスタに引数の値を指定するため、引数の操作がやりにくくなっている。

引数の順番はWinとgccで違ったりする、CTFだとだいたいgcc版になる。 gcc版だと一番目からrdi, rsi, rdx, rcx, r8, r9になる。 exe版だとrcx, rdx, r8, r9になる。 4個以上の引数がある関数はあまり見ないので知らない。

レジスタ

64bit長になった。 つまり、pushやpopは8バイト単位で動作し、アドレス長も8バイトになっている。 さらにレジスタ自体の数も増えて、R8やR9などが追加されている。 x86レジスタは、RAXやRSPのようにRを付けると64ビットでアクセスできる。 EAXやEBXで下位32ビットにアクセスできる。 それ以外はx86と同じようにアクセスできる(下位32ビットに対して)。

アドレス

バッファオーバーフローでリターンアドレスを書き換えEIPを奪うのはpwnではx86での上等手段だった。 例えば長い文字列を渡しバッファを溢れさせると、eipの値が0x41414141などになっているのがgdbなどで確認できる。 しかし、x64ではアドレス空間が64bitに拡張され、有効な命令アドレスの範囲は0x00007FFFFFFFFFFFまでに制限されている。 したがって、リターンアドレスを書き換え過ぎるとripが書き換えられないでプログラムがSIGSEGVされる。 このままだと確認しにくいので、ret命令時のスタックトップがリターンアドレスとして読み込まれるので、その値からオフセットを割り出し適切な長さに調節するとよい。

環境変数

環境変数はプログラム実行時にスタックに積まれているので、環境変数が設定できるなら文字列を設定し、そのアドレス引数にしたりするテクニックが使える。

ROP

レジスタを介して引数を渡すため、ROPを使う場面が増える。 例えばpop rsi; retという命令へのアドレスをリターンアドレスとして設定すると、スタック上でリターンアドレスの次にある値がRSIに設定できる。 これを利用してレジスタに値を設定することで関数に引数を渡せる。