某pdf转word v6.3.0.2算法分析

【文章标题】某pdf转word v6.3.0.2算法分析 
【文章作者】jieliuhouzi
【原版下载】www.pdfcword.cn 
【保护方式】序列号 
【分析过程】

一. 去掉随机基址

直接OD载入程序,入口是“一call一jmp”,基本上就是VS高版本编译的

 
为了避免随机基址的影响,先去除随机基址。找到“PE”下一行偏移为6的字节处,将“02”修改为“03”,可去掉随机基址

二. 注册码分析

随便输入一串注册码

 
此处MessageBox是断不下来的,使用“F12大法”:OD中按F12暂停程序,Alt+K查看调用堆栈

 
“黄框”部分是ycomuiu.DuiLib::CWindowWnd::ShowModal,搞这个软件之前我也没用过这个框架,百度了一下“在duilib中,可以调用CWindowWnd::ShowModal()来实现模态框的显示”, 在段尾retn处下断点 
注意:消息循环是在CWindowWnd::ShowModal()中实现的,假如在ShowModal下面的几个函数返回处下断点,基本上就迷失在消息循环中了,由于不了解DuiLib我在这里耗了很久才跳出来的 
然后点击“确定”按钮,会在刚才下断的地方段下来,F8

  1 .text:00467F03                 lea     ecx, [ebp+var_148]
  2 .text:00467F09                 push    ecx
  3 .text:00467F0A                 mov     ecx, [ebp+var_3B0]
  4 .text:00467F10                 call    sub_467D30
  5 .text:00467F15                 push    ecx
  6 .text:00467F16                 mov     ecx, esp
  7 .text:00467F18                 lea     edx, [ebp+var_10]
  8 .text:00467F1B                 push    edx
  9 .text:00467F1C                 call    sub_4069F0
 10 .text:00467F21                 lea     eax, [ebp+var_144]
 11 .text:00467F27                 push    eax             ; 机器码
 12 .text:00467F28                 call    sub_4325C0      ; 计算出注册码
 13 .text:00467F2D                 add     esp, 8
 14 .text:00467F30                 lea     ecx, [ebp+var_148]
 15 .text:00467F36                 call    unknown_libname_1 ; Microsoft VisualC 2-14/net runtime
 16 .text:00467F3B                 push    eax             ; 我们自己输入的假码
 17 .text:00467F3C                 lea     ecx, [ebp+var_144]
 18 .text:00467F42                 call    sub_42C270      ; 注册码比较
 19 .text:00467F47                 test    eax, eax
 20 .text:00467F49                 jz      short loc_467F87 ; 跳过注册码错误
 21 .text:00467F4B                 push    40h
 22 .text:00467F4D                 mov     ecx, [ebp+var_3B0]
 23 .text:00467F53                 mov     edx, [ecx+4]
 24 .text:00467F56                 push    edx
 25 .text:00467F57                 push    offset aSnerror
 26 .text:00467F5C                 call    sub_409A20      ; 弹窗提示“序列号错误,请重新输入”
 27 .text:00467F61                 add     esp, 0Ch
 28 .text:00467F64                 lea     ecx, [ebp+var_144]
 29 .text:00467F6A                 call    sub_4013B0
 30 .text:00467F6F                 lea     ecx, [ebp+var_148]
 31 .text:00467F75                 call    sub_4013B0
 32 .text:00467F7A                 lea     ecx, [ebp+var_10]
 33 .text:00467F7D                 call    sub_4013B0
 34 .text:00467F82                 jmp     loc_4683F1
 35 .text:00467F87 ; ---------------------------------------------------------------------------
 36 .text:00467F87
 37 .text:00467F87 loc_467F87:                             ; CODE XREF: sub_467EA0+A9↑j
 38 .text:00467F87                 lea     eax, [ebp+var_4]
 39 .text:00467F8A                 push    eax
 40 .text:00467F8B                 call    sub_433840
 41 .text:00467F90                 add     esp, 4
 42 .text:00467F93                 lea     ecx, [ebp+var_4]
 43 .text:00467F96                 call    sub_4070F0
 44 .text:00467F9B                 movzx   ecx, al
 45 .text:00467F9E                 test    ecx, ecx
 46 .text:00467FA0                 jz      loc_46802D
 47 .text:00467FA6                 mov     edx, [ebp+var_3B0]
 48 .text:00467FAC                 mov     eax, [edx+4]
 49 .text:00467FAF                 push    eax
 50 .text:00467FB0                 lea     ecx, [ebp+var_3A0]
 51 .text:00467FB6                 push    ecx
 52 .text:00467FB7                 call    sub_40D670      ; 弹出验证邮箱对话框
 53 .text:00467FBC                 add     esp, 8
 54 .text:00467FBF                 push    eax
 55 .text:00467FC0                 lea     ecx, [ebp+var_4]
 56 .text:00467FC3                 call    sub_4068A0
 57 .text:00467FC8                 lea     ecx, [ebp+var_3A0]
 58 .text:00467FCE                 call    sub_4013B0
 59 .text:00467FD3                 lea     ecx, [ebp+var_4] ; 检查邮箱是否合法
 60 .text:00467FD6                 call    sub_4070F0
 61 .text:00467FDB                 movzx   edx, al
 62 .text:00467FDE                 test    edx, edx
 63 .text:00467FE0                 jz      short loc_468026 ; 跳过“不提供购买邮箱的错误提示”
 64 .text:00467FE2                 push    40h
 65 .text:00467FE4                 mov     eax, [ebp+var_3B0]
 66 .text:00467FEA                 mov     ecx, [eax+4]
 67 .text:00467FED                 push    ecx
 68 .text:00467FEE                 push    offset aVipnotemail ;
 69 .text:00467FF3                 call    sub_409A20
 70 .text:00467FF8                 add     esp, 0Ch
 71 .text:00467FFB                 lea     ecx, [ebp+var_4]
 72 .text:00467FFE                 call    sub_4013B0
 73 .text:00468003                 lea     ecx, [ebp+var_144]
 74 .text:00468009                 call    sub_4013B0
 75 .text:0046800E                 lea     ecx, [ebp+var_148]
 76 .text:00468014                 call    sub_4013B0
 77 .text:00468019                 lea     ecx, [ebp+var_10]
 78 .text:0046801C                 call    sub_4013B0
 79 .text:00468021                 jmp     loc_4683F1
 80 .text:00468026 ; ---------------------------------------------------------------------------
 81 .text:00468026
 82 .text:00468026 loc_468026:                             ; CODE XREF: sub_467EA0+140↑j
 83 .text:00468026                 mov     [ebp+var_14], 1
 84 .text:0046802D
 85 .text:0046802D loc_46802D:                             ; CODE XREF: sub_467EA0+100↑j
 86 .text:0046802D                 lea     ecx, [ebp+var_1C]
 87 .text:00468030                 call    ds:??0CWaitCursor@DuiLib@@QAE@XZ
 88 .text:00468036                 push    ecx
 89 .text:00468037                 mov     ecx, esp
 90 .text:00468039                 lea     edx, [ebp+var_4]
 91 .text:0046803C                 push    edx             ; 邮箱
 92 .text:0046803D                 call    sub_4069F0
 93 .text:00468042                 mov     ecx, offset unk_4CB420
 94 .text:00468047                 call    sub_46CC70      ; 网络验证:验证注册邮箱是否合法
 95 .text:0046804C                 mov     [ebp+var_C], eax
 96 .text:0046804F                 cmp     [ebp+var_C], 1
 97 .text:00468053                 jnz     short loc_4680B4 ; 跳转到“升级成功,谢谢您的支持!”
 98 .text:00468055                 push    offset word_4B2D6C
 99 .text:0046805A                 call    sub_433800
