![](/style/images/good.png)
![](/style/images/bad.png)
【Pwn 笔记】Glibc 利用中那些偏门的技巧
source link: https://binlep.github.io/2020/04/15/%E3%80%90Pwn%20%E7%AC%94%E8%AE%B0%E3%80%91glibc%20%E5%88%A9%E7%94%A8%E4%B8%AD%E9%82%A3%E4%BA%9B%E5%81%8F%E9%97%A8%E7%9A%84%E6%8A%80%E5%B7%A7/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
记录 Pwn 题中的各种骚操作
泄露程序基址
修改 _dl_rtld_libname
通过调试可以看出这是一个结构体,存在于 ld.so 文件里
pwndbg> p _dl_rtld_libname
$10 = {
name = 0x55e4fceaf238 "/lib64/ld-linux-x86-64.so.2",
next = 0x7fdc9baf4fe0 <newname>,
dont_free = 0
}
pwndbg> x/6gx &_dl_rtld_libname
0x7fdc9baf5030 <_dl_rtld_libname>: 0x000055e4fceaf238 0x00007fdc9baf4fe0
0x7fdc9baf5040 <_dl_rtld_libname+16>: 0x0000000000000000 0x0000000000000000
0x7fdc9baf5050 <max_dirnamelen>: 0x000000000000001a 0x0000000000000000
读取该地址上面的内容可以泄露出程序基地址,该地址为主程序加载 ld 文件时存储 ld 文件名的地址
_dl_rtld_libname
和 libc 基地址的偏移是不固定的,不同的 ld 文件有不同的偏移,需要手测或者爆破
程序基地址为0x55e4fceaf238 - 0x238 == 0x55e4fceaf000
这个地址的偏移一般离程序基地址来说始终是 0x238
通过 stdin stdout stderr
这里我只测了 stdin,就拿它来说吧
pwndbg> stack 50
00:0000│ rsp 0x7fbe027a4fb0 → 0x55f001f75020 → 0x7fbe027a5a00 (_IO_2_1_stdin_) ← 0xfbad208b
01:0008│ 0x7fbe027a4fb8 → 0x7fbe027a5408 (__check_rhosts_file) ← 0x1
02:0010│ 0x7fbe027a4fc0 → 0x7fbe027a5340 (opterr) ← 0x100000001
03:0018│ 0x7fbe027a4fc8 → 0x7fbe027a56e0 (__ctype32_toupper) → 0x7fbe025583c0 (_nl_C_LC_CTYPE_toupper+512) ← add byte ptr [rax], al
04:0020│ 0x7fbe027a4fd0 → 0x7fbe027a5c28 (__realloc_hook) ← 0x0
05:0028│ 0x7fbe027a4fd8 → 0x7fbe02c19740 (_dl_argv) → 0x7ffdf6dc47a8 → 0x7ffdf6dc628c ← 0x4c00706172632f2e /* './crap' */
06:0030│ 0x7fbe027a4fe0 → 0x7fbe027aaa20 (rpc_createerr) ← 0x0
07:0038│ 0x7fbe027a4fe8 ← 0xffffffffffffff78
08:0040│ 0x7fbe027a4ff0 ← 0x0
... ↓
14a:0a50│ rbx r14 0x7fbe027a5a00 (_IO_2_1_stdin_) ← 0xfbad208b
可以看到 0x7f8a3b926fb0 地址处存着 bss 端上对应的 stdin 的地址
这个地址的权限是可读,用 vmmap 可以查到:
0x7fbe023ba000 0x7fbe025a1000 r-xp 1e7000 0 /lib/x86_64-linux-gnu/libc-2.27.so
0x7fbe025a1000 0x7fbe027a1000 ---p 200000 1e7000 /lib/x86_64-linux-gnu/libc-2.27.so
0x7fbe027a1000 0x7fbe027a5000 r--p 4000 1e7000 /lib/x86_64-linux-gnu/libc-2.27.so
而_IO_2_1_stdin_
的地址里这个存着 stdin 地址的地址的距离大约是 0xa50
可以从 0xa00 开始爆破,多尝试几次应该就能出来
stdout stderr 同理
执行任意地址
若程序有类似这样的功能:
int __cdecl main(int argc, const char **argv, const char **envp)
{
unsigned int buf[10]; // [rsp+20h] [rbp-30h]
unsigned __int64 v5; // [rsp+48h] [rbp-8h]
v5 = __readfsqword(0x28u);
write(1u, "addr:", 5uLL);
read(0, buf, 0xC8uLL);
write(1u, "data:", 5uLL);
read(0, buf[0], 0x18uLL);
return 0;
}
修改 .fini_array
可以修改.fini_array
的值为任意地址,不过.fini_array
只能用一次
修改 __libc_atexit
调用方法:
elf.sym['__elf_set___libc_atexit_element__IO_cleanup__']
程序是静态编译的时候,可以修改__libc_atexit
来实现任意地址跳转
这个可以无限次使用,在 ida 里也能找到它
利用 _dl_fini 执行函数
具体的利用在 exit 函数的_dl_fini
函数里:
0x7f22d9fdca02 <_dl_fini+98> lea rdi, [rip + 0x217f5f] <0x7f22da1f4968>
0x7f22d9fdca09 <_dl_fini+105> call qword ptr [rip + 0x218551] <0x7f22d9c2a440>
rdi: 0x7f22da1f4968 (_rtld_global+2312) ← 0x68732f6e69622f /* '/bin/sh' */
rsi: 0x0
rdx: 0x7f22d9fdc9a0 (_dl_fini) ← push rbp
rcx: 0x1
call qword ptr [rip + 0x218551]
是_rtld_global
上面的地址,rdi 也是用的_rtld_global
上面的地址
不同的 ld 对应的_rtld_global
不一样,需要手调爆破
修改 _stack_chk_fail 内部函数的 .got.plt 表
__libc_message 函数
该函数内部函数按调用顺序排列如下:
strchrnul 函数
mempcpy 函数
strlen_ifunc 函数
got 表内有可改函数就可以改为 main 函数的地址来进行无限循环
制造栈迁移
修改 .fini_array 为 leave ; ret
这个方法可以使程序跳转到可写地址,达到栈迁移的作用
具体实现如下:
addr_fini_array + 0x18 --> addr_fini_array + 0x18
...
在 addr_fini_array + 0x20 及之后填充完整的 rop 链
...
addr_fini_array - 0x10 --> addr_fini_array + 0x10
addr_fini_array - 0x08 --> addr_fini_array + 0x18
addr_fini_array - 0x00 --> rop_leave_ret
返回地址 --> addr___libc_start_main_1072[不定,这就是正常程序在主函数 ret 结束时所到达的 __libc_start_main 地址]
之后栈就会被转移到addr_fini_array + 0x20
利用 setcontext
这个函数载 libc 里,函数内部汇编利用点如下:
0000000000052070 <setcontext@@GLIBC_2.2.5>:
52070: 57 push rdi
52071: 48 8d b7 28 01 00 00 lea rsi,[rdi+0x128]
52078: 31 d2 xor edx,edx
5207a: bf 02 00 00 00 mov edi,0x2
5207f: 41 ba 08 00 00 00 mov r10d,0x8
52085: b8 0e 00 00 00 mov eax,0xe
5208a: 0f 05 syscall
5208c: 5f pop rdi
5208d: 48 3d 01 f0 ff ff cmp rax,0xfffffffffffff001
52093: 73 5b jae 520f0 <setcontext@@GLIBC_2.2.5+0x80>
52095: 48 8b 8f e0 00 00 00 mov rcx,QWORD PTR [rdi+0xe0]
5209c: d9 21 fldenv [rcx]
5209e: 0f ae 97 c0 01 00 00 ldmxcsr DWORD PTR [rdi+0x1c0]
520a5: 48 8b a7 a0 00 00 00 mov rsp,QWORD PTR [rdi+0xa0]
520ac: 48 8b 9f 80 00 00 00 mov rbx,QWORD PTR [rdi+0x80]
520b3: 48 8b 6f 78 mov rbp,QWORD PTR [rdi+0x78]
520b7: 4c 8b 67 48 mov r12,QWORD PTR [rdi+0x48]
520bb: 4c 8b 6f 50 mov r13,QWORD PTR [rdi+0x50]
520bf: 4c 8b 77 58 mov r14,QWORD PTR [rdi+0x58]
520c3: 4c 8b 7f 60 mov r15,QWORD PTR [rdi+0x60]
520c7: 48 8b 8f a8 00 00 00 mov rcx,QWORD PTR [rdi+0xa8]
520ce: 51 push rcx
520cf: 48 8b 77 70 mov rsi,QWORD PTR [rdi+0x70]
520d3: 48 8b 97 88 00 00 00 mov rdx,QWORD PTR [rdi+0x88]
520da: 48 8b 8f 98 00 00 00 mov rcx,QWORD PTR [rdi+0x98]
520e1: 4c 8b 47 28 mov r8,QWORD PTR [rdi+0x28]
520e5: 4c 8b 4f 30 mov r9,QWORD PTR [rdi+0x30]
520e9: 48 8b 7f 68 mov rdi,QWORD PTR [rdi+0x68]
520ed: 31 c0 xor eax,eax
520ef: c3 ret
520f0: 48 8b 0d 71 8d 39 00 mov rcx,QWORD PTR [rip+0x398d71] # 3eae68 <h_errlist@@GLIBC_2.2.5+0xdc8>
520f7: f7 d8 neg eax
520f9: 64 89 01 mov DWORD PTR fs:[rcx],eax
520fc: 48 83 c8 ff or rax,0xffffffffffffffff
52100: c3 ret
52101: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0]
52108: 00 00 00
5210b: 0f 1f 44 00 00 nop DWORD PTR [rax+rax*1+0x0]
该样例来自于libc6_2.27-3ubuntu1_amd64.so
,可以看到在setcontext+0x35
处可以修改 rsp 的值
底下也可以修改基本所有可以用到的寄存器,不过这个传参方式是 rdi,不同的 libc 库的传参寄存器可能不同
下面的例子来自于libc6_2.31-0ubuntu6_amd64.so
,传参方式是 rdx
0000000000045b70 <setcontext>:
45b70: 57 push rdi
45b71: 48 8d b7 28 01 00 00 lea rsi,[rdi+0x128]
45b78: 31 d2 xor edx,edx
45b7a: bf 02 00 00 00 mov edi,0x2
45b7f: 41 ba 08 00 00 00 mov r10d,0x8
45b85: b8 0e 00 00 00 mov eax,0xe
45b8a: 0f 05 syscall
45b8c: 5a pop rdx
45b8d: 48 3d 01 f0 ff ff cmp rax,0xfffffffffffff001
45b93: 73 5b jae 45bf0 <setcontext+0x80>
45b95: 48 8b 8a e0 00 00 00 mov rcx,QWORD PTR [rdx+0xe0]
45b9c: d9 21 fldenv [rcx]
45b9e: 0f ae 92 c0 01 00 00 ldmxcsr DWORD PTR [rdx+0x1c0]
45ba5: 48 8b a2 a0 00 00 00 mov rsp,QWORD PTR [rdx+0xa0]
45bac: 48 8b 9a 80 00 00 00 mov rbx,QWORD PTR [rdx+0x80]
45bb3: 48 8b 6a 78 mov rbp,QWORD PTR [rdx+0x78]
45bb7: 4c 8b 62 48 mov r12,QWORD PTR [rdx+0x48]
45bbb: 4c 8b 6a 50 mov r13,QWORD PTR [rdx+0x50]
45bbf: 4c 8b 72 58 mov r14,QWORD PTR [rdx+0x58]
45bc3: 4c 8b 7a 60 mov r15,QWORD PTR [rdx+0x60]
45bc7: 48 8b 8a a8 00 00 00 mov rcx,QWORD PTR [rdx+0xa8]
45bce: 51 push rcx
45bcf: 48 8b 72 70 mov rsi,QWORD PTR [rdx+0x70]
45bd3: 48 8b 7a 68 mov rdi,QWORD PTR [rdx+0x68]
45bd7: 48 8b 8a 98 00 00 00 mov rcx,QWORD PTR [rdx+0x98]
45bde: 4c 8b 42 28 mov r8,QWORD PTR [rdx+0x28]
45be2: 4c 8b 4a 30 mov r9,QWORD PTR [rdx+0x30]
45be6: 48 8b 92 88 00 00 00 mov rdx,QWORD PTR [rdx+0x88]
45bed: 31 c0 xor eax,eax
45bef: c3 ret
45bf0: 48 8b 0d 79 f2 36 00 mov rcx,QWORD PTR [rip+0x36f279] # 3b4e70 <.got+0x110>
45bf7: f7 d8 neg eax
45bf9: 64 89 01 mov DWORD PTR fs:[rcx],eax
45bfc: 48 83 c8 ff or rax,0xffffffffffffffff
45c00: c3 ret
45c01: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0]
45c08: 00 00 00
45c0b: 0f 1f 44 00 00 nop DWORD PTR [rax+rax*1+0x0]
主要利用的地址如下:
45ba5: 48 8b a2 a0 00 00 00 mov rsp,QWORD PTR [rdx+0xa0]
45bc7: 48 8b 8a a8 00 00 00 mov rcx,QWORD PTR [rdx+0xa8]
45bce: 51 push rcx
45c00: c3 ret
利用方法,以上面libc6-amd64_2.31-0ubuntu6_i386.so
为例:
按如下方式构造即可完成栈迁移:
<地址 A: 可读写> <地址 B: 目标地址>
QWORD PTR [rdx+0xa0] <地址 A: 可读写>
QWORD PTR [rdx+0xa8] <pop rsp ; ret 的地址>
这样程序最终就会跳到地址 B
执行代码
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK