LyScript 插件集成的内置API函数可灵活的实现绕过各类反调试保护机制,前段时间发布的那一篇文章并没有详细讲解各类反调试机制的绕过措施,本次将补充这方面的知识点,运用LyScript实现绕过大多数通用调试机制,实现隐藏调试器的目的。

我们以此实现Patches如下函数:

  • IsDebuggerPresent
  • ZwQueryInformationProcess
  • CheckRemoteDebuggerPresent
  • PEB.IsDebugged
  • PEB.ProcessHeap.Flag
  • PEB.NtGlobalFlag
  • PEB.Ldr 0xFEEEFEEE filling
  • GetTickCount
  • ZwQuerySystemInformation
  • FindWindowA
  • FindWindowW
  • FindWindowExA
  • FindWindowExW
  • EnumWindows

首先第一步我们需要自己封装实现一个反汇编转机器码的函数,其作用是当用户传入汇编列表时,自动将其转为机器码并输出为列表格式。

from LyScript32 import MyDebug

# 传入汇编代码,得到对应机器码
def get_opcode_from_assemble(dbg_ptr,asm):
byte_code = bytearray() addr = dbg_ptr.create_alloc(1024)
if addr != 0:
asm_size = dbg_ptr.assemble_code_size(asm)
# print("汇编代码占用字节: {}".format(asm_size)) write = dbg_ptr.assemble_write_memory(addr,asm)
if write == True:
for index in range(0,asm_size):
read = dbg_ptr.read_memory_byte(addr + index)
# print("{:02x} ".format(read),end="")
byte_code.append(read)
dbg_ptr.delete_alloc(addr)
return byte_code
else:
return bytearray(0) # 传入汇编机器码得到机器码列表
def GetOpCode(dbg, Code):
ShellCode = [] for index in Code:
ref = get_opcode_from_assemble(dbg,index)
for opcode in ref:
ShellCode.append(opcode) return ShellCode if __name__ == "__main__":
dbg = MyDebug() connect = dbg.connect() ShellCode = GetOpCode(dbg, ["DB 0x64","mov eax,dword ptr ds:[18]","sub eax,eax","ret"]) print(ShellCode) dbg.close()

输出效果如下:

Patch_PEB

PEB结构存在许多反调试变量,首先我们需要先将这些变量填充为空。

# ----------------------------------------------
# By: LyShark
# Email: me@lyshark.com
# Project: https://github.com/lyshark/LyScript
# ---------------------------------------------- from LyScript32 import MyDebug # 传入汇编代码,得到对应机器码
def get_opcode_from_assemble(dbg_ptr,asm):
byte_code = bytearray() addr = dbg_ptr.create_alloc(1024)
if addr != 0:
asm_size = dbg_ptr.assemble_code_size(asm)
# print("汇编代码占用字节: {}".format(asm_size)) write = dbg_ptr.assemble_write_memory(addr,asm)
if write == True:
for index in range(0,asm_size):
read = dbg_ptr.read_memory_byte(addr + index)
# print("{:02x} ".format(read),end="")
byte_code.append(read)
dbg_ptr.delete_alloc(addr)
return byte_code
else:
return bytearray(0) # 传入汇编机器码得到机器码列表
def GetOpCode(dbg, Code):
ShellCode = [] for index in Code:
ref = get_opcode_from_assemble(dbg,index)
for opcode in ref:
ShellCode.append(opcode) return ShellCode def Patch_PEB(dbg):
PEB = dbg.get_peb_address(dbg.get_process_id())
if PEB == 0:
return 0 # 写出0 Patching PEB.IsDebugged
dbg.write_memory_byte(PEB + 0x2,GetOpCode(dbg,["db 0"])[0])
print("补丁地址: {}".format(hex(PEB+0x2))) # 写出0 Patching PEB.ProcessHeap.Flag
temp = dbg.read_memory_dword(PEB + 0x18)
temp += 0x10
dbg.write_memory_dword(temp, GetOpCode(dbg,["db 0"])[0])
print(("补丁地址: {}".format(hex(temp)))) # 写出0 atching PEB.NtGlobalFlag
dbg.write_memory_dword(PEB+0x68, 0)
print(("补丁地址: {}".format(hex(PEB+0x68)))) # 循环替换 Patch PEB_LDR_DATA 0xFEEEFEEE fill bytes about 3000 of them
addr = dbg.read_memory_dword(PEB + 0x0c) while addr != 0:
addr += 1 try:
b = dbg.read_memory_dword(addr)
c = dbg.read_memory_dword(addr + 4) # 仅修补填充运行
print(b)
if (b == 0xFEEEFEEE) and (c == 0xFEEEFEEE):
dbg.write_memory_dword(addr,0)
dbg.write_memory_dword(addr + 4, 0)
print("patch")
except Exception:
break if __name__ == "__main__":
dbg = MyDebug() connect = dbg.connect() Patch_PEB(dbg) dbg.close()