100 .text:0046805F                 add     esp, 4
101 .text:00468062                 push    40h
102 .text:00468064                 mov     eax, [ebp+var_3B0]
103 .text:0046806A                 mov     ecx, [eax+4]
104 .text:0046806D                 push    ecx
105 .text:0046806E                 push    offset aVipnotfoundema ; "VIPNotFoundEmail"
106 .text:00468073                 call    sub_409A20      ; 弹出“邮箱验证失败,无法注册软件”
107 .text:00468078                 add     esp, 0Ch
108 .text:0046807B                 lea     ecx, [ebp+var_1C]
109 .text:0046807E                 call    ds:??1CWaitCursor@DuiLib@@QAE@XZ
110 .text:00468084                 lea     ecx, [ebp+var_4]
111 .text:00468087                 call    sub_4013B0
112 .text:0046808C                 lea     ecx, [ebp+var_144]
113 .text:00468092                 call    sub_4013B0
114 .text:00468097                 lea     ecx, [ebp+var_148]
115 .text:0046809D                 call    sub_4013B0
116 .text:004680A2                 lea     ecx, [ebp+var_10]
117 .text:004680A5                 call    sub_4013B0
118 .text:004680AA                 jmp     loc_4683F1
119 .text:004680AF ; ---------------------------------------------------------------------------
120 .text:004680AF                 jmp     loc_468140
121 .text:004680B4 ; ---------------------------------------------------------------------------
122 .text:004680B4
123 .text:004680B4 loc_4680B4:                             ; CODE XREF: sub_467EA0+1B3↑j
124 .text:004680B4                 cmp     [ebp+var_C], 0

此处重点分析注册码生成部分:

.text:004325E0                 lea     ecx, [ebp+arg_4] ; 机器码
.text:004325E3                 call    sub_4017A0      ; 将机器码转换为字符串
.text:004325E8                 mov     [ebp+lpString], eax
.text:004325EB                 cmp     [ebp+lpString], 0
.text:004325EF                 jnz     short loc_4325FD
.text:004325F1                 mov     [ebp+var_C4], 0
.text:004325FB                 jmp     short loc_432661
.text:004325FD ; ---------------------------------------------------------------------------
.text:004325FD
.text:004325FD loc_4325FD:                             ; CODE XREF: sub_4325C0+2F↑j
.text:004325FD                 mov     eax, [ebp+lpString]
.text:00432600                 push    eax
.text:00432601                 call    ds:lstrlenW
.text:00432607                 add     eax, 1
.text:0043260A                 mov     [ebp+var_8], eax
.text:0043260D                 cmp     [ebp+var_8], 3FFFFFFFh
.text:00432614                 jle     short loc_432622
.text:00432616                 mov     [ebp+var_C8], 0
.text:00432620                 jmp     short loc_432655
.text:00432622 ; ---------------------------------------------------------------------------
.text:00432622
.text:00432622 loc_432622:                             ; CODE XREF: sub_4325C0+54↑j
.text:00432622                 mov     eax, [ebp+var_8]
.text:00432625                 shl     eax, 1
.text:00432627                 call    __alloca_probe_16 ; alloc申请空间
.text:0043262C                 mov     [ebp+lpMultiByteStr], esp
.text:00432632                 mov     ecx, [ebp+CodePage]
.text:00432638                 push    ecx
.text:00432639                 mov     edx, [ebp+var_8]
.text:0043263C                 shl     edx, 1
.text:0043263E                 push    edx
.text:0043263F                 mov     eax, [ebp+lpString]
.text:00432642                 push    eax             ; 机器码UNICODE字符串
.text:00432643                 mov     ecx, [ebp+lpMultiByteStr]
.text:00432649                 push    ecx             ; 接收转换完的ASCII字符串
.text:0043264A                 call    sub_408BC0      ; Unicode机器码转Ascii
.text:0043264F                 mov     [ebp+var_C8], eax
.text:00432655
.text:00432655 loc_432655:                             ; CODE XREF: sub_4325C0+60↑j
.text:00432655                 mov     edx, [ebp+var_C8]
.text:0043265B                 mov     [ebp+var_C4], edx
.text:00432661
.text:00432661 loc_432661:                             ; CODE XREF: sub_4325C0+3B↑j
.text:00432661                 mov     eax, [ebp+var_C4]
.text:00432667                 mov     [ebp+var_4], eax
.text:0043266A                 push    0FFFFFFFFh
.text:0043266C                 lea     ecx, [ebp+arg_4] ; 机器码地址
.text:0043266F                 call    sub_409B30      ; 获取长度
.text:00432674                 mov     cl, ds:byte_4A5912
.text:0043267A                 mov     [ebp+var_98], cl
.text:00432680                 push    3Fh
.text:00432682                 push    0
.text:00432684                 lea     edx, [ebp+var_97]
.text:0043268A                 push    edx
.text:0043268B                 call    _memset
.text:00432690                 add     esp, 0Ch
.text:00432693                 push    40h
.text:00432695                 push    0
.text:00432697                 lea     eax, [ebp+var_98]
.text:0043269D                 push    eax
.text:0043269E                 call    _memset
.text:004326A3                 add     esp, 0Ch
.text:004326A6                 push    40h
.text:004326A8                 lea     ecx, [ebp+var_98]
.text:004326AE                 push    ecx             ; szBuffer
.text:004326AF                 mov     edx, [ebp+var_4]
.text:004326B2                 push    edx             ; ASCII格式机器码字符串
.text:004326B3                 call    Get_MD5         ; 计算机器码的MD5值
.text:004326B8                 add     esp, 0Ch
.text:004326BB                 lea     eax, [ebp+var_98]
.text:004326C1                 push    eax
.text:004326C2                 call    __strupr        ; 机器码的MD5字符串转换为大写字符串
.text:004326C7                 add     esp, 4
.text:004326CA                 mov     cl, ds:byte_4A5913
.text:004326D0                 mov     [ebp+MultiByteStr], cl
.text:004326D3                 push    3Fh
.text:004326D5                 push    0
.text:004326D7                 lea     edx, [ebp+var_4F]
.text:004326DA                 push    edx
.text:004326DB                 call    _memset
.text:004326E0                 add     esp, 0Ch
.text:004326E3                 push    40h
.text:004326E5                 push    0
.text:004326E7                 lea     eax, [ebp+MultiByteStr]
.text:004326EA                 push    eax
.text:004326EB                 call    _memset
.text:004326F0                 add     esp, 0Ch
.text:004326F3                 push    40h
.text:004326F5                 lea     ecx, [ebp+MultiByteStr]
.text:004326F8                 push    ecx
.text:004326F9                 lea     edx, [ebp+var_98]
.text:004326FF                 push    edx
.text:00432700                 call    Get_MD5         ; 将机器码的MD5字符串再次计算MD5
.text:00432705                 add     esp, 0Ch
.text:00432708                 lea     eax, [ebp+MultiByteStr]
.text:0043270B                 push    eax
.text:0043270C                 lea     ecx, [ebp+obj_A8]
.text:00432712                 call    sub_430200      ; 将ASCII转为UNICODE
.text:00432717                 push    4
.text:00432719                 lea     ecx, [ebp+var_AC]
.text:0043271F                 push    ecx             ; 传出参数:用来存储计算完的注册码的第4段
.text:00432720                 lea     ecx, [ebp+obj_A8] ; MD5字符串
.text:00432726                 call    RegistrationCode_4 ; 注册码的第四段
.text:0043272B                 push    eax
.text:0043272C                 push    4
.text:0043272E                 push    16h
.text:00432730                 lea     edx, [ebp+var_B0]
.text:00432736                 push    edx
.text:00432737                 lea     ecx, [ebp+obj_A8]
.text:0043273D                 call    RegistrationCode_2_3 ; 注册码的第三段
.text:00432742                 push    eax
.text:00432743                 push    4
.text:00432745                 push    0Ah
.text:00432747                 lea     eax, [ebp+var_B4]
.text:0043274D                 push    eax
.text:0043274E                 lea     ecx, [ebp+obj_A8]
.text:00432754                 call    RegistrationCode_2_3 ; 注册码的第二段
.text:00432759                 push    eax
.text:0043275A                 push    4
.text:0043275C                 lea     ecx, [ebp+var_B8]
.text:00432762                 push    ecx
.text:00432763                 lea     ecx, [ebp+obj_A8]
.text:00432769                 call    RegistrationCode_1 ; 注册码的第一段
.text:0043276E                 push    eax
.text:0043276F                 lea     edx, [ebp+var_BC]
.text:00432775                 push    edx
.text:00432776                 call    MyStrCat        ; 类似于strcat功能
.text:0043277B                 add     esp, 0Ch
.text:0043277E                 push    eax
.text:0043277F                 lea     eax, [ebp+var_C0]
.text:00432785                 push    eax
.text:00432786                 call    MyStrCat
.text:0043278B                 add     esp, 0Ch
.text:0043278E                 push    eax
.text:0043278F                 lea     ecx, [ebp+var_A4]
.text:00432795                 push    ecx
.text:00432796                 call    MyStrCat
.text:0043279B                 add     esp, 0Ch

