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

推荐订阅源

B
Blog RSS Feed
C
CERT Recently Published Vulnerability Notes
P
Proofpoint News Feed
Y
Y Combinator Blog
T
The Blog of Author Tim Ferriss
云风的 BLOG
云风的 BLOG
H
Help Net Security
Recorded Future
Recorded Future
The Register - Security
The Register - Security
F
Full Disclosure
N
Netflix TechBlog - Medium
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hackread – Cybersecurity News, Data Breaches, AI and More
爱范儿
爱范儿
Security Archives - TechRepublic
Security Archives - TechRepublic
Simon Willison's Weblog
Simon Willison's Weblog
Cisco Talos Blog
Cisco Talos Blog
I
InfoQ
T
Tenable Blog
T
Tor Project blog
人人都是产品经理
人人都是产品经理
D
DataBreaches.Net
NISL@THU
NISL@THU
Google DeepMind News
Google DeepMind News
博客园 - 叶小钗
B
Blog
V
V2EX
Jina AI
Jina AI
L
LangChain Blog
月光博客
月光博客
W
WeLiveSecurity
U
Unit 42
AWS News Blog
AWS News Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
博客园 - 聂微东
V
Visual Studio Blog
A
Arctic Wolf
T
Tailwind CSS Blog
The Cloudflare Blog
SecWiki News
SecWiki News
S
SegmentFault 最新的问题
Hacker News - Newest:
Hacker News - Newest: "LLM"
宝玉的分享
宝玉的分享
MyScale Blog
MyScale Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
S
Securelist
www.infosecurity-magazine.com
www.infosecurity-magazine.com
腾讯CDC
雷峰网
雷峰网

卡瓦邦噶!

服务器高性能网络调优 | 卡瓦邦噶! 为何写作 | 卡瓦邦噶! 读《金阁寺》 | 卡瓦邦噶! 雨季又来 | 卡瓦邦噶! 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——《被解救的姜戈》 简洁之道
用 LD_PRELOAD 写魔法程序
laixintao · 2025-01-08 · via 卡瓦邦噶!

我和我的同事们排查网路问题非常喜欢 MTR,它是 traceroute 和 ping 的结合,可以快速告诉我们一个网络包的路径。是哪一跳丢包,或者延迟太高。

这些路径使用 IP 地址的形式表示的。没有人能记住这么多 IP 地址,所以我们需要有意义的名字。我在公司里写了一个平台,集成了其他的二十多个系统,给一个 IP,能查询出来这个 IP 对应的网络设备,容器,物理机,虚拟机等等。

复制 IP 到这个系统中查看结果,还是有些不方便,于是就想能不能让 MTR 直接展示设备的名字。

最终效果如图,可以展示 mtr 路径中所有的网络设备的名字。敏感信息已经隐藏。

MTR 支持 DNS PTR 反查,如果查到记录,会优先展示名字。这些名字在公网上通常没有什么意义。我们的内网 DNS 没有支持 PTR,所以这个 PTR 记录在默认的情况下也没有什么用。如果通过 DNS 系统来支持 PTR 记录的话,成本就有些大了,得对 DNS 做一些改造,DNS 又是一个比较重要的系统。那能不能有一个影响比较小的旁路系统来做到这个 feature 呢?

看了下 MTR 的代码,MTR 对 DNS PTR 的支持是通过 libc 的函数 getnameinfo(3) 来实现的。那么我就可以用 LD_PRELOAD 这个 hack,自己写一个 getnameinfo(3) 编译成 so,告诉 MTR 在寻找 getnameinfo(3) 的时候,先寻找我的 so 文件。这样,我就可以自己定义 getnameinfo(3) 的行为了。(就像魔法一样)

其实,proxychains1 程序也是用这种方式工作的,你只要在运行的命令前面添加 proxychains,proxychains 就会对后面运行的命令注入 LD_PRELOAD 环境变量,从而让程序调用的 socket API 是 proxychains 定义的,然后 proxychains 就会对 socket 做一些代理转发。

POC

可以写一个最简单的程序验证这样是否可行。

我们写一个最简单的函数,声明和 getnameinfo(3) 一模一样。

#include <stdio.h>
#include <netdb.h>
#include <string.h>
#include <sys/socket.h>

int getnameinfo(const struct sockaddr *__restrict addr, socklen_t addrlen,
                char *__restrict host, socklen_t hostlen,
                char *__restrict serv, socklen_t servlen, int flags) {

    strncpy(host, "kawabangga.com", hostlen);

    return 0;
}

不过,这个函数无论对于什么 ip,都会返回 kawabangga.com. 然后编译,运行 traceroute 程序。

编译命令:gcc -shared -fPIC -o libmylib.so mylib.c -ldl

Shell

1

2

3

4

5

6

7

8

9

10

11

12

$ LD_PRELOAD=./libmylib.so traceroute 1.1.1.1

traceroute to 1.1.1.1 (kawabangga.com), 30 hops max, 60 byte packets

1  kawabangga.com (kawabangga.com)  0.248 ms  0.386 ms  0.360 ms

2  kawabangga.com (kawabangga.com)  5.223 ms  5.412 ms  5.375 ms

3  kawabangga.com (kawabangga.com)  8.764 ms  9.206 ms  10.454 ms

4  kawabangga.com (kawabangga.com)  10.403 ms  10.001 ms  10.321 ms