Patch_IsDebuggerPresent

该函数用于检测自身是否处于调试状态,其C系列代码如下所示,绕过此种方式很简单,只需要在函数头部写出ret指令即可。

#include <Windows.h>
#include <stdio.h> int _tmain(int argc, _TCHAR* argv[])
{
BOOL ref = IsDebuggerPresent();
printf("是否被调试: %d \n", ref); getchar();
return 0;
}

注意:此Api检查PEB中的值,因此如果修补PEB,则无需修补Api,这段绕过代码如下。

from LyScript32 import MyDebug

# 传入汇编代码,得到对应机器码
def get_opcode_from_assemble(dbg_ptr,asm):
byte_code = bytearray() addr = dbg_ptr.create_alloc(1024)
if addr != 0:
asm_size = dbg_ptr.assemble_code_size(asm)
# print("汇编代码占用字节: {}".format(asm_size)) write = dbg_ptr.assemble_write_memory(addr,asm)
if write == True:
for index in range(0,asm_size):
read = dbg_ptr.read_memory_byte(addr + index)
# print("{:02x} ".format(read),end="")
byte_code.append(read)
dbg_ptr.delete_alloc(addr)
return byte_code
else:
return bytearray(0) # 传入汇编机器码得到机器码列表
def GetOpCode(dbg, Code):
ShellCode = [] for index in Code:
ref = get_opcode_from_assemble(dbg,index)
for opcode in ref:
ShellCode.append(opcode) return ShellCode def Patch_IsDebuggerPresent(dbg):
# 得到模块句柄
ispresent = dbg.get_module_from_function("kernel32.dll","IsDebuggerPresent")
print(hex(ispresent)) if(ispresent <= 0):
print("无法得到模块基地址,请以管理员方式运行调试器.")
return 0 # 将反调试语句转为机器码
ShellCode = GetOpCode(dbg, ["DB 0x64", "mov eax,dword ptr ds:[18]", "sub eax,eax", "ret"])
print(ShellCode) flag = 0
for index in range(0,len(ShellCode)):
flag = dbg.write_memory_byte(ispresent + index,ShellCode[index])
if flag:
flag = 1
else:
flag = 0
return flag if __name__ == "__main__":
dbg = MyDebug() connect = dbg.connect() ref = Patch_IsDebuggerPresent(dbg)
print("补丁状态: {}".format(ref)) dbg.close()

当程序运行后会向IsDebuggerPresent函数写出返回,从而实现绕过调试的目的。

Patch_CheckRemoteDebuggerPresent

此Api调用ZwQueryInformationProcess因此通常不需要对两者进行修补。

from LyScript32 import MyDebug

# 传入汇编代码,得到对应机器码
def get_opcode_from_assemble(dbg_ptr,asm):
byte_code = bytearray() addr = dbg_ptr.create_alloc(1024)
if addr != 0:
asm_size = dbg_ptr.assemble_code_size(asm)
# print("汇编代码占用字节: {}".format(asm_size)) write = dbg_ptr.assemble_write_memory(addr,asm)
if write == True:
for index in range(0,asm_size):
read = dbg_ptr.read_memory_byte(addr + index)
# print("{:02x} ".format(read),end="")
byte_code.append(read)
dbg_ptr.delete_alloc(addr)
return byte_code
else:
return bytearray(0) # 传入汇编机器码得到机器码列表
def GetOpCode(dbg, Code):
ShellCode = [] for index in Code:
ref = get_opcode_from_assemble(dbg,index)
for opcode in ref:
ShellCode.append(opcode) return ShellCode def Patch_CheckRemoteDebuggerPresent(dbg):
# 得到模块句柄
ispresent = dbg.get_module_from_function("kernel32.dll","CheckRemoteDebuggerPresent")
print(hex(ispresent)) # 将反调试语句转为机器码
ShellCode = GetOpCode(dbg,
[
"mov edi,edi",
"push ebp",
"mov ebp,esp",
"mov eax,[ebp+0xc]",
"push 0",
"pop dword ptr ds:[eax]",
"xor eax,eax",
"pop ebp",
"ret 8"
]
) print(ShellCode) flag = 0
for index in range(0,len(ShellCode)):
flag = dbg.write_memory_byte(ispresent + index,ShellCode[index])
if flag:
flag = 1
else:
flag = 0
return flag if __name__ == "__main__":
dbg = MyDebug() connect = dbg.connect() ref = Patch_CheckRemoteDebuggerPresent(dbg)
print("写出状态: {}".format(ref)) dbg.close()

