0x1 分析
核心代码位于flag,进入发现前面为混淆干扰,最后按已知seed生成伪随机数
0x2 脚本
注意有个坑是windows生成的随机数和ubuntu下生成的不一样,用ubuntu生成结果正确
1 |
|
核心代码位于flag,进入发现前面为混淆干扰,最后按已知seed生成伪随机数
注意有个坑是windows生成的随机数和ubuntu下生成的不一样,用ubuntu生成结果正确
1 |
|
安卓so文件逆向
IDA打开 lib/arm64-v8a/libapp.so
看看有没有跟42比较大小的(检测输入长度是否与flag长度相同)
等待加载完成后 Alt + T
搜索 cmp
再 Ctrl + F
搜索#0x2a (即42) 发现还真有
进入函数 F5 反汇编
观察到一个长度刚好为42的数组赋值
接下来程序大概逻辑: 遍历数组 每个数除以2 与0x66异或 再作比较
可以发现数组内全是偶数所以不会触发115行的条件
1 | l=[0,20,14,2,58,190,160,6,160,166,160,190,162,150,166,6,8,8,150,164,162, |
发现程序会调用strcmp 还有一个42长的byte数组(可见CISCN2023 MOVEASIDE WRITEUP(1))
即使都是mov指令,调用库函数还是要去到对应plt的 因此在strcmp@plt
处下断点
然后用IDA GDB动态调试 方法:IDA远程连接GDB调试
F4 和 F8 调试执行 如果遇到Exception 点击Yes (pass to app) 注意要在gdbserver命令行输入flag内容 (我这里输入的是’0123456789abcdef{-}’
执行到strcmp@plt
处 可以看到栈上的参数: 08600154
和 0860014c
按G 跳转发现一个为0x51
一个为0x67
说明是一个一个字符比较的 0x67即内置数组的第0位数据 说明0x51为输入处理后的结果 即字符0对应0x51
同理可以获取到各个字符对应的字节映射 (手动或者python脚本调用idaapi,其实’flag{-}’这些字符对应关系不用试也就已知)
1 | raw = [0x67, 0x9D, 0x60, 0x66, 0x8A, 0x56, 0x49, 0x50, 0x65, 0x65, |
一个汇编全是mov
的程序,第一次见
搜索一下发现是通过movfuscator
加密混淆过的,RITSEC CTF 2018中有一道类似的题目
发现有个解混淆工具 demovfuscator
下载按指示安装使用后再看汇编,还是一堆mov,解混淆不了一点,不知道是不是我的使用姿势不太对
发现一个提示输入字符串ok input your flag:
和一个可疑字符串VIPeeUd\\eHPQ\\UgQW\\IgTc\\TbRVTTPISRRV
可以推测如果输入正确则会输出’yes!’
转成数组发现刚好是42个byte(flag长度),第一想法应该是做了什么位运算得到的 而且很直观的是 四个 \
正好对应四个 -
,说明是一一对应关系(很重要,后面会用到)
复制数据内容
我们已知的flag结果为 开头的’flag{‘,结尾的’}’ 以及中间的四个’-‘
与数组中的对应位进行二进制比较: 发现还是有些规律的 bit0取反,bit1,2,3保持不变
1 | idx 0: 0110 0111 0x67 |
字节范围内遍历异或,查看所有16进制可见字符的可能
1 | # flag的16进制字母表 |
输出如下 可见可以暴力枚举 但也有2^23的数量 考虑进一步优化
1 | 5, 0x56: 7 |
前面推测得到两个数组直接应该是一一映射的关系,即特定的输入对应特定的输出
由于我们已经知道了 f
和 a
对应的输入为0x67和0x9d, 那么0x57就应该对应6
和1
同理 如果0x54对应e
那么所有0x54都对应e
实际上也就并不需要枚举2^23种可能 只需确定[b,2] [c,3] [d,4] [e,5]的对应关系即2^4=16种可能
爆破脚本:
1 | from pwn import * |
gdbserver
1 | sudo apt-get install gdbserver |
gdbserver
9999为你指定的端口号 bin为你想要调试的可执行文件
1 | gdbserver 0.0.0.0:9999 ./bin |
选择工具栏 Debugger -> Select debugger 或直接在图标导航栏
选择 Remote GDB Debugger
按F9调试,显示配置信息parameters
为程序执行所需要的参数 没有则不填hostname
填gdbserver运行所在的ip,若是本机则为127.0.0.1,若是虚拟机或其他,需查看ip地址再填入port
为刚刚gdbserver运行指定的端口号
点击OK 即可进入调试
可参考Debugger
工具栏下的各调试命令
F7 单步进入
F8 单步跳过
F4 执行到断点
F9 开始调试
这题是go语言程序 感觉逆向的占比大于pwn…
没开PIE, 而且可以发现程序是静态链接的
无符号表,难以分析,尝试利用
两种方式:
go
可以看到版本信息strings shellwego | grep "go"
可以看到版本为go1.20.4
IDA 可以根据选定signature库中的函数签名特征与当前程序各函数进行匹配 从而恢复函数名
Apply new signature...
Search
搜索 go
go_std
仅可用于 go1.10-1.16
, 不可行ida go1.20 signature
发现工具 GoReSym (也可以试试其他符号恢复方法)
使用:
GoReSym.exe -t -d -p /path/to/shellwego > output.json
goresym_rename.py
再选择刚才输出的 output.json
IDA就能自动恢复函数名了前后对比可以看到恢复效果非常理想
ciscnshell$
,再获取输入,然后调用函数 main_unk_func0b05
main_unk_func0b05
函数Cert Is A Must
所以先来看cert命令 按照程序流程可见应该还要输入 nAcDsMic
+N
也就是 nAcDsMicN
cert nAcDsMicN
会出现提示 Missing parameter
, 明显是要三个参数 main_unk_func0b01
main_unk_func0b01
函数同样方法转换字符串,可以发现大致流程:
很明了了,先base64解密成byte数组, 再用已知key通过rc4解密 最后得到 S33UAga1n@#!
执行程序输入 cert nAcDsMicN S33UAga1n@#!
, 成功通过验证
main_unk_func0b05
函数尝试执行函数中提到的不同命令,可以cat flag
但是是假的flag
发现仅echo命令存在可能漏洞(main_unk_func0b04中)
main_unk_func0b04
函数
结合动态调试发现:
+
不复制直接栈溢出到ret_addr会出现Segmentation Fault,调试分析问题出现在指令movzx edx, byte ptr [rbx+rax]
原因rbx从栈上取了一个地址值,而我们改写了栈使其不再是有效地址值,所以解析的时候会报段错误
绕开方式: 刚好遇到 +
不会复制,也就保留原来的值
1 | from pwn import * |
刚开始学习pwn的时候大部分人都会遇见由于libc版本与程序要求不匹配而导致的无法调试或运行的大坑。这是因为大部分程序是动态链接生成的,在首次执行到某些系统函数的时候会先在libc.so (shared object) 当中找到对应函数并执行,所以连接到错误版本的glibc,会出现攻击脚本失败,调试过程出错等情况。查看程序当前使用的libc版本的命令:ldd ./bin
解决方法如下:
1 | git clone https://github.com/matrix1001/glibc-all-in-one.git |
可以看到有list, old_list; download, download_old 文件
1 | cat list |
文件中download对应下载list中的glibc, download_old对应下载old_list中的glibc
下载后的glibc在./libs
目录中
1 | ./download 2.23-0ubuntu3_amd64 |
1 | git clone https://github.com/NixOS/patchelf.git |
1 | patchelf --set-interpreter glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-linux-x86-64.so.2 ./bin |
version GLIBC_2.34’ not found
解决方法(稍为繁琐):在ubuntu18或ubuntu16中用gcc (gcc版本为7-) 编译,再将文件导入当前系统并进行以上patchelf操作,就可以正常执行了
具体原因笔者还没找到,可能是因为你当前linux系统版本较高,对应的gcc版本高,编译后的二进制文件无法与过早的glibc版本连接,但我尝试过在kali 6.0.0 (gcc版本为12.2.0) 中下载低版本gcc进行编译以及多种方式,还是同样的报错结果(如果有大神发现具体原因和更简便的解决方法能告诉我吗,感激)
预处理 -> 编译 -> 汇编 -> 链接
1 | gcc -E filename.c > filename.i ; preprocessed source |
可移植性强,文件大
1 | gcc file.c -static -o bin |
1 | gef➤ vmmap |
可移植性差,文件小
1 | gef➤ vmmap |
Section | 说明 | 权限 |
---|---|---|
.init | 初始化代码,先于main执行 | R_X |
.fini | 结束代码,在最后执行 | R_X |
.text | 程序汇编指令 | R_X |
.rodata | 存放只读数据,一般是字符串常量(代码中直接使用的字符串也算) | R__ |
.data | 保存已经初始化(非零初始化)的全局变量和静态局部变量 | RW_ |
.bss | 未初始化(零初始化)的全局变量和静态局部变量保存在bss段 | RW_ |
.got | Global Offset Table 存放外部符号的实际偏移 | RW_ |
.got.plt | 和.plt共同发挥作用,存放.plt所需的偏移量 | RW_ | R__ (full-RELRO) |
.plt | Procedure Linkage Table程序链接表 作为跳板调用外部函数 要么在.got.plt 节中拿到地址,并跳转。要么当.got.plt 没有所需地址的时候,触发链接器去找到所需的地址 |
R_X |
.plt.got | 仅当开启full-RELRO时出现 | R_X |
.dynamic | 存储动态链接器的加载 |
调用外部函数,首先都跳转至对应PLT区,再跳转至对应GOT区
若为首次调用该函数
非首次调用
call func -> func@plt -> func@got -> 真实地址
参考
https://hackthedeveloper.com/c-program-compilation-process/
https://www.matteomalvica.com/minutes/binary_analysis/
https://intezer.com/blog/research/executable-linkable-format-101-part1-sections-segments/
https://intezer.com/blog/malware-analysis/executable-linkable-format-101-part-4-dynamic-linking/
代码区、工具区、函数区
快捷键 | 功能 |
---|---|
Shift+F12 | 查看字符串 |
Space | 源码/流图 切换 |
a | 切换为字符串 |
d | 切换为data(byte, word, …) |
g | 跳转至地址 |
右键->Array | 定义数组 |
Shift+E | 导出数据 |
; | 注释 |
F5 | 反编译 |
n | 修改函数名、变量名 |
y | 修改变量类型、函数返回值类型 |
/ | 注释 |
x | 查看函数调用 |
(更多功能在实战中探索…)
快速下载
要求:GDB 8.0+ python 3.6+
1 | bash -c "$(curl -fsSL https://gef.blah.cat/sh)" |
查看 ~/.gdbinit
如果有 source ~/.*gef*.py 说明安装成功
1 | cat ~/.gdbinit |
Git下载
1 | git clone https://github.com/hugsy/gef.git |
直接执行 gdb <filename>
1 | $ gdb bin |
命令 | 功能 |
---|---|
aslr on/off | 开启/关闭aslr |
start | 启动调试 |
b * [addr] | 在addr处下断点 |
info b | 查看所有断点(对应标号) |
d [断点标号] | 删去断点 |
c | 执行到下个断点 |
ni [n] | 执行一(n)个指令 |
n | 执行单行代码(带源码情况下) |
x/[size+type] [addr] | 查看地址内容 |
context | 界面展示指定内容 |
define | 自定义命令 |
gef config | 配置 |
stack [n] | 查看栈上n行信息 |
heap [chunks|bins|arena] | 查看堆信息 |
1 | pip3 install pwntools |
1 | from pwn import * |
1 | pip3 install ROPgadget |
1 | apt install ruby |
1 | git clone https://github.com/matrix1001/glibc-all-in-one |
1 | cat list |
1 | apt install patchelf |
1 | patchelf --set-interpreter [ld.so] [filename] |
首先编译安装 DNSTracer:
1 | wget http://www.mavetju.org/download/dnstracer-1.9.tar.gz |
传入一段超长的字符串作为参数即可触发栈溢出:
1 | dnstracer -v $(python -c 'print "A"*1025') |
漏洞原因:把参数 argv[0]
复制到数组 argv0
的时候没有做长度检查,如果大于 1024 字节,就会导致栈溢出:
1 | // dnstracer_broker.h |
1 | // dnstracer.c |
要修这个漏洞的话,在调用 strcpy()
前加上对参数长度的检查就可以了:
1 | /*CVE-2017-9430 Fix*/ |