首页
社区
课程
招聘
[原创]看雪.京东 2018CTF 第四题 WP
2018-6-24 09:10 4423

[原创]看雪.京东 2018CTF 第四题 WP

2018-6-24 09:10
4423

本题考点:代码层面是花指令和进程枚举反调试;算法层面是运用miracl大数库的RSA加密算法和AES加密算法。

花指令分析及清除

这部分直接上代码看,静态分析。

.text:0040126C _main           proc near               ; CODE XREF: start+E4↓p
.text:0040126C                 jmp     _main_0
.text:0040126C _main           endp

startmain中间有个main的stub。

.text:00403130 _main_0         proc near               ; CODE XREF: _main↑j
.text:00403130                 jmp     _main_0_0
.text:00403130 _main_0         endp
.text:00403130

这本来应该是真正的main函数,但这却又是一个跳转,而且跳往的目标代码不正常,有花指令,应该是后来加上的。继续往下看。

.text:0045C106 _main_0_0:                              ; CODE XREF: _main_0↑j
.text:0045C106                 jz      short loc_45C10C
.text:0045C108                 jnz     short loc_45C10C
.text:0045C10A                 jmp     short near ptr unk_45C10E
.text:0045C10C ; ---------------------------------------------------------------------------
.text:0045C10C
.text:0045C10C loc_45C10C:                             ; CODE XREF: .text:_main_0_0↑j
.text:0045C10C                                         ; .text:0045C108↑j
.text:0045C10C                 jmp     short loc_45C10F
.text:0045C10C ; ---------------------------------------------------------------------------
.text:0045C10E unk_45C10E      db  81h                 ; CODE XREF: .text:0045C10A↑j
.text:0045C10F ; ---------------------------------------------------------------------------
.text:0045C10F
.text:0045C10F loc_45C10F:                             ; CODE XREF: .text:loc_45C10C↑j
.text:0045C10F                 push    ebp
.text:0045C110                 jz      short loc_45C116
.text:0045C112                 jnz     short loc_45C116
.text:0045C112 ; ---------------------------------------------------------------------------
.text:0045C114                 db 0E8h
.text:0045C115                 db    2
.text:0045C116 ; ---------------------------------------------------------------------------
.text:0045C116
.text:0045C116 loc_45C116:                             ; CODE XREF: .text:0045C110↑j
.text:0045C116                                         ; .text:0045C112↑j
.text:0045C116                 jmp     short loc_45C119
.text:0045C116 ; ---------------------------------------------------------------------------
.text:0045C118                 db  81h
.text:0045C119 ; ---------------------------------------------------------------------------
.text:0045C119
.text:0045C119 loc_45C119:                             ; CODE XREF: .text:loc_45C116↑j
.text:0045C119                 mov     ebp, esp
.text:0045C11B                 jo      short loc_45C120
.text:0045C11D                 jno     short loc_45C120
.text:0045C11D ; ---------------------------------------------------------------------------
.text:0045C11F                 db 0E8h
.text:0045C120 ; ---------------------------------------------------------------------------
.text:0045C120
.text:0045C120 loc_45C120:                             ; CODE XREF: .text:0045C11B↑j
.text:0045C120                                         ; .text:0045C11D↑j
.text:0045C120                 sub     esp, 7Ch
.text:0045C123                 jmp     loc_403136

里面有些花指令,手动调整了下,还并未作任何patch。
其实这里面的指令等价于:

push ebp
mov ebp,esp
sub esp,7Ch
jmp loc_403136

再看这个跳转目标loc_403136:

