defcon XX CTF Quals Binary L33tness (b400) Write-up

b400 是一个运行在 FreeBSD x64 下的 TCP Server 程序,在此下载

题目给出的提示是 No takebacks,即 b400 在运行中不会给出提示,如果 client 提交的数据出现错误,直接断开连接。不过对我们来说没有太大关系。


拿出 IDA 64-bit 进行分析。为了能让程序在本地运行,先爆破掉几个关键点:

.text:0000000000401767 loc_401767:
.text:0000000000401767                 mov     edi, offset name ; "takeback"
; 下面三条指令用来判断系统中是否存在一个名为takeback的用户。直接 nop 掉。
.text:000000000040176C                 call    _getpwnam
.text:0000000000401771                 test    rax, rax
.text:0000000000401774                 jz      short loc_40178A
.text:0000000000401776                 mov     rdi, rax
; 下面几条指令进一步获取 takeback 用户的信息。我们只要让它跳到 0x401794 就
可以了。可以修改成 xor eax, eax; jz short loc_401794。
.text:0000000000401779                 call    sub_401150
.text:000000000040177E                 cmp     eax, 0FFFFFFFFh
.text:0000000000401781                 mov     edi, eax        ; status
.text:0000000000401783                 jnz     short loc_401794
.text:0000000000401785
.text:0000000000401785 loc_401785:
.text:0000000000401785                 call    _exit
.text:0000000000401794 loc_401794:
.text:0000000000401794                 mov     edi, ebx        ; fd
.text:0000000000401796                 call    _close
; 下面两条指令设定了15 s 之后触发一个ALARM消息,影响调试。nop 掉。
.text:000000000040179B                 mov     edi, 0Fh        ; seconds
.text:00000000004017A0                 call    _alarm
.text:00000000004017A5                 mov     edi, ebp
.text:00000000004017A7                 call    r14
.text:00000000004017AA                 mov     edi, ebp        ; fd
.text:00000000004017AC                 mov     ebx, eax
.text:00000000004017AE                 call    _close
.text:00000000004017B3                 mov     edi, ebx
.text:00000000004017B5                 jmp     short loc_401785
.text:00000000004017B5 sub_401710      endp

爆掉之后,程序就可以跑在FreeBSD上了。配合着gdb调试,我们可以整理出程序的流程:

.text:0000000000401710 ; int __fastcall sub_401710(int fd)
.text:0000000000401710 sub_401710      proc near
.text:0000000000401710
.text:0000000000401710 addr_len        = dword ptr -2Ch
.text:0000000000401710
.text:0000000000401710                 push    r14
.text:0000000000401712                 mov     r14, rsi
.text:0000000000401715                 push    r13
.text:0000000000401717                 push    r12
.text:0000000000401719                 push    rbp
.text:000000000040171A                 push    rbx
.text:000000000040171B                 mov     ebx, edi
.text:000000000040171D                 sub     rsp, 20h
.text:0000000000401721                 lea     r12, [rsp+48h+addr_len]
.text:0000000000401726                 mov     r13, rsp
.text:0000000000401729                 nop     dword ptr [rax+00000000h]
.text:0000000000401730
.text:0000000000401730 loc_401730:
.text:0000000000401730                 mov     rdx, r12        ; addr_len
.text:0000000000401733                 mov     rsi, r13        ; addr
.text:0000000000401736                 mov     edi, ebx        ; fd
.text:0000000000401738                 mov     [rsp+48h+addr_len], 10h
.text:0000000000401740                 call    _accept ; 接受新连接
.text:0000000000401745                 cmp     eax, 0FFFFFFFFh
.text:0000000000401748                 mov     ebp, eax
.text:000000000040174A                 jz      short loc_401730
.text:000000000040174C                 call    _fork  ;fork 出新的子进程
.text:0000000000401751                 cmp     eax, 0FFFFFFFFh
.text:0000000000401754                 jz      short loc_401730
.text:0000000000401756                 test    eax, eax
.text:0000000000401758                 jz      short loc_401767
.text:000000000040175A                 mov     edi, ebp        ; fd
.text:000000000040175C                 nop     dword ptr [rax+00h]
.text:0000000000401760                 call    _close
.text:0000000000401765                 jmp     short loc_401730
.text:0000000000401767 ; -------------------------------------------
.text:0000000000401767
.text:0000000000401767 loc_401767:
.text:0000000000401767                 mov     edi, offset name ; "takeback" ;
这下面已经被爆破掉了
.text:000000000040176C                 call    _getpwnam
.text:0000000000401771                 test    rax, rax
.text:0000000000401774                 jz      short loc_40178A
.text:0000000000401776                 mov     rdi, rax
.text:0000000000401779                 call    sub_401150
.text:000000000040177E                 cmp     eax, 0FFFFFFFFh
.text:0000000000401781                 mov     edi, eax        ; status
.text:0000000000401783                 jnz     short loc_401794
.text:0000000000401785
.text:0000000000401785 loc_401785:
.text:0000000000401785                 call    _exit
.text:000000000040178A ; ---------------------------------------------------------------------------
.text:000000000040178A
.text:000000000040178A loc_40178A:
.text:000000000040178A                 mov     edi, 0FFFFFFFFh ; status
.text:000000000040178F                 call    _exit
.text:0000000000401794 ; ---------------------------------------------------------------------------
.text:0000000000401794
.text:0000000000401794 loc_401794:
.text:0000000000401794                 mov     edi, ebx        ; fd
.text:0000000000401796                 call    _close
.text:000000000040179B                 mov     edi, 0Fh        ; 这下面两行也被爆破掉了
.text:00000000004017A0                 call    _alarm
.text:00000000004017A5                 mov     edi, ebp
.text:00000000004017A7                 call    r14 ; 隐藏调用,
call hiddenFunction() 0x4017f0。这个地址来自 0x4017d0
.text:00000000004017AA                 mov     edi, ebp        ; fd
.text:00000000004017AC                 mov     ebx, eax
.text:00000000004017AE                 call    _close
.text:00000000004017B3                 mov     edi, ebx
.text:00000000004017B5                 jmp     short loc_401785
.text:00000000004017B5 sub_401710      endp
.text:00000000004017B5
.text:00000000004017B5 ; ---------------------------------------------------------------------------
.text:00000000004017B7                 align 20h
.text:00000000004017C0
.text:00000000004017C0 ; =============== S U B R O U T I N E =======================================
.text:00000000004017C0
.text:00000000004017C0 ; Attributes: noreturn
.text:00000000004017C0
.text:00000000004017C0 sub_4017C0      proc near
.text:00000000004017C0                 sub     rsp, 8
.text:00000000004017C4                 movsx   edi, cs:word_602358
.text:00000000004017CB                 call    sub_4012A0
.text:00000000004017D0                 mov     esi, offset hiddenFunction
.text:00000000004017D5                 mov     edi, eax        ; fd
.text:00000000004017D7                 call    sub_401710
.text:00000000004017D7 sub_4017C0      endp
.text:00000000004017D7
.text:00000000004017D7 ;

注意到hiddenFunction()是一个关键点,我们继续分析。