注册码分为4小段,其实算法基本上都一样,只是取的区间不同,注册码的第4段

.text:0042C3E0 RegistrationCode_4 proc near            ; CODE XREF: sub_4277C0+B1↑p
.text:0042C3E0
.text:0042C3E0                 push    ebp
.text:0042C3E1                 mov     ebp, esp
.text:0042C3E3                 sub     esp, 8
.text:0042C3E6                 mov     [ebp+var_8], ecx
.text:0042C3E9                 cmp     [ebp+arg_4], 0
.text:0042C3ED                 jge     short loc_42C3F6
.text:0042C3EF                 mov     [ebp+arg_4], 0
.text:0042C3F6
.text:0042C3F6 loc_42C3F6:                             ; CODE XREF: RegistrationCode_4+D↑j
.text:0042C3F6                 mov     ecx, [ebp+var_8]
.text:0042C3F9                 call    sub_4017C0      ; 获取MD5字串长度
.text:0042C3FE                 mov     [ebp+var_4], eax
.text:0042C401                 mov     eax, [ebp+arg_4]
.text:0042C404                 cmp     eax, [ebp+var_4]
.text:0042C407                 jl      short loc_42C41A
.text:0042C409                 mov     ecx, [ebp+var_8]
.text:0042C40C                 push    ecx
.text:0042C40D                 mov     ecx, [ebp+arg_0]
.text:0042C410                 call    sub_4069F0
.text:0042C415                 mov     eax, [ebp+arg_0]
.text:0042C418                 jmp     short loc_42C448
.text:0042C41A ; ---------------------------------------------------------------------------
.text:0042C41A
.text:0042C41A loc_42C41A:                             ; CODE XREF: RegistrationCode_4+27↑j
.text:0042C41A                 mov     ecx, [ebp+var_8]
.text:0042C41D                 call    sub_408D50
.text:0042C422                 push    eax             ; int
.text:0042C423                 mov     edx, [ebp+arg_4]
.text:0042C426                 push    edx             ; int
.text:0042C427                 mov     ecx, [ebp+var_8]
.text:0042C42A                 call    GetString       ; 获取字符串的首地址
.text:0042C42F                 mov     ecx, [ebp+var_4] ; 其实关键的就这几行代码
.text:0042C432                 lea     edx, [eax+ecx*2] ; edx指针:移动到字符串的尾部
.text:0042C435                 mov     eax, [ebp+arg_4]
.text:0042C438                 shl     eax, 1
.text:0042C43A                 sub     edx, eax
.text:0042C43C                 push    edx             ; 截取的注册码的一部分
.text:0042C43D                 mov     ecx, [ebp+arg_0]
.text:0042C440                 call    memcpy          ; 内联的memcpy,这东西坑了我很久
.text:0042C445                 mov     eax, [ebp+arg_0]
.text:0042C448
.text:0042C448 loc_42C448:                             ; CODE XREF: RegistrationCode_4+38↑j
.text:0042C448                 mov     esp, ebp
.text:0042C44A                 pop     ebp
.text:0042C44B                 retn    8
.text:0042C44B RegistrationCode_4 endp

注册码的第2和3段,其他几段大同小异

三. 网络验证去除

网络验证部分的代码:

.text:00468039                 lea     edx, [ebp+var_4]
.text:0046803C                 push    edx             ; 邮箱
.text:0046803D                 call    sub_4069F0
.text:00468042                 mov     ecx, offset unk_4CB420
.text:00468047                 call    sub_46CC70      ; 网络验证:验证注册邮箱是否合法
.text:0046804C                 mov     [ebp+var_C], eax
.text:0046804F                 cmp     [ebp+var_C], 1
.text:00468053                 jnz     short loc_4680B4 ; 跳转到“升级成功,谢谢您的支持!”
.text:00468055                 push    offset word_4B2D6C
.text:0046805A                 call    sub_433800
.text:0046805F                 add     esp, 4
.text:00468062                 push    40h
.text:00468064                 mov     eax, [ebp+var_3B0]
.text:0046806A                 mov     ecx, [eax+4]
.text:0046806D                 push    ecx
.text:0046806E                 push    offset aVipnotfoundema
.text:00468073                 call    sub_409A20      ; 弹出“邮箱验证失败,无法注册软件”

网络验证函数sub_46CC70()返回1表示验证失败,打补丁修改返回值即可去除网络验证

四. 机器码获取分析

编写注册机,其实没必要分析机器码的生成,由于我不了解一般软件如何获取机器码的,我就慢慢的从头到尾跟一遍,这一跟才发现机器码的生成比注册码的生成麻烦多了,不过也学到了不少东西,如何获取硬件信息,如何获取物理内存信息等等

1. 流程分析

打开软件,点击:注册–>购买序列号,在下图弹窗找到本机的机器码“89354AF54032753D”

 
问题:这个机器码是在那里生成的? 
最挫的方法:一步一步跟,注册码是根据机器码算出来的,所以检验注册码的上面一定会有注册码 
找到出现机器码的那段代码,一步一步的回溯,代码如下:

.text:00467EA0                 push    ebp
.text:00467EA1                 mov     ebp, esp
.text:00467EA3                 sub     esp, 3B0h
.text:00467EA9                 mov     eax, ___security_cookie
.text:00467EAE                 xor     eax, ebp
.text:00467EB0                 mov     [ebp+var_20], eax
.text:00467EB3                 mov     [ebp+var_3B0], ecx
.text:00467EB9                 mov     [ebp+var_14], 0
.text:00467EC0                 lea     eax, [ebp+var_10]
.text:00467EC3                 push    eax
.text:00467EC4                 mov     ecx, offset unk_4CB400
.text:00467EC9                 call    GetMachineID
.text:00467ECE                 lea     ecx, [ebp+var_10]
.text:00467ED1                 call    sub_4070F0
.text:00467ED6                 movzx   ecx, al
.text:00467ED9                 test    ecx, ecx
.text:00467EDB                 jz      short loc_467F03
.text:00467EDD                 push    40h             ; int
.text:00467EDF                 mov     edx, [ebp+var_3B0]
.text:00467EE5                 mov     eax, [edx+4]
.text:00467EE8                 push    eax             ; int
.text:00467EE9                 push    offset aNomachineid_0 ; "NoMachineId"
.text:00467EEE                 call    sub_409A20
.text:00467EF3                 add     esp, 0Ch
.text:00467EF6                 lea     ecx, [ebp+var_10]
.text:00467EF9                 call    sub_4013B0
.text:00467EFE                 jmp     loc_4683F1
.text:00467F03 ; ---------------------------------------------------------------------------
.text:00467F03
.text:00467F03 loc_467F03:                             ; CODE XREF: sub_467EA0+3B↑j
.text:00467F03                 lea     ecx, [ebp+var_148]
.text:00467F09                 push    ecx
.text:00467F0A                 mov     ecx, [ebp+var_3B0]
.text:00467F10                 call    sub_467D30
.text:00467F15                 push    ecx
.text:00467F16                 mov     ecx, esp
.text:00467F18                 lea     edx, [ebp+var_10]
.text:00467F1B                 push    edx             ; 出现机器码
.text:00467F1C                 call    sub_4069F0

已知的出现机器码的是.text:00467F1B push edx,其中edx存放机器码的地址一层一层的往上回溯

00467F1B:edx                   //edx存放机器码地址
00467F18:var_10                //var_10存储机器码
00467EC9:GetMachineID          //函数中进行一系列处理得到机器码
00467EC4:offset unk_4CB400     //这是个全局对象,第一个成员是一个堆地址,其中存储机器码

到这里定位到了全局对象,但是全局对象是在那里获取机器码的,只能下内存断点了。对全局对象的第一个成员的位置下硬件写入断点,注意是004CB404而不是004CB400,因为第一个位置是虚表指针,后推一个才是第一个数据成员

 
重新载入程序,会断下来好几次,断下来之后再对堆地址指向的内容下硬件写入断点,其中有一次来到下面这个位置,此时恰巧刚填写机器码的前两位“89”

 
一次一次的retn之后来到下面这段代码中00469527地址处

.text:004694D6                 call    _memset
.text:004694DB                 add     esp, 0Ch
.text:004694DE                 lea     edx, [ebp+pcbData]
.text:004694E4                 push    edx
.text:004694E5                 lea     eax, [ebp+pvData]
.text:004694EB                 push    eax
.text:004694EC                 push    0
.text:004694EE                 push    offset aPdfcword_2 ; Pdfcword
.text:004694F3                 push    offset aSoftwareMicros_16 ;
.text:004694F8                 push    80000002h       ; hkey
.text:004694FD                 call    ds:SHGetValueW    ; 该函数获取注册表的键值
.text:00469503                 mov     [ebp+var_210], eax
.text:00469509                 cmp     [ebp+var_210], 0
.text:00469510                 jnz     short loc_469527
.text:00469512                 lea     ecx, [ebp+pvData]
.text:00469518                 push    ecx
.text:00469519                 mov     ecx, [ebp+var_214]
.text:0046951F                 add     ecx, 4
.text:00469522                 call    sub_4013D0
.text:00469527
.text:00469527 loc_469527:                             ; CODE XREF: GetMachineID+2C↑j
.text:00469527                                         ; GetMachineID+80↑j
.text:00469527                 mov     edx, [ebp+var_214]

遇到SHGetValueW就说明了机器码被写到注册表了。可以通过参数定位到注册表路径 
路径:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Pdfcword

 
此处只是读取注册表中的机器码,并不是直接计算出机器码,我们先把注册表项给删掉,重新载入程序,对SHSetValueA下断 
注意:此处不能对RegSetValue(A)、RegSetValueEx(A)下断,不然会很痛苦的,由于自个的无知被这里也折磨了好久 
软件跑起来后,点击“注册–>购买序列号”就会断下来,查看堆栈窗口此时正准备向注册表写入机器码,那么写入之前一定有地方获取机器码

 
返回到0x0043A08,并在段首下断点 
下面函数生成字符串: “27fd2a8ad66d99c944a52b0a2f9b6ff1”

1 004328B3   .  6A 40         push 0x40
2 004328B5   .  8D95 68FDFFFF lea edx,dword ptr ss:[ebp-0x298]
3 004328BB   .  52            push edx
4 004328BC   .  E8 9F5D0300   call PDFConve.00468660            ;计算出字符串

下面函数生成字符串: “e3a725d4be6392fae82a71339d6e381b”

004329B2   > \6A 40         push 0x40
004329B4   .  8D95 20FDFFFF lea edx,dword ptr ss:[ebp-0x2E0]
004329BA   .  52            push edx
004329BB   .  8B85 14FDFFFF mov eax,dword ptr ss:[ebp-0x2EC]
004329C1   .  50            push eax
004329C2   .  E8 A9EBFFFF   call <PDFConve.Get_MD5>

最后截取并转换为大写就是机器码:”E3A725D4BE6392FA” 
机器码生成的大致流程:

2. 取硬盘信息

定位到:004686AE call sub_40C8B0 ;取硬盘序列号 
继续向里面跟进来到:0040C8DB call sub_40BCE0 
IDA中F5的代码如下所示: 
用CreateFile函数打开\.\PhysicalDrive%d 
然后用DeviceIoControl来获取硬盘的信息(扇区数,磁头数,柱面数)等

3. 取物理内存信息

定位到:004686C3 call sub_40D160 ;取物理内存信息 
限于篇幅原因,函数就不跟进去了

 1 .text:0040D1D1                 call    sub_40CFA0      ; 文件映射,拷贝数据
 2 .text:0040D1D6                 add     esp, 0Ch
 3 .text:0040D1D9                 test    eax, eax
 4 .text:0040D1DB                 jnz     short loc_40D1EC
 5 .text:0040D1DD                 call    sub_40CF70      ; 关闭内核对象,释放空间
 6 .text:0040D1E2                 mov     eax, offset byte_4C9B40
 7 .text:0040D1E7                 jmp     loc_40D2E9
 8 .text:0040D1EC ; ---------------------------------------------------------------------------
 9 .text:0040D1EC
10 .text:0040D1EC loc_40D1EC:
11 .text:0040D1EC                 jmp     short loc_40D207
12 .text:0040D1EE ; ---------------------------------------------------------------------------
13 .text:0040D1EE
14 .text:0040D1EE loc_40D1EE:
15 .text:0040D1EE                 push    1000h           ; size_t
16 .text:0040D1F3                 push    0FE000h         ; void *
17 .text:0040D1F8                 lea     eax, [ebp+var_1008]
18 .text:0040D1FE                 push    eax             ; void *
19 .text:0040D1FF                 call    _memcpy
20 .text:0040D204                 add     esp, 0Ch
21 .text:0040D207
22 .text:0040D207 loc_40D207:
23 .text:0040D207                 call    sub_40D030      ; 填表函数
24 .text:0040D20C                 lea     ecx, [ebp+var_1008]
25 .text:0040D212                 mov     [ebp+var_100C], ecx
26 .text:0040D218                 mov     [ebp+var_1010], 0
27 .text:0040D222                 jmp     short loc_40D233
28 .text:0040D224 ; ---------------------------------------------------------------------------
29 .text:0040D224
30 .text:0040D224 loc_40D224:
31 .text:0040D224                 mov     edx, [ebp+var_1010]
32 .text:0040D22A                 add     edx, 1
33 .text:0040D22D                 mov     [ebp+var_1010], edx
34 .text:0040D233
35 .text:0040D233 loc_40D233:
36 .text:0040D233                 cmp     [ebp+var_1010], 2
37 .text:0040D23A                 jge     loc_40D2DF
38 .text:0040D240                 mov     [ebp+var_1030], 0
39 .text:0040D247                 xor     eax, eax
40 .text:0040D249                 mov     [ebp+var_102F], eax
41 .text:0040D24F                 mov     [ebp+var_102B], eax
42 .text:0040D255                 mov     [ebp+var_1027], eax
43 .text:0040D25B                 mov     [ebp+var_1023], eax
44 .text:0040D261                 mov     [ebp+var_101F], eax
45 .text:0040D267                 mov     [ebp+var_101B], eax
46 .text:0040D26D                 mov     [ebp+var_1017], eax
47 .text:0040D273                 mov     [ebp+var_1013], ax
48 .text:0040D27A                 mov     [ebp+var_1011], al
49 .text:0040D280                 push    800h
50 .text:0040D285                 mov     ecx, [ebp+var_100C]
51 .text:0040D28B                 push    ecx
52 .text:0040D28C                 call    sub_40D0B0      ; 关键函数:通过物理内存的信息计算出一串字符串
53 .text:0040D291                 add     esp, 8
54 .text:0040D294                 mov     [ebp+var_4], eax
55 .text:0040D297                 mov     edx, [ebp+var_4]
56 .text:0040D29A                 push    edx
57 .text:0040D29B                 push    offset a04x     ; "%04X"
58 .text:0040D2A0                 lea     eax, [ebp+var_1030]
59 .text:0040D2A6                 push    eax             ; char *
60 .text:0040D2A7                 call    _sprintf
61 .text:0040D2AC                 add     esp, 0Ch
62 .text:0040D2AF                 push    1000h           ; size_t
63 .text:0040D2B4                 lea     ecx, [ebp+var_1030]
64 .text:0040D2BA                 push    ecx             ; char *
65 .text:0040D2BB                 push    offset byte_4C9B40 ; char *
66 .text:0040D2C0                 call    _strncat              ;拼接字符串
67 .text:0040D2C5                 add     esp, 0Ch
68 .text:0040D2C8                 mov     edx, [ebp+var_100C]
69 .text:0040D2CE                 add     edx, 400h
70 .text:0040D2D4                 mov     [ebp+var_100C], edx
71 .text:0040D2DA                 jmp     loc_40D224

五. 注册机编写

不能算是注册机,因为还有网络验证,虽然本地的注册算法验证可以通过,但是绕不过去网络验证,对于网络验证只能打补丁

1. 取硬盘信息

取硬盘信息部分:

  1 void ChangeByteOrder(PCHAR szString, USHORT uscStrSize)
  2 {
  3     USHORT  i = 0;
  4     CHAR    temp = '\0';
  5     for (i = 0; i < uscStrSize; i += 2)
  6     {
  7         temp = szString[i];
  8         szString[i] = szString[i + 1];
  9         szString[i + 1] = temp;
 10     }
 11 }
 12 //--------------------------------------------------------------
 13 //功能:硬盘序列号
 14 //参数:
 15 //  lpszHD:传出参数,存储最终计算的硬盘信息
 16 //  len:默认参128
 17 //--------------------------------------------------------------
 18 BOOL GetHDSerial(char *lpszHD, int len/*=128*/)
 19 {
 20     BOOL        bRet = FALSE;
 21     DWORD       bytesRtn = 0;
 22     char        szhd[80] = { 0 };
 23     PIDSECTOR   phdinfo;
 24     HANDLE      hDrive = NULL;
 25     GETVERSIONOUTPARAMS vers;
 26     SENDCMDINPARAMS     in;
 27     SENDCMDOUTPARAMS    out;
 28     ZeroMemory(&vers, sizeof(vers));
 29     ZeroMemory(&in, sizeof(in));
 30     ZeroMemory(&out, sizeof(out));
 31     //搜索四个物理硬盘,取第一个有数据的物理硬盘
 32     for (int j = 0; j < 4; j++)
 33     {
 34         sprintf(szhd, "\\\\.\\PhysicalDrive%d", j);
 35         hDrive = CreateFileA(szhd,
 36             GENERIC_READ | GENERIC_WRITE,
 37             FILE_SHARE_READ | FILE_SHARE_WRITE,
 38             0,
 39             OPEN_EXISTING,
 40             0,
 41             0);
 42         if (NULL == hDrive)
 43         {
 44             continue;
 45         }
 46         if (!DeviceIoControl(hDrive, DFP_GET_VERSION, 0, 0, &vers, sizeof(vers), &bytesRtn, 0))
 47         {
 48             CloseHandle(hDrive);
 49             hDrive = NULL;
 50             continue;
 51         }
 52         //If IDE identify command not supported, fails
 53         if (!(vers.fCapabilities & 1))
 54         {
 55             CloseHandle(hDrive);
 56             hDrive = NULL;
 57             continue;
 58         }
 59         //Identify the IDE drives
 60         if (j & 1)
 61         {
 62             in.irDriveRegs.bDriveHeadReg = 0xb0;
 63         }
 64         else
 65         {
 66             in.irDriveRegs.bDriveHeadReg = 0xa0;
 67         }
 68         if (vers.fCapabilities&(16 >> j))
 69         {
 70             //We don't detect a ATAPI device.
 71             CloseHandle(hDrive);
 72             hDrive = NULL;
 73             continue;
 74         }
 75         else
 76         {
 77             in.irDriveRegs.bCommandReg = 0xec;
 78         }
 79         in.bDriveNumber = j;
 80         in.irDriveRegs.bSectorCountReg = 1;
 81         in.irDriveRegs.bSectorNumberReg = 1;
 82         in.cBufferSize = 512;
 83         if (!DeviceIoControl(hDrive, DFP_RECEIVE_DRIVE_DATA, &in, sizeof(in), &out, sizeof(out), &bytesRtn, 0))
 84         {
 85             //"DeviceIoControl failed:DFP_RECEIVE_DRIVE_DATA"<<endl;
 86             CloseHandle(hDrive);
 87             hDrive = NULL;
 88             continue;
 89         }
 90         phdinfo = (PIDSECTOR)out.bBuffer;
 91         char    s[21] = { 0 };
 92         memcpy(s, phdinfo->sSerialNumber, 20);
 93         s[20] = 0;
 94         ChangeByteOrder(s, 20);
 95         memcpy(lpszHD, s, 20);
 96         if (strlen(lpszHD) != 0)
 97         {
 98             bRet = TRUE;
 99         }
100         break;
101     }
102     CloseHandle(hDrive);
103     hDrive = NULL;
104     return bRet;
105 }

2. 取物理内存信息

获取物理内存信息:

  1 int  g_nTable[256] = { 0 };       //全局数组,作为表使用
  2 PFNZwOpenSection            ZwOpenSection = NULL;
  3 PFNZwMapViewOfSection       ZwMapViewOfSection = NULL;
  4 PFNZwUnmapViewOfSection     ZwUnmapViewOfSection = NULL;
  5 PFNRtlInitUnicodeString     RtlInitUnicodeString = NULL;
  6 HMODULE hLibModule = NULL;
  7 HANDLE hPhysicalMemoryHandle = NULL;
  8 //从NTDLL获取我们需要的几个函数指针,并调用ZwOpenSection
  9 BOOL sub_40CE30()
 10 {
 11     UNICODE_STRING PhysicalMemoryUnicodeString;
 12     OBJECT_ATTRIBUTES ObjectAttributes;
 13     wchar_t szBuffer[100] = L"\\Device\\PhysicalMemory";
 14     //获取函数指针
 15     hLibModule = LoadLibraryA("ntdll.dll");
 16     if (!hLibModule)
 17         return FALSE;
 18     ZwOpenSection = (PFNZwOpenSection)GetProcAddress(hLibModule, "ZwOpenSection");
 19     if (!ZwOpenSection)
 20         return FALSE;
 21     ZwMapViewOfSection = (PFNZwMapViewOfSection)GetProcAddress(hLibModule, "ZwMapViewOfSection");
 22     if (!ZwMapViewOfSection)
 23         return FALSE;
 24     ZwUnmapViewOfSection = (PFNZwUnmapViewOfSection)GetProcAddress(hLibModule, "ZwUnmapViewOfSection");
 25     if (!ZwUnmapViewOfSection)
 26         return FALSE;
 27     RtlInitUnicodeString = (PFNRtlInitUnicodeString)GetProcAddress(hLibModule, "RtlInitUnicodeString");
 28     if (!RtlInitUnicodeString)
 29         return FALSE;
 30     RtlInitUnicodeString(&PhysicalMemoryUnicodeString, szBuffer);
 31     ObjectAttributes.Length = sizeof(OBJECT_ATTRIBUTES);
 32     ObjectAttributes.RootDirectory = 0;
 33     ObjectAttributes.ObjectName = &PhysicalMemoryUnicodeString;
 34     ObjectAttributes.Attributes = 0;
 35     ObjectAttributes.SecurityDescriptor = NULL;
 36     ObjectAttributes.SecurityQualityOfService = NULL;
 37     NTSTATUS status = ZwOpenSection(&hPhysicalMemoryHandle,
 38         4,
 39         &ObjectAttributes);
 40     if (NT_SUCCESS(status))
 41         return TRUE;
 42     return FALSE;
 43 }
 44 //文件映射,拷贝数据
 45 BOOL __cdecl sub_40CFA0(PVOID pvDataBuffer, DWORD dwAddress, DWORD dwLength)
 46 {
 47     PVOID pvVirtualAddress;          // 映射的虚地址
 48     LARGE_INTEGER base;              // 物理内存地址
 49     DWORD dwOutLenth = dwLength;
 50     base.QuadPart = (ULONGLONG)(dwAddress);
 51     pvVirtualAddress = NULL;
 52     NTSTATUS status = ZwMapViewOfSection(hPhysicalMemoryHandle,
 53         (HANDLE)-1,
 54         &pvVirtualAddress,
 55         0,
 56         dwLength,
 57         &base,
 58         &dwOutLenth,
 59         ViewShare,
 60         0,
 61         PAGE_READONLY);
 62     if (!NT_SUCCESS(status))
 63         return FALSE;
 64     //当前进程的虚地址空间中,复制数据到输出缓冲区
 65     memcpy(pvDataBuffer, pvVirtualAddress, dwLength);
 66     //完成访问,取消地址映射
 67     return ZwUnmapViewOfSection((HANDLE)-1, pvVirtualAddress) >= 0;
 68 }
 69 //关闭内核对象,释放空间
 70 BOOL sub_40CF70()
 71 {
 72     BOOL bRet = FALSE;
 73     if (&hPhysicalMemoryHandle)
 74         bRet = CloseHandle(&hPhysicalMemoryHandle);
 75     if (hLibModule)
 76         bRet = FreeLibrary(hLibModule);
 77     return bRet;
 78 }
 79 //填表函数:将数据进行一系列运算,填入全局数组
 80 int sub_40D030()
 81 {
 82     int nResult = 0;
 83     signed int j;
 84     unsigned int uNum = 0;
 85     signed int i;
 86     for (i = 0; i < 256; ++i)
 87     {
 88         uNum = i;
 89         for (j = 8; j > 0; --j)
 90         {
 91             if (uNum & 1)
 92                 uNum = (uNum >> 1) ^ 0xEDB88320;
 93             else
 94                 uNum >>= 1;
 95         }
 96         g_nTable[i] = uNum;
 97         nResult = i + 1;
 98     }
 99     return nResult;
100 }
101 //查表计算
102 int __cdecl sub_40D300(BYTE bArg1, int* pArg2)
103 {
104     int nResult = 0;
105     nResult = *pArg2 & 0xFF;
106     *pArg2 = g_nTable[nResult ^ bArg1] ^ ((DWORD)*pArg2 >> 8);
107     return nResult;
108 }
109 int __cdecl sub_40D0B0(char* pStr, int nLen)
110 {
111     BYTE *bPTmp = NULL;
112     int nResult = -1;
113     bPTmp = (BYTE*)pStr;
114     for (int i = 0; i < nLen; ++i)
115         sub_40D300(*bPTmp++, &nResult);
116     return ~nResult;
117 }
118 //获取第二部分字符串
119 //地址:0040D28C  call sub_40D0B0
120 //说明:参数是我自己后来添加的
121 BOOL GetBiosSerial(char* pBios)
122 {
123     char szBuf[0x1000] = { 0 };
124     char szTmp[16] = { 0 };
125     char *pTmp = NULL;
126     //获取文件映射的函数指针
127     BOOL bRet = sub_40CE30();
128     if (bRet)
129     {
130         //创建文件映射,拷贝数据
131         bRet = sub_40CFA0(szBuf, 0x0FE000, 0x1000);
132         if (!bRet)
133         {
134             //关闭内核对象,释放资源
135             sub_40CF70();
136             return FALSE;
137         }
138         //填表函数
139         sub_40D030();
140         //计算最终的字符串
141         pTmp = szBuf;
142         for (int i = 0; i < 2; ++i)
143         {
144             char szTmp[32] = { 0 };
145             int nRet = sub_40D0B0(pTmp, 2048);
146             sprintf(szTmp, "%04X", nRet);
147             strcat(pBios, szTmp);
148             pTmp += 1024;
149         }
150         bRet = TRUE;
151     }
152     return bRet;
153 }

