0CTF Reverse150(r0ops) WriteUp

Reading time ~1 minute

这几次比赛下来,真的是深刻的让我感受到了自己逆向水平的不足,这次的0ctf也更是好不容易在第二天下午才发现这题怎么做,才总算没有在逆向题上无功而返。

这道题说实在的,确实是挺简单的,不过很麻烦倒是真的,唯独的一个问题就是不能用Hex-rays来看,反编译得到的代码是有问题的。

signed __int64 __usercall handle@<rax>(__int64 a1@<rbp>)
{
  __int64 *src; // rsi@1
  __int64 *dst; // rdi@1
  signed __int64 i; // rcx@1

  *(_DWORD *)(a1 - 4) = accept(3, 0LL, 0LL);
  recv(*(_DWORD *)(a1 - 4), save_flag, 0x1000uLL, 0);
  close(*(_DWORD *)(a1 - 4));
  src = qword_E0B00A0;
  dst = qword_E0AF0A0;
  for ( i = 0x200LL; i; --i )
  {
    *dst = *src;
    ++src;
    ++dst;
  }
  return 0xE0AF8A0LL;
}

处理部分反编译代码如上,我们会发现非常奇怪的是,这里做了一个字符串固定字符串的复制之后接返回了,什么事都没做啊,遇到这种情况当然得看看汇编代码,但不知道自己眼睛怎么长的,最开始一直没有看到问题……

然后,我还尝试通过strings往回找flag相关部分,发现确实是有输出flag的地方,可完全不知道从哪里跳过来的,然后发现了一段乱七八糟的代码片段,每个片段都很短,如下:

.text:000000000DEAD1E4 jmp     short loc_DEAD1E8
.text:000000000DEAD1E6 ; ---------------------------------------------------------------------------
.text:000000000DEAD1E6 jp      short loc_DEAD181
.text:000000000DEAD1E8
.text:000000000DEAD1E8 loc_DEAD1E8:                            ; CODE XREF: .text:000000000DEAD1E4
.text:000000000DEAD1E8 pop     rax
.text:000000000DEAD1E9 retn

看起来似乎000000000DEAD1E6那里有2个无意义的字节,问题肯定就在这些代码片段里了,然后还发现,程序复制的那个字符串里中间有些部分看起来是一些指向这些代码片段的指针,但还是没有什么解题的想法。

最后,某次突然在这段汇编代码的最后部分发现了问题:

.text:000000000DEAD40E mov     eax, offset save_flag
.text:000000000DEAD413 mov     rdi, rax
.text:000000000DEAD416 mov     eax, offset unk_E0B20C0
.text:000000000DEAD41B mov     rsi, rax
.text:000000000DEAD41E mov     eax, (offset qword_E0AF0A0+800h)
.text:000000000DEAD423 mov     rsp, rax
.text:000000000DEAD426 retn
.text:000000000DEAD426 handle end

仔细一看,这里000000000DEAD423这一行竟然把rsp给改了,我勒个去,这是直接换栈了,那这下就啥都说的通了,它复制的那个字符串里面都是他提前设定好的地址,然后那些代码片段最后就通过ret来返回到特定的地方,一句话说就是以ROP的方式在运行程序,只能说感觉我也是醉了。

知道方法后,做起来也是麻烦,没有什么好的办法,只能一点点的手动把代码扒出来。

首先,先是把那个字符串中看起来是需要的部分拎出来:

arr = [233492980, 8, 233493105, 1384820248253759637, 233492771, 233492717, 233492996, 233493095, 233492728, 233492739, 233492717, 233493114, 233493006, 233492728, 233492972, 51966, 233492801, 233492717, 233492996, 233493124, 233492728, 233492717, 233493114, 233493006, 233492728, 233492972, 48879, 233492781, 233492717, 233492996, 233493124, 233492728, 233493192, 1, 233493134, 13337, 233492964, 0, 233492972, 0, 233492988, 472, 233492891, 233492717, 233493143, 233493182, 233492728, 233492717, 233493172, 233493006, 233492728, 233492972, 1, 233492851, 233492717, 233492996, 233493182, 233492728, 233492717, 233493172, 233493006, 233492728, 233492972, 1, 233492988, 104, 233492906, 233492717, 233493201, 233493006, 233492728, 233492717, 233493085, 233493026, 233492728, 233492801, 233492717, 233492996, 233493211, 233492728, 233492717, 233493085, 233493006, 233492728, 233492717, 233493085, 233493026, 233492728, 233492801, 233492717, 233492996, 233493095, 233492728, 233492717, 233493143, 233493006, 233492728, 233492881, 233492717, 233492996, 233493153, 233492728, 233492717, 233493143, 233493006, 233492728, 233492972, 0, 233492988, 18446744073709551072L, 233492906, 233492717, 233493201, 233493006, 233492728, 233492717, 233493114, 233493026, 233492728, 233492988, 32, 233492906, 233492988, 18446744073709550648L, 233492951, 233493308] #, 233493423, 14950972352827054927L, 13643839583502281931L]

然后一点点的得到地址与代码的对应关系表:

d = {}
d[0x000000000DEAD1F4]      = 'pop     rcx'
d[0x000000000DEAD271]      = 'pop     r9'
d[0x000000000DEAD123]      = 'mov     rax, [rdi]'
d[0x000000000DEAD0ED]      = 'add     rsi, 8'
d[0x000000000DEAD204]      = 'mov     [rsi], rax'
d[0x000000000DEAD267]      = 'mov     r8, [rsi]'
d[0x000000000DEAD0F8]      = 'sub     rsi, 8'
d[0x000000000DEAD103]      = 'add     rdi, 8'
d[0xdead27a]    =   'mov     [rsi], r9'
d[0xdead20e]    =   'mov     rax, [rsi]'
d[0xdead1ec]    =   'pop     rbx'
d[0xdead141]    =   'imul    rax, rbx'
d[0xdead284]    =   'mov     r9, [rsi]'
d[0xdead12d]    =   'add     rax, rbx'
d[0xdead2c8]    =   'pop     r12'
d[0xdead28e]    =   'pop     r10'
d[0xdead1e4]    =   'pop     rax'
d[0xdead1fc]    =   'pop     rdx'
d[0xdead19b]    =   'cmp     rax, rbxnjnz     out1nadd     rsp, rdxnout1:'
d[0xdead297]    =   'mov     [rsi], r10'
d[0xdead2be]    =   'mov     r11, [rsi]'
d[0xdead2b4]    =   'mov     [rsi], r11'
d[0xdead173]    =   'and     rax, rbx'
d[0xdead1aa]    =   'cmp     rax, rbxnjz     out2nadd     rsp, rdxnout2:'
d[0xdead2d1]    =   'mov     [rsi], r12'
d[0xdead25d]    =   'mov     [rsi], r8'
d[0xdead222]    =   'mov     rbx, [rsi]'
d[0xdead2db]    =   'mov     r12, [rsi]'
d[0xdead191]    =   'shr     rax, 1'
d[0xdead2a1]    =   'mov     r10, [rsi]'
d[0xdead1d7]    =   'loop    success, out3nsuccess:nadd     rsp, rdxnout3:'
d[0xdead33c]    =   'get flag'

[d[one] for one in arr if one > 0xdead000 and one < 0xdeaf000]

然后按这个表替换之后得到代码如下:

pop     rcx
pop     r9
mov     rax, [rdi]
add     rsi, 8
mov     [rsi], rax
mov     r8, [rsi]
sub     rsi, 8
add     rdi, 8
...
mov     [rsi], r9
mov     rbx, [rsi]
sub     rsi, 8
pop     rdx
cmp     rax, rbx
jz next3
add     rsp, rdx
next3:
pop     rdx
loop    success1, fail2
add     rsp, rdx
get flag

