LyScript实现Hook隐藏调试器的方法详解

 更新时间:2022年09月18日 08:00:58   作者:lyshark  
LyScript 插件集成的内置API函数可灵活的实现绕过各类反调试保护机制。本文将运用LyScript实现绕过大多数通用调试机制,实现隐藏调试器的目的,需要的可以参考一下

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隐藏调试器的方法详解的详细内容,更多关于LyScript Hook隐藏调试器的资料请关注脚本之家其它相关文章!

相关文章

  • Python中sorted()用法案例代码

    Python中sorted()用法案例代码

    sorted() 作为 Python 内置函数之一,其功能是对序列(列表、元组、字典、集合、还包括字符串)进行排序,这篇文章主要介绍了Python中sorted()用法,需要的朋友可以参考下
    2023-02-02
  • Python可视化学习之seaborn绘制矩阵图详解

    Python可视化学习之seaborn绘制矩阵图详解

    矩阵图即用一张图绘制多个变量之间的关系,数据挖掘中常用于初期数据探索。本文介绍python中seaborn.pairplot和seaborn.PairGrid绘制矩阵图,需要的可以参考一下
    2022-02-02
  • Django零基础入门之模板变量详解

    Django零基础入门之模板变量详解

    这篇文章主要介绍了Django零基础入门之模板变量详解,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-09-09
  • 深入理解Python中的元类(metaclass)

    深入理解Python中的元类(metaclass)

    这篇文章主要介绍了深入理解Python中的元类(metaclass),本文是一篇相当精彩的外文翻译,讲解了类也是对象、动态地创建类、到底什么是元类,需要的朋友可以参考下
    2015-02-02
  • PyTorch中torch.nn.functional.cosine_similarity使用详解

    PyTorch中torch.nn.functional.cosine_similarity使用详解

    在pytorch中可以使用torch.cosine_similarity函数对两个向量或者张量计算余弦相似度,这篇文章主要给大家介绍了关于PyTorch中torch.nn.functional.cosine_similarity使用的相关资料,需要的朋友可以参考下
    2022-03-03
  • Python Unittest ddt数据驱动的实现

    Python Unittest ddt数据驱动的实现

    本文主要介绍了Python Unittest ddt数据驱动的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-02-02
  • 在Python中使用next()方法操作文件的教程

    在Python中使用next()方法操作文件的教程

    这篇文章主要介绍了在Python中使用next()方法操作文件的教程,是Python入门中的基础知识,需要的朋友可以参考下
    2015-05-05
  • Python面向对象程序设计之继承与多继承用法分析

    Python面向对象程序设计之继承与多继承用法分析

    这篇文章主要介绍了Python面向对象程序设计之继承与多继承用法,结合实例形式分析了Python继承与多继承的简单定义与使用方法,需要的朋友可以参考下
    2018-07-07
  • Selenium webdriver添加cookie实现过程详解

    Selenium webdriver添加cookie实现过程详解

    这篇文章主要介绍了Selenium webdriver添加cookie实现过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-08-08
  • python pandas模块进行数据分析

    python pandas模块进行数据分析

    Python的Pandas模块是一个强大的数据处理工具,可以用来读取、处理和分析各种数据,本文主要介绍了python pandas模块进行数据分析,具有一定的参考价值,感兴趣的可以了解一下
    2024-01-01

最新评论