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. KVM--基本管理

    #!/bin/bash iso=/iso/CentOS-7-x86_64-Minimal-1708.iso #本机镜像文件位置 centos=centos7.0 #操作系统版本 disk_path=/ ...

  2. VA21 创建报价单

    1.前台 报价是提供给客户交付货物或服务的一份文件,客户想要知道产品价格以及装运时间. 事务代码VA21 输入报价单类型和销售组织.分销渠道.产品组 输入售达方和行项目的物料信息,订购数量等信息 输入 ...

  3. 使用Expression代替反射读取IDataReader或IDataRecord给实体类赋值

    ExpressionMapper代码 using System; using System.Collections.Concurrent; using System.Collections.Gener ...

  4. Java | Spring Boot统一日志框架

    在项目开发中,日志十分的重要,不管是记录运行情况还是定位线上问题,都离不开对日志的分析.在 Java 领域里存在着多种日志框架,如 JCL.SLF4J.Jboss-logging.jUL.log4j. ...

  5. Python | BitMap算法及其实现

    BitMap概述 本文介绍 BitMap 算法的应用背景,算法思想和相关实现细节. 概括而言,BitMap 主要用来解决海量数据中元素查询,去重.以及排序等问题.这里对海量数据场景的强调,似乎暗示了这 ...

  6. Codeforces Round #690 (Div. 3) (简单题解记录)

    Codeforces Round #690 (Div. 3) 1462A. Favorite Sequence 简单看懂题即可,左边输出一个然后右边输出一个. void solve() { int n ...

  7. 无需修改代码,用 fcapp.run 运行你的 REST 应用

    作者 | 阿里云 Serverless 技术研发 落语 背景 阿里云函数计算产品在较早的时候支持了HTTP触发器能力,支持用户使用 HTTP 协议进行函数调用.函数计算后端通过一个共享的 APISer ...

  8. SpringCloud学习 系列九、Ribbon

    系列导航 SpringCloud学习 系列一. 前言-为什么要学习微服务 SpringCloud学习 系列二. 简介 SpringCloud学习 系列三. 创建一个没有使用springCloud的服务 ...

  9. 真实感渲染:WebGPU介绍和使用光栅化管线绘制一个三角形

    大家好~本课程为"真实感渲染"的线上课程,从0开始,介绍相关的图形学算法和数学基础,给出详细的数学推导.伪代码和实现代码,最终带领大家开发出基于物理的渲染器 线上课程资料: 本节课 ...

  10. Ribbon默认负载均衡规则替换为NacosRule

    近期博主在参与一个 Spring Cloud 搭建,版本为 Hoxton.SR12,服务注册发现组件为 Nacos 的老项目时,发现项目负载均衡组件 Ribbon 的负载均衡规则在某些场景下不够完美, ...