学习 z3 时复现的一道题,了解了 SSE 指令以及学习了 gdb 和 qemu 之间的调试。

Analysis

$ file main.bin
main.bin: DOS/MBR boot sector

题目给的是一个 DOS 程序,直接拖进 IDA,以 16 位的模式打开。定位到一处检测字符串前缀的代码,基本可以判断出 1234h 即我们输入的字符串在内存中的地址。

seg000:006F                 cmp     dword ptr ds:1234h, 'galf'
seg000:0078                 jnz     loc_14D
seg000:007C                 movaps  xmm0, xmmword ptr ds:1238h
seg000:0081                 movaps  xmm5, xmmword ptr ds:7C00h
seg000:0086                 pshufd  xmm0, xmm0, 1Eh

因为 IDA 不能反编译,所以基本上是直接看汇编,那么需要稍微了解一下 SSE 指令

指令 作用
MOVAPS 把源存储器内容值送入目的寄存器。当有 m128 时, 内存地址必须是 16 字节对齐的。
PSHUFD 有三个操作数,从左往右,第一个操作数是目的操作数保存结果,第二个操作数是源操作数,第三个操作数是一个 8 位立即数,指定以怎样的顺序将源操作数中数据保存到目的操作数。
ANDPS 按位与
PSADBW 绝对差值求和

接下来的汇编部分有一些小复杂,动态调试会比较清晰。先用 qemu 启动程序(-s 参数开启远程调试服务):

qemu-system-i386 -drive format=raw,file=main.bin -s

gdb 进行远程连接,并设置指令架构,同时在比较完 flag 前缀后设下断点(MBR 的加载地址是 0x7C00):

gdb -ex 'target remote localhost:1234' \
    -ex 'set architecture i8086' \
    -ex 'break *0x7c6f' \
    -ex 'continue'

然后在程序中输入字符串,以 flag 为前缀,后面的部分用 a 到 p 来填充:

输入完成后我们可以看到 gdb 断了下来,此时可以开始和 IDA 对照着调试。可能是因为指令的结构不一样,所以 gdb 中只有当前指向的指令是基本正确的(有时可能也不正确,还是要看 IDA 的汇编)。同时在每次对 xmm 寄存器操作后,可以使用 p 命令输出并查看寄存器中的值。

首先单步调一下,看看两条 movaps 指令:

seg000:007C                 movaps  xmm0, xmmword ptr ds:1238h
seg000:0081                 movaps  xmm5, xmmword ptr ds:7C00h

单步后可以看到 xmm0 中存放了我们输入的字符串中 flag 之后的部分(我是在 mac 下调试的,gdb 中可能有点 bug,寄存器存的值明显偏了 32 位)。接着执行下一条指令,可以看出 xmm5 中存放了内存中另一段的数据,可以打印出来看看:

gef> p $xmm5
$6 = {
  v4_float = {-2.50091934, -1.48039995e-36, 1.93815862e-18, 0},
  v2_double = {-1.787847107871084e-289, 2.8231360405480285e-315},
  v16_int8 = {0x10, 0xf, 0x20, 0xc0, 0x83, 0xe0, 0xfb, 0x83, 0xc8, 0x2, 0xf, 0x22, 0x0, 0x0, 0x0, 0x0},
  v8_int16 = {0xf10, 0xc020, 0xe083, 0x83fb, 0x2c8, 0x220f, 0x0, 0x0},
  v4_int32 = {0xc0200f10, 0x83fbe083, 0x220f02c8, 0x0},
  v2_int64 = {0x83fbe083c0200f10, 0x220f02c8},
  uint128 = 0x220f02c883fbe083c0200f10
}
gef> p $xmm4
$7 = {
  v4_float = {0, 0, 0, -134298496},
  v2_double = {0, -8.2671312985563202e+62},
  v16_int8 = {0x0 <repeats 12 times>, 0xb8, 0x13, 0x0, 0xcd},
  v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x13b8, 0xcd00},
  v4_int32 = {0x0, 0x0, 0x0, 0xcd0013b8},
  v2_int64 = {0x0, 0xcd0013b800000000},
  uint128 = 0xcd0013b8000000000000000000000000
}
seg000:0086                 pshufd  xmm0, xmm0, 1Eh
seg000:008B                 mov     si, 8

接下来单步执行 pshufd 这条指令。不关注 pshufd 的具体作用的话,可以在调试中看到,运行过后发现 xmm0 中的值从 0x706f6e6d6c6b6a696867666564636261 变成了 0x6463626168676665706f6e6d6c6b6a69,数据的顺序变化了。然后将 si 置为 8,在后面的调试中可以判断出,si 中存放的为循环次数。

seg000:008E                 movaps  xmm2, xmm0
seg000:0091                 andps   xmm2, xmmword ptr [si+7D90h]

接下来两条指令分别将 xmm0 赋给 xmm2 以及将 xmm2 和内存中的一段数据按位与。看一看到 xmm2 中的值即为变化后的输入:

