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
2
pop ip//当前读取指令的地址
pop cs//cs寄存器

如果此时栈中有0x33,retf会将0x33弹出到CS寄存器,实现32位程序转换到64位代码的过程。所以retf是识别32位程序调用64位代码的重要标志

利用好这一点就可以实现64位代码和32位代码来回切换

解决

dump

使用idc将64位的代码段dump下来

1
2
3
4
5
6
7
8
9
static main(void)
{
auto fp, begin, end, dexbyte;
fp = fopen("./final", "wb");
begin = 0x4011C0;//起始地址
end = 0x4012A0;//结束地址
for ( dexbyte = begin; dexbyte < end; dexbyte ++ )
fputc(Byte(dexbyte), fp);
}

使用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

StartAddress函数

就是对flag的格式进行判断,并且对最后的几个字符进行异或

但是此时我们还未输入,按理说会一直卡在while循环,但是单步步过CreateNewThread时都是正常的

compare_input函数

判断输入前几位是否为ISCC

encode_and_compare函数

通过动态调试可以知道前面的while循环是将每八个输入存储为QWORD类型,第二段就是对key,也就是命令行参数的第二个进行加密,然后传入encode()函数

encode()函数

这一段就是开辟空间和初始化直接动调,关键还是对该函数的调用

注意要先添加命令行参数

开启调试到达该函数,发现了切换为64位工作模式的代码

将这一段dump下来

这里调用了920000地址处的函数,所以也要一起dump下来

1
2
3
4
5
6
7
8
9
static main(void)
{
auto fp, begin, end, dexbyte;
fp = fopen("./final1", "wb");
begin = 0x920000;//起始地址
end = 0x93006B;//结束地址
for ( dexbyte = begin; dexbyte < end; dexbyte ++ )
fputc(Byte(dexbyte), fp);
}

拖进ida64,可以看出来是tea加密

切换回32位

经此全部分析就已经结束了,前十六个字符先转long long型,然后进行tea加密,后5个字符进行异或,tea的密钥直接在调用encode函数处提取即可

加密代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
unsigned __int64 tmp0;
unsigned __int64 tmp1;
unsigned char flag[32] = { 0 };
unsigned int a[4]={0x11,0x4,0x25,0xf};
unsigned __int64 temp[2] = { 0 };
unsigned __int64 v11[] =
{
0x425B1CD73C3598F1, 0x72C63C6A5ACF14BA
};
for (int i = 0; i < 2; i += 2)
{
tmp0 = v11[i];
tmp1 = v11[i + 1];
__int64 sum = 0;
for (int j = 0; j < 32; ++j)
{
sum += 0x9E3779B9;
tmp0 += (sum + tmp1) ^ (a[0] + 16 * tmp1) ^ (a[1] + (tmp1 >> 5));
tmp1 += (sum + tmp0) ^ (a[2] + 16 * tmp0) ^ (a[3] + (tmp0 >> 5));
}
v11[i] = tmp0;
v11[i + 1] = tmp1;
}
//0x425B1CD73C3598F1, 0x72C63C6A5ACF14BA

这里我不知道为啥我拿输入去加密得到的结果和程序的一致,但是解不出来😢

解不出来的解密代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
unsigned __int64 tmp0;
unsigned __int64 tmp1;
unsigned char flag[32] = { 0 };
unsigned int a[4]={0x11,0x4,0x25,0xf};
unsigned __int64 temp[2] = { 0 };
unsigned __int64 v11[] =
{
0x425B1CD73C3598F1, 0x72C63C6A5ACF14BA
};

tmp0 = v11[0];
tmp1 = v11[1];
__int64 sum = 32 * 0x9E3779B9;
for (int j = 0; j < 32; ++j)
{
tmp1 -= (sum + tmp0) ^ (a[2] + 16 * tmp0) ^ (a[3] + (tmp0 >> 5));
tmp0 -= (sum + tmp1) ^ (a[0] + 16 * tmp1) ^ (a[1] + (tmp1 >> 5));
sum -= 0x9E3779B9;
}
v11[0]= tmp0;
v11[1]= tmp1;

想动态调试这段汇编好像会出问题,只能单步步过

不过好像使用WINDBG可以调试

linux

代码分析

可以知道这一段代码是在0xDEAD000开辟空间,然后赋值后再SMC解密,然后就出现了无法识别的代码,看汇编动调发现和前面是一样的

使用retf实现jmp far

解决方法同上