.text:00403136                 push    ebx
.text:00403137                 push    esi
.text:00403138                 push    edi
.text:00403139                 lea     edi, [ebp-7Ch]
.text:0040313C                 mov     ecx, 1Fh
.text:00403141                 mov     eax, 0CCCCCCCCh
.text:00403146                 rep stosd
.text:00403148                 mov     dword ptr [ebp-4], 0
.text:0040314F                 mov     dword ptr [ebp-8], 0
.text:00403156                 call    loc_403160
.text:00403156 ; ---------------------------------------------------------------------------
.text:0040315B                 db 0E8h
.text:0040315C ; ---------------------------------------------------------------------------
.text:0040315C
.text:0040315C loc_40315C:                             ; CODE XREF: .text:loc_403160↓p
.text:0040315C                 jmp     short loc_403165
.text:0040315C ; ---------------------------------------------------------------------------
.text:0040315E                 db    0
.text:0040315F                 db    0
.text:00403160 ; ---------------------------------------------------------------------------
.text:00403160
.text:00403160 loc_403160:                             ; CODE XREF: .text:00403156↑p
.text:00403160                 call    loc_40315C
.text:00403165
.text:00403165 loc_403165:                             ; CODE XREF: .text:loc_40315C↑j
.text:00403165                                         ; .text:0040317A↓j
.text:00403165                 add     esp, 8
.text:00403168                 rdtsc
.text:0040316A                 push    eax
.text:0040316B                 rdtsc
.text:0040316D                 sub     eax, [esp]
.text:00403170                 add     esp, 4
.text:00403173                 cmp     eax, 0FFFh
.text:00403178                 jbe     short loc_40317C
.text:0040317A                 jmp     short loc_403165

这里是截取了一部分代码,这段代码除了有些花指令,反调试代码,其它都正常,加上后面代码的功能,猜测应该是真正的main函数部分代码。
再看,403136紧临403130,上面就说403130处的跳转不正常。事出反常必有妖。所以403130确实是main函数的入口,只不过被改了指令。先还原这部分指令,去年跳转。

.text:00403130                 push    ebp            .text:00403131                 mov     ebp, esp        
.text:00403133                 sub     esp, 7Ch        
.text:00403136                 push    ebx
.text:00403137                 push    esi

403156处也有花指令,而且发现不止一处。模式为:

    call l2
    junk
l1:
    jmp l3
    junk
l2:
    call l1
l3:
    add esp ,8
    code

直接上脚本patch成nop

 

顺便把rdtsc的代码也patch掉。

 

main函数patch后的伪代码如下:

int main_0()
{
  int v0; // eax
  int result; // eax
  char v2; // [esp+50h] [ebp-40h]
  __int16 v3; // [esp+51h] [ebp-3Fh]
  char input[24]; // [esp+54h] [ebp-3Ch]
  char error[8]; // [esp+6Ch] [ebp-24h]
  char success[8]; // [esp+78h] [ebp-18h]
  __int16 v7; // [esp+80h] [ebp-10h]
  BOOL v8; // [esp+84h] [ebp-Ch]
  int v9; // [esp+88h] [ebp-8h]

  v9 = 0;
  v8 = 0;
  strcpy(success, "rw`g`ut");
  v7 = 0;
  strcpy(error, "dpqkw");
  *(_DWORD *)&error[6] = 0;
  input[0] = 0;
  *(_DWORD *)&input[1] = 0;
  *(_DWORD *)&input[5] = 0;
  *(_DWORD *)&input[9] = 0;
  *(_DWORD *)&input[13] = 0;
  *(_DWORD *)&input[17] = 0;
  *(_WORD *)&input[21] = 0;
  input[23] = 0;
  v2 = 0;
  v3 = 0;
  print_banner();                               // print
  sub_40100A();                                 // anti
  j_xor_with_idx_1((int)success);
  j_xor_with_idx_1((int)error);
  scanf("%s", input, 24);
  if ( strlen(input) > 0x17 )
  {
    printf(error);
    exit(0);
  }
  v0 = strlen(&input[3]);
  unhex((int)&input[3], (int)&str_hex_495660, v0);
  v9 = j_check1();
  memcpy(&v2, input, 3u);
  if ( j_check_isdigit(&v2) )
  {
    v8 = j_check2((int)&v2);
    if ( v8 + v9 == 2 )
      printf(success);
    else
      printf(error);
    system("pause");
    result = 0;
  }
  else
  {
    printf(error);
    result = 0;
  }
  return result;
}

