( 原文地址:https://0xffff.one/d/337 )

前言·

首先不知道什么是glibc的可以先看看这里,或这里。如果看了还是不知道的话可能下面就不适合你看了 - -

然后,这篇文章会大概讲一下怎么编译一个带debug_info的glibc以及编了以后要怎么用。

以上。

正文·

首先,第一步是要得到glibc的源码,可以在这里下载,建议先下载和自己系统相同版本的(这里假设你的系统是Linux,不过如果是Win**ws的话编来有个**用啊 - -)

如果要看自己系统的libc版本的话可以用命令(我这里的是ubuntu,其他的应该也一样吧,方法不唯一)

1
ls -l /lib/ld-linux.so.2

像我是Ubuntu18.04的话版本是2.27。

下载好以后解压然后cd进去(以下操作都是在这个目录或里面的子目录里面进行),然后先创建几个文件夹

1
mkdir build build32 x64 x86

其中

  • build目录是用来放编译64位版本的中间文件的
  • build32目录是用来放编译32位版本的中间文件的
  • x64目录是编译好64位的版本后install的位置
  • x86目录是编译好32位的版本后install的位置 (名字和位置可以改,知道哪个是哪个就好了)

编译64位的glibc·

(用过make的话下面都会看得懂的 - -) 首先进入build目录,就是编译64位的目录,然后configure一下,命令是:

PS:记得修改/path/to/install/glibc/x64为刚才新建的x64文件夹的绝对路径

1
2
3
4
5
# in glibc-x.xx/build/
CC="gcc" CXX="g++" \
CFLAGS="-g -g3 -ggdb -gdwarf-4 -Og -Wno-error -fno-stack-protector" \
CXXFLAGS="-g -g3 -ggdb -gdwarf-4 -Og -Wno-error -fno-stack-protector" \
../configure --prefix=/path/to/install/glibc/x64 --disable-werror

上面的"-g -g3 -ggdb -gdwarf-4"指的是加上各种debug_info;“-Og"指不进行优化,这样的话编译出来的东西人才能更好地看懂;”-Wno-error “和” --disable-werror"是防止一些奇奇怪怪但有不怎么重要的error的(其实好像没什么用- -);“-fno-stack-protector"是我编2.23版本时遇到某一个error的时候加上的,如果编系统一样版本的话应该用不上(2023.10.17注:还是建议删掉,我在编2.34的时候发现会引发奇怪的报错)。 “–prefix=/path/to/install/glibc/x64"这里要改成安装的目录,上面按着我的来的话就是那个新建的x64文件夹,注意这个一定要写而且不能写成跟系统安装的一样(不能写到”/usr”,这个非常重要😅,不然会发生什么我也不知道)

configure成功以后就是make(注意有Error的话不算成功)

1
2
# 可根据电脑配置开多线程 -> make -j n  (n是线程数,如果电脑很闲的话建议是核数的两倍)
make

make成功以后就是make install(注意有Error的话不算成功)

1
make install

install完以后先看看安装的目录,如果有下面的东西的话说明应该是安装成功了。

然后build这个目录可以make clean删掉(如果你磁盘空间够大的话当我没说🤔)

编译32位的glibc·

方法基本上跟64位的一样,只是configure要变一下,目录也要换成build32。命令:

PS:记得修改/path/to/glibc/result/glibc-x.xx/x86为刚才新建的x86文件夹的绝对路径

1
2
3
4
5
6
7
8
9
10
11
12
    cd build32
# configure 换一下,具体说明下面说
CC="gcc -m32" CXX="g++ -m32" \
CFLAGS="-g -g3 -ggdb -gdwarf-4 -Og -Wno-error -fno-stack-protector" \
CXXFLAGS="-g -g3 -ggdb -gdwarf-4 -Og -Wno-error -fno-stack-protector" \
../configure --prefix=/path/to/install/glibc/x86 --host=i686-linux-gnu --disable-werror
# configure成功后
make
# make成功后
make install
# install成功后,也可以直接删掉build32目录
make clean

上面configure中加多了几个参数:“-m32"是指明gcc/g++编译32位;还有” --host=i686-linux-gnu"指明架构(应该是吧- -),这里不写i386是因为会报错(应该是太旧了)。然后为了i686这个东西还要多安装一些东西,不然会有一些奇奇怪怪的错(其实不知道是不是要全装的,反正嫌麻烦一次性全装了)

1
sudo apt install binutils-i686-gnu gcc-i686-linux-gnu binutils-i686-gnu-dbg g++-i686-linux-gnu

如无以外安装完后x86目录跟上面的一样。然后build32这个目录可以删掉。

装好后注意源码不要删,不然千辛万苦弄出来的debug_info就没了(build的可以删)。而且位置最好别改,不然。。。会比较麻烦(后面也会说一下)

测试装好了能不能用·

先说明一下简单的原理。其实就是换库。

在上面安装好的文件中,有两个特别有用的文件,ld-linux.so.2(64位是ld-linux-x86-64.so.2) 和libc.so.6,(没有这两个的话可能就是没装成功了)

其中libc.so.6就是主要的库,我们平时用的一些库函数就是在这里面;ld-linux.so.2里面是放一些加载libc.so.6的函数(大概意思应该是这样吧)。实际上是两个库都要换的,但因为在编译时(注意是编译时)换了ld-linux.so.2的话,它会自动换libc.so.6,所以如果是编译过程中的换库的话只用换ld-linux.so.2就好。

下面是测试正文:首先随便写一个C的代码(hello world之类的,假设取名叫a.c好了),然后用下面命令编译("–dynamic-linker="后面的路径自己换成对应的):

1
2
3
4
5
6
# 32位
gcc a.c -m32 -z lazy -g -o a \
-Wl,--dynamic-linker=/path/to/ld-linux.so.2/installed
# 64位
gcc a.c -z lazy -g -o a \
-Wl,--dynamic-linker=/path/to/ld-linux-x86-64.so.2/installed

编出来的东西能运行的话说明可以了。

编译不同版本的glibc·

因为太新太旧等问题,编译不同于自己系统的glibc的话会有各种问题。通常来讲,编译比自己系统新的版本的话基本上是没什么问题的(除了太新的);而比自己系统旧的版本的话,越旧问题越多。

下面是在ubuntu18.04中编译时遇到的一些问题就解决方法:

  • 2.31-2.33的librt__clock_*未定义引用:在/path/to/install/glibc/malloc/Depend新加一行写上rt,参考这里的BUG报告和这里的PATCH

  • 2.36的dl-cachestrcpy未定义:config时把Og改成O2,参考这里的BUG报告,虽然某些代码优化了,但总比没有好。你也可以自己在文件里自己写一个,但后面有多个文件都会报这个错误,如果你都能自己写的话。。。

。。。 。。。(算了太麻烦了有人看再写吧 - -)

编译出的带debug_info的glibc有什么用·

首先一个是因为没事找事 (编一个标准库听起来很帅的感觉有没有,划掉)

其实是有些程序需要调试标准库里面的一些函数的时候有源码和一些结构信息的话会看着明显和好看一点,主要是用在调试上的,所以要借助调试的工具(我的是gdb)。下面放几个图就算了 - - (我的gdb因为装了插件所以跟原版的可能有些不一样,不过都是差不多的)

已编译好的程序的换库·

这个就要一点技巧了🤔

首先要先创建一些符号链接:把安装好的32位的ld-linux.so.2链接到一个绝对路径有18个符号(ASCII)的地方,64位的ld-linux-x86-64.so.2链接到一个绝对路径有27个符号(ASCII)的地方,为什么有这个符号数量的限制?看下面。链接成什么名字自己取就好了,比如我的就:

搞好上面的之后就看那个编译好的程序,用vim打开(当然其他编辑器也可以)。32位程序的话搜索"/lib/ld-linux.so.2"(64位的话搜索"/lib/ld-linux-x86-64.so.2"),然后把这个东西改成上面连接号的文件的绝对路径,刚才为什么有字数限制就是因为这里改的字数要是一样的,不然会改乱了后面的程序就运行不起来。

改好后正常是运行不起来的(如果换的版本是跟系统一样的话可能可以运行,但其实是没有换到库的),原因是到这里只换了ld-linux.so.2这个链接用的库,没有换libc.so.6这个主体。所以要换这个库的话还要改以下环境变量(建议运行时加在前面,不要export,我也没试过,不知道会怎么样),加个"LD_LIBRARY_PATH=装好的glibc的lib目录"

在gdb中换库·

首先在vim中按上面的方法改好ld这个库,然后gdb打开,同样改一下环境变量(注意是打开后在gdb里面改,不是打开gdb时改),命令:

1
2
# in gdb
(gdb) set env LD_LIBRARY_PATH=装好的glibc的lib目录

然后库就换好了。

有一种情况是装好后源码移了位置(或者在别的机子上编好了移过来用)的,这样的话gdb会读不到debug_symbol的,这样的话就要重新设置一下源码的位置,用gdb的"directory"(可简写"dir")设置(怎么用的话google一下吧)。但是directory有个问题是它不会遍历子目录,所以如果要加整个glibc的话就要写脚本遍历(你很闲的话一个个加也是可以的🤔)。下面是我写的py脚本(可以参考一下或者改一下直接用也可以,下面会说怎么改)(而且要能用python的gdb才行,可以自己编):

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#! -*- coding:utf8 -*-
import gdb
import os

def gdbDir_rec(f):
fs = os.listdir(f)
#print(f)
result = []
for f1 in fs:
tmp_path = os.path.join(f,f1)
if os.path.isdir(tmp_path):
#gdb.execute('set dir ' + tmp_path,True,True)
result.append(tmp_path)
result += gdbDir_rec(tmp_path)
return result

# arg in path of glibc installed(32/64), and path/.. is root of glibc source
class Gcdir(gdb.Command):
def __init__(self):
super(self.__class__, self).__init__("gcdir", gdb.COMMAND_USER)

def invoke(self, args, from_tty):
argv = gdb.string_to_argv(args)
if len(argv) != 1:
raise gdb.GdbError('argv error!')
if args[-1] == '/':
args += '../'
else:
args += '/../'
fin_path = gdbDir_rec(args)
fin_path = ':'.join(fin_path)
gdb.execute('set dir ' + fin_path,True,True)
print('glibc -> '+args)

class Gcinit(gdb.Command):
def __init__(self):
super(self.__class__, self).__init__("gcinit", gdb.COMMAND_USER)

def invoke(self, args, from_tty):
argv = gdb.string_to_argv(args)
if len(argv)==1 and (argv[0]=='-h' or argv[0]=='--help'):
print('-m32 version ---> 32 bit version of glibc ')
print('-m64 version ---> 64 bit version of glibc ')
print('-p path ---> set path of glibc installed ')
if len(argv) != 2:
raise gdb.GdbError('argv error!')

# 这里设置好放各版本glibc的总目录
glibc_root = '/home/tover/Lab/gnu_c/glibc/glibc-' # 这里把前缀顺便加上去了
if argv[0]=='-m32':
fin_path = glibc_root+argv[1]+'/x86/'
elif argv[0]=='-m64':
fin_path = glibc_root+argv[1]+'/x64/'
elif argv[0]=='-p':
fin_path = argv[1]
if fin_path[-1] != '/':
fin_path += '/'
else:
raise gdb.GdbError('input error!')
print(fin_path)
gdb.execute('set env LD_LIBRARY_PATH= '+fin_path+'lib/',True,True)
gdb.execute('gcdir '+fin_path,True,True)

Gcdir()
Gcinit()

## example usage(in gdb):
# gcdir /home/tover/Lab/glibc/glibc-2.23/x86
#
# gcinit -m32 2.23
# gcinit -p /home/tover/Lab/glibc/glibc-2.23/x86

用法就是在gdb中gcdir加安装的库的目录(自动递归遍历子目录)。另外还写了个设环境变量的,用法上面也有,这个需要改一下 glibc_root 换成自己对应的的glibc目录,而且目录结构是 “glibc_root(要改的那个)->各版本的glibc->安装目录(名字要是x64/x86,不是的话改一下脚本就好了)”。

脚本可以随便放一个文件,然后在 “~/.gdbinit” 这个文件(没有的话新建一个)激活一下:

1
2
#这里换成脚本放的位置
source /path/to/script

Ps: 经过我的测试,32位的可以正常运行,64位的话直接用gdb会有奇怪的错误,但是attach进去的话(要设LD_LIBRARY_PATH)可以正常使用(怎么attach的话google一下 - -)

这是结尾·

其实有个叫 libc-dbg 的东西的🤔

1
2
sudo apt-get install libc-dbg
sudo apt-get install libc-dbg:i386

2023.10.17注:参考这里,其实还可以用ld-linux.so去换库,比如

1
./ld-linux-x86-64.so.2 --library-path /path/to/libc.so.6 ./pwn

另外编译好的程序换库也可以试试patchelf参考