盒子
盒子
文章目录
  1. 前言:
  2. 解析:
    1. 第一个函数:
    2. 第二个函数:
    3. 第三个函数:
    4. 第四个函数:
    5. 第五个函数:
      1. 逻辑位移与算数位移:
    6. 第六个函数:
    7. 最后一个函数:
  • 解题脚本:
  • 吉林省网络安全初赛ELF逆向题解析

    前言:

    昨天刚打完第一届吉林省的省初赛,感觉这个质量..不怎么高啊,也没有pwn题,说好所有类型题都有的呢..只好硬着头皮做逆向,已经好多个月没有碰逆向了,还是做了一道pe的crackme和一道elf,这道elf还是刚了好久才解决。学到了。

    解析:

    拿到题目看见是elf文件之后运行了一下,发现是输入flag验证是否正确。

    扔进IDA。

    屏幕快照 2018-05-16 下午3.44.57

    ida看了一下发现

    屏幕快照 2018-05-16 下午3.49.50

    一个main函数其中有很多自加自减的无用操作,然后自加自减中有七个函数。

    第一个函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    .text:0804857B                 public sub_804857B
    .text:0804857B sub_804857B proc near ; CODE XREF: .text:080488CA↓p
    .text:0804857B ; __unwind {
    .text:0804857B push ebp
    .text:0804857C mov ebp, esp
    .text:0804857E sub esp, 8
    .text:08048581 sub esp, 0Ch
    .text:08048584 push offset format ; "please input the flag:"
    .text:08048589 call _printf
    .text:0804858E add esp, 10h
    .text:08048591 sub esp, 8
    .text:08048594 push offset flag
    .text:08048599 push offset aS ; "%s"
    .text:0804859E call ___isoc99_scanf
    .text:080485A3 add esp, 10h
    .text:080485A6 nop
    .text:080485A7 leave
    .text:080485A8 retn
    .text:080485A8 ; } // starts at 804857B

    直接看汇编可以知道这只是一个输入flag的函数,没有什么用,往下看。

    第二个函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    .text:080485A9                 public sub_80485A9
    .text:080485A9 sub_80485A9 proc near ; CODE XREF: .text:0804896C↓p
    .text:080485A9 ; __unwind {
    .text:080485A9 push ebp
    .text:080485AA mov ebp, esp
    .text:080485AC sub esp, 8
    .text:080485AF sub esp, 0Ch
    .text:080485B2 push offset flag ; s
    .text:080485B7 call _strlen
    .text:080485BC add esp, 10h
    .text:080485BF cmp eax, 20h
    .text:080485C2 jz short loc_80485DE
    .text:080485C4 sub esp, 0Ch
    .text:080485C7 push offset s ; "worry."
    .text:080485CC call _puts
    .text:080485D1 add esp, 10h
    .text:080485D4 sub esp, 0Ch
    .text:080485D7 push 0 ; status
    .text:080485D9 call _exit
    .text:080485DE ; ---------------------------------------------------------------------------
    .text:080485DE
    .text:080485DE loc_80485DE: ; CODE XREF: sub_80485A9+19↑j
    .text:080485DE nop
    .text:080485DF leave
    .text:080485E0 retn
    .text:080485E0 ; } // starts at 80485A9
    .text:080485E0 sub_80485A9 endp

    这个函数是判断输入字符串的个数的函数,0x080485BF的汇编代码处可以知道输入的字符串长度必须是32位。

    第三个函数:

    第三个函数开始就是对字符串进行操作的函数了。直接看伪代码

    1
    2
    3
    4
    5
    6
    7
    void sub_80485E1()
    {
    size_t i; // [esp+Ch] [ebp-Ch]

    for ( i = 0; strlen(flag) > i; ++i )
    *(_BYTE *)(i + 134525184) ^= 0x76u;
    }

    可以看到是对所有字符进行了异或的运算。

    继续往下看。

    第四个函数:

    看伪代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void sub_804862A()
    {
    signed int i; // [esp+Ch] [ebp-4h]

    for ( i = 5; i <= 9; ++i )
    {
    *(_BYTE *)(i + 134525184) ^= 0xADu;
    *(_BYTE *)(i + 134525184) = ((*(_BYTE *)(i + 134525184) & 0xAA) >> 1) | 2 * *(_BYTE *)(i + 134525184) & 0xAA;
    }
    }

    这里的伪代码具体含义不太清楚,自己用gdb动态调试一步一步看过去才会比较清楚,如果纯用这里的伪代码来写脚本的话是实现不了的。在这个函数处下个断点自行调试。

    屏幕快照 2018-05-16 下午5.01.47

    调试完后可以明白这个函数的流程就是先与0xffffffad异或后取低八位字节再乘以二以后和0xffffffaa相与…具体就不说了,贴上第一步的解题代码,我这里用的是爆破的方法,用所有可见字符一个一个按照加密过程加密一遍之后再和key相比较。因为实在是想不出常规的逆向脚本怎么写了..

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    for i in range(5,10) :          #第一段
    for j in range(32,127) :
    a = j ^ 0x76
    x = (2*((a ^ 0xffffffad) - 0xffffff00)) & 0xffffffaa
    y = ((((a ^ 0xffffffad) - 0xffffff00) + 0xffffff00) & 0xaa) >> 1
    jieguo = x | y
    jieguo = jieguo - 0x100
    if jieguo == shuzi[i] :
    #print jieguo
    st.append(chr(j))

    第五个函数:

    上一个函数是对5-9位字符操作,这个函数是对11-15个字符操作,可以大致猜测出来各个字符串之间可能会有’_’拼接,而且是五个字符串一组。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void sub_8048693()
    {
    signed int i; // [esp+Ch] [ebp-4h]

    for ( i = 11; i <= 15; ++i )
    {
    *(_BYTE *)(i + 134525184) ^= 0xEu;
    *(_BYTE *)(i + 134525184) = ((*(_BYTE *)(i + 134525184) & 0xCC) >> 2) | 4 * *(_BYTE *)(i + 134525184) & 0xCC;
    }
    }

    这里的伪代码其实也不完全正确,那里的乘以4就是错误的,所以还是得靠自己用gdb一步一步的去调试理解,这里遇到了一个坑,就是上个函数中全都是加密过后所得到的都是不可见的字符串,所以需要减去0x100取低位字节,但是这里自己实验的时候恰好遇见了没有遇到不可见的字符串,所以在遇到不可见字符串的时候没有减去0x100,导致漏掉了一个字符串,后面又回过头好好看了一遍汇编,发现也确实只取了低位字节。

    还有一个点就是这里遇到了一个逻辑左位移和一个算术右位移,在这里卡了半天,发现到头来逻辑左位移用算数运算符’<<’也是一样的,这里我具体也不太明白是为什么,可能遇到的都是有符号的正数所以没有区别的缘故。可是这两者明明是概念完全不同的东西。(一般情况下,在做题的过程中应该两者在代码中都能用’>>’或’<<’来表示)

    这里再来插一下知识点,关于逻辑运算的:

    逻辑位移与算数位移:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    1. 逻辑左移时,最高位丢失,最低位补0;
    逻辑右移时,最高位补0,最低位丢失;
    算术左移时,依次左移一位,尾部补0,最高的符号位保持不变。
    算术右移时,依次右移一位,尾部丢失,符号位右移后,原位置上复制一个符 号位;
    2. 汇编shl和shr,sar:
    shl是逻辑左移指令,它的功能为:
    (1)将一个寄存器或内存单元中的数据向左移位;
    (2)将最后移出的一位写入CF中;
    (3)最低位用0补充。
    shr是逻辑右移指令
    sar是算术右移指令

    这里还遇到了就是两块字符串最后需要or运算一下,但是经过我的实践发现在代码’|’和’^’运算下结果都是一样的。

    附上函数五的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    for i in range(11,16) :          #第二段
    for j in range(32,127) :
    a = j ^ 0x76
    x = ((a ^ 0xe) << 2) & 0xffffffcc
    y = ((a ^ 0xe) & 0xcc) >> 2
    jie = x | y
    if jie > 0x100: #包含有结果不是可见字符的纯数字,只放低字节,比如0x15c的情况所以只存0x5c
    jie = jie - 0x100
    if jie == shuzi[i] :
    #print i
    st.append(chr(j))

    第六个函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    void sub_80486FE()
    {
    signed int v0; // [esp+8h] [ebp-8h]
    signed int i; // [esp+Ch] [ebp-4h]

    v0 = 17;
    for ( i = 30; v0 < i; --i )
    {
    *(_BYTE *)(v0 + 134525184) ^= *(_BYTE *)(i + 134525184) + 1;
    *(_BYTE *)(i + 134525184) ^= *(_BYTE *)(v0 + 134525184);
    *(_BYTE *)(v0++ + 134525184) ^= *(_BYTE *)(i + 134525184);
    }
    }

    这一段函数用爆破的话是根本解不出来的,至少我是,这里最好的方法是运用自己的数学思维方法。

    设vo处为a,x,i为b,y

    由伪代码可知:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    1. x = x ^ (y+1)
    2. y = y ^ x ^ (y+1)
    3. x = x ^ (y+1) ^ y ^ x ^ (y+1)
    |
    |
    |
    转换之后:
    1. y = b ^ a ^ (b+1)
    2. x = b

    所以现在看就变得很简单了,前七位的x就是倒序的y,再解原x即a的时候就用异或的逆函数还是异或解答就行。这里的0x76别忘记了,容易丢掉,这里很重要!!!我在这里卡了半天。

    1
    2
    3
    4
    for i in range(0,7) :
    a = shuzi[i+17] ^ shuzi[30-i] ^ (shuzi[i+17] + 1) ^ 0x76
    st.append(chr(a))
    print st

    最后一个函数:

    最后一个函数就是判断是否和key值相等。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    void sub_8048790()
    {
    signed int i; // [esp+Ch] [ebp-Ch]

    for ( i = 0; i <= 31 && *(char *)(i + 134525184) == key[i]; ++i )
    ;
    if ( i == 32 )
    puts("right.");
    else
    puts("worry.");
    }

    意义不大。

    所以总体来说难度不算特别大,但是需要很大的耐心和细心,自己解的时候遇到的难点也是一步一步的慢慢解决了。

    解题脚本:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    #!/usr/bin/python2.6
    # -*- coding: utf-8 -*-

    shuzi = [0x10,0x1a,0x17,0x11,0x0d,0x4f,0x73,0x5d,0x7a,0x70,0x29,0x4e,0x12,0x43,0x47,0x0e,0x29,0x1a,0x42,0x13,0x12,0x29,0x13,0x45,0x07,0x19,0x01,0x28,0x23,0x18,0x0f,0x0b]
    st = []
    shuzi2 = []
    s = ''
    for i in range(0,32) :
    shuzi2.append(shuzi[i] ^ 0x76)
    s += chr((shuzi[i] ^ 0x76))
    #print shuzi
    print s
    print shuzi2
    for i in range(5,10) : #第一段
    for j in range(32,127) :
    a = j ^ 0x76
    x = (2*((a ^ 0xffffffad) - 0xffffff00)) & 0xffffffaa
    y = ((((a ^ 0xffffffad) - 0xffffff00) + 0xffffff00) & 0xaa) >> 1
    jieguo = x | y
    jieguo = jieguo - 0x100
    if jieguo == shuzi[i] :
    #print jieguo
    st.append(chr(j))
    #print chr(j)
    print st

    for i in range(11,16) : #第二段
    for j in range(32,127) :
    a = j ^ 0x76
    x = ((a ^ 0xe) << 2) & 0xffffffcc
    y = ((a ^ 0xe) & 0xcc) >> 2
    jie = x | y
    if jie > 0x100: #包含有结果不是可见字符的纯数字,只放低字节,比如0x15c的情况所以只存0x5c
    jie = jie - 0x100
    if jie == shuzi[i] :
    #print i
    st.append(chr(j))
    print st

    # x = a
    # y = b
    #
    # x = x ^ (y+1)
    # |
    # y = y ^ x ^ (y+1)
    # |
    # x = x ^ (y+1) ^ y ^ x ^ (y+1)

    # x = x ^ (y+1)
    # y = y ^ x ^ (y+1)
    # x = y

    # y = b ^ a ^ (b+1)
    # x = b

    print s[17:31]
    s4 = 'l4ed_e3' #第三段
    for i in range(0,7) :
    a = shuzi[i+17] ^ shuzi[30-i] ^ (shuzi[i+17] + 1) ^ 0x76
    st.append(chr(a))
    print st
    for i in range(0,7) :
    st.append(s4[6-i])
    st.insert(5,'_')
    st.insert(11,'_')
    st.insert(0,'flag{')
    st.append('}')
    print st
    # for i in range(13,28) :
    # x = ord(st[i])
    # x = x ^ 0x76
    # st[i] = chr(x)
    print ''.join(st)
    支持一下
    扫一扫,支持v1nke
    • 微信扫一扫
    • 支付宝扫一扫