我们发现里面从来没有做过任何修改栈上值的操作,以及唯一对rsp的修改就是add rsp,也就是相当于把代码跳转回去,就是实现循环的作用了。

于是乎,为了看的更清楚,我们给每个语句编上地址,使我们可以知道每次add rsp是跳转到哪里,以及每次pop出来的值是多少:

[8, 1384820248253759637, 51966, 48879, 1, 13337, 0, 0, 472, 1, 1, 104, 0, 18446744073709551072L, 32, 18446744073709550648L]

i = 0
for one in s:
    if one.find('pop') != -1:
        print one.replace('pop  ', 'mov_s') + ', ' + hex(t[i])
        i += 1
    else:
        print one

我们这里会将pop替换成mov_s指令,当做mov指令看就好,加个_s是为了区分下而已:

dst         = 0xE0AF0A0
src         = 0xE0B00A0
buffer      = 0xE0B20C0
save_flag   = input

rdi = save_flag
rsi = buffer

0x008:  mov_s   rcx, 0x8
0x018:  mov_s   r9, 0x1337deadbeef0095
0x028:  mov     rax, [rdi]
0x030:  add     rsi, 8
0x038:  mov     [rsi], rax
0x040:  mov     r8, [rsi]
0x048:  sub     rsi, 8
0x050:  add     rdi, 8
0x058:  add     rsi, 8
0x060:  mov     [rsi], r9
0x068:  mov     rax, [rsi]
0x070:  sub     rsi, 8
//
0x078:  mov_s   rbx, 0xcafe
0x088:  imul    rax, rbx
0x090:  add     rsi, 8
0x098:  mov     [rsi], rax
0x0a0:  mov     r9, [rsi]
0x0a8:  sub     rsi, 8
0x0b0:  add     rsi, 8
0x0b8:  mov     [rsi], r9
0x0c0:  mov     rax, [rsi]
0x0c8:  sub     rsi, 8
0x0d0:  mov_s   rbx, 0xbeef
0x0e0:  add     rax, rbx
0x0e8:  add     rsi, 8
0x0f0:  mov     [rsi], rax
0x0f8:  mov     r9, [rsi]
0x100:  sub     rsi, 8
0x108:  mov_s   r12, 0x1
0x118:  mov_s   r10, 0x3419
0x128:  mov_s   rax, 0x0
0x138:  mov_s   rbx, 0x0
0x148:  mov_s   rdx, 0x1d8
0x158:  cmp     rax, rbx
jnz     out1
add     rsp, rdx
out1:
0x160:  add     rsi, 8
0x168:  mov     [rsi], r10
0x170:  mov     r11, [rsi]
0x178:  sub     rsi, 8
0x180:  add     rsi, 8
0x188:  mov     [rsi], r11
0x190:  mov     rax, [rsi]
0x198:  sub     rsi, 8
0x1a0:  mov_s   rbx, 0x1
0x1b0:  and     rax, rbx
0x1b8:  add     rsi, 8
0x1c0:  mov     [rsi], rax
0x1c8:  mov     r11, [rsi]
0x1d0:  sub     rsi, 8
0x1d8:  add     rsi, 8
0x1e0:  mov     [rsi], r11
0x1e8:  mov     rax, [rsi]
0x1f0:  sub     rsi, 8
0x1f8:  mov_s   rbx, 0x1
0x208:  mov_s   rdx, 0x68
0x218:  cmp     rax, rbx
jz     out2
add     rsp, rdx
out2:
0x220:  add     rsi, 8
0x228:  mov     [rsi], r12
0x230:  mov     rax, [rsi]
0x238:  sub     rsi, 8
0x240:  add     rsi, 8
0x248:  mov     [rsi], r8
0x250:  mov     rbx, [rsi]
0x258:  sub     rsi, 8
0x260:  imul    rax, rbx
0x268:  add     rsi, 8
0x270:  mov     [rsi], rax
0x278:  mov     r12, [rsi]
0x280:  sub     rsi, 8
0x288:  add     rsi, 8
0x290:  mov     [rsi], r8
0x298:  mov     rax, [rsi]
0x2a0:  sub     rsi, 8
0x2a8:  add     rsi, 8
0x2b0:  mov     [rsi], r8
0x2b8:  mov     rbx, [rsi]
0x2c0:  sub     rsi, 8
0x2c8:  imul    rax, rbx
0x2d0:  add     rsi, 8
0x2d8:  mov     [rsi], rax
0x2e0:  mov     r8, [rsi]
0x2e8:  sub     rsi, 8
0x2f0:  add     rsi, 8
0x2f8:  mov     [rsi], r10
0x300:  mov     rax, [rsi]
0x308:  sub     rsi, 8
0x310:  shr     rax, 1
0x318:  add     rsi, 8
0x320:  mov     [rsi], rax
0x328:  mov     r10, [rsi]
0x330:  sub     rsi, 8
0x338:  add     rsi, 8
0x340:  mov     [rsi], r10
0x348:  mov     rax, [rsi]
0x350:  sub     rsi, 8
0x358:  mov_s   rbx, 0x0
0x368:  mov_s   rdx, 0xfffffffffffffde0L
0x378:  cmp     rax, rbx
jz     out2
add     rsp, rdx
out2:
0x380:  add     rsi, 8
0x388:  mov     [rsi], r12
0x390:  mov     rax, [rsi]
0x398:  sub     rsi, 8
0x3a0:  add     rsi, 8
0x3a8:  mov     [rsi], r9
0x3b0:  mov     rbx, [rsi]
0x3b8:  sub     rsi, 8
0x3c0:  mov_s   rdx, 0x20
0x3d0:  cmp     rax, rbx
jz     out2
add     rsp, rdx
out2:
0x3d8:  mov_s   rdx, 0xfffffffffffffc38L
0x3e8:  loop    success, out3
success:
add     rsp, rdx
out3:
0x3f0:  get flag

然后这里看跳转的规则是,用那句add esp前面的标号,加上这句指令给rsp增加的值,得到的地址,就是下一条指令了。

于是乎,慢慢看下这段程序,会发现就是一个快速幂(PS:这里最后几句代码可能对应的有点问题,但不太影响整体逻辑的理解),就是求输入8个字节(作为一个长整数看)的0x3419次方,与一个目标值比较,然后会这样比较9轮(好吧,我承认这一部分是动态调出来了,看出快速幂后就没管这段程序了),而目标值的初始值是0x1337DEADBEEF0095(初始值不算一轮),下一轮的值为前一轮的值乘以0xcafe后加0xbeef。

果断计算出每轮目标值,然后用WolframAlpha算出应该的输入(比如第一轮的输入),最后得flag的程序如下:

import zio

# aim = 0x1337DEADBEEF0095
# for i in xrange(9):
#     aim = aim * 0xcafe + 0xbeef
#     print aim % (2 ** 64)
#
# WolframAlpha

ans = [15397851891508532645,
5882479468299745861,
6203462900357968133,
11756062250229433221,
6010465035347615365,
10697597399494305925,
14617956564689458309,
9036388475871214725,
17260895165766855813]

payload = ''
for i in ans:
    payload += zio.l64(i)

io = zio.zio(('127.0.0.1', 0x3419))
io.write(payload)

这题150分搞成这样,也是有点无语了,知道方法后,最后的一点苦力工作当时都不想干了,都是交给队友看的了~~~

挂载网络文件夹后网络故障时文件操作命令卡死

挂载 NFS 或者 Samba 的时候,经常会由于网络故障导致挂载好的链接断掉。此时如果尝试进行 ls、cd、df 等各种命令,只要与此目录沾上边,就会卡住。如果使用了类似 oh-my-zsh 这种配置的,只要在网络目录中,弹出命令提示符前就会直接卡住。这个时候第一反应就是...… Continue reading

路由折腾记 第四弹

Published on September 02, 2017