惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

爱范儿
爱范儿
P
Palo Alto Networks Blog
月光博客
月光博客
H
Hackread – Cybersecurity News, Data Breaches, AI and More
I
InfoQ
aimingoo的专栏
aimingoo的专栏
腾讯CDC
T
Threatpost
D
DataBreaches.Net
Vercel News
Vercel News
F
Fortinet All Blogs
Engineering at Meta
Engineering at Meta
C
Cybersecurity and Infrastructure Security Agency CISA
Forbes - Security
Forbes - Security
U
Unit 42
C
Check Point Blog
Blog — PlanetScale
Blog — PlanetScale
O
OpenAI News
量子位
TaoSecurity Blog
TaoSecurity Blog
Microsoft Azure Blog
Microsoft Azure Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
V
Visual Studio Blog
Recorded Future
Recorded Future
云风的 BLOG
云风的 BLOG
Security Archives - TechRepublic
Security Archives - TechRepublic
The Last Watchdog
The Last Watchdog
S
Security Affairs
Attack and Defense Labs
Attack and Defense Labs
罗磊的独立博客
Stack Overflow Blog
Stack Overflow Blog
Microsoft Security Blog
Microsoft Security Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
V
V2EX
小众软件
小众软件
S
SegmentFault 最新的问题
www.infosecurity-magazine.com
www.infosecurity-magazine.com
W
WeLiveSecurity
AI
AI
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
博客园 - 聂微东
I
Intezer
Know Your Adversary
Know Your Adversary
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
P
Proofpoint News Feed
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The Cloudflare Blog
博客园_首页
NISL@THU
NISL@THU
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO

卡瓦邦噶!

服务器高性能网络调优 | 卡瓦邦噶! 为何写作 | 卡瓦邦噶! 读《金阁寺》 | 卡瓦邦噶! 雨季又来 | 卡瓦邦噶! MTU Probe 引起的初始延迟 | 卡瓦邦噶! 3.5 秒的固定延迟问题 | 卡瓦邦噶! 学习网络的一点经验 | 卡瓦邦噶! ARP 问题诊断 | 卡瓦邦噶! 网络断断续续…… | 卡瓦邦噶! Piccolo P2P 镜像分发 | 卡瓦邦噶! 一起看电影 | 卡瓦邦噶! 《征服C指针》 | 卡瓦邦噶! 我的姥姥 | 卡瓦邦噶! Python的哲学 Python 3.5的新特性 学校不教的计算机课 垃圾回收(GC)的三种基本方式 在编程中体验纯粹的快乐 从《美丽新世界》谈自由 在快钱实习 迷人的嗓音和迷人的故事——《Sleepyhead》 Python 的十个自然语言处理工具 记一个愚蠢的bug 一年炉石传说的游戏体验 《以撒的结合:重生》网页版图鉴 分清 C++的指针、引用和数组 笑话三则 自由比皇帝更伟大——《悲惨世界》笔记 Git 10 周年访谈:Linus 讲述背后故事 用 0x3f3f3f3f 设定最大int值的优点 Joel给计算机系学生的建议 一个词法分析器的简单实现 怎样才算健康的生活方式 MacVim 配置攻略 学习培训课程的视频效果好吗? 语言的控制 可悲的大多数 CSS样式思维导图 2014年终总结 奥巴马成为首位写程序的美国总统 青岛老城区的下水道好在哪里? 做优秀 UI 的七个建议(第二部分) 选择爱情的骑士 Git简明教程 Java集合总览 读 《1984》 java问答:终极父类(六)——等待/唤醒和接口 Java 问答:终极父类(五)——toString() Hyperlapse快速视频背后的技术细节 平庸之恶 Java程序员须知的七个日志管理工具 苏州 逛书摊随想 关于考试作弊 你的工作不仅仅是编程 推荐在线学习Java的英文资源 关注女性命运——《千禧年三部曲》 用好你的幻灯片——《演说之禅》 使用ReentrantLock和Lambda表达式让同步更纯净 新手学编程,从哪里开始? 程序员都是工程师吗? 不要学习代码,要学会思考 Java 问答:终极父类(四)——hashCode() Java 问答:终极父类(三)——finalize()和 getClass() 程序员职业之路的选择 我是一名摄影家 写给何小树的城市指南 为什么一些语言会比别的快? Java的常见误区与细节 跟朋友在一起玩游戏 死神永生——读《三体》 五种类型的程序员 创业圣经——读《黑客与画家》 纪念加西亚·马尔克斯 Junit中处理异常的另一种方式:catch-exception Java8采用Martin Fowler的方法创建内部DSL Linux HotSopt虚拟机GC线程的CPU占用率 J2EE概念介绍 如何成为一名黑客 Java 问答:终极父类(二)——equals()方法 为什么我喜欢Java Java 问答:终极父类(一)——clone()方法 七个改变世界的Java项目 java中默认类型转换的小问题 传统与创新 欢迎来到互联网 莫言和马尔克斯——读《生死疲劳》 位运算的妙用 读《人为什么活着》 写博客教会我的事情 中国特色操作系统 2013年总结 《永不妥协》影评 恨不相逢未嫁时——《廊桥遗梦》影评 如何优雅地使用PPT 给明年依然年轻的我们 天才与柱子 黑客守则和黑客精神 Looking for Freedom——《被解救的姜戈》 简洁之道
针对进程设置路由规则
laixintao · 2023-02-25 · via 卡瓦邦噶!