.text:00000000004017F0 ; FUNCTION CHUNK AT .text:0000000000401890 SIZE 00000170 BYTES
.text:00000000004017F0
.text:00000000004017F0                 mov     [rsp+var_28], rbp
.text:00000000004017F5                 mov     [rsp+var_20], r12
.text:00000000004017FA                 mov     r12d, edi
.text:00000000004017FD                 mov     [rsp+var_30], rbx
.text:0000000000401802                 mov     [rsp+var_18], r13
.text:0000000000401807                 mov     edi, 4          ; size
.text:000000000040180C                 mov     [rsp+var_10], r14
.text:0000000000401811                 mov     [rsp+var_8], r15
.text:0000000000401816                 sub     rsp, 38h
.text:000000000040181A                 call    _malloc ; 创建缓冲区
.text:000000000040181F                 test    rax, rax
.text:0000000000401822                 mov     rbp, rax        ; $rbx == buffer
.text:0000000000401825                 jz      loc_401A9B
.text:000000000040182B                 mov     edx, 4
.text:0000000000401830                 mov     rsi, rax
.text:0000000000401833                 mov     edi, r12d       ; fd
.text:0000000000401836                 call    recv_ ; 从socket读入4字节
.text:000000000040183B                 movzx   ebx, byte ptr [rbp+0]
.text:000000000040183F                 movzx   eax, byte ptr [rbp+3]
.text:0000000000401843                 mov     rdi, rbp        ; ptr
.text:0000000000401846                 shl     ebx, 18h
.text:0000000000401849                 or      ebx, eax
.text:000000000040184B                 movzx   eax, byte ptr [rbp+1]
.text:000000000040184F                 shl     eax, 10h
.text:0000000000401852                 or      ebx, eax
.text:0000000000401854                 movzx   eax, byte ptr [rbp+2]
.text:0000000000401858                 shl     eax, 8
.text:000000000040185B                 or      ebx, eax ; 将读入的4个字节从Big Endian转换为Little Endian,并存放于ebx中
.text:000000000040185D                 call    _free ; 释放缓冲区
.text:0000000000401862                 cmp     ebx, 53794550h ; 由这里可知,客户端第一次发送的integer应该是 0x53794550
.text:0000000000401868                 jz      short loc_401890
.text:000000000040186A                 xor     eax, eax ; 如果执行到这里,程序就返回了
.text:000000000040186C                 mov     rbx, [rsp+arg_0]
.text:0000000000401871                 mov     rbp, [rsp+arg_8]
.text:0000000000401876                 mov     r12, [rsp+arg_10]
.text:000000000040187B                 mov     r13, [rsp+arg_18]
.text:0000000000401880                 mov     r14, [rsp+arg_20]
.text:0000000000401885                 mov     r15, [rsp+arg_28]
.text:000000000040188A                 add     rsp, 38h
.text:000000000040188E                 retn
---------------------------------------------------------------------------
.text:000000000040188F                 align 10h
.text:0000000000401890
.text:0000000000401890 loc_401890:                             ; CODE XREF: hiddenFunction+78 j
.text:0000000000401890                 mov     edi, 4          ; size
.text:0000000000401895                 call    _malloc
.text:000000000040189A                 test    rax, rax
.text:000000000040189D                 mov     rbp, rax
.text:00000000004018A0                 jz      loc_401A9B
.text:00000000004018A6                 mov     edx, 4
.text:00000000004018AB                 mov     rsi, rax
.text:00000000004018AE                 mov     edi, r12d       ; fd
.text:00000000004018B1                 call    recv_
.text:00000000004018B6                 movzx   ebx, byte ptr [rbp+0]
.text:00000000004018BA                 movzx   eax, byte ptr [rbp+3]
.text:00000000004018BE                 mov     rdi, rbp        ; ptr
.text:00000000004018C1                 shl     ebx, 18h
.text:00000000004018C4                 or      ebx, eax
.text:00000000004018C6                 movzx   eax, byte ptr [rbp+1]
.text:00000000004018CA                 shl     eax, 10h
.text:00000000004018CD                 or      ebx, eax
.text:00000000004018CF                 movzx   eax, byte ptr [rbp+2]
.text:00000000004018D3                 shl     eax, 8
.text:00000000004018D6                 or      ebx, eax
.text:00000000004018D8                 call    _free
.text:00000000004018DD                 cmp     ebx, 4A75402Ch ; 第二个DWORD是0x4a75402c
.text:00000000004018E3                 jnz     short loc_40186A
.text:00000000004018E5                 mov     edi, 4          ; size
.text:00000000004018EA                 call    _malloc
.text:00000000004018EF                 test    rax, rax
.text:00000000004018F2                 mov     rbp, rax
.text:00000000004018F5                 jz      loc_401A9B
.text:00000000004018FB                 mov     edx, 4
.text:0000000000401900                 mov     rsi, rax
.text:0000000000401903                 mov     edi, r12d       ; fd
.text:0000000000401906                 call    recv_
.text:000000000040190B                 movzx   ebx, byte ptr [rbp+0]
.text:000000000040190F                 movzx   eax, byte ptr [rbp+3]
.text:0000000000401913                 mov     rdi, rbp        ; ptr
.text:0000000000401916                 shl     ebx, 18h
.text:0000000000401919                 or      ebx, eax
.text:000000000040191B                 movzx   eax, byte ptr [rbp+1]
.text:000000000040191F                 shl     eax, 10h
.text:0000000000401922                 or      ebx, eax
.text:0000000000401924                 movzx   eax, byte ptr [rbp+2]
.text:0000000000401928                 shl     eax, 8
.text:000000000040192B                 or      ebx, eax
.text:000000000040192D                 call    _free
.text:0000000000401932                 cmp     ebx, 3818A37h ; 第三次发送的 DWORD,0x03818a37
.text:0000000000401938                 jnz     loc_40186A
.text:000000000040193E                 mov     edi, 4          ; size
.text:0000000000401943                 call    _malloc
.text:0000000000401948                 test    rax, rax
.text:000000000040194B                 mov     rbp, rax
.text:000000000040194E                 jz      loc_401A9B
.text:0000000000401954                 mov     edx, 4
.text:0000000000401959                 mov     rsi, rax
.text:000000000040195C                 mov     edi, r12d       ; fd
.text:000000000040195F                 call    recv_
.text:0000000000401964                 movzx   ebx, byte ptr [rbp+0]
.text:0000000000401968                 movzx   eax, byte ptr [rbp+3]
.text:000000000040196C                 mov     rdi, rbp        ; ptr
.text:000000000040196F                 shl     ebx, 18h
.text:0000000000401972                 or      ebx, eax
.text:0000000000401974                 movzx   eax, byte ptr [rbp+1]
.text:0000000000401978                 shl     eax, 10h
.text:000000000040197B                 or      ebx, eax
.text:000000000040197D                 movzx   eax, byte ptr [rbp+2]
.text:0000000000401981                 shl     eax, 8
.text:0000000000401984                 or      ebx, eax
.text:0000000000401986                 call    _free
.text:000000000040198B                 cmp     ebx, 0ACF7BC51h ; 第四个 DWORD,0xacf7bc51
.text:0000000000401991                 jnz     loc_40186A
.text:0000000000401997                 mov     edi, 4          ; size
.text:000000000040199C                 call    _malloc
.text:00000000004019A1                 test    rax, rax
.text:00000000004019A4                 mov     rbp, rax
.text:00000000004019A7                 jz      loc_401A9B
.text:00000000004019AD                 mov     edx, 4
.text:00000000004019B2                 mov     rsi, rbp
.text:00000000004019B5                 mov     edi, r12d       ; fd
.text:00000000004019B8                 call    recv_
.text:00000000004019BD                 movzx   ebx, byte ptr [rbp+0] ; First char
.text:00000000004019C1                 movzx   eax, byte ptr [rbp+3] ; last char
.text:00000000004019C5                 mov     rdi, rbp        ; ptr
.text:00000000004019C8                 shl     ebx, 18h        ; ebx << 24
.text:00000000004019CB                 or      ebx, eax
.text:00000000004019CD                 movzx   eax, byte ptr [rbp+1]
.text:00000000004019D1                 shl     eax, 10h
.text:00000000004019D4                 or      ebx, eax
.text:00000000004019D6                 movzx   eax, byte ptr [rbp+2]
.text:00000000004019DA                 shl     eax, 8
.text:00000000004019DD                 or      ebx, eax ; 第5个integer存放在ebx中
.text:00000000004019DF                 call    _free
.text:00000000004019E4                 mov     eax, ebx
.text:00000000004019E6                 test    ebx, ebx        ; Saved in ebx
.text:00000000004019E8                 mov     [rsp+0], rax ; [rsp] 中存放的是第5个 DWORD
.text:00000000004019EC                 jz      loc_401AF2
.text:00000000004019F2                 mov     r13d, 0FFFFFFFFh ; r13是标志位,后面会用到它
.text:00000000004019F8                 xor     r14d, r14d
.text:00000000004019FB                 xor     r15d, r15d ; 初始化r14、r15两个寄存器
.text:00000000004019FE                 jmp     short loc_401A1E ; 进入循环。这是整个 b400 的重点所在
.text:0000000000401A00 ; ---------------------------------------------------------------------------
.text:0000000000401A00                 mov     eax, 1
.text:0000000000401A05                 mov     ecx, ebp        ; ebp == our input last
.text:0000000000401A07                 add     r15, 1          ; counter
.text:0000000000401A0B                 shl     rax, cl
.text:0000000000401A0E                 mov     r13d, ebp
.text:0000000000401A11                 xor     r14, rax        ; r14 xors (1 << our lowest byte)
.text:0000000000401A14                 cmp     [rsp+0], r15    ; rsp is what we input in 0x4019e8
.text:0000000000401A18                 jz      sub_401ADA ; 0x401ADA 是检测函数,
也是我们的最终目标。当r15与我们输入的第5个DWORD的最低字节相等时则跳。因此第5个 DWORD的作用是循环控制
.text:0000000000401A1E
.text:0000000000401A1E loc_401A1E:                             ; CODE XREF: hiddenFunction+20E j
.text:0000000000401A1E                 mov     edi, 4          ; size
.text:0000000000401A23                 call    _malloc
.text:0000000000401A28                 test    rax, rax
.text:0000000000401A2B                 mov     rbx, rax
.text:0000000000401A2E                 jz      short loc_401A9B
.text:0000000000401A30                 mov     edx, 4
.text:0000000000401A35                 mov     rsi, rax
.text:0000000000401A38                 mov     edi, r12d       ; fd
.text:0000000000401A3B                 call    recv_ ; 读入一个 DWORD
.text:0000000000401A40                 movzx   ebp, byte ptr [rbx]
.text:0000000000401A43                 movzx   eax, byte ptr [rbx+3]
.text:0000000000401A47                 mov     rdi, rbx        ; ptr
.text:0000000000401A4A                 shl     ebp, 18h
.text:0000000000401A4D                 or      ebp, eax
.text:0000000000401A4F                 movzx   eax, byte ptr [rbx+1]
.text:0000000000401A53                 shl     eax, 10h
.text:0000000000401A56                 or      ebp, eax
.text:0000000000401A58                 movzx   eax, byte ptr [rbx+2]
.text:0000000000401A5C                 shl     eax, 8
.text:0000000000401A5F                 or      ebp, eax
.text:0000000000401A61                 call    _free
.text:0000000000401A66                 cmp     ebp, 3Fh        ; Input of each time must equal to or less than 0x3f
.text:0000000000401A69                 jg      short func_returned ; Can't be greater than 0x3f
.text:0000000000401A6B                 cmp     r13d, 0FFFFFFFFh ; 标志位比较。
.text:0000000000401A6F                 jz      short loc_401A00 ; 第二次循环输入时,
r13d 会被输入的值覆盖(0x401A0E处),这里便不会跳转。
.text:0000000000401A71                 mov     eax, r13d       ; Our last input
.text:0000000000401A74                 sar     eax, 31         ; if(eax > 0)
.text:0000000000401A74                                         ; {
.text:0000000000401A74                                         ;     eax = 0;
.text:0000000000401A74                                         ; }
.text:0000000000401A74                                         ; else
.text:0000000000401A74                                         ; {
.text:0000000000401A74                                         ;     eax = 4;
.text:0000000000401A74                                         ; }
.text:0000000000401A77                 shr     eax, 29
.text:0000000000401A7A                 lea     edx, [r13+rax+0]
.text:0000000000401A7F                 and     edx, 7
.text:0000000000401A82                 sub     edx, eax        ; edx = lowest 3 bytes of r13
.text:0000000000401A84                 mov     eax, ebp
.text:0000000000401A86                 sub     eax, r13d       ; eax = what we input this time - what we input last time
.text:0000000000401A89                 add     eax, 11h
.text:0000000000401A8C                 cmp     eax, 22h        ; difference between each input should be less than or equal to 17
.text:0000000000401A8C                                         ; eax = difference between our inputs + 17
.text:0000000000401A8F                 jbe     short loc_401AA2 ; rax = 0  ==> edx > 0, diff = -17
rax = 2  ==> edx <= 6, diff = -15
.text:0000000000401A8F                                         ; rax = 7  ==> edx > 1, diff = -10
.text:0000000000401A8F                                         ; rax = 11 ==> edx <= 5, diff = -6
.text:0000000000401A8F                                         ; rax = 23 ==> edx > 1, diff = 6
.text:0000000000401A8F                                         ; rax = 27 ==> edx <= 5, diff = 10
.text:0000000000401A8F                                         ; rax = 32 ==> edx > 0, diff = 15
.text:0000000000401A8F                                         ; rax = 34 ==> edx <= 6, diff = 17

我们再来看看最终目标 0x401ADA处:

.text:0000000000401ADA                 test    r14, r14
.text:0000000000401ADD                 jz      short loc_401AF2 ; if [r14] == 0, return
.text:0000000000401ADF                 xor     edx, edx
.text:0000000000401AE1
.text:0000000000401AE1 loc_401AE1:
.text:0000000000401AE1                 lea     rax, [r14-1] ; rax = r14
.text:0000000000401AE5                 add     edx, 1 ; edx is a counter
.text:0000000000401AE8                 and     r14, rax        ; r14 &= r14 - 1
.text:0000000000401AEB                 jnz     short loc_401AE1
.text:0000000000401AED                 cmp     edx, 40h
.text:0000000000401AF0                 jz      short output_key
.text:0000000000401AF2
.text:0000000000401AF2 loc_401AF2:
.text:0000000000401AF2                                         ; return_if_edx_le_zero+C j
.text:0000000000401AF2                 mov     eax, 0FFFFFFFEh
.text:0000000000401AF7                 jmp     loc_40186C

注意到0x401AE1 至0x401AEB处为一个进行r14验证的小循环。整个过程翻译成C代码如下:

while(r14 != 0)
{
  ++edx;
  r14 &= r14 – 1;
}
if(edx == 0x40)
{
  output_key();
}
else
{
  return -2;
}

