【文章标题】: OD 写补丁代码插件 SkyPatch 的 bug 修正
【文章作者】: CCDebuger
【软件名称】: SkyPatch 1.1
【下载地址】: 自己搜索下载
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
【详细过程】
SkyPatch 插件用来在 OD 中写补丁代码是比较方便的。不过这个插件有个 bug,就是当写入的字串放在 VA 地址 00A00000 以后,则无法构建。如果构建的话,则会出现类似这样的错误:
错误的命令位于行: 18
PUSH a17250 Unknown identifier
构建失败
补丁代码类似于这样:
@0x00A17250:
$str1
"xxxxxx"
以下代码省略...
PUSH $str1
这里要在 OD 中正常汇编的话,转换后的指令应该是:
PUSH 0a17250
这样才能正常编译。可见 SkyPatch 插件在把地址的
DWORD 值转换为字串时,把前面的 0 丢掉了。如果地址是低于 00A00000 的值,这样没什么问题,但如果转换的字串地址大于 00A00000,则无法编译了。现在试着修复这个错误。用 IDA 分析一下 SkyPatch 插件,根据出错字串“错误的命令位于行”很容易发现有三个地方调用。两个地方都是调用 OD 的汇编功能的,不是我们要找的地方。那就只有这个地方:
.text:1000F1DF loc_1000F1DF:
; CODE XREF: _Convert_Param_to_Str+EFj
.text:1000F1DF
mov edx, [
eax+0Ch]
.text:1000F1E2
mov ecx, (
offset LibFileName+124h)
.text:1000F1E7
and dh, 0F9h
.text:1000F1EA
or dh, 8
.text:1000F1ED
mov [
eax+0Ch],
edx
.text:1000F1F0
mov eax, [
esp+3Ch+arg_0]
.text:1000F1F4
push eax ; 这里就是地址的DWORD值
.text:1000F1F5
call ds:
std::basic_ostream<char,
std::char_traits<char>>::operator<<(uint)
.text:1000F1F5
.text:1000F1FB
lea ecx, [
esp+3Ch+var_1C]
.text:1000F1FF
push ecx
.text:1000F200
mov ecx, (
offset LibFileName+11Ch)
.text:1000F205
call ds:
std::basic_stringstream<char,
std::char_traits<char>,
std::allocator<char>>::
str(void)
.text:1000F205
.text:1000F20B
mov esi,
eax
.text:1000F20D
mov edx,
ds:uint
const std::basic_string<char,
std::char_traits<char>,
std::allocator<char>>::npos
.text:1000F213
mov ecx, [
esi+8]
.text:1000F216
mov [
esp+3Ch+var_4], 1
.text:1000F21E
mov eax, [
edx]
.text:1000F220
mov edi,
eax
.text:1000F222
cmp ecx,
edi
.text:1000F224
jnb short loc_1000F228
.text:1000F224
.text:1000F226
mov edi,
ecx ; 在 EDI 中保留转换后的地址字串长度值
.text:1000F226
.text:1000F228
.text:1000F228 loc_1000F228:
; CODE XREF: _Convert_Param_to_Str+144j
.text:1000F228
sub eax, [
ebp+8]
.text:1000F22B
cmp eax,
edi
.text:1000F22D
ja short loc_1000F235
.text:1000F22D
.text:1000F22F
call ds:
std::_Xlen(void)
.text:1000F22F
.text:1000F235
.text:1000F235 loc_1000F235:
; CODE XREF: _Convert_Param_to_Str+14Dj
.text:1000F235
test edi,
edi
.text:1000F237
jbe short loc_1000F27C
.text:1000F237
.text:1000F239
mov ebx, [
ebp+8]
.text:1000F23C
push 0
.text:1000F23E
add ebx,
edi ; 地址字串长度或指令+地址字串长度送到 EBX
.text:1000F240
mov ecx,
ebp
.text:1000F242
push ebx
.text:1000F243
call ds:
std::basic_string<char,
std::char_traits<char>,
std::allocator<char>>::_Grow(uint,bool)
.text:1000F243
.text:1000F249
test al,
al
.text:1000F24B
jz short loc_1000F27C
.text:1000F24B
.text:1000F24D
mov esi, [
esi+4]
; [ESI+4]中就是转换后的地址字串
.text:1000F250
test esi,
esi
.text:1000F252
jnz short loc_1000F25A
.text:1000F252
.text:1000F254
mov esi,
ds:char
const `std::basic_string<char,
std::char_traits<char>,
std::allocator<char>>::_Nullstr(void)
'::`2'::_C
.text:1000F254
.text:1000F25A
.text:1000F25A loc_1000F25A:
; CODE XREF: _Convert_Param_to_Str+172j
.text:1000F25A
mov eax, [
ebp+4]
.text:1000F25D
mov ecx,
edi
.text:1000F25F
mov edi, [
ebp+8]
.text:1000F262
add edi,
eax
.text:1000F264
mov eax,
ecx
----------------------------------------------------------------------------------------------------------
现在复制一个原版的 OllyDBG,只配置 SkyPatch 这一个插件,这个新配置的 OD 我们设为 OD1。现在用我们自己常用的 OD 打开 OD1,用 OD1 载入我们要打补丁的程序,调用 SkyPatch 打开要写入的补丁脚本。现在转到我们自己常用的 OD 中,ALT+E 打开模块窗口,双击 SkyPatch 模块,在汇编窗口中定位到我们上面看到的代码处,设断点。当然上面的代码是我已经结合 OD 分析过的,第一次我们可能定位的位置并不一定对。不过没关系,可以根据断下来后程序的执行情况再调整断点。这个各位可以自己测试。现在说一下在 OD 中调试看到的情况:
026AF1F4 |. 50
PUSH EAX ; EAX 就是参数经过转换后所得到的地址 DWORD 值
026AF1F5 |. FF15 60306B02
CALL DWORD PTR DS:[<&MSVCP60.??6?$basic_ostream@>
; MSVCP60.??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@I@Z
026AF1FB |. 8D4C24 20
LEA ECX,
DWORD PTR SS:[
ESP+20]
026AF1FF |. 51
PUSH ECX
026AF200 |. B9 807C6B02
MOV ECX,026B7C80
026AF205 |. FF15 F8306B02
CALL DWORD PTR DS:[<&MSVCP60.?str@?$basic_string>
; MSVCP60.?str@?$basic_stringstream@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QBE?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@2@XZ
026AF20B |. 8BF0
MOV ESI,
EAX
026AF20D |. 8B15 BC306B02
MOV EDX,
DWORD PTR DS:[<&MSVCP60.?npos@?$basic_st>
; MSVCP60.?npos@?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@2IB
026AF213 |. 8B4E 08
MOV ECX,
DWORD PTR DS:[
ESI+8]
; 转换后的地址字串长度
026AF216 |. C74424 38 010>
MOV DWORD PTR SS:[
ESP+38],1
026AF21E |. 8B02
MOV EAX,
DWORD PTR DS:[
EDX]
026AF220 |. 8BF8
MOV EDI,
EAX
026AF222 |. 3BCF
CMP ECX,
EDI ; 比较字串长度是否大于等于0
026AF224 |. 73 02
JNB SHORT 026AF228
026AF226 |. 8BF9
MOV EDI,
ECX ; 在 EDI 中保留转换后的地址字串长度值
026AF228 2B45 08
SUB EAX,
DWORD PTR SS:[
EBP+8]
026AF22B 3BC7
CMP EAX,
EDI
026AF22D |. 77 06
JA SHORT 026AF235
026AF22F |. FF15 CC306B02
CALL DWORD PTR DS:[<&MSVCP60.?_Xlen@std@@YAXXZ>]
; MSVCP60.?_Xlen@std@@YAXXZ
026AF235 |> 85FF
TEST EDI,
EDI ; 判断字串长度是否等于0
026AF237 |. 76 43
JBE SHORT 026AF27C
026AF239 |. 8B5D 08
MOV EBX,
DWORD PTR SS:[
EBP+8]
026AF23C |. 6A 00
PUSH 0
026AF23E |. 03DF
ADD EBX,
EDI ; 地址字串长度或指令+地址字串长度送到 EBX
026AF240 |. 8BCD
MOV ECX,
EBP
026AF242 |. 53
PUSH EBX
026AF243 |. FF15 C0306B02
CALL DWORD PTR DS:[<&MSVCP60.?_Grow@?$basic_stri>
; MSVCP60.?_Grow@?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@AAE_NI_N@Z
026AF249 |. 84C0
TEST AL,
AL
026AF24B |. 74 2F
JE SHORT 026AF27C
026AF24D 8B76 04
MOV ESI,
DWORD PTR DS:[
ESI+4]
; [ESI+4]中就是转换后的地址字串
026AF250 85F6
TEST ESI,
ESI
026AF252 |. 75 06
JNZ SHORT 026AF25A
026AF254 |. 8B35 1C316B02
MOV ESI,
DWORD PTR DS:[<&MSVCP60.?_C@?1??_Nullstr>
; MSVCP60.?_C@?1??_Nullstr@?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@CAPBDXZ@4DB
026AF25A 8B45 04
MOV EAX,
DWORD PTR SS:[
EBP+4]
; 这里改成跳到我们的补丁代码处执行
026AF25D 8BCF
MOV ECX,
EDI
026AF25F |. 8B7D 08
MOV EDI,
DWORD PTR SS:[
EBP+8]
026AF262 |. 03F8
ADD EDI,
EAX
026AF264 |. 8BC1
MOV EAX,
ECX
026AF266 |. C1E9 02
SHR ECX,2
026AF269 |. F3:A5
REP MOVS DWORD PTR ES:[
EDI],
DWORD PTR DS:[
ESI]
026AF26B |. 8BC8
MOV ECX,
EAX
026AF26D |. 83E1 03
AND ECX,3
026AF270 |. F3:A4
REP MOVS BYTE PTR ES:[
EDI],
BYTE PTR DS:[
ESI]
----------------------------------------------------------------------------------------------------------
从上面可以看出,程序是调用了 STL 模板库中的标准函数把地址的
DWORD 转成了字串,不过后面没对转换后字串的第一个字符是否位于“a”到“f”间作判断,就直接拿来用了。导致会出现我们开始时提到的那个错误。现在我们只要加上这个判断,如果第一个字符在“a”到“f”之间,我们就在字串前面插一个“0”字符。这里就偷个懒,直接判断第一个字符是否大于字符“9”,如果大于则在字串前面插一个“0”字符。找一个空白地方写我们的代码,我这把补丁代码放在 RVA 125C0 处。根据程序在我这重定位后的基址,我在 VA 026B25C0 处写补丁代码。先把上面地址 026AF25A 处的代码改为跳到我们的补丁代码处执行:
----------------------------------------------------------------------------------------------------------
修改后代码:
026AF25A E9 61330000
JMP 026B25C0
; 这里改成跳到我们的补丁代码处执行
026AF25F |. 8B7D 08
MOV EDI,
DWORD PTR SS:[
EBP+8]
----------------------------------------------------------------------------------------------------------
要写的补丁代码:
026B25C0 60
PUSHAD ; 保护现场
026B25C1 9C
PUSHFD
026B25C2 803E 39
CMP BYTE PTR DS:[
ESI],39
; 第一个字符是否大于9
026B25C5 7F 0C
JG SHORT 026B25D3
; 大于9则处理,否则返回
026B25C7 9D
POPFD
026B25C8 61
POPAD
026B25C9 8B45 04
MOV EAX,
DWORD PTR SS:[
EBP+4]
026B25CC 8BCF
MOV ECX,
EDI
026B25CE ^ E9 8CCCFFFF
JMP 026AF25F
026B25D3 C6443E 01 00
MOV BYTE PTR DS:[
ESI+
EDI+1],0
; 地址字串最后面添加一个0字节
026B25D8 8A443E FF
MOV AL,
BYTE PTR DS:[
ESI+
EDI-1]
026B25DC 88043E
MOV BYTE PTR DS:[
ESI+
EDI],
AL ; 这里依次把地址字串的字符向后移一位,以便我们在字串开始的地方插一个0
026B25DF 4F
DEC EDI
026B25E0 83FF 00
CMP EDI,0
; 判断字串是否都已移完
026B25E3 ^ 75 F3
JNZ SHORT 026B25D8
026B25E5 C606 30
MOV BYTE PTR DS:[
ESI],30
; 在地址字串前面加一个字符0
026B25E8 9D
POPFD
026B25E9 61
POPAD
026B25EA 43
INC EBX ; 指令字串总长度加1
026B25EB 47
INC EDI ; 地址字串长度加1
026B25EC 8B45 04
MOV EAX,
DWORD PTR SS:[
EBP+4]
; 恢复原始代码并返回继续执行
026B25EF 8BCF
MOV ECX,
EDI
026B25F1 ^ E9 69CCFFFF
JMP 026AF25F
; 返回到原程序的下一句代码继续执行
附件是 dup 制作的补丁,可以直接对 SkyPatch 1.1 进行 patch 修正 bug。
--------------------------------------------------------------------------------
【版权声明】: 本文纯属技术交流, 转载请注明作者并保持文章的完整, 谢谢!
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课