写在最前面的注意事项!!!
- 下面所提供的方式方法并不一定适用于各种FnOS版本,笔者只在x86的1.1.19版本中进行过测试。
- 对
pxy程序进行改造存在一定风险,确定知道自己在干什么再尝试。笔者不为任何损失承担责任。
- 虽然标题中称
穿上铠甲,但实际上即便使用 WAF也并不能保证可以抵御所有种类的攻击。笔者写本文只为交流学习,自己使用更安全的环境,并不指望仅靠 WAF可以保证不受 0day攻击。
- 仅供有一定动手能力的用户尝试,笔者无法保证方法的稳定性,不要在生产环境下使用!!!
1、动机
相信不少 FnOS用户和我一样是把 FnOS的 NAS作为主力 NAS再用,里面存放了不少并不希望公开的资料。对 NAS的安全性非常敏感,如果 NAS遭受 0day攻击必会带来经济损失。
笔者看到论坛上有不少关于搭建 WAF的文章,且购买设备的时候有获得赠送的 FN Connect,便想在“免费”赠送的 FN Connect上搭建 WAF通过外网"安全"(注意事项3)的访问 NAS,但发现两者并不方便结合在一起。
2、学习
带着问题,笔者尝试来学习一下 FN Connect是如何把端口中转到外网的。
通过分析系统端口和进程,不难发现 pxy是进行中转的主要程序,简单分析一下这个程序,应该基于 frp的实现,采用 frp的插件来进行转发。
当然添加了非常多的改动,为了在上面搭建 WAF笔者比较关注两个功能的加入。
一个是通过获取配置自动改变映射端口。
函数 FUN_00786640用来获取端口配置
*************************************************************
* FUNCTION
*************************************************************
undefined FUN_00786640 ()
undefined <UNASSIGNED> <RETURN>
FUN_00786640 XREF[2]: 007866e0 (j) ,
FUN_007fdcc0:007fdd00 (c)
00786640 CMP RSP ,qword ptr [R14 + 0x10 ]
00786644 JBE LAB_007866d6
0078664a PUSH RBP
0078664b MOV RBP ,RSP
0078664e SUB RSP ,0x48
00786652 LEA RAX ,[s_com.trim.network_00d6175f ] = "com.trim.network"
00786659 MOV EBX ,0x10
0078665e LEA RCX ,[s_gw.getting_00d5cd5a ] = "gw.getting"
00786665 MOV EDI ,0xa
0078666a XOR ESI ,ESI
0078666c XOR R8D ,R8D
0078666f CALL FUN_00786700 undefined FUN_00786700()
00786674 TEST RCX ,RCX
00786677 JZ LAB_00786687
00786679 XOR EAX ,EAX
0078667b MOV RBX ,RCX
0078667e MOV RCX ,RDI
00786681 ADD RSP ,0x48
00786685 POP RBP
00786686 RET
LAB_00786687 XREF[1]: 00786677 (j)
00786687 MOV qword ptr [RSP + 0x30 ],RAX
0078668c MOV qword ptr [RSP + 0x38 ],RBX
00786691 LEA RAX ,[DAT_00cb58e0 ] = 18h
00786698 CALL FUN_00417d20 undefined FUN_00417d20()
0078669d MOV qword ptr [RSP + 0x40 ],RAX
007866a2 MOV RCX ,qword ptr [RSP + 0x30 ]
007866a7 MOV RCX ,qword ptr [RCX + 0x18 ]
007866ab MOV RAX ,qword ptr [RSP + 0x38 ]
007866b0 CALL RCX
007866b2 LEA RDI ,[DAT_00be8320 ] = 08h
007866b9 MOV RSI ,qword ptr [RSP + 0x40 ]
007866be NOP
007866c0 CALL FUN_00515d00 undefined FUN_00515d00()
007866c5 MOV RCX ,RBX
007866c8 MOV RBX ,RAX
007866cb MOV RAX ,qword ptr [RSP + 0x40 ]
007866d0 ADD RSP ,0x48
007866d4 POP RBP
007866d5 RET
LAB_007866d6 XREF[1]: 00786644 (j)
007866d6 CALL FUN_00478720 undefined FUN_00478720()
007866db NOP dword ptr [RAX + RAX *0x1 ]
007866e0 JMP FUN_00786640
通过函数引用,不难找到函数 FUN_007fdcc0负责自动从配置中更新映射端口
void FUN_007fdcc0(void)
{
char cVar1;
long *plVar2;
undefined *puVar3;
undefined *puVar4;
long unaff_RBX;
undefined8 *in_R11;
long unaff_R14;
undefined1 auVar5 [16];
undefined8 *puStack_60;
undefined8 uStack_58;
undefined8 *puStack_50;
undefined8 uStack_48;
undefined8 *puStack_40;
undefined8 uStack_38;
undefined8 *puStack_30;
undefined8 uStack_28;
undefined8 *puStack_20;
undefined8 uStack_18;
undefined **ppuStack_10;
while (&puStack_50 <= *(undefined8 ***)(unaff_R14 + 0x10)) {
FUN_00478720();
}
ppuStack_10 = &PTR_FUN_00d98f40;
plVar2 = (long *)FUN_00786640();
if (unaff_RBX != 0) {
(**(code **)(unaff_RBX + 0x18))();
auVar5 = FUN_0046ff00();
uStack_18 = auVar5._0_8_;
puStack_20 = &DAT_00c07900;
FUN_0059dc60(0x20,&puStack_20,auVar5._8_8_,"get gateway http port error : %s",1,1);
}
puVar4 = PTR_s_8000_01403e00;
if ((0 < *plVar2) &&
((puVar3 = (undefined *)FUN_0048afc0(), DAT_01403e08 != 10 ||
(cVar1 = FUN_004044a0(), puVar4 = PTR_s_8000_01403e00, cVar1 == '\0')))) {
uStack_38 = FUN_0046ff00();
puStack_40 = &DAT_00c07900;
auVar5 = FUN_0046ff00();
uStack_28 = auVar5._0_8_;
puStack_30 = &DAT_00c07900;
FUN_0059dc60(0x2a,&puStack_40,auVar5._8_8_,"update gateway http port from [%s] to [%s]",2,2) ;
DAT_01403e08 = 10;
puVar4 = puVar3;
if (DAT_01449d70 != 0) {
FUN_0047a620();
*in_R11 = puVar3;
in_R11[1] = PTR_s_8000_01403e00;
}
}
PTR_s_8000_01403e00 = puVar4;
if ((0 < plVar2[1]) &&
((puVar4 = (undefined *)FUN_0048afc0(), DAT_01403e18 != 10 ||
(cVar1 = FUN_004044a0(), cVar1 == '\0')))) {
uStack_58 = FUN_0046ff00();
puStack_60 = &DAT_00c07900;
auVar5 = FUN_0046ff00();
uStack_48 = auVar5._0_8_;
puStack_50 = &DAT_00c07900;
FUN_0059dc60(0x2b,&puStack_60,auVar5._8_8_,"update gateway https port from [%s] to [%s]",2,2 );
DAT_01403e18 = 10;
if (DAT_01449d70 != 0) {
FUN_0047a620();
*in_R11 = puVar4;
in_R11[1] = PTR_DAT_01403e10;
}
PTR_DAT_01403e10 = puVar4;
FUN_007fbf80();
}
FUN_007879a0();
return;
}
类似的程序中还有的函数 FUN_007fe000从dbus中获取配置,做了同样的事情。
void FUN_007fe000(void)
{
char cVar1;
long in_RAX;
long lVar2;
undefined *puVar3;
undefined *puVar4;
undefined8 *in_R11;
long unaff_R14;
undefined1 auVar5 [16];
undefined8 *puStack_68;
undefined8 uStack_60;
undefined8 *puStack_58;
undefined8 uStack_50;
undefined8 *puStack_48;
undefined8 uStack_40;
undefined8 *puStack_38;
undefined8 uStack_30;
undefined8 *puStack_28;
undefined8 uStack_20;
long *plStack_18;
undefined **ppuStack_10;
while (&uStack_60 <= *(undefined8 **)(unaff_R14 + 0x10)) {
FUN_00478720();
}
ppuStack_10 = &PTR_FUN_00d98f40;
if (*(long *)(in_RAX + 0x38) != 0) {
if ((undefined8 *)**(undefined8 **)(in_RAX + 0x30) != &DAT_00c07900) {
FUN_004140a0();
FUN_0043b5c0();
return;
}
plStack_18 = (long *)FUN_00417d20();
FUN_0045a4e0();
lVar2 = FUN_00515d00(&DAT_00be8320,plStack_18);
if (lVar2 != 0) {
(**(code **)(lVar2 + 0x18))();
auVar5 = FUN_0046ff00();
uStack_20 = auVar5._0_8_;
puStack_28 = &DAT_00c07900;
FUN_0059dc60(0x1a,&puStack_28,auVar5._8_8_,"get dbus signal error : %s",1,1);
}
puVar4 = PTR_s_8000_01403e00;
if ((0 < *plStack_18) &&
((puVar3 = (undefined *)FUN_0048afc0(), DAT_01403e08 != 10 ||
(cVar1 = FUN_004044a0(), puVar4 = PTR_s_8000_01403e00, cVar1 == '\0')))) {
uStack_40 = FUN_0046ff00();
puStack_48 = &DAT_00c07900;
auVar5 = FUN_0046ff00();
uStack_30 = auVar5._0_8_;
puStack_38 = &DAT_00c07900;
FUN_0059dc60(0x34,&puStack_48,auVar5._8_8_,
"update gateway http port from dbus from [%s] to [%s]",2,2);
DAT_01403e08 = 10;
puVar4 = puVar3;
if (DAT_01449d70 != 0) {
FUN_0047a620();
*in_R11 = puVar3;
in_R11[1] = PTR_s_8000_01403e00;
}
}
PTR_s_8000_01403e00 = puVar4;
if ((0 < plStack_18[1]) &&
((puVar4 = (undefined *)FUN_0048afc0(), DAT_01403e18 != 10 ||
(cVar1 = FUN_004044a0(), cVar1 == '\0')))) {
uStack_60 = FUN_0046ff00();
puStack_68 = &DAT_00c07900;
auVar5 = FUN_0046ff00();
uStack_50 = auVar5._0_8_;
puStack_58 = &DAT_00c07900;
FUN_0059dc60(0x35,&puStack_68,auVar5._8_8_,
"update gateway https port from dbus from [%s] to [%s]",2,2);
DAT_01403e18 = 10;
if (DAT_01449d70 != 0) {
FUN_0047a620();
*in_R11 = puVar4;
in_R11[1] = PTR_DAT_01403e10;
}
PTR_DAT_01403e10 = puVar4;
FUN_007fbf80();
}
}
FUN_007879a0();
return;
}
二是默认使用的映射端口。
PTR_s_8000_01403e00 XREF[16]: FUN_007fdcc0:007fdd9d (R) ,
FUN_007fdcc0:007fddc9 (R) ,
FUN_007fdcc0:007fde73 (R) ,
FUN_007fdcc0:007fde7e (W) ,
FUN_007fe000:007fe149 (R) ,
FUN_007fe000:007fe17a (R) ,
FUN_007fe000:007fe221 (R) ,
FUN_007fe000:007fe22c (W) ,
FUN_00a395a0:00a39782 (R) ,
00a3b0cd (R) ,
FUN_00a3bf40:00a3bff2 (R) ,
FUN_00a4b820:00a4b852 (R) ,
FUN_00a4b820:00a4b86a (R) ,
FUN_00a4f2e0:00a4f8e7 (R) ,
FUN_00a4f2e0:00a4fcb8 (R) ,
FUN_00a51680:00a51983 (R)
01403e00 addr s_8000_00d506da = "8000"
通过调查引用不难发现,改变端口号全局变量的引用只在上面两个函数中存在。
3、改造
基本思路是停掉自动获取 gateway端口的功能,换掉默认映射的端口,这样就可以让 FN Connect映射我们指定的端口。
那么我们将函数 FUN_007fdcc0与 FUN_007fe000改为 ret直接返回不做任何操作,并改变地址 01403e00上的端口号就能固定使用一个我们指定的端口号来让中继转发。在这个端口上套上对 FnOS WebUI的 WAF转发既能达成目的。因为是 pxy是利用插件来做的,服从 frp的方式在应用 WAF时我们采用 http来中转即可。
4、总结
通过改造 pxy我们可以给 FN Connect上套层 WAF一定程度上带来一些"安全感",但本方法并不能保证跨版本依旧可行,且有点费事,还是希望官方能将提供更多配置选项方便用户配置。最后笔者还需强调一下,对于防御 0day攻击通过搭建 WAF带来的安全性依旧有限,请各位根据自身情况谨慎选择。
5、参考连接