这里的r14来自0x401A11。同样地,我们也可以写出计算出r14的C伪代码:

while(r15 < last_byte_of_our_fifth_dword)
{
ecx = read_dword();
r13 = ecx;
++r15;
rax = 1 << (byte)ecx;
r14 ^= rax;
}
check_r14_0x401ada();

如果上面的伪代码是正确的,那么这道题就很简单了。不幸的是,上面的伪代码中并没有包括对标志r13的判断。对r13的判断在0x401A6B处。上面贴过了,这里再贴一遍:

.text:0000000000401A66                 cmp     ebp, 3Fh        ; Input of each time must equal to or less than 0x3f
.text:0000000000401A69                 jg      short func_returned ; Can't be greater than 0x3f
.text:0000000000401A6B                 cmp     r13d, 0FFFFFFFFh ; 标志位比较。
.text:0000000000401A6F                 jz      short loc_401A00 ; 第二次循环输入时,
r13d 会被输入的值覆盖(0x401A0E处),这里便不会跳转。
.text:0000000000401A71                 mov     eax, r13d       ; Our last input
.text:0000000000401A74                 sar     eax, 31
.text:0000000000401A77                 shr     eax, 29
.text:0000000000401A7A                 lea     edx, [r13+rax+0]
.text:0000000000401A7F                 and     edx, 7
.text:0000000000401A82                 sub     edx, eax        ; edx 拿到
了我们上一次输入的DWORD的低三位
.text:0000000000401A84                 mov     eax, ebp
.text:0000000000401A86                 sub     eax, r13d       ; eax存储的
是这一次输入和上一次输入之差
.text:0000000000401A89                 add     eax, 11h
.text:0000000000401A8C                 cmp     eax, 22h        ; 两次输入的
差值的绝对值需要小于等于17(11h)
.text:0000000000401A8F                 jbe     short loc_401AA2

经过分析容易得知,如果想要一次循环之后就跳到0x401ADA,那么r14就一定是2的n次幂。而在验证 r14 &= r14 – 1时,只需要一次运算就会变成0,而不是题目中要求的64次。因此在这个循环中只输入一个DWORD是不行的。如果输入多个DWORD,又有个要求是每一个数都不能大于63(0x3f),标志r13便会被修改,0x401A8F处一定会跳转的。我们只好继续分析0x401AA2了。
0x401AA2处是一个跳转:

.text:0000000000401AA2                 mov     eax, eax ; eax 是两次输入的差值 + 17
.text:0000000000401AA4                 jmp     ds:off_401C28[rax*8] ; 注意此时edx中
存储的是上一次输入的低三位,跳转之后会用来进行判断

0x401C28处则是一个跳转表。通过进一步分析每一个跳转过程,我们可以依照它们的功能进行重命名,列表如下:

.rodata:0000000000401C28 off_401C28      dq offset return_if_edx_le_zero ;(*)
.rodata:0000000000401C30                 dq offset func_returned
.rodata:0000000000401C38                 dq offset return_if_edx_greater_than_6 ;(*)
.rodata:0000000000401C40                 dq offset func_returned
.rodata:0000000000401C48                 dq offset func_returned
.rodata:0000000000401C50                 dq offset func_returned
.rodata:0000000000401C58                 dq offset func_returned
.rodata:0000000000401C60                 dq offset return_if_edx_le_1 ;(*)
.rodata:0000000000401C68                 dq offset func_returned
.rodata:0000000000401C70                 dq offset func_returned
.rodata:0000000000401C78                 dq offset func_returned
.rodata:0000000000401C80                 dq offset return_if_edx_greater_than_5 ;(*)
.rodata:0000000000401C88                 dq offset func_returned
.rodata:0000000000401C90                 dq offset func_returned
.rodata:0000000000401C98                 dq offset func_returned
.rodata:0000000000401CA0                 dq offset func_returned
.rodata:0000000000401CA8                 dq offset func_returned
.rodata:0000000000401CB0                 dq offset func_returned
.rodata:0000000000401CB8                 dq offset func_returned
.rodata:0000000000401CC0                 dq offset func_returned
.rodata:0000000000401CC8                 dq offset func_returned
.rodata:0000000000401CD0                 dq offset func_returned
.rodata:0000000000401CD8                 dq offset func_returned
.rodata:0000000000401CE0                 dq offset return_if_edx_le_1 ;(*)
.rodata:0000000000401CE8                 dq offset func_returned
.rodata:0000000000401CF0                 dq offset func_returned
.rodata:0000000000401CF8                 dq offset func_returned
.rodata:0000000000401D00                 dq offset return_if_edx_greater_than_5 ;(*)
.rodata:0000000000401D08                 dq offset func_returned
.rodata:0000000000401D10                 dq offset func_returned
.rodata:0000000000401D18                 dq offset func_returned
.rodata:0000000000401D20                 dq offset func_returned
.rodata:0000000000401D28                 dq offset return_if_edx_le_zero ;(*)
.rodata:0000000000401D30                 dq offset func_returned
.rodata:0000000000401D38                 dq offset return_if_edx_greater_than_6 ;(*)

