前置基础
Pwn 5
先查看文件,发现是elf文件,然后给它加上运行权限并运行它。
(注意,在Linux中这种外来文件如果不专门添加运行权限我们是无法运行它的。)
1 | └─$ file Welcome_to_CTFshow |
所以flag为:
1 | ctfshow{Welcome_to_CTFshow_PWN} |
Pwn 6
我们先来仔细地看一下这个程序(利用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
47public 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 | mov eax, 0Bh |
0Bh
,1BF48h
的h
都是十六进制的后缀,所以这段内容相当于:
1 | eax = 0x0000000B |
所以flag为:
1 | ctfshow{114514} |
栈溢出
Pwn35
检查一下文件的保护措施:
1 | └─$ checksec pwn |
1 | $ ls |
看一下这个ctfshow函数:
1 | char *__cdecl ctfshow(char *src) |
不难发现常见的漏洞函数strcpy
,但该怎么利用它呢?
注意到这一行:
1 | signal(11, (__sighandler_t)sigsegv_handler); |
1 | void __noreturn sigsegv_handler() |
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
当作字符串,写到标准错误流stderr
。fprintf
会格式化并写入缓冲区,最终通过底层write
输出到终端或重定向的目标。
fflush(stderr);
- 强制把
stderr
的缓冲区刷出到文件描述符,确保输出立刻可见(尤其是当stderr
被行缓冲或全缓冲时)。 - 由于
stderr
常常是行缓冲/无缓冲,fflush
可提高输出可靠性,确保在随后退出前内容已写出。
简单总结一下:
当 strcpy(dest, src)
写出栈边界并产生段错误时,内核发送 SIGSEGV
,程序控制流会跳到 sigsegv_handler
。handler
里会将flag写到stderr
,然后退出程序。
也就是说我们只需要想办法利用Buffer Overflow触发segmentation fault,便可以得到flag。
因为给dest分配的是104个字节,所以我们直接输入105个字节即可。
1 | print("A"*105) |
1 | $ ./pwnme AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA |