我们之前讨论过操作数表示,但今天我们将使用一种特定的表示法来找到隐藏在其中的彩蛋。
更具体地说,是这张截图:
函数surprise
调用了printf
,但传递给它的参数似乎都是数字。printf()
不是通常处理字符串吗?这是怎么回事?
数字和字符
如你所知,计算机实际上并不区分数字和字符——对它们来说,这些都只是一些位的集合。因此,这完全是解释或表示的问题。例如,以下所有内容都由相同的位模式表示:
-
65
(十进制数) -
0x41
、41h
、H'41
(十六进制数) -
0101
或101o
(八进制数) -
1000001b
或0b1000001
(二进制数) -
'A'
(ASCII字符) -
WM_COMPACTING
(Win32 API常量) -
(以及许多其他变体)
字符操作数表示
事实上,截图中的列表已从默认设置修改,以使彩蛋不那么明显。以下是原始版本的文本:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
.text:00401010 ; intsurprise(...)
.text:00401010 _surprise proc near ; CODE XREF: _main↑p
.text:00401010
.text:00401010 var_24= dword ptr -24h
.text:00401010 var_20= dword ptr -20h
.text:00401010 _Format= byte ptr -1Ch
.text:00401010 var_18= dword ptr -18h
.text:00401010 var_14= dword ptr -14h
.text:00401010 var_10= dword ptr -10h
.text:00401010 var_C= dword ptr -0Ch
.text:00401010 var_8= dword ptr -8
.text:00401010 var_4= dword ptr -4
.text:00401010
.text:00401010 sub esp, 24h
.text:00401013 mov eax, ___security_cookie
.text:00401018xor eax, esp
.text:0040101A mov [esp+24h+var_4], eax
.text:0040101E lea eax, [esp+24h+var_24]
.text:00401021 mov dword ptr [esp+24h+_Format], 70747468h
.text:00401029 pusheax
.text:0040102A lea eax, [esp+28h+_Format]
.text:0040102E mov [esp+28h+var_18], 2F2F3A73h
.text:00401036 pusheax ; _Format
.text:00401037 mov [esp+2Ch+var_14], 2D786568h
.text:0040103F mov [esp+2Ch+var_10], 73796172h
.text:00401047 mov [esp+2Ch+var_C], 6D6F632Eh
.text:0040104F mov [esp+2Ch+var_8], 73252Fh
.text:00401057 mov [esp+2Ch+var_24], 74736165h
.text:0040105F mov [esp+2Ch+var_20], 7265h
.text:00401067 call_printf
.text:0040106C mov ecx, [esp+2Ch+var_4]
.text:00401070 add esp, 8
.text:00401073xor ecx, esp; StackCookie
.text:00401075xor eax, eax
.text:00401077 call@__security_check_cookie@4; __security_check_cookie(x)
.text:0040107C add esp, 24h
.text:0040107F retn
.text:0040107F _surprise endp
在十六进制中几乎立刻就能看出:“数字”实际上是ASCII文本的短片段。代码正在逐块在栈上构建字符串。通过将数字转换为字符操作数类型(快捷键R),可以更明确地表示这一点。
为了帮助你判断这种操作数类型是否有意义,IDA在上下文菜单中显示了预览:
这样就很清楚“数字”实际上是一个文本片段。将所有“数字”转换为字符常量后,模式开始显现:
由于x86处理器家族的小端内存组织,单个片段必须反向读取(即字符字面量'ptth'
对应于字符串片段"http"
)。
反编译器和优化的字符串操作
现在几乎可以看出结果应该是什么,但实际上还有一种更简单的方法来发现它。
因为处理寄存器大小的短字符串的方法通常被编译器用来内联实现常见的C运行时函数,而不是调用库函数,反编译器使用启发式方法来检测这种代码模式,并再次将其显示为等效的函数调用。如果我们反编译这个函数,反编译器会重新组装字符串,并在伪代码中显示它们:
栈字符串
恶意软件通常使用类似的方法在栈上逐块(通常是逐字符)构建字符串,因为这样完整的字符串不会出现在二进制文件中,无法通过简单搜索发现。感谢IDA为没有显式分配类型的操作数显示的自动注释,它们在反汇编中通常很明显:
反编译器轻松恢复完整字符串:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
void __noreturn start()
{
char v0[36]; // [esp+0h] [ebp-28h] BYREF
qmemcpy(v0, "FLAG{STACK-STRINGS-ARE-BEST-STRINGS}", sizeof(v0));
[...]
}
学习资源
立即关注【二进制磨剑】公众号
原文始发于微信公众号(二进制磨剑):IDA技巧(88)字符操作数类型和栈字符串
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论