这意味着可行的eax只有8种,意味着前后输入的DWORD的差值也只能为这8种之一,同时我们也看到上一次输入的DWORD的低三位也被限制了。以 return_if_edx_le_zero为例:

.text:0000000000401AD1 return_if_edx_le_zero:
.text:0000000000401AD1
.text:0000000000401AD1                 xor     eax, eax
.text:0000000000401AD3                 test    edx, edx
.text:0000000000401AD5                 setnle  al ; 如果上一次的输入的低三位大于0,则置al为1
.text:0000000000401AD8                 jmp     short loc_401AB3
……
.text:0000000000401AB3                 test    eax, eax
.text:0000000000401AB5                 jnz     loc_401A00 ; 如果eax != 0,那么这里就回到了原来的那个循环,bingo!
.text:0000000000401ABB                 jmp     short func_returned

因此我们可以整理出一个表格:
情形 eax 前后两次DWORD的差 前一次输入DWORD的低三位
1 0 -17 > 0
2 2 -15 <= 6
3 7 -10 > 1
4 11 -6 <= 5
5 23 6 > 1
6 27 10 <= 5
7 32 15 > 0
8 34 17 <= 6
好,下面让我们看看这个表格的作用。回顾一下验证r14的小循环:

while(r14 != 0)
{
  ++edx;
  r14 &= r14 – 1;
}
if(edx == 0x40)
{
  output_key();
}
else
{
  return -2;
}

要求经过64次变换之后r14 == 0。如果你亲自尝试一下,就会发现运算r14 &= r14 – 1会从r14的二进制表示中去掉一个“1”。这意味着输入的r14应该等于2 ^ 64 – 1。让我们再回顾下r14是如何生成的:

.text:0000000000401A00                 mov     eax, 1
.text:0000000000401A05                 mov     ecx, ebp        ; ebp == our input last
.text:0000000000401A07                 add     r15, 1          ; counter
.text:0000000000401A0B                 shl     rax, cl
.text:0000000000401A0E                 mov     r13d, ebp
.text:0000000000401A11                 xor     r14, rax        ; r14 xors (1 << our lowest byte)
.text:0000000000401A14                 cmp     [rsp+0], r15    ; rsp is what we input in 0x4019e8
.text:0000000000401A18                 jz      sub_401ADA

容易想到,如果这个循环进行64次,每次提供一个不同的cl,那么r14的64位就都可以通过异或运算变成1。
OK,到现在为止解题的所有条件都具备了。第五个DWORD可以确定是0x00000040(因为要进行64次循环呀)。最后要做的事情就是找到一个唯一包含0 ~ 63的一个序列,且前后两数满足上面那个表格中的约束。我们写个程序来解决它。

int data[63] = {0};
bool searchforsequence(int lastInput, int _count)
{
  if(_count < 0)
  {
    cout<<"!!!"<<_count<<endl;
  }
  if(data[lastInput] == 1)
  {
    return false;
  }
  data[lastInput] = 1;
  if(_count == 63)
  {
    cout<<lastInput<<"-";
    return true;
  }
  else
  {
    // Determination
    int last3Bytes = lastInput & 0x7;
    bool ret = false;
    if(lastInput - 17 >= 0 && last3Bytes > 0)
    {
      ret = searchforsequence(lastInput - 17, _count + 1);
      if(ret)
      {
        cout<<lastInput<<"-";
        return true;
      }
    }
    if(lastInput - 15 >= 0 && last3Bytes <= 6)
    {
      ret = searchforsequence(lastInput - 15, _count + 1);
      if(ret)
      {
        cout<<lastInput<<"-";
        return true;
      }
    }
    if(lastInput - 10 >= 0 && last3Bytes > 1)
    {
      ret = searchforsequence(lastInput - 10, _count + 1);
      if(ret)
      {
        cout<<lastInput<<"-";
        return true;
      }
    }
    if(lastInput - 6 >= 0 && last3Bytes <= 5)
    {
      ret = searchforsequence(lastInput - 6, _count + 1);
      if(ret)
      {
        cout<<lastInput<<"-";
        return true;
      }
    }
    if(lastInput + 6 < 64 && last3Bytes > 1)
    {
      ret = searchforsequence(lastInput + 6, _count + 1);
      if(ret)
      {
        cout<<lastInput<<"-";
        return true;
      }
    }
    if(lastInput + 10 < 64 && last3Bytes <= 5)
    {
      ret = searchforsequence(lastInput + 10, _count + 1);
      if(ret)
      {
        cout<<lastInput<<"-";
        return true;
      }
    }
    if(lastInput + 15 < 64 && last3Bytes > 0)
    {
      ret = searchforsequence(lastInput + 15, _count + 1);
      if(ret)
      {
        cout<<lastInput<<"-";
        return true;
      }
    }
    if(lastInput + 17 < 64 && last3Bytes <= 6)
    {
      ret = searchforsequence(lastInput + 17, _count + 1);
      if(ret)
      {
        cout<<lastInput<<"-";
        return true;
      }
    }
    data[lastInput] = 0;
    return false;
  }
}

得到一个可行序列之后,直接发包给目标服务器就可以了。我的Python实现如下:

import asyncore, socket
sequence = [24, 41, 56, 50, 60, 54, 39, 45, 62, 52, 58, \
48, 42, 57, 51, 36, 30, 47, 37, 31, 46, 63, 53, 59, 44, 61,\
 55, 38, 28, 43, 49, 32, 26, 16, 33, 27, 17, 34, 40, 25, 35,\
 29, 23, 13, 7, 22, 12, 6, 21, 15, 5, 11, 1, 18, 8, 2, 19, 9,\
 3, 20, 14, 4, 10, 0]
class SolveClient(asyncore.dispatcher):
    def __init__(self):
        asyncore.dispatcher.__init__(self)
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.connect(("140.197.217.239", 11553))
        self.buffer = '\x53\x79\x45\x50\x4a\x75\x40\x2c\x03\x81\x8a\x37\xac\xf7\xbc\x51' + '\x00\x00\x00\x40'
        for i in range(0, 0x40):
            self.buffer += '\x00\x00\x00'
            self.buffer += chr(sequence[i])
        def handle_connect(self):
            pass
        def handle_close(self):
            self.close()
            print('closed')
        def handle_read(self):
            print self.recv(8192)
        def writable(self):
            return (len(self.buffer) > 0)
        def handle_write(self):
            sent = self.send(self.buffer)
            self.buffer = self.buffer[sent:]

client = SolveClient()
asyncore.loop()

得到的key为59e22b484b703801c019d4da0f7a3316。

此条目发表在技术报告分类目录。将固定链接加入收藏夹。

defcon XX CTF Quals Binary L33tness (b400) Write-up》有 1 条评论

  1. lukesun629说:

    膜拜fish大神!