babyre
程序替换了dex,新的dex可以在手机数据目录/data/user/0/com.me.crackme下找到
easyre
key是伪随机,动调拿出来即可
懒得改代码了,脚本的v值我是手动替换拼起来结果的。
#include <stdio.h>
#include <stdint.h>
/* take 64 bits of data in v[0] and v[1] and 128 bits of key[0] - key[3] */
void encipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
unsigned int i;
uint32_t v0=v[0], v1=v[1], sum=0, delta=0x9E3779B9;
for (i=0; i < num_rounds; i++) {
v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
sum += delta;
v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
}
v[0]=v0; v[1]=v1;
}
void decipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
unsigned int i;
uint32_t v0=v[0], v1=v[1], delta=0x9E3779B9, sum=delta*num_rounds;
for (i=0; i < num_rounds; i++) {
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
sum -= delta;
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
}
v[0]=v0; v[1]=v1;
}
int main()
{
uint32_t v[2]={0xF191C71E, 0x6790767B};
uint32_t const k[4]={0x00000029, 0x00004823, 0x000018BE, 0x00006784};
unsigned int r=32;//num_rounds建议取值为32
// v为要加密的数据是两个32位无符号整数
// k为加密解密密钥,为4个32位无符号整数,即密钥长度为128位
// printf("加密前原始数据:%u %u\n",v[0],v[1]);
// encipher(r, v, k);
// printf("加密后的数据:%u %u\n",v[0],v[1]);
decipher(r, v, k);
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 4; j++)
{
printf("%c", (v[i] >> (j * 8)) & 0xFF);
}
}
// printf("解密后的数据:%x%x\n",v[0],v[1]);
return 0;
}
pyre
一些牢骚话
这题我只能说 有出的好的地方,但是不好的地方有点多。
先说不好的,首先就是我对这种往题目里面硬塞答辩ollvm的感到非常的不认同。十个题目有三个都在往里面塞ollvm,有的是魔改的,说实话这种东西对熟练的来说就是脚本或者插件一把嗦,你塞魔改的那是出题人有能力,能把大家难倒又能让大家学习ollvm相关的东西,但是塞正常的ollvm,每个函数都塞依托,那是纯纯答辩。
其次,脑洞成分太多了,多到我觉得是misc题目。最首要的就是那个38 giant snakes,还加了个yesterday。不得不说当一个misc题可能是好的,我还特意去google限制时间搜了一下题目文件修改时间的前一天,还真搜到38 giant snakes的相关信息。然后有个python的字眼,这才想到是python38。还有这个aes加密函数 我一开始以为是题目用了一些手法调用了这个函数对.bin文件进行了一些操作,但是交叉引用又找不到,我就猜想可能是不是在init里面调用了 然后没有。我以为这题在玩一些很新的东西,用了一些我不知道的技术去调用这个函数,结果是没有的,就纯是出题人把这个函数留在题目里面,然后让大家猜这个iv和key。好这我也能接受,但是第三部分这个假flag,输入进题目你跳congratulations是什么意思,以前从来没有遇见过哪个题是说输假flag弹出congratulations的,挺抽象的。
最后说一下我觉得好的地方,第一步这个加密就比较新,在玩一种新的东西。让猜数字和映射相同,我是手动推理出来的,首先肯定能想到偏后位置的一定是0,并且0居多,根据这个逻辑 十几分钟就能推理出来这个结果。
套个盾 这以上都是我自己的看法,出题人如果看到了也不要打我,我纯彩笔没做出来这个题目 还在群里问flag交不上去。
wp
首先打开这个题目可以看到一些IO
check1
很容易就能看出来check1是这个sub_401280,点进去是依托ollvm,这里直接使用d810就能解决。但是我的电脑有点小问题 ,还原出来不是很好看,于是直接断点在比较的这个位置看数据猜一下加密。很容易就猜出来了是一个十位数,让数字出现的次数等于其映射位置上的数。手动推理一下,首先肯定能想到偏后位置的一定是0,并且0居多,根据这个逻辑 十几分钟就能推理出来结果是6210001000
check2
然后是check2,不知道是出题人失误还是怎么回事,这个用来check的md5能在线查到啊。前面的加密都没用了。sub_401600就是checkmd5的位置。
这里可以直接看到这个md5.
check3
这个部分是靠脑洞,首先根据38 giant snakes得知是python38 python即巨蛇。然后题目内有出题人留的aes加密magic.file到secret.bin的函数。往上看看可以知道key和iv是从函数参数传过来的。
这一部分密文经过后面的异或就变成了文件名,最后是将magic.file进行aes cbc加密再写入secret.bin这样一个过程。
函数参数不知道,就只能猜是已知信息,已知信息里面check2的字符是十六位,那只能用这个当iv和key,解出来一个python38的pyc。可以直接用pycdas还原出字节码,但是pycdc还原python代码只有一部分,decode函数没了。观察一下pycdas的可以发现里面有一些invalid的代码,可以考虑把这一部分代码全部改成09(pyc字节码的NOP。具体的值可以在python对应版本的opcode.h找到)pyc反编译的工具对于指令要求较高,不能识别的指令会直接导致分析中断。
这里说到pyc字节码,根据ctfwiki misc板块下的pyc文件可以知道,python3.6一下的字节码是1位或者3位字节的变长指令,而python3.8则是固定的一位指令,带上参数即是两个字节。那么我们可以根据pycdas出来的代码找到在文件中对应的位置:例如这三句,最左边的是指令位数,中间的是指令名称,右边的是指令参数。我们可以通过查找对应的opcode.h,把下面这三句翻译成字节即是(64 0F)(5A 14)(6E 24)
152 LOAD_CONST 15: 2
154 STORE_NAME 20: res
156 JUMP_FORWARD 36 (to 194)
相对应的,我们可以用010editor在文件内找到对应的位置:
可以通过相同的方式找到这些invalid指令后面的那一条指令,从而实现用09替换这些指令。
题目两处都用这种方法替换之后能识别出decode函数了,但是main函数还是有问题,这里我就不明白具体问题了,只有三种可能,一种是pycdc不支持try except finally这种写法的代码,一种是在其他地方还有一些花指令导致识别出错,一种是nop的修改还不足以让pycdc恢复识别。我觉得比较可能是其他地方还有花指令。
最后我们使用pycdc能得到一个大致的代码
import string
ascii_uppercase = string.ascii_uppercase
ascii_lowercase = string.ascii_lowercase
digits = string.digits
class II00O0III0o0o0o0oo:
def __init__(self):
table = 0
length = 1
temptable = 2
for _ in range(10):
table += length
length ^= table
temptable *= temptable
basetable = ascii_uppercase + ascii_lowercase + digits + '+/'
self.table = basetable
length = len(self.table)
temptable = list(self.table.encode('utf-8', **('encoding',)))
for i in range(0, length // 2):
for j in range(0, length - 1 - i):
if temptable[j] > temptable[j + 1]:
temp = temptable[j]
temptable[j] = temptable[j + 1]
temptable[j + 1] = temp
continue
continue
basetable = bytes(temptable).decode()
self.table = basetable
return None
def e(self = None, msg = None):
BASE_CHAR = self.table
CHARSET = 'ASCII'
b_msg = bytes(msg, CHARSET)
CHAR_PRE_GROUP = 3
zero_cnt = CHAR_PRE_GROUP - len(b_msg) % CHAR_PRE_GROUP
if zero_cnt == CHAR_PRE_GROUP:
zero_cnt = 0
msg += str(chr(0)) * zero_cnt
b_msg = bytes(msg, CHARSET)
msg = 0
encoded = 1
i_msg = 2
for _ in range(20):
encoded += i_msg
msg ^= encoded
i_msg *= i_msg
encoded = ''
for i in range(0, len(b_msg), 3):
i_msg = (lambda .0: [ int(i) for i in .0 ])(b_msg[i:i + 3])
new_msg = [
None] * 4
new_msg[0] = i_msg[0] >> 2
new_msg[1] = ((i_msg[0] & 3) << 6 | i_msg[1] >> 2) >> 2
new_msg[2] = ((i_msg[1] & 15) << 4 | i_msg[2] >> 4) >> 2
new_msg[3] = i_msg[2] & 63
None += None((lambda .0 = None: [ BASE_CHAR[i] for i in .0 ])(new_msg))
return encoded + '=' * zero_cnt
class IIoo00IIIo0o0oo0oo:
def __init__(self):
self.d = 0x87654321
k0 = 1732584193
k1 = 0xEFCDAB89
k2 = 0x98BADCFE
k3 = 271733878
self.k = [
k0,
k1,
k2,
k3]
def e(self, n, v):
c_uint32 = c_uint32
import ctypes
def MX(z = None, y = None, total = None, key = None, p = None, e = None):
temp1 = (z.value >> 6 ^ y.value << 4) + (y.value >> 2 ^ z.value << 5)
temp2 = (total.value ^ y.value) + (key[p & 3 ^ e.value] ^ z.value)
return c_uint32(temp1 ^ temp2)
key = self.k
delta = self.d
rounds = 6 + 52 // n
total = 0
z = 1
e = 2
for _ in range(10):
z += e
e ^= total
e *= z
total = c_uint32(0)
z = c_uint32(v[n - 1])
e = c_uint32(0)
if rounds > 0:
total.value += delta
e.value = total.value >> 2 & 3
for p in range(n - 1):
y = c_uint32(v[p + 1])
v[p] = c_uint32(v[p] + MX(z, y, total, key, p, e).value).value
z.value = v[p]
y = c_uint32(v[0])
v[n - 1] = c_uint32(v[n - 1] + MX(z, y, total, key, n - 1, e).value).value
z.value = v[n - 1]
rounds -= 1
return v
class chall:
def __init__(self):
self.c1 = II00O0III0o0o0o0oo()
self.c2 = IIoo00IIIo0o0oo0oo()
def ints2bytes(self = None, v = None):
n = len(v)
res = b''
for i in range(n // 2):
res += int.to_bytes(v[2 * i], 4, 'little')
res += int.to_bytes(v[2 * i + 1], 4, 'little')
return res
def bytes2ints(self = None, cs = None):
new_length = len(cs) + (8 - len(cs) % 8) % 8
barray = cs.ljust(new_length, b'\x00')
i = 0
v = []
if i < new_length:
v0 = int.from_bytes(barray[i:i + 4], 'little')
v1 = int.from_bytes(barray[i + 4:i + 8], 'little')
v.append(v0)
v.append(v1)
i += 8
return v
def decode(self = None, text = None):
code1 = self.c1.e(text)
length = len(code1)
padding_length = length + (8 - length % 8) % 8
code2 = code1.ljust(padding_length, '\x00').encode()
v = self.bytes2ints(code2)
n = len(v)
code1 = 0
code3 = 1
code4 = 2
code3 = self.c2.e(n, v)
code4 = self.ints2bytes(code3)
return code4
def check(instr = None, checklist = None):
cipher = chall()
output = list(cipher.decode(instr))
i = 0
if i < len(checklist) and i < len(output) and output[i] == checklist[i]:
i += 1
if i == len(checklist):
return 1
if __name__ == '__main__':
with open('encrypted', 'rb') as rf:
data = list(rf.read())
check_list = data[:len(data) // 2]
check_list1 = data[len(data) // 2:]
print(check_list)
print(check_list1)
flag = input('Please input the last part of the flag:')
main函数可以使用chatgpt恢复一小部分,直接把字节码丢进去是完全错误的。
以上,可以得出check3的大部分逻辑,小部分需要自己猜测。(我就是因为这部分没猜到没做出来(悲))
这一小部分的用了一个异常处理,看起来好像是除0异常,然后跳到了finally里面,用encrypt后文件半部分作为xorlist,前半部分作为checklist,然后异或,再调用check函数。
看代码可以看到就是一个魔改xxtea和一个换表base64,这个换表还是原题的,我直接拿别人wp里面的表就能用。
总结出来解密流程就是先把encrypt文件前半部分和后半部分异或,再xxtea解密,再换表base64解密。
魔改xxtea
#include <stdio.h>
#include <stdint.h>
#define DELTA 0x87654321
#define MX (((z>>6^y<<4) + (y>>2^z<<5)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))
void btea(uint32_t *v, int n, uint32_t const key[4])
{
uint32_t y, z, sum;
unsigned p, rounds, e;
if (n > 1) /* Coding Part */
{
rounds = 6 + 52/n;
sum = 0;
z = v[n-1];
do
{
sum += DELTA;
e = (sum >> 2) & 3;
for (p=0; p<n-1; p++)
{
y = v[p+1];
z = v[p] += MX;
}
y = v[0];
z = v[n-1] += MX;
}
while (--rounds);
}
else if (n < -1) /* Decoding Part */
{
n = -n;
rounds = 6 + 52/n;
sum = rounds*DELTA;
y = v[0];
do
{
e = (sum >> 2) & 3;
for (p=n-1; p>0; p--)
{
z = v[p-1];
y = v[p] -= MX;
}
z = v[n-1];
y = v[0] -= MX;
sum -= DELTA;
}
while (--rounds);
}
}
int main()
{
// uint32_t v[10]= {2381651913,154517149,2207197885,2948914538,1297438674,2778237469,3899242504,536948396,2740260929,2014530248};
// uint32_t v[10]= {3374314893,2646488329,3173683075,1792918703,3528938829,495622309,146303464,2888696096,1091589539,3360297848};
// uint32_t v[10]= {243744876,1449451470,858997228,2635499997,3422466335,2668982149,915379719,3379046531,3428777632,3034478195};
uint32_t v[10]= {0x83725fa5,0x5f516153,0xb0bc6b51,0x32d254b7,0x86abe6cd,0x3a8de598,0xdee62e0f,0xe969062f,0x6f0beee1,0xcccd3cbb};
uint32_t const k[4]= {1732584193,0xEFCDAB89,0x98BADCFE,271733878};
int n=10; //n的绝对值表示v的长度,取正表示加密,取负表示解密
// v为要加密的数据是两个32位无符号整数
// k为加密解密密钥,为4个32位无符号整数,即密钥长度为128位
// printf("加密前原始数据:%u %u\n",v[0],v[1]);
// btea(v, n, k);
// printf("加密后的数据:%u %u\n",v[0],v[1]);
btea(v, -n, k);
// printf("解密后的数据:%x %x\n",v[0],v[1]);
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 4; j++)
{
printf("%c", (v[i] >> (j * 8)) & 0xFF);
}
}
return 0;
}
换表base64直接用cyberchef,怎么快怎么来就行
最后拼起来即可
flag{6210001000_yOUar3g0oD@tc4nd_PytH0n_KZBxDwfkIzbEgUOY}
巡抚好腻害 😘
大爹,等你带我