写出效果如下:

Patch_GetTickCount

GetTickCount返回(retrieve)从操作系统启动所经过(elapsed)的毫秒数,常用于定时计数,绕过方式只需初始化即可。

from LyScript32 import MyDebug

# 传入汇编代码,得到对应机器码
def get_opcode_from_assemble(dbg_ptr,asm):
byte_code = bytearray() addr = dbg_ptr.create_alloc(1024)
if addr != 0:
asm_size = dbg_ptr.assemble_code_size(asm)
# print("汇编代码占用字节: {}".format(asm_size)) write = dbg_ptr.assemble_write_memory(addr,asm)
if write == True:
for index in range(0,asm_size):
read = dbg_ptr.read_memory_byte(addr + index)
# print("{:02x} ".format(read),end="")
byte_code.append(read)
dbg_ptr.delete_alloc(addr)
return byte_code
else:
return bytearray(0) # 传入汇编机器码得到机器码列表
def GetOpCode(dbg, Code):
ShellCode = [] for index in Code:
ref = get_opcode_from_assemble(dbg,index)
for opcode in ref:
ShellCode.append(opcode) return ShellCode def Patch_GetTickCount(dbg):
# 得到模块句柄
ispresent = dbg.get_module_from_function("kernel32.dll","GetTickCount")
print(hex(ispresent)) # 将反调试语句转为机器码
ShellCode = GetOpCode(dbg,
[
"mov edx,0x7ffe0000",
"sub eax,eax",
"add eax,0xB0B1560D",
"ret"
]
) print(ShellCode) flag = 0
for index in range(0,len(ShellCode)):
flag = dbg.write_memory_byte(ispresent + index,ShellCode[index])
if flag:
flag = 1
else:
flag = 0
return flag if __name__ == "__main__":
dbg = MyDebug() connect = dbg.connect() ref = Patch_GetTickCount(dbg)
print("写出状态: {}".format(ref)) dbg.close()

写出效果如下:

Patch_ZwQueryInformationProcess

此函数打补丁需要跳转两次,原因是因为函数开头部分无法填充更多指令,需要我们自己来申请空间,并实现跳转。

# ----------------------------------------------
# By: LyShark
# Email: me@lyshark.com
# Project: https://github.com/lyshark/LyScript
# ---------------------------------------------- from LyScript32 import MyDebug # 传入汇编代码,得到对应机器码
def get_opcode_from_assemble(dbg_ptr,asm):
byte_code = bytearray() addr = dbg_ptr.create_alloc(1024)
if addr != 0:
asm_size = dbg_ptr.assemble_code_size(asm)
# print("汇编代码占用字节: {}".format(asm_size)) write = dbg_ptr.assemble_write_memory(addr,asm)
if write == True:
for index in range(0,asm_size):
read = dbg_ptr.read_memory_byte(addr + index)
# print("{:02x} ".format(read),end="")
byte_code.append(read)
dbg_ptr.delete_alloc(addr)
return byte_code
else:
return bytearray(0) # 传入汇编机器码得到机器码列表
def GetOpCode(dbg, Code):
ShellCode = [] for index in Code:
ref = get_opcode_from_assemble(dbg,index)
for opcode in ref:
ShellCode.append(opcode) return ShellCode # 获取指定位置前index条指令的长度
def GetOpCodeSize(dbg,address,index):
ref_size = 0 dasm = dbg.get_disasm_code(address,index)
for index in dasm:
count = dbg.assemble_code_size(index.get("opcode"))
ref_size += count
return ref_size def Patch_ZwQueryInformationProcess(dbg):
# 得到模块句柄
ispresent = dbg.get_module_from_function("ntdll.dll","ZwQueryInformationProcess")
print(hex(ispresent)) create_address = dbg.create_alloc(1024)
print("分配空间: {}".format(hex(create_address))) # 将反调试语句转为机器码
ShellCode = GetOpCode(dbg,
[
"cmp dword [esp + 8],7",
"DB 0x74",
"DB 0x06",
f"push {hex(ispresent)}",
"ret",
"mov eax,dword [esp +0x0c]",
"push 0",
"pop dword [eax]",
"xor eax,eax",
"ret 14"
]
) print(ShellCode) # 把shellcode写出到自己分配的堆中
flag = 0
for index in range(0,len(ShellCode)):
flag = dbg.write_memory_byte(create_address + index,ShellCode[index])
if flag:
flag = 1
else:
flag = 0 # 填充跳转位置
jmp_shellcode = GetOpCode(dbg,
[
f"push {hex(create_address)}",
"ret"
]
)
for index in range(0,len(jmp_shellcode)):
flag = dbg.write_memory_byte(ispresent + index,jmp_shellcode[index])
if flag:
flag = 1
else:
flag = 0 return flag if __name__ == "__main__":
dbg = MyDebug() connect = dbg.connect() ref = Patch_ZwQueryInformationProcess(dbg) print("补丁状态: {}".format(ref)) dbg.close()