5  kawabangga.com (kawabangga.com)  11.578 ms  11.826 ms kawabangga.com (kawabangga.com)  13.214 ms

6  kawabangga.com (kawabangga.com)  12.858 ms kawabangga.com (kawabangga.com)  14.859 ms kawabangga.com (kawabangga.com)  13.905 ms

7  kawabangga.com (kawabangga.com)  13.204 ms  8.645 ms kawabangga.com (kawabangga.com)  10.036 ms

8  kawabangga.com (kawabangga.com)  10.166 ms  8.282 ms  9.155 ms

9  kawabangga.com (kawabangga.com)  7.035 ms kawabangga.com (kawabangga.com)  8.533 ms kawabangga.com (kawabangga.com)  21.939 ms

10  kawabangga.com (kawabangga.com)  8.789 ms  7.832 ms  8.259 ms

可以看到,traceroute 显示每一跳的名字都是 kawabangga.com 了。

用 Go 语言 POC

我比较倾向于用 Go 语言来实现逻辑,而不是用 C 语言。

Go 语言也是支持编译到 shared lib 的2。hello world 代码如下:

Go

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

/*

#include <stdlib.h>

#include <string.h>

#include <sys/socket.h>

*/

import "C"

import (

        "unsafe"

)

//export getnameinfo

func getnameinfo(sa *C.struct_sockaddr, salen C.socklen_t, host *C.char, hostlen C.size_t, serv *C.char, servlen C.size_t, flags C.int) C.int {

        hostStr := "foobar"

        hostCString := C.CString(hostStr)

        defer C.free(unsafe.Pointer(hostCString))

        C.strncpy(host, hostCString, hostlen)

        servStr := "80"

        servCString := C.CString(servStr)

        defer C.free(unsafe.Pointer(servCString))

        C.strncpy(serv, servCString, servlen)

        return C.int(0)

}

func main() {}

编译命令是:go build -o libmylib.so -buildmode=c-shared mylib.go

程序运行的命令一样,也可以看到 getnameinfo(3) 被成功 hook 了。

剩下的只需要在程序里面写逻辑代码就可以了,应该很简单(实际发现不简单)。

遇到问题 1: log 不打印

因为 traceroute 和 mtr 这种程序都是往 stdout 打印的,我开发代码又需要用 print 来调试,所以为了不干扰正常输出,就把日志打印到一个文件,通过 ENV 来控制日志是否需要打印,以及打印的日志路径。

结果就遇到了问题:日志打印在 traceroute 中是正常的,但是在 mtr 中看不到日志。

因为程序是「寄生」在 mtr 的代码中的,而且在 traceroute 中没有问题,所以应该和 mtr 的代码有关。

去看了一下代码,发现 mtr 和 traceroute 不一样的地方是:mtr 是用了异步的方式来执行 getnameinfo 函数,因为这个函数可能使用 DNS PTR 记录,涉及到网络请求,耗时可能很长。所以在调用的时候,mtr 会 fork 一个进程,专门执行这个函数。fork 出来的进程使用 PIPE 和主进程通信,并且 fork 之后就把除了 stdin, stdout, stderr 和 PIPE 之外的 fd 都关闭了。跟着关闭的,也包括我们的日志文件 fd。

解决办法就是修改了一下程序,不写日志到文件了,而是写到 stderr。在 debug 的时候,就 mtr 2>/tmp/stderr.log 这样,就可以了。

问题2: Golang 程序卡住

之前的 POC 代码运行正常,我把它改成通过 HTTP 请求 IP 信息服务的时候,居然就出问题了。mtr 显示的是 IP 而不是名字。从现象看,是函数执行失败了。

但是失败在哪里呢?

经过一段时间的排查,发现了这么几个现象:

  1. 每次程序运行之后,都有一些 mtr 进程残留在系统中没有结束;
  2. traceroute 还是正常的,但是 mtr 每次都会出问题;
  3. 不断加 print 来 debug,发现程序的问题出现在发送 HTTP 请求的地方,但是把这个地方的代码改成直接返回固定字符串,程序就正常了;

使用 gdb 去 debug,backtrace 如下,也看不出什么信息。

Golang 程序的 backtrace

看起来这个线程好像没有什么事情可以做。

花了一天排查无果,问了朋友,最后发现这个问题:Goroutines cause deadlocks after fork() when run in shared library #155383,而且开发人员的回复是:This is to be expected. It’s almost impossible for multithreaded Go runtime to handle arbitrary forks.

而 mtr 正好执行了 fork,所以这算是一个 Golang 的 runtime 问题——如果以 shared-lib 的方式运行,那么主程序是不能 fork 的,如果 fork,Go runtime 中的 goroutine 管理与多线程模型,fork 后线程状态的不一致可能会导致无法正常恢复,从而触发死锁。

最后,我把程序的逻辑用 C 语言实现了一下,就没有问题了。把它打包成 deb 包发布到了内网中。打包推荐用 nfpm4,非常方便,传统的用 apt 工具链打包太复杂了。

  1. Proxychains 项目:https://github.com/rofl0r/proxychains-ng,我的博客:编译安装proxychains4 ↩︎
  2. Fun building shared libraries in Go, https://medium.com/@walkert/fun-building-shared-libraries-in-go-639500a6a669 ↩︎
  3. https://github.com/golang/go/issues/15538 ↩︎
  4. https://github.com/goreleaser/nfpm ↩︎