2024极客大挑战wp
MISC
2024 极客挑战赛!
- 关注微信号即可得flag
- flag:
1 | SYC{weLc0mE-tO-2_o_2_100-GeeK@cha11Enge!!} |
ez_climbstairs
- 动态规划基础题,爬楼梯。现场学了一下,还要使用pwntools进行交互。
一开始以为题目默认只能垮1、2个楼梯,结果发现是一次能垮1、2、3个楼梯
直接贴exp:
1 | import sys |
- flag:
1 | SYC{7745e931-0e83-4889-b78f-daa57df1d02a} |
ez_pcap_1
- 主要考简单的流量分析,然后了解文件传输使用的有哪些协议,就很好找到flag
- 最后在
SMB2
协议中找到flag
- flag:
1 | SYC{smb_pcapng_1s_g00d!} |
RE
hello_re
- 打开附件直接查看,发现加密逻辑全部都在这里,数组v7是加密后的密文,v5则是key而key的值为
SYCLOVER
- 加密逻辑如下,按照int类型放入v4数组中,然后再进行取余操作和异或操作进行加密,
- 然后就直接开始写脚本解密
1 | def decrypt(encrypted_data): |
先来一道签到题
- 这题附件给的是一个.s文件的汇编代码
- 这个位置存储的是加密后的字符串
- 通过scanf函数让用户输出flag字符串,输入的字符串数据存储在栈上
- 通过查看这边发现L4这边是flag判断为错的时候将要输出的内容
- 所以主要加密逻辑在L3上,L2后半部分是判断明文输入进去后是否和密文相同
- 解密代码如下:
1 | def decrypt(data): |
让我康康你的调试
使用IDA打开发现是
ELF
文件,结合题目需要在Linux下进行远程调试现在逐个分析,先分析main函数,发现加密后的密文就是
S2
数组里面的内容,而这里注意syclover
是一个密钥
- 然后查看
sub_14A6
这个函数,发现进行了加密
- 再查看
sub_11C9
这个函数,发现是对密钥进行了操作,这题根据题目所说,动态调试是看执行完sub_11C9
这个函数v9
的内存值,这题算法其实也可以直接用AI跑,也可以自己慢慢逆
- 这边为了提高逆算法的能力,我采取逆算法的方式解密,所以得到解密
sub_11C9
这个函数得到
1 | v9 = [] |
- 然后继续解密
sub_14A6
这个函数
1 | s2_1 = '0xA67A02C9047D5B94' |
我勒个z3啊
- 拿到题目运行一下,发现要先输入密钥,再输入flag
- 然后使用IDA进行反编译该文件,然后再看代码,发现了
check_key
函数以及后面check_key
函数后面的函数都是检查flag的函数
- 查看一下
check_key
函数,发现是将指定字符串转化为特定字符,然后再与str2
做比较
- 所以可以写出解密代码,得到key后运行程序输入,结果为密钥正确
1 | a = [0x2A, 0x0E, 0x0E, 0x14, 0x3F, 0x3F, 0x3F, 0x26, 0x11, 0x0A, |
- 接下来输入flag,所以要查看加密flag相关的函数,发现
sub_401AAC
对flag做了加密,然后sub_40179A
对flag做了判断
- 查看
sub_401AAC
函数就会发现还有一个函数对flag进行了操作
- 然后查看
sub_4019EB
- 再来查看检查flag的这个函数
sub_40179A
,发现加密后的密文保存在dword_4040A0
这边
- 所以我们要先用
z3
线性约束利用dword_4040A0
里面的密文,求解出加密后的flag对应的值,直接使用shift+E
提取该值
- 使用z3求解出flag
1 | from z3 import * |
- 接下来开始逆加密算法,在逆算法的过程中这个位置卡了很久,而且思维定势了,后来逐渐理解了该算法的过程之后就写出了对应的解密算法了,总之还是算法敲少了
1 | from z3 import * |
ezzzz
- 下载附件,看到是一个安卓逆向,直接使用
jadx
反编译该代码 - 我们会发现在主类这边调用了
Enc
对象中的encrypt
方法,该方法传入字符串为参数(该字符串就是我们输入的字符串),加密后的数据会与R
对象中的子对象String
,与target
这个int值对应的资源ID的字符串进行对比,如果对比成功则"Wow!You are right!!!"
- 接下来我查看一下这个资源ID对应的字符串,这个资源ID对应的字符串在
res/values/strings.xml
里面,这里直接搜索target
即可找到,但是找到的是这么一长串数字,应该是被hash加密或者是被编码过了,所以这时我们只能从对应的加密那边入手(这里我还并不知道该字符串就是明文加密后要对比的字符串,还是该字符串经过了加密处理)
1 | <string name="target">f1f186b25a96c782e6c63a0b70b61b5ced6bf84889700d6b09381b5ccb2f24fab1c79e796d822d9cdcc55f760f780e750d65c4afb89084a9e978c3827a8dd81091f28df3a84dbacab4d75f75f19af8e5b90f80fcfc10a5c3d20679fb2bc734c8ccb31c921ac52ad3e7f922b72e24d923fb4ce9f53548a9e571ebc25adf38862e10059186327509463dd4d54c905abc36c26d5312d2cd42c0772d99e50cd4c4665c3178d63a7ffe71ada251c070568d5a5798c2921ec0f7fc3ae9d8418460762930ca6a2dccef51d2a1a8085491b0f82d686ca34774c52d0f0f26449fc28d362c86f3311b8adc4fb1a4497e34e0f0915d</string> |
接下来逆向加密的那个对象,了解一下加密过程,然后再编写解密脚本
首先分析主类调用的这个方法,这里也要注意一下上方的
DELTA
,综合特征显示应该是个TEA加密- 先将输入的明文转为逐个字符转为整型,并存储在整型数组
iArr
里面 - 在将字符串
GEEK
转化为整型,存如数组iArr2
里面 - 然后定义整型数组
iArr3
,该整型数组每次取俩个iArr
里面的元素,并且调用encrypt
方法传入iArr3
和iArr2
进行加密,然后更新iArr
数组里面的值 - 之后
new
一个StringBuilder
对象,将加密后的整型数组iArr
以16进制转化为字符串的形式拼接后输出,然后并返回该拼接后的字符串,之后就与target
里面的字符串做对比(这时我更偏向于认为target字符串是加密后的十六进制字符串,而不是字符串对应的hash值) - 这里还要注意,这个方法就是将数组对应的整型转换为16进制,但是会有填充到8个字符,例如:
255
输出会变成000000ff
。所以俩个字符追加上去就会变成00000098000001c7
,所以得到的字符是8个字符转换为一个数组元素
- 先将输入的明文转为逐个字符转为整型,并存储在整型数组
1 | sb.append(String.format("%08x",Integer.valueOf(iArr[i5]))) |
- 然后再来查看
encrypt
这个方法,很典型的一个茶加密
- 直接开逆,这里密钥应该就是
GEEK
这个字符串,加密过程中还有使用int DELTA = -1640531527
- 由于java是强类型语言,与C语言一样都是强类型语言,而Python是弱类型语言,在使用Python编写脚本的过程中会出现问题,所以这种加密我使用C语言写解密脚本
1 |
|
- 然后Python转字符串
1 | m =[83,89,67,123,103,48,111,100,95,106,48,98,95,119,119,101,76,67,111,77,101,84,111,111,111,83,83,83,121,67,95,122,122,95,49,95,101,116,51,115,116,97,114,116,95,121,111,85,114,95,106,48,117,114,110,101,121,33,33,125] |
好像是python?
- 这里积累一下Python字节码
1 | LOAD_CONST |
Python逆向,直接看Python字节码,直接给字节码而不是给
pyc
或者exe
文件,反编译不了,但是可以AI一把梭,可是这样学不到什么,所以直接逆(最近在摆脱对AI的依赖)。使用010Editor
打开,直接看字节码逆向第一部分内容如下:
- 大概意思就是:
1 | flag = 'SYC{MD5(input)}' |
- 接下来,要求用户输入,然后定义两个函数,
test
和test1
- 定义的test2和test函数在这边
1 | Disassembly of <code object test2 at 0x00000245D4F44B30, file "program.py", line 6>: |
1 |
|
- 然后接下来就是程序顺序执行的地方
1 | flag = 'SYC{MD5(input)}' |
- 所以整个程序如下所示:
1 | def test2(s2): |
程序逻辑就是先经过凯撒加密后经过一个不知道什么加密的,然后得到密文
接下来写解密脚本,这里注意一下,在num这个列表是
[-1,-36,26,-5,14,41,6,-9,60,29,-28,17,21,7,35,38,26,48]
而不是从48开始。- 接下来解密脚本:
1 | num1 = [-1,-36,26,-5,14,41,6,-9,60,29,-28,17,21,7,35,38,26,48] |
贝斯!贝斯!
- 逆向顺序
1 | sub_4018FF----->sub_4019EE------>sub_401AF6----->sub_401C6A |
- 打开附件查看main函数,看到这一部分与加密有关
- 之后再查看
sub_401C6A
函数,查看该函数后发现要进行逆向得先逆向这个函数里面的部分
- 然后再打开这个逆向我们要先逆向这里面的函数
- 所以我们先要逆向这个位置
- 逆向过程,这一部分逆向出来了
1 | c = 'RjB6Myu#,>Bgoq&u.H(nBgdIaOKJbgEYj1GR4S.w' |
- 然后再逆向这个函数
sub_4019EE
1 | def deckey2(num1): |
- 接下来就需要逆向
sub_401AF6
这个函数,这一部分逆向后代码如下
1 | def deckey3(num2): |
PWN
简单的签到
- 拿到附件先使用IDA进行分析
- 得到的结果就是,随机生成俩个数字,求他们的乘积
这题就是简单的计算题,但是这题是有时间限制的,所以这就需要我们会使用
pwntools
写脚本下面就是对应的交互脚本
1 | from pwn import * |
- 得到flag
1 | SYC{479822ae-143b-4980-8a30-c9098a1f0040} |
你会栈溢出吗
- 还是下载好附件后使用IDA反编译该文件,发现
gets
- 然后还发现了
system("/bin/sh")
这个函数
- 思路就是
ret2text
,栈溢出到key
这边即可拿到shell
1 | from pwn import * |
1 | SYC{e2842599-dd38-45c7-a451-dae0e62e4105} |
ez_shellcode
- 简单的shellcode+栈溢出的运用
- 使用IDA打开对该二进制文件进行反编译后就会出现如下代码
- 发现有
gets
栈溢出,然后还开辟了一个内存空间给我们注入shellcode
- 思路就是将shellcode注入开辟的内存空间,然后使用
gets
栈溢出返回到shellcode,并且执行shellcode,然后getshell - exp如下:
1 | from pwn import * |
- 打出来可以直接获得flag
1 | SYC{26692bf7-e59b-4995-9a7a-9b6fc2435e13} |
over_flow??
- 拿到附件后老样子,查看保护机制,发现开了金丝雀
- 之后使用IDA打开,对该文件进行反编译,发现是一个文件管理系统
- 然后在自定义read文件的函数这边找到了一个溢出一个字节的漏洞
- 然后再查看溢出到什么位置,发现可以溢出改变
open_fd
的值
- 而溢出改变的值刚好会当做系统调用号去使用
- 这时思路就来了,直接溢出改成
execve
的系统调用号,然后将filename
前八字节写入/bin/sh\x00
,这样原本filename
被当做地址传入,恰好又是sh
的地址,直接getshell - exp如下:
1 | from pwn import * |
00000
- 继续使用IDA打开附件并反编译该附件,然后发现,该题会生成一个密码,然后使用
strcmp
函数比较用户输入的字符串和随机生成的密码是否相等
- 接下来查看
generate_password
函数,该函数会读取/dev/urandom
文件,该文件是用来生成随机数的
本题就是利用
strcmp
漏洞,输入\x00
对该字符串比较进行截断,然后随机数生成凭借概率,会让第一位生成\x00
,这时比较两个正好相等,即可cat flag
exp如下:
1 | from pwn import * |
- 这样即可得到flag
1 | SYC{6bf77f02-7b07-4ffd-9946-0d1afa5c9167} |
买黑吗喽了吗
- 下载好附件后先checksec一下该二进制附件,这里主要是开了pie,还要泄露程序的地址
- 然后再来使用IDA反编译该二进制文件,反编译后就会发现这里面有一个栈溢出漏洞
- 但是由于开了pie,所以我们还需要泄露一下程序的基地址,这样才能泄露libc
- 通过代码动态调试和代码审计可以得到如下逻辑
- 该处可以泄露Balance的地址,所以就泄露了main函数的地址
- 但是要泄露Balance的地址就要满足
!strcmp(str1, str2)
为0,即strcmp(str1, str2)
不为0 - 再查看这俩个字符串
str1
就需要变得和str2不一样,并且之后printf函数的输出是str1字符串内的内容
- 所以我们要修改str1,修改str1就需要执行如下,发现在比较Balance的时候是无符号整数的型式
- 然后根据程序逻辑,我们要购买8次
wukong
,然后再购买一次<<The Journey to the West>>
即可造成整数溢出 - 同时发现有个礼物
- 所以思路如下,可以直接编写exp:
1 | from pwn import * |
这里的空间有点小啊
- 拿到附件先检查一下保护机制,发现没开PIE和金丝雀
- 然后使用IDA反编译该代码,同时发现溢出点
- 但是只够溢出到返回地址,注意到汇编指令中有leave指令
- 所以本题采用的就是栈迁移的方法来打
- exp如下:
1 | from pwn import * |
苏~~~~
- 简单的ret2libc
- 溢出点如下:
- exp如下:
1 | from pwn import * |
真能走到后门吗
- 不多说抽象题目,考格式化字符串漏洞,还有一个off-by-one,思路限制的非常死。
- 拿到附件先查看一下保护机制,发现金丝雀开了
- 然后拿到附件查看,main函数没有什么漏洞溢出点
- 再查看vuln函数,发现有明显的格式化字符串漏洞
- 然后我还注意到
read_str
存在off-by-one,会溢出一个字节
- 这题光看是没有用的,要动态调试才能找到利用方式,这题牢了我好几个小时,利用思路就是利用格式化字符串漏洞,修改返回地址
0x4013F1
,中的0x13
为0x12
,然后再使用off-by-one将0xF1
修改为0x85
(这里不修改为0x7D的原因是存在栈对齐,如果修改为0x7D执行后面函数时,栈不会对齐16字节) - exp如下:
1 | from pwn import * |
- 图片如下:
- 最后得到flag
SYC{64121dc2-6c8b-4232-877b-7d0c40c185a9}
orz?orw!
- 基础的orw题型,正常思路,没写shellcode,比较麻烦
- 拿到附件先check一下,发现没开PIE,然后开了canary。
- 然后再来使用IDA反编译,发现buf那边存在溢出,可以先泄露Canary,然后还可以溢出导致read,v6那边可以写入更多字节,然后导致栈溢出
- 同时发现有sandbox
- 查看一下沙箱,发现最醒目的是禁用了execve,这样我们就不能getshell,所以需要orw
- 这题orw使用csu这条ROP链传参。先泄露libc,得到Syscall地址,然后将Syscall地址和./flag写入bss段,然后再open,read,puts,就可以得到flag
SYC{9f347143-9ba9-4cfc-9913-6a4d2525f8f3}
- exp:
1 | from pwn import * |
FindG????t
- 非常抽象的题目,不知道是不是我没对上思路还是出题人就想这么搞。
- 拿到附件先老样子,check一下
- 然后使用IDA打开该文件,发现这个地方可以造成溢出,首先想到的是覆盖返回值,但是只能覆盖一个字节
- 经过动态调试发现,覆盖返回值的一个字节(返回值刚好是
libc_start_main
),然后就可以再次执行一遍main函数的。但是这个并没有什么用处。所以继续动态调试。 - 经过动态调试发现,rax的值我们可以控制,rdi的值为v4这个字节数组的地址,而我发现,当覆盖为
0xb4
的会返回到Syscall函数(在本地调试的时候会出现该情况)
之后思路来了,在本地打通了。但是在远程可能是因为libc版本导致偏移量不同。所以就采用爆破字节的方法来得到Syscall的正确位置,从而getshell
exp:如下
1 | from pwn import * |
flag如下:flag{0d05fdd8-0b94-4efd-8535-f7682567b4d5}
stack_overflow
- 拿到附件先查看保护机制,发现有开Canary,没有开pie,RELRO也没开,这就意味着got表可被改写
- 接下来使用IDA反编该可执行文件,会发现这里可以指定内存地址写入或者修改内容,但是我们指定内存地址并不能指定为末尾为
0x0
或者0x8
的内存地址
- 然后又看到后面存在溢出,但是存在Canary保护
- 所以本题思路就是直接修改
__stack_chk_fail
函数的got表,然他返回到每次call该函数的时候就直接call到000000000040137F leave
- 这样就可以绕过Canary了,但是由于不能指定修改的地址结尾为
0x0
或者0x8
所以我指定buf为0x403380-0x1
所以这就会导致puts的got表也被修改,之后就只能用printf函数泄露libc地址了 之后就是正常的ret2libc了
exp如下:
1 | from pwn import * |
- 复现后得到flag:
stdout
- 还是老样子,打开附件检查一下保护机制,发现开了pie,但是没有开Canary
- 使用IDA反编译查看一下代码,在初始化这边看到了与其他题目不同的地方,标准输出被修改成了全缓冲,而不是无缓冲模式,这就意味着要等
buf
这个缓冲区满了之后,程序才会输出出来
- 这里还有一个gift,可以用来泄露栈上的rbp的地址
- 这里我们在运行程序的时候还注意到,write可以直接输出,而不是放入缓冲区中
- 之后
vuln
函数存在溢出
- 思路是这样的,先泄露栈地址,然后再利用内存分页机制,导致代码段末尾地址一样通过off-by-one,返回到main函数的write函数那边直接就可以泄露一下main函数的地址,接收完后再通过栈迁移泄露libc地址。最后就可以ret2libc得到shell。
- 这里为什么可以通过write泄露地址是通过动态调试发现的,动态调试发现恰好rdi、rsi、rdx这三个寄存器能符合泄露main函数地址的条件。
- 注意泄露栈地址和泄露libc地址都需要将缓冲区给充满,才能泄露出这俩个地址
- exp如下:
1 | import time |
- 复现后得到flag:
SYC{9f3c06ae-4cfe-4111-9f58-a6fc7eff8ce9}
Crypto
凯撒加密
- 随波逐流一把梭,flag
SYC{WELCOME_TO_2024_GEEK_CHALLENGE}
X0R
- 题目如下:
1 | from Crypto.Util.number import * |
- 这题考的就是pwntools中字节类型的异或和整数类型的异或
这题主要求出key就可以很快得到flag了
由于
1 | e2 = e1^f2 |
- 所以可得
1 | e1 = e2^f2 |
再将密文转化为字节类型
题目告诉我们flag开头为
SYC{
- 所以根据异或的运算特点就可以得到key,从而得到flag
- exp如下:
1 | import ascii |
- flag:
SYC{a_part_0f_X0R}
RSA
- 题目如下:
1 | from Crypto.Util.number import bytes_to_long, getPrime |
- RSA的入门难度,给了n、p、q、c,直接编写解密脚本即可,正常rsa解密,无需多余操作
1 | import libnum |
- flag:
SYC{RSA_is_easy}
ECC
- 题目指明考察椭圆曲线加密
- 题目附件如下,说是Python附件,其实是sage附件,真正写脚本需要使用sagemath进行解密
1 | from Crypto.Util.number import getPrime |
- flag被分成了两部分,然后随机生成256位的素数
a、b
,然后再随机生成一个256位的素数p
,作为ECC加密算法的模数,构造一个模数下的椭圆曲线的坐标方程。但是这里的素数a
与所给的椭圆曲线的参数a
对不上 - 然后随机生成两个椭圆曲线上的点
m
和G
,再随机生成一个256位的素数k
和r
- 本题主要还是求椭圆曲线上的随机点
m
,这个m
点已知,马上就能算出来flag 题目给了
C1
、C2
俩个点,而这俩个点又与m有关,所以我们要利用C1
、C2
、k
这几个已知两算出m
点原本这题想独立思考解出,但是最后还是看了网上的类似题目,本题实际上是
hgame2022
的椭圆曲线原题- 这里主要还是很不理解
m = c1 - k * c2
为什么这样可以直接算出m
1 | p = 93202687891394085633786409619308940289806301885603002539703165565954917915237 |
- 然后最后得到结果:
flag:SYC{Ecc$is!sO@HaRd}
1 | (56959194109531701863457752191002700715316295982070114133947449531256105121967 : 65183385071251798726595129652797025728347063543536362274969327580075443284111 : 1) |
ECBpad
- AES-ECB选择明文攻击,暑假在打国外的一场比赛和BaseCTF都写过这种题型,攻击方法就是利用相同明文的块加密后的密文也相同对明文进行爆破。
- 爆破过程如下,先构造
payload='aaaaaaaaaaaaaaa?(可见字符)aaaaaaaaaaaaaaaS'
然后对发送出来的密文块 1,密文块 2 进行判断是否相同,如果相同即可得出可见字符 (?) 对应的字符是
S
,那么 flag 第一个字符就给爆破出来了具体脚本如下:
1 | from pwn import * |
- 从而得到flag:
SYC{CRY9T0HACK_A_GREA7_W38S17E}
ezRSA
- 模板题,而且题目也有给了提示coppersmith攻击
- 然后在网上了解一下原理之后就写脚本即可
- 这里通过h计算出m的高位
1 | h = 111518648179416351438603824560360041496706848494616308866057817087295675324528913254309319829895222661760009533326673551072163865 |
- 然后使用sage解出m
1 | n = 98776098002891477120992675696155328927086322526307976337988006606436135336004472363084175941067711391936982491358233723506086793155908108571814951698009309071244571404116817767749308434991695075517682979438837852005396491907180020541510210086588426719828012276157990720969176680296088209573781988504138607511 |
- 最后得到flag
1 | import libnum |
nc
- 题目如下:
1 | from hashlib import sha256 |
- 该题附件大致就是,随机生成可显字符串,然后将可显字符串进行sha256加密,并将sha256值告诉我们,题目要我们求随机生成的可显字符串的前4个字符
- 解出来后就可以逐个获取flag了,既然只叫我们求前4个字符,那么就直接明文爆破即可
- exp如下:
1 | from pwn import * |
- 得到flag如下:
SYC{MAYB3_Y0U_KN0W_A1AN-B3<K3R?}
dp
- dp泄露模版题目,根据已知可以推出这个式子,从而得到攻击方法,遍历后得到满足条件的值,该值就为
p-1
- 这样就可以得到p,n就可以分解了
1 | import libnum |
共模攻击
共模攻击该题就是模数n相同,e1,e2不同,所以c1=pow(m,e1,n),c2 = pow(m,e2,n)
题目如下:
1 | from Crypto.Util.number import * |
- 可以直接使用拓展欧几里得算法,得到s1、s2,然后最后再套用公式,即可得到答案
- exp如下:
1 | import libnum |
- flag:
SYC{U_can_really_attack}
LLL
- 这个就是一个简单的格密码入门题目,本题主要还是在调参数上。
- 我这里直接选择爆破求参
- exp如下:
1 | from Crypto.Util.number import * |
- flag:
SYC{1e989433efffd767589e989ad0f091075c06}