2. 生成机器码

生成机器码:

 1 //获取机器码:计算MD5的函数实现本处省略
 2 char* GetMachineID(char* pMac, char* pBios, char* pMachineID)
 3 {
 4     BYTE uMD5Buf[32] = { 0 };
 5     BYTE uTmpBuf[32] = { 0 };
 6     char* pTmp = NULL;
 7     //连接字符串
 8     if (pMac == NULL)
 9     {
10         if (pBios != NULL)
11         {
12             pTmp = pBios;
13         }
14     }
15     else
16     {
17         if (pBios != NULL)
18         {
19             strcat(pMac, pBios);
20             pTmp = pMac;
21         }
22         pTmp = pMac;
23     }
24     //计算MD5值:这个MD5不能出来空字符串,需要单独提出来处理
25     if (pTmp == NULL)
26     {
27         strcpy((char*)uTmpBuf, "d41d8cd98f00b204e9800998ecf8427e");
28     }
29     else
30     {
31         MDString(pTmp, uMD5Buf);
32         HexToStr(uTmpBuf, uMD5Buf, 16);  //将MD5数据转为16进制字符串
33         strlwr((char*)uTmpBuf);          //将字符串转为小写
34     }
35     //尾部追加#
36     strcat((char*)uTmpBuf, "#");
37     //计算MD5值
38     MDString((char*)uTmpBuf, uMD5Buf);
39     HexToStr(uTmpBuf, uMD5Buf, 16);  //将MD5数据转为16进制字符串
40     //截取前半部分字符串
41     uTmpBuf[16] = '\0';
42     memcpy(pMachineID, uTmpBuf, 16);
43     return pMachineID;
44 }

3.生成注册码

生成注册码:

 1 //获取注册码的第一段
 2 char* RegistrationCode_1(char* pSrc, char* pDest, int nLen)
 3 {
 4     memcpy(pDest, pSrc, nLen);
 5     return pDest;
 6 }
 7 //获取注册码的第二/三段
 8 char* RegistrationCode_2_3(char* pSrc, char* pDest, int nStart, int nLen)
 9 {
10     if (nStart < 0)
11     {
12         nStart = 0;
13     }
14     if (nLen < 0)
15     {
16         nLen = 0;
17     }
18     //....
19     memcpy(pDest, pSrc + nStart, nLen);
20     return pDest;
21 }
22 //获取注册码的第四段
23 char* RegistrationCode_4(char* pSrc, char* pDest, int nLen)
24 {
25     if (nLen < 0)
26     {
27         nLen = 0;
28     }
29     int nSrcLen = strlen(pSrc);
30     if (nLen < nSrcLen)
31     {
32         pSrc += nSrcLen;
33         pSrc -= nLen;
34         memcpy(pDest, pSrc, nLen);
35     }
36     return pDest;
37 }
38 //功能:根据机器码获取注册码
39 //参数:
40 //  pMachineID:传入参数,机器码
41 //  pKey:传出参数,存储计算完的注册码
42 char* GetSerialNumber(char* pMachineID, char* pKey)
43 {
44     char szTmpBuf[5] = { 0 };
45     BYTE uMD5Buf[CODE_LEN] = { 0 };
46     BYTE uTmpBuf[CODE_LEN] = { 0 };
47     //计算机器码的MD5值
48     MDString(pMachineID, uMD5Buf);
49     //将MD5数据转为16进制字符串
50     HexToStr(uTmpBuf, uMD5Buf, 16);
51     //将机器码的MD5字符串再次计算MD5
52     MDString((char*)uTmpBuf, uMD5Buf);
53     //将MD5数据转为16进制字符串
54     HexToStr(uTmpBuf, uMD5Buf, 16);
55     //将字符串转为小写
56     strlwr((char*)uTmpBuf);
57     //注册码的第一段
58     RegistrationCode_1((char*)uTmpBuf, szTmpBuf, 4);
59     strcat(pKey, szTmpBuf);
60     //注册码的第二段
61     RegistrationCode_2_3((char*)uTmpBuf, szTmpBuf, 0x0A, 0x4);
62     sprintf(pKey, "%s-%s", pKey, szTmpBuf);
63     //注册码的第三段
64     RegistrationCode_2_3((char*)uTmpBuf, szTmpBuf, 0x16, 0x4);
65     sprintf(pKey, "%s-%s", pKey, szTmpBuf);
66     //注册码的第四段
67     RegistrationCode_4((char*)uTmpBuf, szTmpBuf, 4);
68     sprintf(pKey, "%s-%s", pKey, szTmpBuf);
69     return pKey;
70 }

总结

之前从来没有写过KeyGen,该软件的注册码生成基本上没啥难度,倒是追机器码搞得我头大

说明:

 1.KeyGen在虚拟机中可能无法获取硬盘信息和物理内存信息,我在XP虚拟机下正常获取,Win7下获取不到硬盘信息和物理内存信息(具体细节没研究),不过该软件在Win7虚拟机下也是获取不到硬盘信息和物理内存信息,KeyGen可以正常获取注册码

 2.Win10下需要管理员权限运行才能拿到硬件信息

 3.工程中的KeyGen不能直接实现软件注册(只用于学习,务作它用),因为该软件除了本地验证外还有网络验证(Windows Defender会报毒直接杀掉,介意的务尝试),只要输入注册码出现以下截图就说明本地注册完成,填写邮箱是网络验证的事情
KeyGen代码地址:https://pan.baidu.com/s/1i5peytJ 密码:ip5q(失效请练习我)

考文档: 
获取硬盘序列号参考代码:http://blog.csdn.net/tody_guo/article/details/26084143 
获取物理内存信息参考代码:http://blog.csdn.net/wangxvfeng101/article/details/7394725

转载于:https://www.cnblogs.com/jieliuhouzi/p/8093294.html

某pdf转word v6.3.0.2算法分析相关推荐

  1. pdf to word android,Scanned PDF to Word

    Scanned PDF to Word 介绍 Scanned PDF to Word Scanned documents are not easy to convert with regular co ...

  2. 用Python制作一个PDF转Word工具

    用Python制作一个PDF转Word工具 工具:Python3.9.13,VSCode1.73.1,pdf2docx0.5.6,tkinter,Win10Home PDF文件不易编辑,想要编辑需要转 ...

  3. PDF转WORD的工具——Solid Converter PDF V6.0 中文破解版

    PDF转WORD的工具--Solid Converter PDF V6.0 中文破解版 Solid Converter PDF是一个完成从PDF格式到Word格式.doc的格式转换的工具,并完全支持中 ...

  4. 福昕PDF编辑器手机版4.0发布,让PDF跟Word一样简单

    说到PDF编辑器你肯定会想到adobe和福昕,要修改PDF文档就必须使用PDF编辑器,而福昕PDF编辑器是为数不多的优秀产品.像小编我,一个普通上班员工,每天都要接触大量的PDF文档,就连手机上也装满 ...

  5. 如何保存PDF、Word和Excel文件到数据库中

    在项目中,有时候我们很需要把PDF.Word和Excel文档等等上传到数据库,以便日后使用.今天这篇文章向大家讲解如何将这些文件保存到数据库的. 详细步骤 第一步:打开数据库,单击新建查询,创建一个名 ...

  6. python实现pdf转word详解_手把手|20行Python代码教你批量将PDF文件转为Word格式(包教包会)...

    在日常工作或学习中,经常会遇到这样的无奈: "小任,你把这个PDF中的文件码出来发我" 倒霉,2M的PDF12点也完不了啊! 很多时候在学习时发现许多文档都是PDF格式,PDF格式 ...

  7. 6行代码!用Python将PDF转为word

    pdf转word应该算是一个很常见的需求了网上有些免费的转换工具,一方面不安全,有文件泄露风险,另一方面有免费转换的次数限制. 今天向大家分享一个很好用的工具:pdf2docx 安装 $ pip in ...

  8. 编写java实用工具-针对未压缩的pdf转word,(java实现),压缩过的pdf勿进

    压缩过的pdf勿进! 压缩过的pdf勿进! 压缩过的pdf勿进! 重要的事情说三遍. 主要是提供两种解决办法 第一种:转成同级目录word 第二种:直接console打印 java的好处之一就是取之不 ...

  9. pdf转word python_Python实现PDF转Word

    "阅读此篇需要三分钟" 首先来看看来个PDF文件 我们来选择其中一个论文摘要: 使用我们的python代码转化后: 是不是很神奇? 现在网络上大部分的PDF转Word都是收费的,基 ...

最新文章

  1. linux 内核 初始化失败,300分求内核初始化及启动中出现的问题,
  2. mysql gtid 5.7_MySQL5.7之GTID复制
  3. 【C#】VS2017 winform 打包
  4. C#类、接口、虚方法和抽象方法-抽象类与接口的区别与联系
  5. (转)淘淘商城系列——导入商品数据到索引库——Service层
  6. “白领复工10大热销商品”榜单:口罩及相关商品位居第一
  7. 计算机行业更看重学历还是更看重技术?
  8. HDU 5974 2016ICPC大连 D: A Simple Math Problem
  9. nginx——ngx_http_gzip_module
  10. noip模拟赛 对刚
  11. 深度学习常见的基本概念整理
  12. js验证身份证营业执照组织机构代码等
  13. Python计算流体动力学(CFD)模拟人体呼吸系统
  14. mysql计算增长率
  15. 小米路由的IPv6支持教程(纯转保存)
  16. ensp华为防火墙及应用
  17. 在计算机中添加用户时提示拒绝访问,教你怎么解决打印机拒绝访问问题
  18. 基于asp.net学生信息管理系统的设计与实现(毕设)
  19. 数字中国城市巡礼之开封:千年古都的智慧新生
  20. C语言编写程序求1到100的和,C语言菜鸟基础教程之求1到100的和

热门文章

  1. 完美用Nlite添加ACHI SATA驱动至XP镜像
  2. java 32位兼容_Java 32位与64位兼容性
  3. [图像特征匹配]SIFT、SURF、ORB算法笔记以及代码实现
  4. 洛谷P2240木材加工
  5. Mac上安装R语言运行环境及RStudio [超详细!~]
  6. 如何下载并安装Firebug插件
  7. 机器学习算法:根据幸福感问卷调查做预测
  8. transformers5--t5模型中encoder与decoder内容不同解读
  9. 《金蝶ERP-K/3完全使用详解》——6.2 产品预测单
  10. 为macbook双系统的windows装驱动