反调试

rdtsc反调试只有一处。patch花指令时同时处理了。
剩下的还有sub_40100A函数,这里能不能算是反调试,我说不清楚。我也没明白这里设置的作用。主要代码功能就是进程检查,如果父进程是explorer.exe,则创建新进程并结束当前进程。因为这里影响AES密钥。我的做法是不用管,事实上对OD没什么作用(其实是如果不patch则后面的调试不了,如果patch注意此对AES的密钥影响),也没有影响AES加密结果。如果直接patch掉,就会影响AES密钥。

流程及算法

main函数中的主要流程是:

  • 打印banner
  • 反调试
  • 解码结果的正确与否的信息字串
  • 输入
  • 检查输入长度不大于24
  • 输入的第4字节开始至结束转成16进制字符进行check1
  • 检查输入的前3字节为数字
  • 输入的前3字节作为参数进行check2

check1

check1算法用了miracl大数库。
基本过程是:

  • 解码两个16进制字符串n,c
  • 将输入转成的16进制串m,3e9及n转成大数
  • 计算m^0x3e9%n的值 c1
  • c1与c比较

很明显这是一个RSA算法计算过程。正好n可以在factordb上直接查到。

p=208096057845685678782766058500526476379
q=273086345401562743300402731618892888991
e=1001
n=0x7da39de66016477b1afc3dc8e309dc429b5de855f0d616d225b570b68b88a585
c=0x208CBB7CD6ECC64516D07D978F5F0681F534EAD235D5C49ADD72D2DB840D5304
>>> d=gmpy2.invert(e,(p-1)*(q-1))
>>> pow(c,d,n)
mpz(601616731606062377067631775469716020478784069937L)
>>> hex(601616731606062377067631775469716020478784069937)
'0x69616d6168616e64736f6d656775796861686131L'
>>> '69616d6168616e64736f6d656775796861686131'.decode('hex')
'iamahandsomeguyhaha1'

现在还差前面的3字节数字了

check2