gef> p $xmm2
$2 = {
  v4_float = {2.96401656e+29, 4.37102201e+24, 1.67779994e+22, 0},
  v2_double = {8.5408834851248547e+194, 8.3212257841951935e-315},
  v16_int8 = {0x6d, 0x6e, 0x6f, 0x70, 0x65, 0x66, 0x67, 0x68, 0x61, 0x62, 0x63, 0x64, 0x0, 0x0, 0x0, 0x0},
  v8_int16 = {0x6e6d, 0x706f, 0x6665, 0x6867, 0x6261, 0x6463, 0x0, 0x0},
  v4_int32 = {0x706f6e6d, 0x68676665, 0x64636261, 0x0},
  v2_int64 = {0x68676665706f6e6d, 0x64636261},
  uint128 = 0x6463626168676665706f6e6d
}
gef> p $xmm1
$3 = {
  v4_float = {0, 0, 0, 1.1384003e+27},
  v2_double = {0, 1.8458895617341177e+214},
  v16_int8 = {0x0 <repeats 12 times>, 0x69, 0x6a, 0x6b, 0x6c},
  v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6a69, 0x6c6b},
  v4_int32 = {0x0, 0x0, 0x0, 0x6c6b6a69},
  v2_int64 = {0x0, 0x6c6b6a6900000000},
  uint128 = 0x6c6b6a69000000000000000000000000
}

查看内存中的数据,可以看到和输入按位与之后,会把对应为 0x00 两个字节给抹掉。并通过下面的分析可以知道总共会有 8 次循环,每次进行按位与的数据是不同的,即每次的数据会左移 4 位:

gef> x/2gx 0x7D98
0x7d98:    0xffffffffffffff00    0xffffffffffffff00
gef> x/2gx 0x7D97
0x7d97:    0xffffffffffff00ff    0xffffffffffff00ff
gef> x/2gx 0x7D96
0x7d96:    0xffffffffff00ffff    0xffffffffff00ffff
gef> x/2gx 0x7D95
0x7d95:    0xffffffff00ffffff    0xffffffff00ffffff
gef> x/2gx 0x7D94
0x7d94:    0xffffff00ffffffff    0xffffff00ffffffff
gef> x/2gx 0x7D93
0x7d93:    0xffff00ffffffffff    0xffff00ffffffffff
gef> x/2gx 0x7D92
0x7d92:    0xff00ffffffffffff    0xff00ffffffffffff
gef> x/2gx 0x7D91
0x7d91:    0x00ffffffffffffff    0x00ffffffffffffff

接着看 psadbw 这条指令,这里将 xmm5 和 xmm2 中的数据进行绝对差值求和(每 64 位的数据逐字节相减,将结果相加后存到前一个操作数中)。接着把高低 64 位经过绝对差值求和的结果(每个结果大小为 16 位,高低两个结果共 32 位)保存到 edi 中:

seg000:0096                 psadbw  xmm5, xmm2
seg000:009A                 movaps  xmmword ptr ds:1268h, xmm5
seg000:009F                 mov     di, ds:1268h
seg000:00A3                 shl     edi, 10h
seg000:00A7                 mov     di, ds:1270h

然后将 edi 中的结果和内存里的值比较,如果不相等,会有个大跳转,IDA 里大概判断一下应该是直接跳到失败处。

seg000:00AB                 mov     dx, si
seg000:00AD                 dec     dx
seg000:00AE                 add     dx, dx
seg000:00B0                 add     dx, dx
seg000:00B2                 cmp     edi, [edx+7DA8h]
seg000:00BA                 jnz     loc_14D
seg000:00BE                 dec     si
seg000:00BF                 test    si, si
seg000:00C1                 jnz     short sub_8E

查看内存中进行比较的值,正好 8 个值,每次都进行一次判断:

gef> x/8wx 0x7DA8
0x7da8:    0x02110270    0x02290255    0x025e0291    0x01f90233
0x7db8:    0x027b0278    0x02090221    0x0290025d    0x02df028f

基本上所有的线性关系都有了,接下来交给 z3 就行了。

Script

#!/usr/bin/env python
from z3 import *

def z3_abs(x):
    return If(x >= 0, x, -x)

def psadbw(xmm0, xmm1):
    a = Sum([z3_abs(b1 - b2) for b1, b2 in zip(xmm0[:8], xmm1[:8])])
    b = Sum([z3_abs(b1 - b2) for b1, b2 in zip(xmm0[8:], xmm1[8:])])
    return a + b * 0x10000

s = Solver()
ZERO = IntVal(0)
xmm5 = '220f02c883fbe083c0200f10cd0013b8'.decode('hex')
xmm5 = [ord(c) for c in xmm5]
xmm5s = [xmm5]

xmm0 = [Int('x%d' % i) for i in range(16)]
for c in xmm0:
    s.add(c >= 32, c <= 126)

check = [0x02df028f, 0x0290025d, 0x02090221, 0x027b0278, 0x01f90233, 0x025e0291, 0x02290255, 0x02110270]
xmm5s += map(lambda e: [0, 0, 0, 0, 0, 0, (e >> 8) & 0xFF, e & 0xFF, 0, 0, 0, 0, 0, 0, e >> 24, (e >> 16) & 0xFF], check)
print xmm5s

for i in range(8):
    xmm5 = xmm5s[i]
    xmm2 = list(xmm0)
    xmm2[7 - i] = ZERO
    xmm2[15 - i] = ZERO
    res = psadbw(xmm5, xmm2)
    s.add(res == check[i])

if s.check() == sat:
    print s.model()
    flag = ''.join(chr(eval(str(s.model()[c]))) for c in xmm0)
    # pshufd xmm0, xmm0, 1Eh
    flag = 'flag' + flag[:4][::-1] + flag[4:8][::-1] + flag[12:][::-1] + flag[8:12][::-1]
    print flag
    # flag{4r3alz_m0d3_y0}

References

https://fortenf.org/e/ctfs/re/2017/09/18/csaw17-realism.html


ctf wp re

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

合天网安实验室-逆向部分
2018-XMan结营赛