最近有一个需求是这样的:Linux 机器上有多个网络的 interface,想要让其中的一个程序使用 eth1 而不是默认的 interface,不影响其他程序。

在 Linux 上发送 TCP 数据,是通过 kernel 提供的 syscall 将数据送给 kernel,然后 kernel 负责最后的发送。而到了 kernel 这一层,TCP 就是根据五元组来确认的了:来源端口,目的端口,来源IP,目的IP,以及协议类型。所以就让“按照进程转发”这个需求变得有些复杂。

Linux 提供了 SO_MARK 可以给流量“打标签”,然后针对打了标签的流量设置 route. 我们的思路是这样的:

  1. 设置一个路由规则,将带了 0x10 mark 的流量通过 table 100 查找路由: ip rule add fwmark 0x10 table 100
  2. table 100 中只有一条 default 路由规则,就是通过 eth1 出去: ip route add default dev eth1 table 100 scope global
  3. 然后给特定程序使用 SO_MARK 打标签。这样,就可以命中我们想要的路由了。

如何给这个流量打标签,变得有趣了起来。

方法1:iptables

如果应用使用的流量有明显的特征,就可以用这种方法。比如我们的流量都是去往 3306 的,就可以针对这个端口设置一条规则:

1

iptables -A PREROUTING -t mangle -i bond0 -p tcp --dport 3306 -j MARK --set-mark 0x01

但是话说回来,既然都知道端口了,可以直接把步骤 3 去掉,在步骤 1 让去往这个端口的流量直接命中 table 100(ip-route(8) 是支持端口的):

1

ip rule add pproto tcp dport 3306 lookup 100

所以这个方法感觉比较鸡肋。而且这种只支持程序的流量必须有一个固定的模式,假如程序请求很多其他网站并且 TCP 无法识别,就麻烦了。

方法2:LD_PRELOAD 魔法

使用这种方式,我们可以不修改程序的代码而又让进程的流量全部带上标记。

第一次见到 LD_PRELOAD 是在 jvns 的博客上,简直是一个颠覆世界观的东西,它这么简单,又这么有效!

它的原理是这样:dynamic link 的程序是在启动的时候寻找应该从哪里 load 一个 symbol 的,通过设置 LD_PRELOAD 给进程,就可以覆盖动态链接的符号的查找顺序。

ldd 展示动态链接的库:

Shell

1

2

3

4

5

6

7

$ ldd /usr/bin/nc

        linux-vdso.so.1 (0x00007ffd118ab000)

        libbsd.so.0 => /lib/x86_64-linux-gnu/libbsd.so.0 (0x00007f5a3fb99000)

        libresolv.so.2 => /lib/x86_64-linux-gnu/libresolv.so.2 (0x00007f5a3fb85000)

        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f5a3f95d000)

        libmd.so.0 => /lib/x86_64-linux-gnu/libmd.so.0 (0x00007f5a3f950000)

        /lib64/ld-linux-x86-64.so.2 (0x00007f5a3fc45000)

所以,当我们编译一个 .so 共享库,并且通过 LD_PRELOAD 设置成共享库的路径,那么在寻找符号的时候就会先寻找 .so 里面的,这样,我们就可以通过编写同名的函数来覆盖系统的函数。

如果你用过 proxychains ,那么你应该已经用过这个 feature 了。

proxychains 是一个代理软件,一般的软件使用代理都需要设置 http_proxy 等环境变量来告诉软件代理的地址。前提是这个软件支持读这些环境变量。但是,有了 Proxychains,你不需要软件自身支持,就可以让它走代理!原理就是,软件通过 proxychains 启动,而启动的时候,proxychains 会设置软件的 LD_PRELOAD 变量,然后去替换系统的 socket 函数,这样,任何调用都会走 proxychains 的代理!source code

所以,使用这种方法,我们只要替换程序的 socket 方法,在每次创建 socket 之后,调用 setsockopt(2) 去设置上 SO_MARK 就好了。

网上已经有一个程序 App-Route-Jail 做了这件事,它的使用方法如下:

1.clone 并且编译程序:

Shell

1

2

3

4

git clone https://github.com/Intika-Linux-Network/App-Route-Jail.git

cd App-Route-Jail

chown 755 make.sh

./make.sh

