题目来源: 纵横网络靶场社区 (fengtaisec.com);
协议分析
打开题目附件, 使用Wireshark进行协议分析. 发现有一系列TCP包频繁出现, 右键单击该TCP包之一, 选择追踪TCP流, 可以看到存在大量的LSIS-XGT字样.
谷歌搜索关键字lsis-xgt protocol manual, 可以找到该协议的手册( XGT FEnet I/F Module - LS Electric ). 另外在CTF-Hub的WriteUp<<协议精准定位分析 | CTFHub>>中也找到一份该协议的手册: LSIS-XGT协议规范书
在手册中查看LSIS-XGT协议的帧结构, 利用Wireshark的Lua插件功能, 编写该协议的解析插件. 导入插件后, 重启Wireshark即可成功解析LSIS-XGT协议. 过滤该协议的包进行分析.
(插件链接: PLC-XGT-protocol-for-Wireshark(github.com)
在Wireshark中添加自定义列, 再Ctrl-A全选所有lsis-xgt协议包, 执行文件 -> 导出分组解析结果 -> As CSV -> Selected packet, 将分组解析结果导出. 使用Excel打开csv文件, 利用筛选功能可以看到所有的指令码.
在Wireshark中添加列 | 在Excel中看到所有的指令码 |
---|---|
在Wireshark中定位到写命令的请求包及其响应包, 发现其负载部分并未有有用信息. 排除所有写命令的包之后, 分析读命令的数据包.
依然使用Excel进行分析, 可以发现存在大量的lsis_xgt.data_count == 100
的数据包.
变量长度 | 数据长度 |
---|---|
在wireshark中依次浏览lsis_xgt.data_count != 100
的数据包, 发现均无有用信息. 此时基本上排除上述无意义的包, 过滤lsis_xgt.data_count == 100
, 尝试提取有用信息.
*到这里我就卡住了, 通过查阅WriteUp, 下一步是将所有的 lsis_xgt.data_address matches "%PB...\*00$
的读命令的响应包过滤, 将其中的负载提取出来进行进一步处理. 通过过滤也可以看到, 这部分地址是很规整的依次递增排布的, 很有可能是一次完整的文件传输操作*
提取数据
尝试利用脚本将所有的 lsis_xgt.data_address matches "%PB...\*00$
的读命令的响应包的负载提取出来. 首先添加过滤器lsis_xgt.data_count == 100
, 在Wireshark中执行文件 -> 导出分组解析结果 -> As Json -> All packets/Displayed, 将所有的包导出为Json文件.
编写PowerShell脚本, 将有效的负载提取出来.
$json = (Get-Content .\temp.json | ConvertFrom-Json );
$lsis = $json | ForEach-Object { $_._source.layers.lsis_xgt };
# $validDat = ($lsis | Where-Object {($_.'lsis_xgt.data_count' -eq "100") -and ($_.'lsis_xgt.frame_direction' -eq "0x00000011" ) });
# $validDat = $validDat | ForEach-Object { $_.'lsis_xgt.data' -replace ":", "" };
$packLen100 = $lsis | Where-Object { $_.'lsis_xgt.data_count' -eq "100" };
$validDat = @();
$len = $packLen100.Count;
for ($i = 0; $i -lt $len; $i += 2) {
# 相当于过滤 lsis_xgt.data_address matches "%PB...\*00$
if ($packLen100[$i].'lsis_xgt.data_address' -like "*30:30" ) {
# $i是请求包, $i+1是响应包
$validDat += $packLen100[$i + 1].'lsis_xgt.data' -replace ":", "" ;
}
}
# 将hex字符串转换为byte, 并与0xff进行异或运算后导出为二进制文件.
$bytes = @();
$validDat | ForEach-Object {
$bytes += (( [byte[]] -split ( $_ -replace "..", '0x$& ')) | ForEach-Object {
$_ -bxor 0xff;
});
};
[System.IO.File]::WriteAllBytes("output.bin", $bytes);
利用HexEditor打开导出的output.bin, 发现文件魔数为"MZ". 重命名为output.exe使用ExeInfoPe读取, 是64位的C# GUI程序.
调试程序
将程序导入到DnSpy x64中反编译(很好, 没有混淆, 庆幸), 首先进行静态调试.
程序的入口和主窗体初始化函数如下所示:
程序入口 | 主窗体初始化 |
---|---|
可以看到, 在主窗体初始化时, 修改了程序的可见性, 这会使得程序运行后在屏幕上"消失不见". 在这里设置断点, 尝试利用动态调试的方式修改该值使其显示.
查看主窗体的布局文件mainwindow.baml, 其内容如下所示:
<Window
Title="MainWindow"
Height="450"
Width="800">
<Grid>
<!--此处省略若干RadioButton-->
<RadioButton
Content=""
HorizontalAlignment="Left"
Margin="733,138,0,0"
VerticalAlignment="Top" />
<Label
Name="lable1csc"
Content="1csc"
HorizontalAlignment="Left"
Height="154"
Margin="26,36,0,0"
VerticalAlignment="Top"
Width="745"
Background="#FFFFFFFF" />
</Grid>
</Window>
窗体中含有大量的RadioButton
和一个Label
. 在DnSpy中运行程序, 在断点处修改base.Visibility
的值为Visible
(要先执行完语句base.Visibility = Hidden
之后再修改内存).
继续运行, 程序窗体正常显示. 但是窗体中只有一个Label, 显示1csc, 没有其它元素.
在xaml中看到有很多RadioButton
, 在运行后的主窗体中却看不到这些单选按钮. 因此这些单选按钮可能是得到flag的关键.
在MainWindow
的初始化方法中并没有发现隐藏RadioButton
的代码, 重新审查mainwindow.baml
也没有发现为RadioButton
设置隐藏属性. 进一步的审查mainwindow.baml
时发现, 主窗体的宽高为800x450, 而Label
控件的宽高为745x154, 因此RadioButton
控件不显示的原因很可能是被Label
控件遮挡了.
重新审查MainWindow
的初始化方法, 发现主窗体对象MainWindow
有一个私有成员label1csc
, 它会在初始化xaml
布局后被赋值为Label
对象的引用. 利用该引用, 可以修改Label
的Visibility
属性, 将其隐藏, 从而使被遮挡的RadioButton
显示出来.
在DnSpy中重新运行该程序, 重复上述操作, 在断点处单步调试, 先修改主窗体的可见性为Visible
, 再找到主窗体的成员label1csc
, 将其可见性修改为Hidden
. 如下所示:
在局部变量中找到label1csc |
修改Label 的可见性为Hidden |
---|---|
继续运行程序, 可以看到此时RadioButton
均已显示. 得到一串字符: ICSC-F1A!.
得到flag{ICSC-F1A!}
PS: 其实除了动态调试的方法之外, 还可以将mainwindow.baml
中的内容导出, 用正则处理一下, 翻译成HTML, 使用绝对定位的方式模拟该布局. 也可以得到flag.
参考文章: