CTF中32位调用64位代码
CTF中32位程序调用64位代码
¶参考文章
https://blog.shi1011.cn/ctf/1750
https://www.anquanke.com/post/id/171111
https://www.psbazx.com/2022/04/06/关于32位与64位程序切换/
https://moliam.github.io/2018/11/17/Wow64-at-the-assemly-level.html
WOW64实现——未实现
¶基础知识
¶CS和IP
CS是代码段寄存器,IP是指令指针寄存器
之所以要这样设计是因为8086CPU是16位的,而物理地址是20位的,他内存的寄存器只能表现16位的地址,因此使用ip寄存器来存放偏移地址
我们假设CS的值为M,IP的值为N,那么8086CPU将从内存M*16+N单元开始,读取下一条指令
工作流程
- 从CS:IP指向的内存单元读取指令,读取的指令进入指令缓冲区
- IP=IP+所读取指令的长度,便于读取下一条指令
- 执行指令,重复第一步
如果我们想修改CS:IP的值,可以使用转移指令,例如JMP指令
以上是基于16位程序中
但是32位以上段寄存器不再被用来指向段了,在本例子中CS存储的值用于判断是32位或64位工作模式
64位:CS=0x33;32位:CS=0x23
¶WOW64
WOW64 (Windows-on-Windows 64-bit)是一个Windows操作系统的子系统, 它为现有的32位应用程序提供了32位的模拟,可以使大多数32 位应用程序在无需修改的情况下运行在 Windows 64 位版本上。
在x64系统下的进程有32位和64位两种工作模式,这两种工作模式的区别在于CS寄存器。32位模式时,CS = 0x23;64位模式时,CS = 0x33
这两种工作模式是可以进行转换的,一般通过retf指令,一条retf指令等效于以下两条汇编指令
1 | pop ip//当前读取指令的地址 |
如果此时栈中有0x33,retf会将0x33弹出到CS寄存器,实现32位程序转换到64位代码的过程。所以retf是识别32位程序调用64位代码的重要标志
利用好这一点就可以实现64位代码和32位代码来回切换
¶解决
¶dump
使用idc将64位的代码段dump下来
1 | static main(void) |
使用ida64打开,选择64-bit mode
如同修改基址为0x4011C0
可以看到已经基本恢复了,剩下只需对比着进行代码分析即可
¶修改为64位程序
将这里的0x10B改成0x20B,然后使用ida64打开
基址重新修改为原本程序的基址
¶例子
¶hgame2022-week4-WOW
在某些函数中ida无法反编译
查看此函数的汇编,因为在此之后才无法反编译
因为retf相当于pop ip;pop cs,而ip是下一条指令的地址
所以这一段代码意思是先将64位工作模式的标志0x33压入栈中,然后通过call和add esp的值改变retf跳转的地址
然后通过retf时将0x33pop到cs中,换为64位工作模式,所以ida32无法正确反编译
再来看第二个函数
这里是一样的,不过这里使用的是mov指令,将0x23mov到esp+4存储的地址中
通过这一段代码恢复回32位工作模式
¶ISCC-擂台题
¶考点
创建子进程和子线程、TEA、WOW64
¶分析
当命令行参数为2时进入第一个函数,否则进入第二个
¶第二个函数
第二个函数传入的参数是命令行参数的第一个,也就是我们的程序绝对路径
进入第二个函数
这一段就是打开当前程序创建子进程(根据第六个参数判断进程的属性),命令行参数为我们打开程序的路径和" DIO",然后
然后根据调试事件判断时候break,这里当报告退出进程调试事件才会break
这样创建的子进程具有两个命令行参数,所以进入第一个函数
¶第一个函数
¶CreateNewThread函数
创建一个子线程,根据第五个参数dwCreationFlags为0可知是线程创建后立即执行,指定函数为StartAddress
就是对flag的格式进行判断,并且对最后的几个字符进行异或
但是此时我们还未输入,按理说会一直卡在while循环,但是单步步过CreateNewThread时都是正常的
¶compare_input函数
判断输入前几位是否为ISCC
¶encode_and_compare函数
通过动态调试可以知道前面的while循环是将每八个输入存储为QWORD类型,第二段就是对key,也就是命令行参数的第二个进行加密,然后传入encode()函数
¶encode()函数
这一段就是开辟空间和初始化直接动调,关键还是对该函数的调用
注意要先添加命令行参数
开启调试到达该函数,发现了切换为64位工作模式的代码
将这一段dump下来
这里调用了920000地址处的函数,所以也要一起dump下来
1 | static main(void) |
拖进ida64,可以看出来是tea加密
经此全部分析就已经结束了,前十六个字符先转long long型,然后进行tea加密,后5个字符进行异或,tea的密钥直接在调用encode函数处提取即可
加密代码
1 | unsigned __int64 tmp0; |
这里我不知道为啥我拿输入去加密得到的结果和程序的一致,但是解不出来😢
解不出来的解密代码
1 | unsigned __int64 tmp0; |
想动态调试这段汇编好像会出问题,只能单步步过
不过好像使用WINDBG可以调试
¶linux
代码分析
可以知道这一段代码是在0xDEAD000开辟空间,然后赋值后再SMC解密,然后就出现了无法识别的代码,看汇编动调发现和前面是一样的
使用retf实现jmp far
解决方法同上
v1.4.14