这段代码运行后,首先会申请内存,然后将特定的一段机器码写出到此内存中。

内存写出以后,再将函数头部替换为跳转,这样一来当函数被调用,也就自动转向了。

Patch_FindWindow

FindWindow函数功能是取窗体句柄,有AW与Ex系列,使用同上方法替代即可。

# ----------------------------------------------
# By: LyShark
# Email: me@lyshark.com
# Project: https://github.com/lyshark/LyScript
# ---------------------------------------------- from LyScript32 import MyDebug
import ctypes # 传入汇编代码,得到对应机器码
def get_opcode_from_assemble(dbg_ptr,asm):
byte_code = bytearray() addr = dbg_ptr.create_alloc(1024)
if addr != 0:
asm_size = dbg_ptr.assemble_code_size(asm)
# print("汇编代码占用字节: {}".format(asm_size)) write = dbg_ptr.assemble_write_memory(addr,asm)
if write == True:
for index in range(0,asm_size):
read = dbg_ptr.read_memory_byte(addr + index)
# print("{:02x} ".format(read),end="")
byte_code.append(read)
dbg_ptr.delete_alloc(addr)
return byte_code
else:
return bytearray(0) # 传入汇编机器码得到机器码列表
def GetOpCode(dbg, Code):
ShellCode = [] for index in Code:
ref = get_opcode_from_assemble(dbg,index)
for opcode in ref:
ShellCode.append(opcode) return ShellCode def Patch_FindWindow(dbg):
# 得到模块句柄
FindWindowA = dbg.get_module_from_function("user32.dll","FindWindowA")
FindWindowW = dbg.get_module_from_function("user32.dll","FindWindowW")
FindWindowExA = dbg.get_module_from_function("user32.dll","FindWindowExA")
FindWindowExW = dbg.get_module_from_function("user32.dll","FindWindowExW")
print("A = {} w = {} exA = {} exW = {}".format(hex(FindWindowA),hex(FindWindowW),hex(FindWindowExA),hex(FindWindowExW))) # 将反调试语句转为机器码
ShellCode = GetOpCode(dbg,
[
"xor eax,eax",
"ret 0x8",
]
) ShellCodeEx = GetOpCode(dbg,
[
"xor eax,eax",
"ret 0x10",
]
)
# 写出
flag = 0
for index in range(0,len(ShellCode)):
flag = dbg.write_memory_byte(FindWindowA + index,ShellCode[index])
flag = dbg.write_memory_byte(FindWindowW + index,ShellCode[index])
if flag:
flag = 1
else:
flag = 0 for index in range(0,len(ShellCodeEx)):
flag = dbg.write_memory_byte(FindWindowExA + index,ShellCodeEx[index])
flag = dbg.write_memory_byte(FindWindowExW + index,ShellCodeEx[index])
if flag:
flag = 1
else:
flag = 0 return flag if __name__ == "__main__":
dbg = MyDebug() connect = dbg.connect() ref = Patch_FindWindow(dbg)
print("补丁状态: {}".format(ref)) dbg.close()

补丁应用会分别替换四个函数。

Patch_EnumWindows

枚举窗体的补丁与上方代码一致,此处就不再分析了。