这部分算法是AES(有改动)。输入数据的前三字节作为了key的前三字节(第3字节与反调试代码有关系)。
AES密钥再引入输入前是0001314000000000。用输入的前三字节替换密钥的相应位置,并将第三字节加上495728处的数值(在sub_40100A`函数中设置),其实就是1。

 

所以密钥也不用枚举了,大家应该都能猜出来5211314000000000,输入是前三字节520
AES加密:

明文pediy 后面补00
密钥 5211314000000000
加密结果:912CA2036A9A0656D17B6B552F157F8E (16进制)

 

所以最终flag为:520iamahandsomeguyhaha1


[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

收藏
点赞1
打赏
分享
最新回复 (12)
雪    币: 16154
活跃值: (5941)
能力值: ( LV13,RANK:861 )
在线值:
发帖
回帖
粉丝
大帅锅 4 2018-6-24 13:57
2
0
雪    币: 2
活跃值: (13)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
leaffish 2018-6-25 00:49
3
0
为什么我的ida没有text:0045C11F这一行,是因为没装什么插件吗
雪    币: 13713
活跃值: (2851)
能力值: ( LV15,RANK:2663 )
在线值:
发帖
回帖
粉丝
poyoten 22 2018-6-25 02:40
4
0
leaffish 为什么我的ida没有text:0045C11F这一行,是因为没装什么插件吗
这是手动调整了下。
雪    币: 4836
活跃值: (61)
能力值: ( LV4,RANK:45 )
在线值:
发帖
回帖
粉丝
HeyLXF 2018-6-25 09:51
5
0
请问能放一下,去花指令的脚本吗
雪    币: 13713
活跃值: (2851)
能力值: ( LV15,RANK:2663 )
在线值:
发帖
回帖
粉丝
poyoten 22 2018-6-28 10:01
6
0
只是idc.FindBinary和PatchByte之类的简单组合,没有判断指令,只是字节数据搜索加修改。
雪    币: 13713
活跃值: (2851)
能力值: ( LV15,RANK:2663 )
在线值:
发帖
回帖
粉丝
poyoten 22 2018-6-28 10:02
7
0
HeyLXF 请问能放一下,去花指令的脚本吗
只是idc.FindBinary和PatchByte之类的简单组合,没有判断指令,只是字节数据搜索加修改。
雪    币: 4
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
JuicyBoy 2018-6-30 23:53
8
0
请问楼主可以把去花指令的脚本发出来学习一下吗,因为刚入门,所以想有个脚本对照着学习更容易理解
雪    币: 13713
活跃值: (2851)
能力值: ( LV15,RANK:2663 )
在线值:
发帖
回帖
粉丝
poyoten 22 2018-7-1 00:12
9
0
def scan(patt,start=0,end=0):
  pattern = patt
  if start:
        addr = start
  else:
        addr = MinEA()
  if end:
        addr1 = end
  else:
        addr1 = MaxEA()
  l = []
#    for i in range(addr,MaxEA()):
  for i in range(addr,addr1):
        addr = idc.FindBinary(addr, SEARCH_DOWN|SEARCH_NEXT, pattern)
        if addr != idc.BADADDR:
            l.append(addr)
  return l


def patch_nop(l,pos,count):    
    for addr in l:        
        for n in xrange(count/2):
            PatchWord(addr+pos+2*n,0x9090)
        if count/2:
            PatchByte(addr+pos+count-1,0x90)

def search_junk(s,pos,length,name,start=MinEA(),end=MaxEA()):
    l = scan(s)
    c = len(l)
#    length = len(s.replace(' ',''))/2
    while c:        
        patch_nop(l,pos,length)        
        print "[*]Find %s %d places,and patched."%(name,c)
        l = scan(s)
        c = len(l)

使用就是search_junk('0F 84 07 00 00 00 0F 85 01 00 00 00',0,13,'jzjnz')此类,并不针对此题。这个只能搜索固定的指令数据,patch成nop。其它需求可自行修改。

最后于 2018-7-1 00:13 被poyoten编辑 ,原因:
雪    币: 1470
活跃值: (74)
能力值: ( LV5,RANK:75 )
在线值:
发帖
回帖
粉丝
新月之铭 2018-7-23 21:48
10
0
楼主师傅功力深厚,膜拜
雪    币: 714
活跃值: (82)
能力值: ( LV3,RANK:35 )
在线值:
发帖
回帖
粉丝
xyy吸氧羊 2018-7-30 18:09
11
0
请教一下:如何手动patch,让IDA能F5我不太明白?(IDA玩的不是6)
是要把有花指令的地方都NOP掉,然后把有用的代码拼接到开头吗?为什么会一直弹出来这个?
另外是不是还要编辑_main_0函数的开头和结尾?而且我看到一篇writeup中.idb的_main_0函数还有很多变量是怎么来的?
上传的附件:
雪    币: 13713
活跃值: (2851)
能力值: ( LV15,RANK:2663 )
在线值:
发帖
回帖
粉丝
poyoten 22 2018-8-5 18:48
12
0

如果f5出错,当然是哪有错改哪。放上idb文件。可以自行参照。

最后于 2018-8-5 18:49 被poyoten编辑 ,原因:
上传的附件:
雪    币: 13713
活跃值: (2851)
能力值: ( LV15,RANK:2663 )
在线值:
发帖
回帖
粉丝
poyoten 22 2018-8-5 18:53
13
0
xyy吸氧羊 请教一下:如何手动patch,让IDA能F5我不太明白?(IDA玩的不是6)是要把有花指令的地方都NOP掉,然后把有用的代码拼接到开头吗?为什么会一直弹出来这个?另外是不是还要编辑_main_0函数的 ...

有些事情,自己花时间弄明白与别人直接告诉你答案的效果是完全不同的。

游客
登录 | 注册 方可回帖
返回