• 前言

    在本文将讲解高级Win32编程的内容,主要讲解静态链接库、动态链接库、以及远程线程的有关内容

    win32笔记-静态链接库

    现在一个软件可以包含许多的功能,就涉及到了软件的模块化开发,不可能将一个软件的所有代码全部写进一个文件,所以,win32就可以通过静态链接库提供一个模块化的开发方式。

    准备

    这里以VS 2019演示,新建项目,分别选择C++,Windows,库,静态库就可以了。如果在VS C++里面,就应该直接选择win32 static library创建项目。

    VS 2019 c++会默认带有“framework.h”、”pch.h”、”pch.cpp”、”文件名.cpp”,这些都没什么用,全部删除。

    例如,新建test.h头文件

    1
    2
    3
    4
    5
    6
    #ifndef TEST_H
    #define TEST_H

    extern int add(int x, int y);

    #endif

    新建test.cpp文件

    1
    2
    3
    4
    5
    6
    #include "test.h"

    int add(int x, int y)
    {
    return x + y;
    }

    然后在-生成-生成解决方案,就会在项目目录生成lib文件

    使用

    将生成的lib文件和test.h文件一起复制到项目的目录,就可以正常使用了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #include <windows.h>
    #include <stdio.h>
    #include "test.h"
    #pragma comment(lib,"StaticLib1.lib")

    int main() {
    printf("%d", add(1, 2));
    return 0;
    }

    将上面的代码用IDA反编译:

    image-20220405200651748

    add函数:

    image-20220405200733516

    可以看出,全局没怎么变。

    静态库主要的原理就是将lib的文件复制到即将生成的exe代码中,导致可执行文件体积变大。其次包含相同的公共代码,造成浪费。

    win32笔记-动态链接库

    说到动态链接库,有什么好处呢?好处就是解决静态链接库的缺点。

    静态链接库的缺点:

    • 使用静态链接库生成的可执行文件体积较大
    • 包含相同的公共代码,造成浪费

    动态链接库(Dynamic Link Library,缩写为DLL),是微软公司在Windows操作系统中,实现共享函数库概念的一种方式。这些函数库的拓展名是“.dll”、“.ocx”(包括ActiveX控制的库)

    准备

    用VS 2019新建项目分别选着C++、windows、库、动态链接库(DLL),完成创建就行了。里面可能有一些软件创建的文件,为了简单快捷,全部删除就好,我们采用自己手动新建。

    新建头文件testdll.h

    1
    2
    3
    4
    5
    6
    #ifndef TESTDLL_H
    #define TESTDLL_H

    extern "C" _declspec(dllexport) int add(int a, int b);

    #endif

    新建testdll.cpp文件

    1
    2
    3
    4
    5
    6
    #include "testdll.h"

    int add(int x, int y)
    {
    return x + y;
    }

    下面就生成解决方案。

    使用DLL文件

    使用动态链接库有两种方法,可以加载时使用动态链接,也可以运行时动态链接。不过两者都需要将动态链接库文件放到程序运行的目录下。所以,这里要将生成的Dll1.dll放到项目运行debug的目录下。

    加载时动态链接

    这种方案还需要将生成的lib文件放到项目目录下(隐式链接)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #include <windows.h>
    #include <stdio.h>

    #pragma comment(lib, "Dll1.lib")
    extern "C" _declspec(dllimport) int add(int a, int b);

    int main() {
    //调用函数
    printf("%d", add(1, 2));

    return 0;
    }

    反编译得到:

    image-20220405222146790

    运行时动态链接

    这种方式只需要运行时,exe文件能找到dll文件就行。(显式链接)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #include <windows.h>
    #include <stdio.h>

    // 定义函数指针
    typedef int(*lpadd)(int,int);
    //声明函数指针变量
    lpadd add;

    int main() {
    //动态加载dll到内存中
    HINSTANCE hmodule = LoadLibrary(L"Dll1.dll");
    if ( hmodule != NULL ) {
    //获取函数地址
    add = (lpadd)GetProcAddress(hmodule, "add");
    //调用函数
    printf("%d", add(1, 2));
    }
    //释放内存
    FreeLibrary(hmodule);
    return 0;
    }

    反编译得到:

    image-20220405222302295

    win32笔记-远程线程

    线程

    • 线程是附属在进程上的执行实体,是代码的执行流程。
    • 代码必须通过线程才能执行。

    一个简单的线程程序

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

    //接上次win32笔记,导入DLL
    #pragma comment(lib, "Dll1.lib")
    extern "C" _declspec(dllimport) int add(int a, int b);

    void myfun()
    {
    Sleep(1000);
    printf("\n%d", add(2,3));
    }


    DWORD WINAPI Thread1(LPVOID LpParma)
    {
    myfun();//线程中调用
    return 0;
    }

    int main() {
    myfun(); //第一次调用
    printf("\n%d", add(1, 2));
    HANDLE hT = CreateThread(NULL, 0, Thread1, NULL, 0, NULL);//线程中调用

    if (hT) {
    printf("\nsuccess");
    CloseHandle(hT);
    }
    else
    {
    printf("error");
    }

    getchar();//用于线程阻塞,防止线程Thread1未执行完成而退出程序
    return 0;
    }

    运行的结果

    image-20220410202637201

    远程线程

    一般程序都是自己创建线程,自己使用,但是微软提供了一个CreateRemoteThread()的函数,用来创建远程线程,意思就是用B程序在A程序里面创建线程。上网一个简单的程序暂时叫做A程序。

    下面就写一个能操作远程线程的B程序

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

    BOOL myCreateRP(DWORD dwProcessID, DWORD dwProcAddr)
    {
    //1.获得进程句柄
    HANDLE hprocess = OpenProcess(
    PROCESS_ALL_ACCESS,//以怎样的权限访问
    false,//句柄是否需要被继承
    dwProcessID//打开进程的id
    );
    if (!hprocess)//判断是否成功获取进程句柄
    {
    printf("hprocess error\n");
    return false;
    }
    //2.创建远程线程
    DWORD threadId = 0;
    HANDLE hThread = CreateRemoteThread(
    hprocess,
    NULL,
    0,
    (LPTHREAD_START_ROUTINE)dwProcAddr,//A程序的一个函数地址
    NULL,
    0,
    &threadId //返回线程id
    );
    if (!hThread) {
    printf("hThread error\n");
    CloseHandle(hprocess);//线程创建失败,关闭打开的进程
    return false;
    }
    //3.关闭资源
    CloseHandle(hThread);//关闭线程
    CloseHandle(hprocess);//关闭进程
    return true;
    }

    int main()
    {
    DWORD id = 0, addr = 0;
    printf("please input id and addr:\n");

    scanf_s("%d %x", &id, &addr);//获取10进制的A程序id和16进制函数地址

    if (id && addr) {
    printf("checking process %d and addr %x\n", id, addr);
    myCreateRP(id, addr);
    }
    else
    {
    printf("please check id and addr");
    }
    return 0;
    }

    开始运行调试

    看到这里,想必已经对上面的程序了如指掌了,那么现在进行调试和理解程序的运行。

    既然是远程对A程序创建的线程进行操作,那么先将A程序运行起来

    image-20220410234324900

    • 运行即将被攻击的A程序

    • 运行攻击程序B

      image-20220410235038144

    • 打开任务管理器,找到需要查看的A程序的ID

      image-20220410235302012可以清楚的看到,A程序的ID是29528

    • 下面找一下A程序中想要额外运行函数地址,先将A.exe拖进IDA中进行分析

      image-20220411000057984

    image-20220411000343414

    要想获得我们需要的地址,必须更改004113C0的基址,最快获得我们想要正确的地址,那就在这里打断点,等它运行起来,就能看到基址了。那也就是我们想要的地址001C13C0。image-20220411000832444

    • 有了ID和函数地址,就可以输入运行测试了。

      image-20220411001638522

    • 为了测试更多的函数地址,那么可以继续探索,比如,由于这个A程序是由VC 2019的debug模式编译出来的,StartAddress的实际StartAddress_0地址并不是一样的,可以试试StartAddress_0处的函数地址。image-20220411002257806

    通过第一次函数地址的测试,可以发现,由于程序运行了一次,现在程序的基地址已经被修改正确,可以直接使用,即001C1F80,运行测试如下:

    image-20220411003038424

    标有数字的第二、三个5都是通过B程序远程线程新建的。同理,还可以使用如下地址001C13C5和001C17E0,第二个地址就是myfun的函数起始地址。第一个001C13C5处的函数就是跳转到001C17E0处myfun函数。image-20220411004510603

    更多可以自行探索。

    上一篇:
    安洵2022——InvisiableMaze解法
    下一篇:
    简单使用gdb
    本文目录
    本文目录