如下案例,实现了在枚举窗体过程中实现弹窗,并不影响窗体的枚举。

from LyScript32 import MyDebug

# 传入汇编代码,得到对应机器码
def get_opcode_from_assemble(dbg_ptr,asm):
byte_code = bytearray() addr = dbg_ptr.create_alloc(1024)
if addr != 0:
asm_size = dbg_ptr.assemble_code_size(asm)
# print("汇编代码占用字节: {}".format(asm_size)) write = dbg_ptr.assemble_write_memory(addr,asm)
if write == True:
for index in range(0,asm_size):
read = dbg_ptr.read_memory_byte(addr + index)
# print("{:02x} ".format(read),end="")
byte_code.append(read)
dbg_ptr.delete_alloc(addr)
return byte_code
else:
return bytearray(0) # 传入汇编机器码得到机器码列表
def GetOpCode(dbg, Code):
ShellCode = [] for index in Code:
ref = get_opcode_from_assemble(dbg,index)
for opcode in ref:
ShellCode.append(opcode) return ShellCode # 获取指定位置前index条指令的长度
def GetOpCodeSize(dbg,address,index):
ref_size = 0 dasm = dbg.get_disasm_code(address,index)
for index in dasm:
count = dbg.assemble_code_size(index.get("opcode"))
ref_size += count
return ref_size def Patch_EnumWindows(dbg):
# 得到模块句柄
address = dbg.get_module_from_function("user32.dll","EnumWindows")
print(hex(address)) msg_box = dbg.get_module_from_function("user32.dll","MessageBoxA")
print(hex(msg_box)) create_address = dbg.create_alloc(1024)
print("分配空间: {}".format(hex(create_address))) # 找call地址,找到后取出他的内存地址
dasm_list = dbg.get_disasm_code(address,20)
call_addr = 0
call_next_addr = 0
for index in range(0,len(dasm_list)): # 如果找到了call,取出call地址以及下一条地址
if dasm_list[index].get("opcode").split(" ")[0] == "call":
call_addr = dasm_list[index].get("addr")
call_next_addr = dasm_list[index+1].get("addr")
print("call = {} call_next = {}".format(hex(call_addr),hex(call_next_addr))) # 将反调试语句转为机器码
ShellCode = GetOpCode(dbg,
[
"push 0",
"push 0",
"push 0",
"push 0",
f"call {hex(msg_box)}",
"mov eax,1",
"pop ebp",
"ret 10", f"call {hex(call_addr)}",
"pop ebp",
"ret 8"
]
) print(ShellCode) # 把shellcode写出到自己分配的堆中
flag = 0
for index in range(0,len(ShellCode)):
flag = dbg.write_memory_byte(create_address + index,ShellCode[index])
if flag:
flag = 1
else:
flag = 0 # 填充跳转位置
jmp_shellcode = GetOpCode(dbg,
[
f"push {hex(create_address)}",
"ret"
]
)
for index in range(0,len(jmp_shellcode)):
flag = dbg.write_memory_byte(call_addr + index,jmp_shellcode[index])
if flag:
flag = 1
else:
flag = 0 return flag if __name__ == "__main__":
dbg = MyDebug()
connect = dbg.connect() ref = Patch_EnumWindows(dbg) dbg.close()

输出效果如下:

