jarvisoj_level0
步骤
检查文件,64位,开启NX保护
拖入IDA打开,查看主函数main
write:这是一个系统调用,用于将数据写入文件描述符。在这里,write 函数将数据写入文件描述符 1,表示标准输出。
1:这是文件描述符,代表标准输出。标准输出的文件描述符为 1。
"Hello, World\n":这是要写入的数据,即字符串 "Hello, World" 后跟一个换行符 \n。
0xDuLL:这是要写入的数据的大小,使用十六进制表示。0xD 对应十进制的 13,表示字符串的长度为 13 个字节。
双击查看vulenerable_function
char buf[128];:这行代码定义了一个大小为 128 字节的字符数组 buf,用于存储从标准输入读取的数据。
return read(0, buf, 0x200uLL);:这行代码调用了 read 系统调用,从标准输入(文件描述符为 0)读取数据,将读取的数据存储到 buf 中。0x200uLL 表示读取的最大字节数为 0x200(十六进制)或 512(十进制)。
read:这是一个系统调用,用于从文件描述符中读取数据。
0:表示文件描述符,其中 0 表示标准输入。
buf:表示用于存储读取数据的缓冲区的地址。
0x200uLL:表示要读取的最大字节数。
可以看到buf的长度只有0x80,即可用栈大小只有108字节,但是read()并没有限制输入,显然存在栈溢出漏洞。
在Functions window可以看到有一个callsystem()函数,按F5反汇编可以看到这是一个系统调用,存在system(“/bin/sh”)
即使用栈溢出令返回地址指向该函数地址即可
题解
jarvisoj_level2_x64
步骤
检查文件,64位,开启NX保护
拖入64位IDA中打开,shift+F12查看发现存在system与/bin/sh,可用rop传参
查看主函数,双击vulnerable_function
vulnerable_function(argc, argv, envp);:这是对函数 vulnerable_function 的调用,其中 argc 是命令行参数的数量,argv 是命令行参数的数组,envp 是环境变量的数组。根据函数名的含义,vulnerable_function 可能是一个存在漏洞或不安全的函数。
return system("echo 'Hello World!'");:这是对 system 函数的调用,执行命令行命令。在这里,system 函数执行的命令是 echo 'Hello World!',即在标准输出打印字符串 "Hello World!"。
发现可栈溢出漏洞点
这段代码定义了一个名为 vulnerable_function 的函数,该函数存在缓冲区溢出的潜在漏洞。
在函数内部,定义了一个长度为 128 字节的字符数组 buf,并通过 read 函数从标准输入中读取最多 0x200 字节(512 字节)的数据存储到 buf 中。
然而,这段代码没有对输入数据的长度进行有效的检查,而 read 函数会将输入数据直接存储到 buf 中,导致可能发生缓冲区溢出。这意味着如果用户输入的数据超过 128 字节,就会覆盖 buf 数组后面的内存区域,可能导致程序崩溃或被恶意利用。
查看溢出量
查看system地址
查看/bin/sh地址
查看文件中rdi寄存器地址,用来传参
题解
ciscn_2019_en_2
步骤
检查文件,64位,只开启了栈不可执行
拖入64位IDA中打开,无bin无system
查看主函数
尝试运行对照意思
程序的逻辑大致如下:
打印欢迎信息,并调用函数begin()。
进入一个无限循环,循环内部有一个读取输入的循环,如果读取到的整数为2,重新调用函数begin()。
如果读取到的整数为3,打印"Bye!"并退出程序。
如果读取到的整数不是1或2或3,则打印"Something Wrong!"并退出程序。
如果读取到的整数为1,则调用函数encrypt(),然后再次调用函数begin()。
- 因此,只有输入1有用,encrypt较为重要
下面看一下encrypt函数
这段代码定义了一个名为 encrypt 的函数,用于对输入的明文进行加密。
函数内部首先定义了一个长度为 48 字节的字符数组 s,并使用 memset 函数将其初始化为全零。接下来定义了一个名为 v3 的 __int16 变量。
在代码执行过程中,首先通过 puts 函数输出提示信息,要求用户输入明文。然后使用 gets 函数从标准输入读取用户输入的数据,并将其存储到 s 数组中。这里使用了不安全的 gets 函数,它无法限制输入的长度,可能导致缓冲区溢出。
接下来,通过一个循环遍历数组 s 中的每个字符,对字符进行加密操作。对于字母字符,根据其范围进行不同的异或操作。具体来说:
如果字符在小写字母范围(97-122),则将其与十六进制值 0xE 进行异或操作。
如果字符在大写字母范围(65-90),则将其与十六进制值 0xD 进行异或操作。
如果字符在数字范围(48-57),则将其与十六进制值 0xC 进行异或操作。
最后,通过 puts 函数输出加密后的密文。
程序通过一个变量x来控制当前遍历到字符串s的哪个位置,每次循环先将x强制转换为无符号整型,赋值给变量v0。然后判断v0是否大于或等于s的长度strlen(s)
v0的值(即遍历加密前的明文字符串s的索引)大于等于s字符串(用户输入的0)的长度(即已经遍历完了整个字符串),则退出循环
strlen(s)是一个字符串函数,用于计算一个字符串的长度。如果v0已经等于或大于s字符串的长度,说明已经对整个字符串进行了加密操作,循环可以结束了
解题思路
-
get()存在栈溢出漏洞
-
用\x00绕过strlen(),形成栈溢出
-
泄露puts()地址,打常规ret2libc
题解
第一次使用 p.recvline() 是为了接收服务器端发送的数据,通常是用于确认连接建立成功后的欢迎消息或其他相关信息。
第二次使用 p.recvline() 是为了接收第一次 payload 执行后的输出结果。根据代码中的注释 p.recvline() 的作用是接收输出结果,因此在这里使用它来接收加密后的结果。
puts_plt = elf.sym["puts"]:这行代码获取可执行文件 elf 中 puts 函数的地址。sym 是 ELF 模块的一个属性,用于获取可执行文件中函数符号的地址。
__libc_start_main = elf.got["__libc_start_main"]:这行代码获取可执行文件 elf 中 __libc_start_main 符号在全局偏移表(GOT)中的地址。got 是 ELF 模块的一个属性,用于获取可执行文件中全局变量符号的地址。
main_addr = elf.sym['main']:这行代码获取可执行文件 elf 中 main 函数的地址。
not_the_same_3dsctf_2016
步骤
检查文件,32位,开启NX保护
拖入32位IDA中打开
查看主函数发现gets,存在栈溢出漏洞
shift+F12,发现flag.txt敏感字符串
找到调用的函数get_secret函数
代码的执行逻辑如下:
v0 = fopen(“flag.txt”, &unk_80CF91B);:这行代码打开名为 “flag.txt” 的文件,并将文件指针赋值给变量 v0。&unk_80CF91B 是一个未知的地址或变量,可能是用于存储文件句柄的位置。
fgets(&fl4g, 45, v0);:这行代码从打开的文件中读取最多 45 个字符,并将其存储在变量 fl4g 所指向的位置。
函数调用后,flag储存在fl4g里面,我们找到fl4g的位置
找到可以输出的write函数
题解
'a'* 0x2d: 这段代码使用字符 'a' 乘以 0x2d(十进制为45),生成了一个长度为45的字符串。这是为了填充数据,达到满足程序中的缓冲区溢出漏洞的目的。
p32(get_secret): p32() 函数是 pwn 库中的一个函数,它将一个32位整数转换为4字节的字符串。这里将 get_secret 函数的地址转换为4字节的字符串。
p32(write_addr): 同样地,将 write 函数的地址转换为4字节的字符串。
p32(0): 将整数0转换为4字节的字符串。这是用作 get_secret 函数的第一个参数,表示标准输入文件描述符的值。
p32(1): 将整数1转换为4字节的字符串。这是用作 write 函数的第一个参数,表示标准输出文件描述符的值。
p32(flag_addr): 将 flag 的地址转换为4字节的字符串。这是作为 write 函数的第二个参数,表示要写入数据的缓冲区地址。
p32(45): 将整数45转换为4字节的字符串。这是作为 write 函数的第三个参数,表示要写入的字节数。
others_shellcode
步骤
检查文件,32位开启NX保护地址随机化
查看主函数
- getshell()主函数中直接给了后门函数,跟进查看。
直接连接即可
题解
ciscn_2019_n_8
步骤
检查文件,32位,保护全开
拖入32位IDA中打开,查看分析主函数,存在system(‘/bin/sh’)
很明显,只要var[13]==17就行,而scanf()又不限制长度,则直接全部输入17就行