buu刷题记录
1.luck_guy

1 | s='icug`of' |
2.Maze

3.Younger-Drive
本题有壳upx,脱完之后拖进ida32,老样子,先查看字符串

去看看input flag

交叉引用看看哪里用到了
进入到这个函数看看,反汇编看看伪代码

可以看到这是一个输入函数,是要我们输入对应的flag的,那就看看Source哪里有用到

一个个查看

多线程,对线程的了解不是太熟悉,只能硬靠百度了

这里对Source也就是我们的flag进行了加密,调用了sub_41112C函数

这里就是对加密后的字符串进行比较了,一共30个字符,正确的话就打印出来,那就看看off_418004是什么

现在可以回过头去看看那个创建多线程的函数了

主要是这两个线程

StartAddress是进行加密的函数

观察第二个线程的函数,sub_411B10,观察这两个函数后,都有个sleep(0X64u),多线程这里应该是等第一个线程获取资源执行完之后sleep等待第二个线程执行,第二个线程执行完后又sleep等待第一个线程执行完,所以应该是交替进行的,再去看看加密函数

在网上看wp时大多这里都看不了,是因为堆栈不平衡,但是不知道为什么我这里直接进来了

上脚本
1 | source="TOiZiZtOrYaToUwPnToBsOaOapsyS" |
4.Java逆向解密
下载题目后是class文件,所以使用jd-gui打开,逻辑比较简单,就直接上脚本了
1 | package Java_reverse; |
5.pyre
下载题目后发现是pyc文件,打也打不开,上网搜索一番后发现需要用uncompyle库来将pyc文件还原成py文件,百度之后知道了pyc文件是py文件编译之后的结果,这样之后再运行的时候就可以省去编译的时间了。
下载好uncompyle库之后想反编译发现报错

去网上搜寻一番
(164条消息) python3.10安装uncompyle6错误(已解决)_principle1的博客-CSDN博客_uncompyle6 安装
是要找到op_imports.py的185,全文搜索canonic_python_version,但是那篇博客修改的是canonic_version,所以应该搜索的是canonic_version

添加完成后重新执行uncompyle6 attachment.pyc > 1.py得到1.py,这样就可以进行逆向了

脚本
1 | code = ['\x1f','\x12','\x1d','(','0','4','\x01','\x06','\x14','4',',','\x1b','U','?','o','6','*',':','\x01','D',';','%','\x13'] |
6.findit
凯撒解密脚本
1 | #key = [ 0x54, 0x68, 0x69, 0x73, 0x49, 0x73, 0x54, 0x68, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x48, 0x6f, 0x6d, 0x65 ] |

7.RSA1
这里回顾一下RSA加密的原理
公钥E=(e,N),私钥D=(d,N),N是公开的,N是由两个质数组成,d是1 < e < 欧拉函数p,q相乘结果T的模反元素,即d与T模1同余

进行公钥解析得到公钥e,N

e=6553
1 | N=86934482296048119190666062003494800588905656017203025617216654058378322103517 |
用RSA Tool2解出另外的几个参数

这里找到了关于这个工具的使用教程
(164条消息) RSA求解明文,安利函数pow()_宁嘉的博客-CSDN博客_rsa算法求明文
之后在Cal.D解出D

跟网上的wp一对比发现D错了,D是跟p,q,e有关,但是N给了,使用的p,q也没错,只可能是e错了,这里我一开始用的是65537的HEX编码,这里才知道HEX编码和十六进制并不一样

65537的十六进制编码是1,这里我懵了,上网搜寻一番才知道,0~65535是无符号数的范围,65535是FFFF,+1后是65536,就是10000,再加1就是65537,就是10001。使用将e改成10001
在网上找了一个脚本,同时学习了一下with open,而且还有给python310添加了rsa库

脚本如下
1 | import rsa |
8.login
拿到题目,是html文件,用vscode打开
1 |
|
这里就是输入的flag如果当前的字符是字母就要replace掉,
replace里面是个嵌套的三目运算符,先判断是否是大写字母,如果是就赋值当前字符c为90,如果不是,就赋值为122,如果赋值完后的字符c1大于等于没赋值前的字符c + 13,c2 = c + 13就把这个字符c1赋值给c2,如果小于则赋值给 c2-26
脚本如下:
1 | s="PyvragFvqrYbtvafNerRnfl@syner-ba.pbz" |

9.CrackRTF
参考了一位大佬的博客(164条消息) BUUCTF reverse:CrackRTF_Lk k k的博客-CSDN博客
知道了第一个密码是哈希的sha1加密,并且输入前半段的范围是要大于100000的,而且必须得是6位,这就得出范围是100000-999999,用python遍历爆破

1 | import hashlib |
得到第一个密码是123321
1 | HRSRC FindResourceA( |
1 | char __cdecl sub_4014D0(LPCSTR lpString) |
接着还有第二个密码的加密

这个0x8003就是对数据进行md5编码,但由于他第二个加密并没有说明范围,所以不能进行爆破
下面又有一个函数sub_40100F,点进去看一看
1 | char __cdecl sub_4014D0(LPCSTR lpString) |
1 | HRSRC FindResourceA( |

然后后面还有个函数
1 | sub_401005(lpString, (int)lpBuffer, nNumberOfBytesToWrite); |
1 | unsigned int __cdecl sub_401420(LPCSTR lpString, int a2, int a3) |
资源的每一位和密码的每一位循环异或
异或结束之后,生成一个rtf文件
我们现在想要的是前六位的密码,循环异或的话,那么也就是说,资源的前六位与密码的前六位异或的结果就是rtf文件的前六位
接下来没思路了,看了一个大佬的思路,可以借鉴一下

(198条消息) BUUCTF reverse:CrackRTF_Lk k k的博客-CSDN博客
10.[WUSTCTF2020]level1
拿到文件,日常检查

然后本来想在虚拟机下直接运行看看

但是发生了这样的错误
于是就搜索了一番
什么是段错误?
“ 段错误(segmentation fault)”是指你的程序尝试访问不允许访问的内存地址的情况。这可能是由于:
试图解引用空指针(你不被允许访问内存地址 0);
试图解引用其他一些不在你内存(LCTT 译注:指不在合法的内存地址区间内)中的指针;
一个已被破坏并且指向错误的地方的 C++ 虚表指针(C++ vtable pointer),这导致程序尝试执行没有执行权限的内存中的指令;
其他一些我不明白的事情,比如我认为访问未对齐的内存地址也可能会导致段错误(LCTT 译注:在要求自然边界对齐的体系结构,如 MIPS、ARM 中更容易因非对齐访问产生段错误)。
知识盲区。。。
只好作罢,开启ida

题目还给了一个output.txt文件,应该是文件输入输出流,那么进一下main函数看看

这里可以看到读入了19长度的字符,如果i是奇数就移位i,如果是偶数就是i*ptr[i]
脚本如下
1 | ptr = [198,232,816,200,1536,300,6144,984,51200,570,92160,1200,565248,756,1474560,800,6291456,1782,65536000] |
11.[GUET-CTF2019]re1
日常检查

upx壳,先脱壳


还是想在Ubuntu下运行看看

那么结果清晰明了,这就是个输入flag并检验的程序
拖入ida

查看字符串信息
双击Correct!,并进入他的引用函数

1 | __int64 __fastcall sub_400E28(__int64 a1, int a2, int a3, int a4, int a5, int a6) |
sub_40F950是一个输入函数
进入到sub_40FA80
1 | __int64 sub_40FA80(__int64 a1, ...) |
可以看到经过若干个判断,他只返回v10并且判断过程中没有对v10进行更改,所以要看第一个对v10进行更改的地方,v10=v9,,而v9又被sub_462860这个函数进行了赋值,跟进看看
打开来看到的是一连串超长的代码,甚至他有6953行

但是整体看完之后,他只返回了-1或是v28,v28又一定是-1(根据分析关于v28的代码得到),但是好像没什么用,还是回到主函数接着看看这个函数
1 | if ( sub_4009AE(&v7) ) |

这里肯定要返回的是5759124 * a1[31] == 719890500
1 | _BOOL8 __fastcall sub_4009AE(char *a1) |
那么大概flag就是a1这个char数组了,是一个很简单的if判断,相除即可还原a1

注意这里a1[16]和a[17换了位置],并且没有a[6]这个东西
脚本如下:
1 |
|
最后得出的是flag{e65421110ba03099a1c039337},但是注意我们这个flag是没有a1[6]的,所以就0-9,a-z,A-Z都试一遍。
最后a1[6]就是1,所以flag就是flag{e165421110ba03099a1c039337}
附:
最后搜寻网上解答是看到了z3解方程的方法,在此处记录下
Z3介绍,什么是Z3?
Z3是由微软公司开发的一个优秀的SMT求解器,它能够检查逻辑表达式的可满足性,可以用来软件/硬件验证和测试,约束求解,混合系统分析,安全性研究,生物学研究(计算机分析)以及几何问题。通俗的来讲我们可以简单理解为它是一个解方程的计算器,Z3py是使用脚本来解决一些实际问题。
Z3在CTF逆向中有什么用?
我们在做CTF逆向题的时候,当我们已经逆向到最后求flag或者具体数值解的时候,例如最简单的:我们知道了未知量x,y,也知道了约束条件x+y=5,那么此时我们就可以使用Z3来求解x和y的值,因为x和y的值肯定有多个解,而我们最后的flag肯定只有一个,那么我们就可以继续添加约束条件来减少解的数量,最后得出正确的flag
1 | pip install z3-solver |
Z3语法简介:
Z3在python中主要有以下数据类型:Int –> 整型Bool –> 布尔型Array –> 数组BitVec(‘a’,8) –> char型(其中的数字不一定是8,例如C语言中的int型可以用BitVec(‘a’,32)表示)
几个常用API
Solver():创建一个通用求解器,创建后我们可以添加我们的约束条件,进行下一步的求解。**
**
add():添加约束条件,通常在solver()命令之后,添加的约束条件通常是一个逻辑等式。
check():通常用来判断在添加完约束条件后,来检测解的情况,有解的时候会回显sat,无解的时候会回显unsat。**
**
model():在存在解的时候,该函数会将每个限制条件所对应的解集取交集,进而得出正解。
问题:假设有两个未知数x和y,已知x+y=5且2x+3y=14,让我们求x和y分别是多少?(可能有的小伙伴会问了,你不会是个傻子吧,这还用编程计算?我口算都能算得出来,这里我只是给大家随便举了个简单的例子,大家别往心里去,主要目的是演示Z3用法 -。-)下面我们按照我们刚才的思路使用Z3进行编写:
1.设未知数:
1 | from z3 import * |
2.列方程:
1 | s = Solver() |
3.解方程判断是否有解
1 | if s.check() == sat: |
4.输出方程的解,没有解则输出无解
1 | print result |
参考文章:
CTF逆向解题辅助工具——Z3约束求解器 - FreeBuf网络安全行业门户
12.[2019红帽杯]easyRE1
日常检查

拖入ida,查看字符串信息

这里有一些变量字符串信息

1 | .rodata:00000000004A23A8 aVm0wd2vhuxhtwg db 'Vm0wd2VHUXhTWGhpUm1SWVYwZDRWVll3Wkc5WFJsbDNXa1pPVlUxV2NIcFhhMk0xV' |
来到主函数查看
1 | v19 = __readfsqword(0x28u); |
可见前面这几个对v12,v13,v14,都进行了一系列初始化操作
v12初始化成Iodl>Qnb(ocy
v13初始化成y.i
v14初始化成d`3w}wek9{iy=~yL@EC
v15可能是flag,长度是36
暂时不知道sub_4406E0(0LL, v15, 37LL);这个是干嘛的,可能是flag的输入
接着往下分析可以看出来,后面还有一段flag,是39位的,先进行第一段的解密吧
这里是判断v15异或是否满足v12的条件,但是v12的长度肯定是不够的,所以猜测应该是v12+v13+v14,这样刚好就是36位

记得要让第十二和第十六位是127
a={73,111,100,108,62,81,110,98,40,111,99,121,127,121,46,105,127,100,96,51,119,125,119,101,107,57,123,105,121,61,126,121,76,64,69,67,}
脚本如下:
1 |
|
结果是这样的:
Info:The first four chars are flag
下面是对第二个信息v18进行加密,长度是39,sub_400E44,点进这个函数,是base64加密,并且进行了10次


顺便在此放上一个b站up主讲的base64编码,讲得非常耐斯
原理到实现 | 一个视频完全掌握base64编码_哔哩哔哩_bilibili
与&运算:比如与3进行与运算,那么3位置上的数他还有,其他全部清零
加密后的结果是off_6CC090,查看他的值,就是我们刚进来时看到的一大长串变量字符串
脚本如下:
1 | import base64 |
结果为:
https://bbs.pediy.com/thread-254172.htm
是个网页。。。什么东西,再回头看看代码

这个下面还有参数,看看哪里调用了

最后来到这
1 | unsigned __int64 sub_400D35() |
看到别人的解答说这个函数调用时在fini段,也就是程序结束的地方调用的


这里可以看到v1是4个字节

这里的这两个参数是连在一块的,是我们在base64编码参数后面看到的那个参数

应该是v1与这一段异或等于flag,因为v1是unsigned int 4个字节,刚好是四个字母,v4又等于v1,所以就是v1与跟这段字符串异或,脚本如下:
1 | v1=['f','l','a','g'] |
13.[MRCTF2020]Transform1

进入主函数


很简单的异或逻辑
脚本如下:
1 | a =[0x67, 0x79, 0x7B, 0x7F, 0x75, 0x2B, 0x3C, 0x52, 0x53, 0x79, 0x57, 0x5E, 0x5D, 0x42, 0x7B, 0x2D, 0x2A, 0x66, 0x42, 0x7E, 0x4C, 0x57, 0x79, 0x41, 0x6B, 0x7E, 0x65, 0x3C, 0x5C, 0x45, 0x6F, 0x62, 0x4D] |
14.[WUSTCTF2020]level21

upx壳,脱壳后拖入ida

直接得到flag
15.[SUCTF2019]SignIn1

拖入ida


v8就是我们输入的flag,进入sub_96A函数看看

看样子是个求字符串长度的函数,然后把v8赋值给了v9


这几个函数都在got表中,动态链接库里才有完成代码

字符串信息中刚好有一个libc.so.6,应该就是链接的文件名,把这个拖进ida看看

没有搜到相关的函数。。。
直接百度这个函数,结果如下
__gmpz_init_set_str() 是一个函数,通常在 GNU MP (GMP) 库中使用。GMP 是一个用于高精度整数和有理数运算的开源数学库。
__gmpz_init_set_str() 函数用于初始化一个 GMP 整数类型 mpz_t 并将其设置为给定的字符串表示的整数值。它的函数原型如下:
1 | cCopy code |
参数说明:
rop:mpz_t类型的指针,表示要进行初始化和设置的目标整数。str:要设置的整数的字符串表示。base:字符串中的进制基数,例如,如果字符串表示十进制数,则将base设置为 10。
该函数将字符串表示的整数值设置到目标整数 rop 中,并进行适当的初始化。在使用完整数之后,应使用 __gmpz_clear() 函数清除相应的内存。
需要注意的是,前缀 __ 表示这是一个内部函数,而不是公共接口。在实际使用中,建议使用公共接口 mpz_init_set_str() 来代替 __gmpz_init_set_str()。
还有这个函数
__gmpz_powm() 是 GMP(GNU MP)库中的一个函数,用于计算模幂运算。
该函数用于计算给定的基数 g 的整数幂对给定的模数 m 取模的结果。函数原型如下:
1 | cCopy code |
参数说明:
rop:mpz_t类型的指针,表示计算结果的目标整数。base:mpz_t类型的整数,表示要进行幂运算的基数。exp:mpz_t类型的整数,表示幂运算的指数。mod:mpz_t类型的整数,表示模数。
函数将计算结果存储在目标整数 rop 中。在使用前,应先使用 __gmpz_init() 或 __gmpz_init2() 初始化目标整数,使用完毕后,应使用 __gmpz_clear() 清除相应的内存。
那么加密流程是将输入的flag设置成16进制数然后进行幂运算再取模,即flag的x次方mod y等于最后的结果,这个跟RSA加密很像,flag=v6,x=v5,y=v4

结果是v7

那么直接RSA解密就行,python内置的RSA库函数有这些
1 | __all__ = ["newkeys", "encrypt", "decrypt", "sign", "verify", 'PublicKey', |
先分解n

p=282164587459512124844245113950593348271
q=366669102002966856876605669837014229419
脚本如下:
1 | import gmpy2 |
suctf{Pwn_@_hundred_years}
16.[ACTF新生赛2020]usualCrypt1

打开ida


跟进加密函数

base64

跟进



看样子是将byte_40E0AA的第七位到第十四位换成了byte_40E0A0,回到base64的加密函数

最后有一个这个函数,跟进看看

值得一提的是,这里的v1=0i64是将v1初始化为0,这是__int 64的一种特殊写法
这里就是一个单纯的大小写转换
那么这题思路就是,先将MXHz3TIgnxLxJhFAdtZn2fFk3lYCrtPC2l9进行大小写转换,然后再进行base64解密,base64的加密表ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/要进行替换(注意这里要加上ABCDEFGHIJ,应该是ida的问题,他把这一个变量分开了),所以实际上是将ABC…开始下标为6-14的字符替换为从KLMN…的6-14,那么就是6-14被替换为16-24
替换
1 | ''' |
大小写转换
1 | flag='zMXHz3TIgnxLxJhFAdtZn2fFk3lYCrtPC2l9' |
换表的base64解密
1 | import base64 |
17.[MRCTF2020]Xor1


这题竟然不能看伪代码

长度为1b,也就是27

这里就是比较的地方了,跟进一下byte_14EA08

直接动调了一下

就是输入的数据和0-27分别异或
脚本如下:
1 | a = 'MSAWB~FXZ:J:`tQJ"N@ bpdd}8g' |
MRCTF{@_R3@1ly_E2_R3verse!}
18.[MRCTF2020]hello_world_go1

打开ida,f5查看伪代码

go语言的反汇编第一次接触,不是很熟悉,查看下字符串信息,检索下flag看看有没有关键点

太多了。。。还是直接看伪代码吧

这里发现有两个比较函数,看看各个变量的信息


结果这里已经存在flag了,选中后按a提取字符串
flag{hello_world_gogogo}
19.相册1
你好,这是上次聚会相片,你看看(病毒,不建议安装到手机,提取完整邮箱即为flag) 注意:得到的 flag 请包上 flag{} 提交
安卓逆向,jadx打开

这里有个Base64的加密

还有个二维码
提取完整邮箱就是flag,那就找找mail相关的

这里发现了flag关键词

同时检索mail关键词时看到了sendMail的字眼,那肯定跟邮箱相关,跟进第一个看看

这里调用了C2class,跟进看看

这里好像是一些发送信息,回到sendMail,看看哪里调用了


这里的MailServer可能就是邮箱,而且这个是C2部分的声明,那回到C2分析

这里可以看到System.loadLibrary,猜测这些变量是调用了名叫core的so文件,用ida打开so文件,搜索NativeMethod

MailServer调用的是NativeMethod.m,跟进看看

Base64解码看看

这个应该就是我们想要的flag了
20.[WUSTCTF2020]level3

无壳,打开ida,主函数如下,再看看字符串信息有没有什么关键的


这里有串Base64编码,并且下面应该是编码表

看起来没有换表

回到主函数分析,输入flag,如果随机数生成是偶数执行else语句,但是else语句提示了好像跟标准的base64加密不一样,,,估计哪里遗漏了信息,找找text段里有没有相关函数吧

看到main函数的上方有个O_OLookAtYou函数,查找引用,反汇编

看来这个就是换表函数了ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
1 | base_table="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" |
脚本如下:
1 | import base64 |
wctf2020{Base64_is_the_start_of_reverse}
21.[FlareOn4]IgniteMe
Hint:本题解出相应字符串后请用flag{}包裹,形如:flag{123456@flare-on.com}

打开ida,查看字符串信息,字符串信息很少

主函数代码如下:

看到了WriteFile函数,前面还有两个GetStdHandle函数,这个函数应该是获取标准输入输出流的句柄,然后将数据写入文件,那个sub_4010F0函数应该是加密函数

dword_403070:这是一个句柄(handle),代表要读取的文件、设备或管道。在你的代码中,它的值为dword_403070,即先前通过GetStdHandle函数获取的标准输入流句柄。v2:这是一个指向缓冲区的指针,用于存储从文件或设备中读取的数据。在你的代码中,它可能是一个变量的名称,用于存储读取的数据。260:这是一个整数值,表示要读取的最大字节数。在这种情况下,最多读取260个字节的数据。&v3:这是一个指向变量的指针,用于存储实际读取的字节数。函数执行后,会将实际读取的字节数存储在v3所指向的变量中。0:这是一个控制标志(dwFlags),用于指定读取操作的一些选项。在这种情况下,值为0表示使用默认的读取选项。
跟进一下sub_401020函数

没什么用,就是为了前面for循环给个结束条件,sub_401020是个赋值函数
那么sub_4010F0函数应该就是读取文件数据的,回到主函数吧
进入sub_401050函数

把输入的值赋给了v1,跟进一下sub_401000函数

这里是循环左移4位? 然后又右移1位,不太清楚这里v4的值是多少,但是可以动调出来

下断点,开始动调

这里eax变成了0x00700004,返回值是int16,也就是十六进制下的后四位,0x0004,所以v4就是4,。这里我后续去搜了一下wp,有个对__ROL__函数的分析__ROL__()函数:循环左移函数,第一个数是循环左移的值,第二个参数是循环左移的位数,最后再整体逻辑右移
即将0x80070000向左循环移动4位,即× 24(相当于十六进制向左移动一位),得到0x00700008。 然后再右移一位得到0x00700004。还要注意到return(int16),即取十六进制的后两个字节。 故v4=0x0004
原文链接:https://blog.csdn.net/qq_61156124/article/details/130133649
回到这个函数:

后续就比较简单了,异或后比较,查看一下byte_403000的值

脚本如下:
1 | a=[13, 38, 73, 69, 42, 23, 120, 68, 43, 108, |
flag{R_y0u_H0t_3n0ugH_t0_1gn1t3@flare-on.com}
23.[GWCTF 2019]xxor

打开ida,查看一下字符串信息

找到关键函数

主函数逻辑比较简单,先输入flag,然后再第二个for循环中进行加密,最后再进行对比。输入要求是6个整数,每个整数提供 4字节 的存储空间。存储到 8字节 的v6数组中。所以这6个数据实际上只占用了3个v6数组。

由于 v6 是 8字节 的而 dword_601078 是 4字节 的。所以直接将 v6[0] 赋给 dword_601078 。实际上只是得到的是 v6[0] 数据低位的 4个字节 。也就是上述中的 p[0] ,而 dword_60107C 就是 p[1]
进入一下加密函数,sub_400686看看,asc_601060数据为2,0,0,0,2,0,0,0,3,0,0,0,4,0,0,0

主函数的sub_400770函数如下

解出a1可以使用Z3约束求解,Z3约束我也是第二次使用,所以此次详细记录一下
1 | from z3 import * |
解出的这六个数据就是我们输入的flag[6],接下来逆向加密函数,加密:
1 | __int64 __fastcall sub_400686(unsigned int *a1, _DWORD *a2) |
解密:
1 |
|
flag{re_is_great!}
24.[WUSTCTF2020]Cr0ssfun

打开ida,来到main函数

进入一下check函数

接着跟进

接着跟进

继续




终于是到finally了

整理一下a1数组,这里没有对flag进行加密,就是简单的判断,所以a1就是flag
脚本如下:
1 | enc = [119,99,116,102,50,48,50,48,123,99,112,112,95,64,110,100,95,114,51,118,101,114,115,101,95,64,114,101,95,102,117,110,125] |
flag{cpp_@nd_r3verse_@re_fun}
25.[FlareOn6]Overlong
The secret of this next challenge is cleverly hidden. However, with the right approach, finding the solution will not take an overlong amount of time.
提示告诉我们,关键信息被巧妙的隐藏了

打开ida,主函数如下:

unk_402088

是个很长的字符串
跟进sub_401160函数,只传入了28的长度

接着跟进

这里有点像base64编码,但是整个程序没有输入,也没有进行对比,加上提示

应该是这句话后面部分没有显示出来,找一下关键的代码

看起来似乎把这个28改长一点就能显示后面的内容

地址在004011C9
打开x32dbg,本想直接跳转到push 1Ch的地方,但是貌似不行

但是这个主函数很短,一下就看到了push 1Ch。


改大一点,单步运行一下看看

flag出现了
flag{I_a_M_t_h_e_e_n_C_o_D_i_n_g@flare-on.com}
26.[UTCTF2020]basic-re

打开ida,查看字符串信息

进入关键函数

可以看到这里就是先输入+-*/,然后再输入两个数进行操作
回到视图表

这里好像,并没有对flag做什么改变,直接提交一下试试看吧
flag{str1ngs_1s_y0ur_fr13nd}
竟然对了。。。。。
27.[FlareOn3]Challenge1

打开ida,进入main函数

逻辑很简单,输入flag,加密,sub_401260应该是flag的加密,跟进看看

很像base64,但是好像不太一样,看看加密表

加密表应该换成ZYXABCDEFGHIJKLMNOPQRSTUVWzyxabcdefghijklmnopqrstuvw0123456789+/
脚本如下:
1 | import base64 |
sh00ting_phish_in_a_barrel@flare-on.com
28.[ACTF新生赛2020]Oruga

打开ida,进入main函数

跟进sub_78A()


byte_201020数据如下

这样一看,里面的while循环里的while循环的条件就是byte_201020里面数为0的
要让第一层while循环终止,就要走到!,所以有点像迷宫,是个16*16的迷宫

可以在16进制看到迷宫图,终点是21h

就是不要走出边界,然后走到21h就行

往右是E,往左是J,往上是W,往下是M
值得一提的是,由于下图的while循环,往下会一直走到头

所以就是MEWEMEWJMEWJM
29.特殊的 BASE64

打开ida,查看字符串信息

进入关键函数

就是输入然后base64加密与v7判断,换表
AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0987654321/+
1 | import base64 |
flag{Special_Base64_By_Lich}
30.[BJDCTF2020]BJD hamburger competition
这是个…..小游戏?

很离谱,第一次碰到这种题,只能先搜寻一下了
.net工具需要使用dnSpy来进行分析
根据大佬的博客,要将BJD hamburger competition_Data
\Managed文件夹中的Assembly-CSharp.dll拖到dnSpy进行分析

这里可以看到有许多类,md5,sha1之类的

这里又可以看到应该是游戏里按钮的实现方法,像eat和menu这些应该没什么好分析的,这个通关猜测是将组合组对,所以要找到判断组合的地方
这里一个个找,在ButtonSpawnFruit里找到了加密的地方

1 | using System; |

这里就是判断了
先把sha1进行解密


然后又对解密出来的值进行md5加密
MD5在线加密 - MD5加密工具 - MD5在线生成 (bmcx.com)

这里有四种,看看代码中的md5加密是哪一种

1 | public static string Md5(string str) |
这里的X2就是将每个字节转换为两位的十六进制字符串,其实也可以一个个试,反正就四个
BJDCTF{B8C37E33DEFDE51CF91E}
31.[ACTF新生赛2020]Universe_final_answer

打开ida,查看字符串信息

进入关键函数

跟进sub_860()看看

这不就是解方程吗,就是未知数有点多,用z3解,这里又搜寻回顾了一下z3库的用法
有理数(Real)型解方程求解
1 | from z3 import * |
位向量(BitVec)型解方程
1 | from z3 import * |
脚本如下:
1 | from z3 import * |
回到主函数,跟进一下sub_C50()函数

应该是把将数字转换为对应字母
70,48,117,82,84,95,121,55,119,64对应的字母就是
1 | a=[ 70,48,117,82,84,95,121,55,119,64] |
F0uRT_y7w@
在Linux下运行程序

flag{F0uRTy_7w@_42}
32.[Zer0pts2020]easy strcmp

打开ida,查看字符串信息

进入关键函数

比较这一串字符串,但是不可能这么简单

main函数前还有这些函数,看看什么情况

一直到sub_6EA,有这些类似加密操作的出现
但当我试图用IDA查看该函数的交叉引用,会发现提示:
Couldn’t find any xrefs!
init函数在程序加载时被调用,它负责设置程序的运行环境和执行一些必要的初始化操作。具体的功能和实现方式取决于被分析的程序。下面是一些init函数可能涉及的任务:
- 程序环境设置:init函数可能会设置程序的全局变量、初始化数据结构或分配内存等,以确保程序在后续执行过程中的正常运行。
- 库初始化:在某些情况下,程序可能依赖于外部库或模块。init函数可以用于加载这些库,并进行必要的初始化操作,以确保程序能够正确地使用这些库。
- 系统资源分配:init函数可能会进行系统资源的分配,例如打开文件、建立网络连接等。这些资源的初始化操作可以在init函数中完成。
- 全局状态初始化:init函数可能会初始化程序的全局状态,例如将全局变量设置为初始值、重置计数器等。
总之,init函数在程序加载时被调用,负责进行初始化操作,确保程序在后续执行过程中的正确运行。具体的功能和实现方式取决于程序本身的设计和要求。
fini函数是在程序即将退出时被调用的函数,它负责执行一些收尾工作。具体的功能和实现方式取决于被分析的程序。下面是一些fini函数可能涉及的任务:
- 资源释放:fini函数通常用于释放程序运行过程中分配的资源,如释放内存、关闭文件句柄、断开网络连接等。这样可以确保在程序退出之前,已经使用的资源都被正确地释放掉,避免资源泄漏。
- 数据保存:有时,fini函数还可以负责将程序运行过程中的数据保存到文件或其他存储介质中。这样可以确保在下次运行程序时,可以加载之前的状态或数据。
- 状态清理:fini函数可能会对程序的全局状态进行清理,例如将全局变量恢复为默认值、关闭运行时的线程等。这样可以确保程序退出时的状态是一致的。
总之,fini函数在程序即将退出时被调用,负责进行清理和释放资源的操作。具体的功能和实现方式取决于程序本身的设计和要求
所以去init函数看看

for循环中调用了一系列的函数,而函数地址从funcs_889开始,跟入便能够发现如下内容:

让我们看看发现了什么东西,sub_6E0,sub_795

sub_650()里面没什么东西,看看sub_795吧

可见,off_201028被置为sub_6EA函数地址了

可以看到,off_2010288实际上是strcmp函数的地址,但现在它被替换成了sub_6EA
因此我们执行strcmp函数时实际上是执行sub_6EA函数,简单的hook,但是这也是我第一次接触到hook
1 | __int64 __fastcall sub_6EA(__int64 a1, __int64 a2) |
shift+提取qword_201060、
unsigned char ida_chars[] =
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x09,
0x4A, 0x49, 0x35, 0x43, 0x0A, 0x41, 0xF0, 0x19, 0xE6, 0x0B,
0xF5, 0xF2, 0x0E, 0x0B, 0x2B, 0x28, 0x35, 0x4A, 0x06, 0x3A,
0x0A, 0x4F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
脚本如下:
1 | data='zer0pts{********CENSORED********}' |
zer0pts{l3ts_m4kijĴńńSOUR_t0d4y}
但是提交结果不对,后面参考了一下大佬的wp,发现没考虑到进位的问题
修改如下
1 | data='zer0pts{********CENSORED********}' |
但是其实还是有点问题的,如果进位是2的话,就会出问题
zer0pts{l3ts_m4k3_4_DETOUR_t0d4y}
33.[WUSTCTF2020]level4

打开ida,进入main函数

Data Structure code,数据结构算法,还有关键词lsft和Right,很可能是左右子树,可能是前后中序遍历
跟进type1
1 | __int64 __fastcall type1(char *a1) |
这个看着很像,中序遍历,因为左中右
跟进type2
1 | int __fastcall type2(char *a1) |
很像后序遍历
type3给注释掉了,main函数暂时没什么有用的信息了,但是前面还有个init函数没有跟进,跟进看看
1 | unsigned __int64 init() |
所以说这里已经出现了类似flag的字符串,中序和后序遍历给出了,应该是求type3的前序遍历,手写也可以,但是还是找了个脚本,,脚本小子
1 |
|
wctf2020{This_IS_A_7reE}
34.[网鼎杯 2020 青龙组]singal

打开ida,这里打开ida看到了这个弹窗

不知道什么是DWARF,chat了一下
DWARF(Debugging With Attributed Record Formats)是一种用于调试信息的标准格式。它是一种用于描述程序的调试信息的规范,旨在帮助开发人员在程序编译后进行调试和分析。
在软件开发过程中,编译器会将源代码转换为机器代码,并且还会生成与之关联的调试信息。这些调试信息包括变量名、函数名、源代码行号等,用于在调试器中定位和理解代码的执行过程。
DWARF规范定义了调试信息的结构和编码方式,以及如何将调试信息与编译后的二进制文件相关联。调试器可以读取这些DWARF调试信息,以便在调试过程中提供有关程序状态和执行上下文的详细信息。
DWARF提供了一种通用的调试信息格式,可用于多种编程语言和体系结构。它支持各种调试功能,如查看堆栈跟踪、变量检查、源代码级别的断点设置等。
总而言之,DWARF是一种用于描述程序的调试信息的标准格式,它在软件开发中扮演着关键的角色,帮助开发人员进行程序调试和分析。
了解一下就行了,接着看程序吧

跟进一下__main()函数
1 | void __main() |
跟进__do_global_ctors();
1 | void __do_global_ctors() |
看起来是对这个程序做了某种初始化操作吧,看不太明白,查看字符串信息,运行程序有一个字符串string,看看这个函数

说明flag长度是15
回到主函数,这里有一个qmemcpy,因为vm_operad用到了v4,看看给v4赋值了什么,这里赋值长度是从

403040+1c8=40 3208
选中,shitf+e
1 | unsigned char ida_chars[] = |
这里的赋值,因为v4是int类型,四字节一个数,而且由于是小端存储,比如0x0A,0x00,0x00,0x00应该是0x00,0x00,0x00,0x0A,所以就是10
脚本如下:
1 | a=[ 0x0A, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, |
回到main函数,跟进vm_operad,传入了v4数组
1 | int __cdecl vm_operad(int *a1, int a2) |
这里大概知道了,v8,v9,v6是用来进行v4与v3赋值的,v3就是我们输入的flag,v8,v9,v6都初始化为0,Switch函数中是对a1数组中的值,这个函数名也告诉我们应该是vm逆向,就是虚拟机逆向
自己定义一套指令,在程序中能有一套函数和结构解释自己定义的指令并执行功能。
这些Switch就是指令集的选择了只有当选择7的时候,输入的flag才会和v4数组里面数为7的下一位进行比较
先筛选出来:
1 | a=[122 ,7 ,30 ,7 ,255 ,255 ,255 ,193 ,7 ,255 ,255 ,255 ,132 ,7 ,40 ,7 ,255 ,255 ,255 ,241 ,7 ,49 ,7 ,255 ,255 ,255 ,167 ,7 ,24 ,7 ,51 ,7 ,114 ,7 ,50 ,7 ,52 ,7 ,63 ,7 ,34 ,7 ,1 ,12 ,8 ,65 ,2 ,1 ,32 ,3 ,8 ,9 ,4 ,1 ,37 ,2 ,8 ,3 ,5 ,1 ,1 ,5 ,8 ,32 ,2 ,1 ,65 ,4 ,8 ,54 ,2 ,1 ,37 ,2 ,8 ,2 ,5 ,1 ,11 ,8 ,12 ,1 ,36 ,4 ,8 ,81 ,2 ,1 ,32 ,3 ,8 ,9 ,4 ,1 ,11 ,8 ,11 ,1 ,33 ,3 ,8 ,3 ,5 ,1 ,4 ,4 ,8 ,12 ,1 ,11 ,8 ,2 ,3 ,1 ,3 ,5 ,8 ,32 ,4 ,1 ,5 ,3 ,8 ,16 ,4 ,10 ] |
这里之所以要逆序回来,是因为内存中的处理好像没有逆序,这里算是一个知识盲点吧,不太清楚,是看了其他师傅的wp,这个15位数组应该就是我们输入的flag了,分支2,3,4,5,11,12就是对输入flag的加密,然后分支1和8对v3进行
这里面有个LOBYTE这个函数,做的时候我去百度了有说是取最右边那位,有说右边四位,没有统一的说法,找了其他师傅的wp,说这是ida的函数,可以忽略这个函数
脚本参考了一下这两个师傅的wp
[原创]2020网鼎杯青龙组_re_signal-软件逆向-看雪-安全社区|安全招聘|kanxue.com
[(117条消息) BUUCTF_网鼎杯 2020 青龙组]singal_buuctf [网鼎杯 2020 青龙组]singal_45149832的博客-CSDN博客
根据opcode分析
1 | 10L, //输入 |
每一轮的操作如下,这里省略了操作数下标的增加,以及最后case1的赋值
1 | //v4[0] = (a1[2] ^ v3[0]) - LOBYTE(a1[5]); -> v3[0] = (v4[0] + LOBYTE(a1[5])) ^ a1[2]; |
脚本如下:
1 |
|
flag{757515121f3d478}
这里还看到了一个新的技术,标记一下,以后有时间了学习
[(117条消息) BUUCTF-网鼎杯 2020 青龙组]singal——angr学习记录_1ens的博客-CSDN博客
35.crackMe
小张从网上下载到一个黑客软件,然而开发者并不打算共享,所以小张注册了一个用户名叫welcomebeijing,但是密码需要进行逆向计算,请求出密码,进行MD5的32位小写哈希,进行提交。 注意:得到的 flag 请包上 flag{} 提交
运行程序试试


先打开ida看看

重点就是这个while循环,动调一下,看看if判断的地址

00401DD,打开x32dbg,本来想直接跳转到这里下断点,但似乎

地址不一样,那还是直接调试吧

查看字符串信息,先在输入密码的那里下个断点吧
一个个单步调试

00691DEC是判断密码正不正确的地方,但是x32的汇编太难看了,还是直接在ida看吧

跟进这个函数

但是这个代码不能反编译,看了看别的师傅的
,这里将爆红的部分nop掉就行,应该是某种反调试技术

我用的keypatch,然后选中这些红色的部分,按p将他们定义为一个函数,就可以反汇编了
1 | int __cdecl sub_4011A0(_BYTE *a1, _BYTE *a2) |
貌似就是将congratulation赋值,这里的result赋值为了1,就说明v3的判断可以满足了

跟进
1 | bool __cdecl sub_401830(int a1, const char *a2) |
- 进入一个
while循环,循环条件是v6 < strlen(a2),即遍历字符串a2的每个字符。 - 在循环中,根据字符的类型进行不同的处理:
- 如果字符是数字,则将其转换为相应的数值。
- 如果字符是十六进制数字,则进行一些额外的操作,同时检查一个条件,如果条件满足,则将当前字符替换为双引号(34 的 ASCII 值)。
- 如果字符不是数字也不是十六进制数字,则进行另一种处理。
- 在每次循环迭代中,执行了两次
__rdtsc()指令,该指令用于获取 CPU 的时间戳计数(TSC)值。 - 计算了一个值
v9,通过对前面处理得到的数值进行操作和移位得到。每当处理了两个字符后,将v9存储到v14数组中,并将v9重置为 0。 - 接下来有一个
while循环,循环条件是v5 < 8。在每次循环迭代中,执行以下操作:- 对一系列名为
byte_416050的数组进行一些计算和交换。 - 检查一个条件,并根据条件修改变量
v12和v11。 - 对数组
byte_416050进行异或操作,并将结果存储到v16数组中。
- 对一系列名为
- 经过上述循环后,调用了一个函数
sub_401470,并将v16和v13作为参数传递给它。 - 最后,将比较
v13的值是否等于0xAB94,如果相等,则返回true,否则返回false。
先分析最后的 sub_401470(&v16, &v13);
1 | _DWORD *__usercall sub_401470@<eax>(int a1@<ebx>, _BYTE *a2, _DWORD *a3) |
因为要满足最后的result==0xAB94,所以这里v16的值应该是dbappsec
然后再倒回去分析sub_401710(&v16, username, v5++)

之后就差byte_5F6050的数据了,打好断点开始动调

在这里就可以看到eax是我们输入的密码,ecx就是我们所要的数据了,一个个记下来,但是不能让跳转发生,把这里改成jnz,0x7E 。。。。这里找出数据后发现和网上的wp不一致,换成x32就一致了,不知道为什么,数据为0x2A, 0xD7, 0x92, 0xE9, 0x53, 0xE2, 0xC4, 0xCD

之后按照流程写脚本逆向
1 | v16_2 = "dbappsec" |
得到密码39d09ffa4cfcc4cc

只不过这里buu答案是错的
36[羊城杯 2020]easyre1
三个解密,exp如下
1 | import base64 |
pwn
1 | file re2 #查看文件信息 |

test_your_nc
下载文件后放到Ubuntu中查看文件信息

从图上可以看出它是一个64位程序,开了NX防护(堆栈不可执行)
代码中:
Relro:Full Relro(重定位表只读)
Relocation Read Only, 重定位表只读。重定位表即.got 和 .plt 两个表。
Stack:No Canary found(能栈溢出)
NX: NX enable(不可执行内存)
Non-Executable Memory,不可执行内存。
PIE: PIE enable(开启ASLR 地址随机化)
Address space layout randomization,地址空间布局随机化。通过将数据随机放置来防止攻击。
打开ida

双击/bin/sh, 点击command,点击X,发现/bin/sh的address在main函数里

发现main函数就是一个简单的system函数.直接调用了/bin/sh,所以根据题目提示直接nc就行.
回到Linux,直接nc做题
简单查看一下nc的用法: nc -help
nc的全名是netcat,其主要用途是建立和监听任意TCP和UDP连接,支持ipv4和ipv6。因此,它可以用来网络调试、端口扫描等等


rip1
也是先检查一下

发现并没有开启保护,并且有可执行可写片段
打开ida,找到main函数

发现main函数并不复杂,只是定义了一个 s ,而且我们很容易就能找到栈溢出的点,我们都知道gets函数是一个危险函数,对于我们来说它可以接受到无限的字符,所以这里就是我们要pwn掉的点。

s有十五个空间

同时main函数下面还有一个fun函数

这里可以直接看到/bin/sh的地址了,但是想试一下pwntool的查找功能
在pwntool里面再试一下

ok,和ida分析的一样,very的好用啊
动态找一下栈溢出的空间

单步运行至gets函数的地方,开始输入,查看栈信息

1 | distance 0xffffd4dc 0xffffd548 |
那么就是十五个字节的垃圾数据加八字节的覆盖rbp垃圾数据加八字节的0x40201b
exp如下:
1 | from pwn import * |
但是没有奏效,很奇怪。。。
后来发现应该我忘了一个很重要的点,我根本没有call system。。。

call system是在0x401191,而且前面也有/bin/sh字段了,所以要从0x401187开始,因为从86开始会push一个新的rbp,反正会导致Ubuntu的栈不对齐然后不成功 那再来试一下

1 | from pwn import * |

flag{2ea2e49d-33f1-479a-8a8e-feb1ba93d082}
warmup_csaw_2016

打开ida,来到main函数

有个return gets(),查看字符串信息

发现了不得了的东西 cat flag.txt


这个函数还可以直接执行,看看压栈地址

那么返回地址就是0x400611了

查看get函数的参数,距离返回地址

这个参数是0x40,那么payload长度就是0x40+8
exp如下:
1 | from pwn import * |
ciscn_2019_n_1

打开ida,进入main函数

跟进func()

所以思路很简单,让gets的返回地址将11.28125写到v2上就行

所以这个栈的情况是这样的

因此,覆盖0x30-0x4=44个字节给v1,另外4个字节给v2
由于v2数值在内存中以16进制存储,所以,找到11.28125的16进制值。


0x41348000应该就是11.28125的十六进制值
exp如下:
1 | from pwn import * |
补充:
| 变量类型 | 存储大小 |
|---|---|
| db | 一字节 |
| dw | 两字节 |
| dd | 四字节 |
| df | 六字节 |
| dq | 八字节 |

libc database search (blukat.me)
Reverse学习
XTEA
- TEA这个简单加密算法在中国如此有名,主要因为腾讯系软件在大量协议和本地数据中使用这个算法。网上很多人甚至直接将TX的加密算法称为TEA算法。
- 0x9e3779b9
- 左移4或者右移5
- 普通TEA


XTEA是TEA的升级版,增加了更多的密钥表,移位和异或操作等等,设计者是Roger Needham, David Wheeler
TEA解密
1 | void decipher(unsigned int num_rounds,uint32_t v[2],uint32_t const key[4]) { |
XXTEA
XXTEA,又称Corrected Block TEA,是XTEA的升级版,设计者是Roger Needham, David Wheeler
- DELTA:算是该加密算法的一个特征值,为黄金分割数(5-2)/2与232的乘积。实际上,该值不影响算法,但能很好的避免一些算法错误。该数值在其他算法中也常有运用。
- MX:对照图例。MIX值(混合,混淆),在本例中请对照加密图解,算是算法的一部分。
- n: 对应明文或密文的数组长度。
- v:明文或密文数组。对应图解中的X. key:密钥数组。对应图解中的Krounds: 加密循环的轮数。.
- sum:对应图解中的D。
- e:对应图解中的(D>>2)
- p:本例中用作明文或密文数组的索引。
- r:(扩散作用)参照代码,类比p&3(该符号在图中而不在代码中)。3的二进制编码为11,任何数与其与运算后,可能产生(00,01,10,11),对应(0,1,2,3)
- 图解符号:
- 方框:相加盒。将指向该盒的变量进行相加。
- 圆圈:异或盒。将指向该盒的变量进行异或。

XXTEA解密
1 |
|
1 | # 给定的密钥(十六进制转字节) |
脏字
U+C(XREF)
1 | addr : |
sp analysis failed(没办法f5)
IDA分析时,需要确认”代码块”和”数据块”并把”有价值”的”代码块”分析为函数但是这种分析过程需要对esp寄存器充分追踪-旦追踪失败便会报错sp analysis failed 或者esp analysis failed
1 | addr1: |
非自然程序流程
- 扁平化的程序控制流
- OLLVM程序流程控制转交
主要是使用XREF来对程序流程进行跟踪和控制
1 | from z3 import * |
angr
00angr
1 | import angr |
01angr
1 | In [6]: p=angr.Project("./01_angr_avoid") |
02angr
1 | import angr |
03angr
1 | import angr |
web
[ACTF2020 新生赛]Include1(文件包含)
这道题没有思路,直接搜的,学到了一个新东西,php伪协议
PHP 伪协议:使用 php://input 访问原始 POST 数据-CSDN博客
在 PHP 中,伪协议(Pseudo Protocols) 也被称为 流包装器,这些伪协议以 php:// 开头,后面跟着一些参数,用于指定 要执行的操作 或 需要访问的资源。
伪协议表明这些协议并不是一个 真实的外部协议,例如 http 或 ftp。PHP 伪协议的出现是为了提供一个 统一的、简洁的 接口来处理 不同的数据流。这些伪协议可以被看作是一种 桥梁,它们允许开发者 使用常规的文件操作函数来处理各种不同的数据流。
pwn学习
Cyclic Pattern
1 | cyclic(0x100)#生成一个0x100大小的pattern,即一个特殊的字符串 |

ELF操作
1 | >>>e = ELF('/bin/cat') |
基本ROP


return to text
最简单的栈溢出利用
利用前提:
开启了NX,栈上无法写入shellcode
shellcode包含system(“/bin/sh”)等
当NX保护开启,就表示题目给了你system(‘/bin/sh’),如果关闭,表示你需要自己去构造shellcode
利用思路:
只要存在后门函数且有调用,就可以劫持EIP然后return to text
手动测量变量溢出长度
还是ctfwiki的ret2text的题PWN入门(1-3-3)-手动测量变量溢出长度 (yuque.com)



从这里就可以看出程序溢出点为:0x62616164
看他报错的意思是(跳转到0x62616164)这步出错了,意思就是没有0x62616164这个位置,那这个位置是从哪来的呢,就是我们200个有序字符其中最先溢出的部分。那么只要知道这个0x62616164是我们输入的第几个,就可以数出来它前头有几个数字了。根据ASCI码表,又根据“0x’是16进制的意思,可以查表得出这串数字转换成字母是’baad’。接下来就要知道cyclic的顺序了,稍微观察一下就可以知道cyclic的规则(4个数4个数有规则),轻易的就可以数出它的位置。当然还有更简单的方法,前文也说了cyclic是有序字符串,自然有一个子函数可以査,“cycic -l”代表着查询你所给的4bit字符前有几个字母。输入【cycic -l 0x62616164】,得到112,说明“baad’前有112个字母,那么这112个字母就是填充空栈的所需量了。接下来多的就会溢出。如下图所示:

return to shellcode
就是自己来填充shellcode然后劫持EIP来执行,同样是ctfwiki的题PWN入门(1-3-4)-基本ROP-ret2shellcode (yuque.com)
利用关键:
1、程序存在溢出,并且还要能够控制返回地址
2、程序运行时,shellcode 所在的区域要拥有执行权限(NX保护关闭、bss段可执行)
3、操作系统还需要关闭 ASLR(地址空间布局随机化)保护。(或关闭PIE保护)
解题步骤
先使用cyclic测试出溢出点,构造初步的payload
确定程序中的溢出位,看是否可在bss段传入数据
使用GDB的vmmap查看bss段(一般为用户提交的变量在bss段中)
先发送为shellcode的数据写入到bss段
在将程序溢出到上一步用户提交变量的地址
在ida中查看buf2的情况

在pwndbg里下好main函数的断点后运行,用vmmap查看buf2对应的bss段是否可以运行

这里显示是可以运行的,这里先计算一下溢出空间


溢出空间还是112
exp如下
1 | from pwn import * |


这是我自己对gadget栈帧变化的理解

ret2syscall
调用系统调用来getshell
32位
1 | eax应该为0xb |
return to libc


动态链接编译文件的时候会先把要连接的函数放入plt表,然后使用的时候先去plt表找,找到后再经过一系列操作找到这个函数的原地址,然后把这个地址放入got_plt,这样第二次再用这个函数的时候就直接去got_plt表中找。

如果要构建调用plt中的system的payload,那要在栈上多加一个exit()


调用plt中的system函数时,要启用/bin/sh得将这个参数压入栈

栈帧结构还是这样,当前所调用的参数是在父函数的栈帧里,比如应该是先push command,然后再call system,此时command是在父函数中,也就是在当前函数的return address前面

这是一个通用结构,payload要调用两个以上的函数时使用

这是简易结构,构造两个函数的payload就使用这个

这个是最终简化版本
那么ret2libc1的exp这里记录一下:
1 | from pwn import * |
ret2libc2

这道题没有给/bin/sh字符串的信息,不能直接构造,所以选择在全局变量buf2中放入数据
1 | io=process("./ret2libc2") |

这里是个通用的payload构造,因为这里是分部分进行的,第一部分进行get函数的执行,执行完之后要恢复栈帧情况,函数的执行一开始需要push ebp,所以需要一个pop出去,然后再return
ret2libc3
这道题没有后门函数,plt也没有system函数的表象,也没有/bin/sh的信息,并且漏洞还隐藏在了strcpy中,那么payload构造应该是要通过libc中的函数来得到
exp如下:
1 | from pwn import * |
记录的一个return to shellcode的exp
1 | from pwn import * |
格式化字符串漏洞

格式化字符串漏洞的发生是在我们要打印栈中某个数据时,比如printf(“%s”,”hello”),但是不给他相应的参数,也就是把hello去掉,那么printf参数就会将栈上相邻的数据打印出来,比如图中就会将0xffffd190往高处相邻的四个参数打印出来,然后这个格式化字符串本身的参数也就是——“%p%p%p%p”存放在这个函数所在栈帧之上的第十一个参数,也就是这个参数我 们可以控制,比如我们写成printf(“0x40001010%11$s”),那么原来的0xffffd1bc就变成了0x40001010%11$s,然后这又是第十一个参数,那么printf就会将这个数据给打印出来,那么想要修改响应地址的参数,比如我要修改0x40001010的,就可以写成”0x40001010%8$n”,这样就可以让0x40001010地址的值 但是%n一次是打印四个字节的数据。也就是0x10000000,想要短一点就把%n写成%hn,就缩短一半,那么还有hhn,就是一半的一半

视频中第一个例子的exp
1 | from pwn import * |
堆


brk mmap

工具
checksec
这里标注一下看到的一个很有用的知识点:
(0x4002c7)(var)==函数(var) 0x4002c7是函数地址
fmtstr_uaf 字符串格式化漏洞 释放重用漏洞
UAF(use after free)漏洞
1 | 应用程序调用free()释放内存时,如果内存块小于256kb,dlmalloc并不马上将内存块释放回内存,而是将内存块标记为空闲状态。这么做的原因有两个:一是内存块不一定能马上释放会内核(比如内存块不是位于堆顶端),二是供应用程序下次申请内存使用(这是主要原因)。当dlmalloc中空闲内存量达到一定值时dlmalloc才将空闲内存释放会内核。如果应用程序申请的内存大于256kb,dlmalloc调用mmap()向内核申请一块内存,返回返还给应用程序使用。如果应用程序释放的内存大于256kb,dlmalloc马上调用munmap()释放内存。dlmalloc不会缓存大于256kb的内存块,因为这样的内存块太大了,最好不要长期占用这么大的内存资源。 |
简单讲就是第一次申请的内存空间在释放过后没有进行内存回收,导致下次申请内存的时候再次使用该内存块,使得以前的内存指针可以访问修改过的内存。
需要注意的是子程序的调用如果只有一个free的话是正确的,因为当函数返回的时候,子程序局部变量的栈针会随着子程序的返回一起销毁
FSB漏洞
格式化字符串是一种很常见的漏洞,其产生根源是printf函数设计的缺陷,即printf()函数并不能确定数据参数arg1,arg2…究竟在什么地方结束,也就是说,它不知道参数的个数。它只会根据format中的打印格式的数目依次打印堆栈中参数format后面地址的内容
在实际程序中,没有安全常识的程序员通常没有对用户输入进行有效地过滤,这些输入数据都作为数据传递给某些执行格式化操作的函数,如printf,sprintf,vprintf,vfprintf。恶意用户可以使用”%s”,”%x”来得到堆栈的数据,甚至可以通过”%n”来对任意地址进行读写,导致任意代码读写
checksec各字段:
ASLR

符号(Symbol)
Canary(堆栈溢出哨兵)
位置无关可执行文件(PIE)
*位置无关可执行文件(Position-Independent Executable)*(PIE),顾名思义,它指的是放置在内存中某处执行的代码,不管其绝对地址的位置,即代码段、数据段地址随机化(ASLR)TEXT、BSS、DATA段
NX(堆栈禁止执行)
NX 代表 *不可执行(non-executable)*。它通常在 CPU 层面上启用,因此启用 NX 的操作系统可以将某些内存区域标记为不可执行。通常,缓冲区溢出漏洞将恶意代码放在堆栈上,然后尝试执行它。但是,让堆栈这些可写区域变得不可执行,可以防止这种攻击。在使用 gcc 对源程序进行编译时,默认启用此安全属性
RELRO(GOT 写保护)
RELRO 代表 “*重定位只读(Relocation Read-Only)*”。可执行链接格式(ELF)二进制文件使用全局偏移表(GOT)来动态地解析函数。启用 RELRO 后,会设置二进制文件中的 GOT 表为只读,从而防止重定位攻击
(204条消息) pwn题shellcode收集_pyiiiiiiiiiiqzvtx30vx4ap0a3hh0a00abaabtaaq2ab2bb0b___lifanxin的博客-CSDN博客
shellstorm
研究案例的外壳代码数据库 (shell-storm.org)
贴一个c语言调用栈的方式

更改输出形式:
1 | checksec --file=./hello --output=json | jq |

一般ida反汇编出来的start函数是程序初始化环境的函数,没有什么用,也无法f5
选中c语言代码,右键copy to assembly,可以看到他的汇编模式
pwntool
1 | from pwn import * |

1 | io.recvline()#让本地程序输出他要执行的一行内容 |
1 | #直接查看got_plt中的函数地址 |

关闭aslr命令

给自己编写的文件去掉pie防护,并且nx防护也没了
pwndbg
1 | gdb re2 |

1 | return #可以直接从子程序返回到main函数 |
1 | vmmap |

1 | pwndbg可以直接用plt查看plt节的内容 |
ROPgadget
安装
1 | sudo apt install python3-pip |
1 | ROPgadget --binary 文件名 --only "pop|ret" |grep eax |
1 | 编译时关掉pie |
linux自带工具
base64编码

查看文件基本信息
1 | file 文件名 |
查看二进制文件字符串信息
1 | strings 文件名 |
查看文件所使用的的动态链接库
1 | ldd 文件名 |