LyScript 实现Hook隐藏调试器的更多相关文章

  1. iOS - 浅谈LLDB调试器

    摘要 LLDB是Xcode默认的调试器,它与LLVM编译器一起,带给我们更丰富的流程控制和数据检测的调试功能.平时用Xcode运行程序,实际走的都是LLDB.熟练使用LLDB,可以让你debug事半功 ...

  2. Linux C编程学习之开发工具2---GDB调试器

    简介 GDB是一个功能强大的交互式程序调试工具,主要工作在字符界面下. GDB不仅可以用来调试C/C++ 语言编写的程序,还可以用来调试 Pascal.Objective-C,以及Fortran等语言 ...

  3. C#调试器导航

    本快速入门演示如何在 Visual Studio 调试会话中导航,以及如何在会话中查看和更改程序状态. 本 快速入门适用于不熟悉用 Visual Studio 进行调试的开发人员,以及要详细了解在 V ...

  4. iOS LLDB调试器和断点调试

    技巧一:运行时修改变量的值 你以前怎么验证是不是某个变量的值导致整段程序不能正常工作?修改代码中的变量的值,然后cmd+r重新启动app?现在你不需要这么做了,只需要设置一个断点,当程序在这进入调试模 ...

  5. 第二章排错的工具:调试器Windbg(下)

    感谢博主 http://book.51cto.com/art/200711/59874.htm 2.2  读懂机器的语言:汇编,CPU执行指令的最小单元2.2.1  需要用汇编来排错的常见情况 汇编是 ...

  6. 32位汇编第一讲x86和8086的区别,以及OllyDbg调试器的使用

    32位汇编第一讲x86和8086的区别,以及OllyDbg调试器的使用 一丶32位(x86也称为80386)与8086(16位)汇编的区别 1.寄存器的改变 AX 变为 EAX  可以这样想,16位通 ...

  7. 学习笔记之--认识Xcode中的重要成员:lldb调试器

    之前对lldb调试器了解比较少,平时主要用来打印日志和暂定时用鼠标查看属性数据以及使用p po一些简单的命令语句. 今天看了一些关于lldb的文章,顿时觉得之前对它了解太少了,原来它还有那么多的功能. ...

  8. .NET dnSpy 程序集编辑器,反编译器和调试器

    https://github.com/0xd4d/dnSpy https://github.com/0xd4d/dnSpy/releases/ dnSpy是反向工程.NET程序集的工具.它包括一个反编 ...

  9. 使用 JDB 调试器

    您可以使用调试 applet 或应用程序的 jdb 命令来调试 Servlet. 为了调试一个 Servlet,我们可以调试 sun.servlet.http.HttpServer,然后把它看成是 H ...

  10. 手把手教你写Windows 64位平台调试器

    本文网页排版有些差,已上传了doc,可以下载阅读.本文中的所有代码已打包,下载地址在此. ------------------------------------------------------- ...

随机推荐

  1. 记一次go应用在k8s pod已用内存告警不准确分析

    版权说明: 本文章版权归本人及博客园共同所有,转载请在文章前标明原文出处( https://www.cnblogs.com/mikevictor07/p/17968696.html ),以下内容为个人 ...

  2. WCF 动态调用 动态代理

    关键词:WCF动态调用.动态调用WCF.WCF使用动态代理精简代码架构.使用反射执行WCF接口 代码地址: https://gitee.com/s0611163/DynamicWCF https:// ...

  3. .net 温故知新【17】:Asp.Net Core WebAPI 中间件

    一.前言 到这篇文章为止,关于.NET "温故知新"系列的基础知识就完结了,从这一系列的系统回顾和再学习,对于.NET core.ASP.NET CORE又有了一个新的认识. 不光 ...

  4. java调用百度地图接口输入名称查经度纬度

    如何注册ak号请参考https://blog.csdn.net/weixin_42512684/article/details/115843299 package manager.tool; impo ...

  5. CommonJS 和 ES6 Module 究竟有什么区别?

    https://juejin.im/post/5e5f10176fb9a07cd443c1e2

  6. 机器学习笔记(二)使用paddlepaddle,再探波士顿房价预测

    目标 用paddlepaddle来重写之前那个手写的梯度下降方案,简化内容 流程 实际上就做了几个事: 数据准备:将一个批次的数据先转换成nparray格式,再转换成Tensor格式 前向计算:将一个 ...

  7. 基于AHB_BUS的eFlash控制器设计-软硬件系统设计

    eFlash软硬件系统设计 软硬件划分 划分好软硬件之后,IP暴露给软件的寄存器和时序如何? 文档体系:详细介绍eflash控制器的设计文档 RTL代码编写:详细介绍eflash控制器的RTL代码 1 ...

  8. [转帖]ntp导致的时钟回拨

    https://zhuanlan.zhihu.com/p/587313130 我们的服务器时间校准一般是通过ntp进程去校准的.但由于校准这个动作,会导致时钟跳跃变化的现象.而这种情况里面,往往回拨最 ...

  9. [转帖]058、集群优化之PD

    PD调度基本概念 调度流程 调度中还有这还缺来了merge,例如合并空region. store: 基本信息,容量,剩余空间,读写流量等 region: 范围,副本分布,副本状态,数据量,读写流量等 ...

  10. [转帖]clickhouse使用clickhouse-keeper代替zookeeper

    目录 异常现象: 1. clickhouse的异常日志 2. 追踪对应节点的zookeeper日志 使用clickhouse-keeper代替 zookeeper的步骤: 1: 准备 clickhou ...