2.启动程序的时候带上 LD_PRELOAD 变量,要设置什么 mark,通过 MARK 环境变量传入

1

MARK=10 LD_PRELOAD=./mark.so firefox

它的程序很简单,就是用一个新的 socket 函数,替换原来的 socket 函数:在每次创建 socket 之后,调用 setsockopt(2) 设置 mark.

等下,那既然 mark.so 里面调用的也是 socket(),怎么避免 linker 再去找一遍这个函数在哪里,然后找到了自己,造成无限循环呢?

mark.c 的代码中,是用 dysym(3) 去 load 的 socket() 这个符号:

C

1

2

if (!_socket)

_socket = (socket_t) dlsym(RTLD_NEXT, "socket");

RTLD_NEXT 就是让 dlsym 从下一个 .so 开始找:

RTLD_NEXT
Find the next occurrence of the desired symbol in the search order after the current object. This allows one to provide a wrapper around a function in another
shared object, so that, for example, the definition of a function in a preloaded shared object (see LD_PRELOAD in ld.so(8)) can find and invoke the “real” func‐
tion provided in another shared object (or for that matter, the “next” definition of the function in cases where there are multiple layers of preloading).

所以这里用的就是 glibc 提供的 socket 函数了。

这个方法也有缺点,就是它只对 dynamic link 的程序有效,像 golang 这种语言默认是全部静态编译的,连 glibc 都不用,LD_PRELOAD 自然就无效了。这种情况我们就只能修改程序的代码,让它自己标记自己的流量了。

方法3:直接在 golang 代码中直接设置  fwmark

我们需要手动创建 Dialer 来进行 tcp 连接。并且给这个 Dialer 设置一个 Control:

Go

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

// setSocketMark sets packet marking on the given socket.

func setSocketMark(fd, mark int) error {

       if err := syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_MARK, mark); err != nil {

               return os.NewSyscallError("failed to set mark", err)

       }

       return nil

}

// use this create a new dialer

dialer := &net.Dialer{

        Control: func(_, _ string, c syscall.RawConn) error {

                return c.Control(func(fd uintptr) {

                        ex := setSocketMark(int(fd), 0x01)

                        if ex != nil {

                                fmt.Printf("net dialer set mark error: %s\n", ex)

                        }

                })

        },

}

感觉使用这种方式在 Chaos Engineering 方面也会很有用,我们一直苦于如何有效对 HTTP(S) 流量进行注入而不影响其他机器的进程。使用 SO_MARK 或许可以解决。


2023年3月1日更新:这篇文章发出之后,网友评论了更多有用和有趣的方法。

方法4:通过 network namespace 配置路由

最先是在 twitter 上看 @Sleepy93216599 介绍的。不过没尝试过,就不详细展开介绍了。

方法5:通过 iptables –uid-owner 匹配

iptables 居然支持直接按照 uid 来匹配,之前一直不知道这个。这样的话只要给进程分配一个单独的 user 来运行就好了。

此外,还支持 gid, pid, sid, cmd owner 来匹配:

–uid-owner userid
Matches if the packet was created by a process with the given effective user id.
–gid-owner groupid
Matches if the packet was created by a process with the given effective group id.
–pid-owner processid
Matches if the packet was created by a process with the given process id.
–sid-owner sessionid
Matches if the packet was created by a process in the given session group.
–cmd-owner name
Matches if the packet was created by a process with the given command name. (this option is present only if iptables was compiled under a kernel supporting this feature)

方法6:使用 ptrace(2) hook 系统调用 connect(2)

这个方法简直惊为天人。

本文评论里介绍了 gg 这个工具,可以用来 hook golang 写的程序,感觉很神奇,但是没看懂怎么实现的,只有 FAQ 里面提过一次 ptrace,然后灵光一闪发现可以用 ptrace 搞事情。

后来看到 graftcp 这个工具,文档和代码写的都很好,看的让人拍大腿。简单来说,就是程序启动的时候,通过 ptrace(2) 进行跟踪,然后当程序开始创建 tcp 的时候,拦截 connect(2) 调用,获取目标地址,然后给这个程序修改成自己的地址,欺骗程序,自己拿到了真正的地址。然后恢复执行。后面程序成功创建了 tcp 连接,但是却是到代理的 tcp 连接,而不是真正到目标地址的链接。这样,代理就在这之间工作了。而这对程序是无感的。

文档还提到一个有趣的点,就是为什么不直接去 hook write(2)read(2)呢? 原因有2:

  • 不方便拿到真正的目标地址(原来的方法 hook connect(2)) 是通过 PIPE 把地址告诉代理进程。
  • 去改写程序的 buffer 内存,会造成缓冲区溢出等错误。(而原来的方法直接修改 connect(2) 的参数是在寄存器,安全的多)。
  • 另外 ptrace 是有性能损耗的,直接hook一次,在连接创建阶段,显然要比每次传输数据都暂停一下,性能要高得多。