来吾爱三年了,没有发过任何帖子,但默默从萌新成长为了略通拳脚的师傅。
趁着好不容易春节回家前几天有空,刷了些题蹭吾爱币。现在继续发题解蹭积分(x
2
这个题我一开始认真逆向了题目的一部分逻辑,感慨吾爱的简单题也还是有点难度的(C++且无调试符号)。直到我看到后面的加密、比较操作时,我才反应过来哪里不对劲:加密是对我原以为的密文做的(所以其实是解密),而比较的目标是选手输入的明文,故而可以直接调试到解密结束,在栈上翻到 flag。
2
3
作为安卓苦手,这个题一开始让我感觉有点难于下手,就差把wuaipojie2025包下的每个类逐一点过去了。
最后在 MainActivity->FoldPagerAdapter->FoldFragment2 找到了一串神秘的 base64 以及“恳求”我寻找flag的任务提示:
3-1
跟着 base64 ,跟进两重调用后:
3-2
提示 xxtea,点进去看也确实是 xxtea 。剩下的就交给赛博厨师了。
3-3
这是第二段 flag ,在 FoldFragment2.onViewCreated.gestureDetector.1#onScroll 里还有一段:flag{
。
不能习惯没有 jni 的 android 题。
4
先看jni_onload
,果然有动态注册:
4-1
4-2
上来一堆检测,但没细看,只关注了下检测的结果导致v9
是a
还是aa
。
4-3
a
和aa
两个函数差不多,都是跟进密钥生成扩展密钥流,扩展密钥流和明文异或得到密文。所以加密过程其实只是一个异或,没细看密钥生成的逻辑(E5220
函数)。
题目友好地给出了x86架构的lib库,非常照顾我这个至今没有android真机调试环境的选手。
重打包-安装过程记得遇到过一些问题,根据adb install
的报错搜索下就能解决。
调试输入0x13个字符串1
,dump出密文b'\x1fz\xdf\xf9\xd1\xa4\xb9v\x81C*Yq\xe1;\xb5FA\xbb'
。明密文异或即得扩展密钥流,再和密文异或得到flag。
ct1 = b'\x1fz\xdf\xf9\xd1\xa4\xb9v\x81C*Yq\xe1;\xb5FA\xbb'
pt1 = b"1" * 0x13
ct = bytes(reversed(bytes.fromhex('F75942B63AE26B0C72079872ECF89BAF8F2748')))
pt = bytes([a^b^c for a, b, c in zip(ct1, pt1, ct)])
print(pt, len(pt))
(忘了真实函数是a
还是aa
了,总之都试下肯定就有了)
得到:flag{md5(uid+2025)}
,我想到了如下一些可能:
import hashlib
print("flag{" + hashlib.md5(str(uid+2025).encode()).hexdigest() + "}")
print("flag{" + hashlib.md5(str(uid+2025).encode()).hexdigest().upper() + "}")
print("flag{" + hashlib.md5(int.to_bytes(uid+2025, 4, 'little')).hexdigest() + "}")
print("flag{" + hashlib.md5(int.to_bytes(uid+2025, 4, 'little')).hexdigest().upper() + "}")
print("flag{" + hashlib.md5(int.to_bytes(uid+2025, 4, 'big')).hexdigest() + "}")
print("flag{" + hashlib.md5(int.to_bytes(uid+2025, 4, 'big')).hexdigest().upper() + "}")
print("flag{" + hashlib.md5(str(uid).encode()+b'2025').hexdigest() + "}")
print("flag{" + hashlib.md5(str(uid).encode()+b'2025').hexdigest().upper() + "}")
嗯,这一步它卡了我一天(一天只能交三次 flag )。
5
动调了一路的一题。
die查出一个没见过的壳,网上没搜到有用的材料,于是开始调试这个壳。
发现到伪C代码208行这里跳转到 OEP 。跳转前调了 TlsCallback。也可以看到跳转前修改了 TlsCallBack 的代码。
5-1
TlsCallBack 恢复后的长这样,调了 140012038 处函数指针数组内的函数。
5-2
其中第二个函数利用ZwSetInformationThread
反调。注意到程序还 patch 了跳转到ZwSetInformationThread
的桩函数,
将
00007FFDD876D680 < | 90 | nop |
00007FFDD876D681 | FF25 00000000 | jmp qword ptr ds:[7FFDD876D687] |
00007FFDD876D687 | 3013 | xor byte ptr ds:[rbx],dl |
00007FFDD876D689 | 34 98 | xor al,98 |
00007FFDD876D68B | 6E | outsb |
00007FFDD876D68C | 0100 | add dword ptr ds:[rax],eax |
00007FFDD876D68E | 0001 | add byte ptr ds:[rcx],al |
00007FFDD876D690 | 75 03 | jne ntdll.7FFDD876D695 |
00007FFDD876D692 | 0F05 | syscall |
00007FFDD876D694 | C3 | ret |
patch 为:
00007FFDD876D680 < | 90 | nop |
00007FFDD876D681 | FF25 00000000 | jmp qword ptr ds:[7FFDD876D687] |
00007FFDD876D687 | 3013 | xor byte ptr ds:[rbx],dl |
00007FFDD876D689 | 34 98 | xor al,98 |
00007FFDD876D68B | 6E | outsb |
00007FFDD876D68C | 0100 | add dword ptr ds:[rax],eax |
00007FFDD876D68E | 0001 | add byte ptr ds:[rcx],al |
00007FFDD876D690 | 85D2 | test edx,edx |
00007FFDD876D692 | 79 02 | jns ntdll.7FFDD876D696 |
00007FFDD876D694 | F7D2 | not edx |
00007FFDD876D696 | EB 1A | jmp ntdll.7FFDD876D6B2 |
00007FFDD876D6B2 | 0F05 | syscall |
00007FFDD876D6B4 | C3 | ret |
主要修改是增加了一条not edx
。同时注意到调用ZwSetInformationThread
前,程序的第二个参数已经被作为一次取反(正常应该是17,程序实际调用参数为-17)。所以这条not edx
的目的是恢复参数。
这里多此一举的目的是实现反-反-反调试。众所周知,ZwSetInformationThread
可以实现反调。因而 ScyllaHide 实现了ZwSetInformationThread
的反-反调试。实现方式为 patch ZwSetInformationThread
的桩函数。实际上,最原始版本的桩函数为(上面给出的 patch 前版本实际是程序 patch 前、 ScyllaHide patch 后):
00007FFDD876D680 < | 4C:8BD1 | mov r10,rcx |
00007FFDD876D683 | B8 0D000000 | mov eax,D | D:'\r'
00007FFDD876D688 | F60425 0803FE7F 01 | test byte ptr ds:[7FFE0308],1 |
00007FFDD876D690 | 75 03 | jne ntdll.7FFDD876D695 |
00007FFDD876D692 | 0F05 | syscall |
00007FFDD876D694 | C3 | ret |
ScyllaHide patch 使其跳转到 00000214C4371330 ,并检测第二个参数是否为 17,如不是,恢复正常执行。程序对第二个参数取反的操作正是为了绕过 ScyllaHide 的检测。
00000214C4371330 | 48:8BC4 | mov rax,rsp |
00000214C4371333 | 48:8958 08 | mov qword ptr ds:[rax+8],rbx |
00000214C4371337 | 48:8968 10 | mov qword ptr ds:[rax+10],rbp |
00000214C437133B | 48:8970 18 | mov qword ptr ds:[rax+18],rsi |
00000214C437133F | 48:8978 20 | mov qword ptr ds:[rax+20],rdi |
00000214C4371343 | 41:56 | push r14 |
00000214C4371345 | 48:83EC 20 | sub rsp,20 |
00000214C4371349 | 41:8BF1 | mov esi,r9d |
00000214C437134C | 4D:8BF0 | mov r14,r8 |
00000214C437134F | 8BEA | mov ebp,edx |
00000214C4371351 | 48:8BF9 | mov rdi,rcx |
00000214C4371354 | 83FA 11 | cmp edx,11 |
00000214C4371357 | 75 24 | jne 214C437137D |
00000214C4371359 | 45:85C9 | test r9d,r9d |
00000214C437135C | 75 1F | jne 214C437137D |
00000214C437135E | 48:83F9 FE | cmp rcx,FFFFFFFFFFFFFFFE |
00000214C4371362 | 74 15 | je 214C4371379 |
00000214C4371364 | 6548:8B0425 30000000 | mov rax,qword ptr gs:[30] |
00000214C437136D | 8B58 40 | mov ebx,dword ptr ds:[rax+40] |
00000214C4371370 | E8 FB1D0000 | call 214C4373170 |
00000214C4371375 | 3BD8 | cmp ebx,eax |
00000214C4371377 | 75 04 | jne 214C437137D |
00000214C4371379 | 33C0 | xor eax,eax |
00000214C437137B | EB 11 | jmp 214C437138E |
00000214C437137D | 44:8BCE | mov r9d,esi |
00000214C4371380 | 4D:8BC6 | mov r8,r14 |
00000214C4371383 | 8BD5 | mov edx,ebp |
00000214C4371385 | 48:8BCF | mov rcx,rdi |
00000214C4371388 | FF15 62630000 | call qword ptr ds:[214C43776F0] |
00000214C437138E | 48:8B5C24 30 | mov rbx,qword ptr ss:[rsp+30] |
00000214C4371393 | 48:8B6C24 38 | mov rbp,qword ptr ss:[rsp+38] |
00000214C4371398 | 48:8B7424 40 | mov rsi,qword ptr ss:[rsp+40] |
00000214C437139D | 48:8B7C24 48 | mov rdi,qword ptr ss:[rsp+48] |
00000214C43713A2 | 48:83C4 20 | add rsp,20 |
00000214C43713A6 | 41:5E | pop r14 |
00000214C43713A8 | C3 | ret |
直接 patch 调用 ZwSetInformationThread
的函数组织好返回值跳到末尾即可绕过这处反调。
回到脱壳后的 main 函数,程序先调用sub_140007940
创建了几个线程,然后调用DialogBoxParamW
创建了一个窗口。跟入DialogBox
的 handle 函数(sub_140009EC0
),它尾调用到sub_140009240
,这里基本是响应用户请求,构造消息发给其它线程处理,并通过 ConditionVariable 唤醒线程。
接下来看线程入口函数sub_140007B50
,基本一个大循环:等待指定的 ConditionVariable 被唤醒、调用某结构体特点偏移的处理函数、继续 sleep 直到下一次被唤醒。
在140007C4D
处下断点,跟入找到每个消息的处理函数。发现sub_140008A80
对用户输入作check,是一个核心函数。期内又构造了四个消息请求由其它线程处理核心的加解密逻辑,依次实现如下功能:
- unhex 密码;
- 解密并 unpad 密码,得到的 20 字节视为这样一个结构体: 8 bytes time | 8 bytes uid | 4 bytes hash;
- 对解密得到的 20 字节的前 16 字节作哈希操作,最终得到 4 字节哈希结果 H;
- 获取由当前时间计算得到的量(半小时更新一次):T = (time() // 1800) * 1800;
最终 check 逻辑:检查输入结构体中的时间和 T 是否相同,输入结构体中的 uid 和 DialogBox 里用户 uid 是否相同,输入结构体里的哈希和计算得到 H 是否相同。
hash 算法为魔改版 md5,魔改点为在填充时给msg_len_in_bits
值加了2。
EXP:
import time
from arc4 import ARC4
def p64(v):
return int.to_bytes(v, 8, "little")
def p32(v):
return int.to_bytes(v, 4, "little")
def toi32arr(data):
return [int.from_bytes(data[i:i+4], "little") for i in range(0, len(data), 4)]
def fri32arr(data):
return b"".join([int.to_bytes(a, 4, "little") for a in data])
def create_msg(t, u, p):
return p64(t) + p64(u) + p32(p) + b"\x04" * 4
rc4_key = lambda c: int.to_bytes(c, 8, "little") + bytes.fromhex('E87964BA0EF1B4A8A175E934E143DFBAF08755F4C18D5AA6')
def enc(data):
assert len(data) % 8 == 0
mask = (1<<32)-1
inpall = toi32arr(data)
for i in range(len(data) // 8):
inp = inpall[i*2:i*2+2]
key = ARC4(rc4_key(i+1)).encrypt(b"\x00"*16)
key = toi32arr(key)
acc = 0
for _ in range(12):
acc -= 0xb979379e
acc &= mask
inp[1] += (inp[0] - acc) ^ (key[1] + (inp[0] >> 5)) ^ (key[0] + 16 * inp[0])
inp[1] &= mask
inp[0] += (inp[1] - acc) ^ (key[3] + (inp[1] >> 5)) ^ (key[2] + 16 * inp[1])
inp[0] &= mask
assert acc == 0x4E516498
inpall[i*2:i*2+2] = inp
return fri32arr(inpall)
from md5 import *
def epad(msg):
msg_len_in_bits = (8*len(msg)) & 0xffffffffffffffff
msg.append(0x80)
while len(msg)%64 != 56:
msg.append(0)
msg_len_in_bits += 2
msg += msg_len_in_bits.to_bytes(8, byteorder='little')
return msg
t = int((time.time() // 1800) * 1800)
uid = 0
salt = bytes.fromhex('3532706F6A6965203230323520E788B1E9A39EE79A84E78CAB00')
m = salt + create_msg(t, uid, 0)[:0x10] * 10
m = epad(bytearray(m))
processed_msg = processMessage(m)
digest = int.to_bytes(processed_msg, 16, "little")
p = digest[15] + (digest[11] << 8) + (digest[7] << 16) + (digest[3] << 24)
m = create_msg(t, uid, p)
m = enc(m)
print(m.hex())
6
这个题 windows 版应该可以直接用 upx 脱壳。里面是一个简单的 vm , vm 的 opcode 指令数目也不多。我的解法是用 R1+Python 重新实现了一遍 vm ,打印 trace ,然后一点点看 trace 完成逆向。
输入 flag 括号内的每段先 base36 解码,然后和 uid 一起算了一个哈希(看到其它wp说是crc32),要求哈希是一个指定常量。很容易逆向求得一组输入:
EXP:
def fc128(a0, a1):
a0 &= 0xff
v1 = a0 ^ a1
for i in range(7, -1, -1):
f = v1 & 1
v1 >>= 1
v1 ^= 0xedb88320 if f else 0
return v1
def fc0b6(uid):
v1 = 0xffffffff
v1 = fc128(0x32, v1)
v1 = fc128(0x30, v1)
v1 = fc128(0x32, v1)
v1 = fc128(0x35, v1)
v2 = v1
v2 = fc128(uid >> 24, v2)
v2 = fc128(0x35, v2)
v2 = fc128(0x32, v2)
v2 = fc128(0x70, v2)
v2 = fc128(0x6f, v2)
v2 = fc128(0x6a, v2)
v2 = fc128(0x69, v2)
v2 = fc128(0x65, v2)
v2 = fc128(uid >> 16, v2)
v2 = fc128(0x61, v2)
v2 = fc128(0x66, v2)
v2 = fc128(0x64, v2)
v2 = fc128(0x6d, v2)
v2 = fc128(uid >> 8, v2)
v2 = fc128(0x32, v2)
v2 = fc128(0x30, v2)
v2 = fc128(0x32, v2)
v2 = fc128(0x35, v2)
v2 = fc128(uid, v2)
return ((~v2) & 0xffffffff) | 0x80808080
def fc089p(data):
return data[:5] == b"flag{" and data[0x1c] == b"}"[0]
def fc003(tab, inp, off, k):
vv = 0
for i in range(5):
v = tab[inp[off + i]]
assert v != 0
vv *= 0x24
vv += v - 1
if vv >> 25:
vv += 1
vv %= (off & 0xff) + k * 0x13541
else:
vv |= 1
return vv
tab = bytearray(b"\x00"*0x100)
tab[0x30:0x38] = int.to_bytes(0x40C132115100A09, 8, 'little')
tab[0x38:0x40] = int.to_bytes(0x1C11, 8, 'little')
tab[0x40:0x48] = int.to_bytes(0x623020D200F0300, 8, 'little')
tab[0x48:0x50] = int.to_bytes(0x12081916010E141B, 8, 'little')
tab[0x50:0x58] = int.to_bytes(0x51A071E0B24171F, 8, 'little')
tab[0x58:0x60] = int.to_bytes(0x221D18, 8, 'little')
def fc089(uid, passwd):
kk = fc0b6(uid)
v = 0
for i in range(4):
v |= fc003(tab, passwd, 5 + 6 * i, kk & 0xff)
kk >>= 8
v -= 1
v &= 0xffffffff
return v ^ 0xc15303fb
def rfc089(uid, expected):
kk = fc0b6(uid)
v = expected ^ 0xc15303fb
v += 1
v &= 0xffffffff
flag = b"flag{"
for i in range(4):
off = 5 + 6 * i
k = kk & 0xff
m = (off & 0xff) + k * 0x13541
e = (((1 << 25) // m) + 1) * m
e -= 1
ans = []
for j in range(5):
ans.append(tab.index((e % 0x24) + 1))
e //= 0x24
flag += bytes(reversed(ans)) + b"-"
kk >>= 8
return flag[:-1] + b"}"
uid = 0
expected = 0x3EACFC04
inp = rfc089(uid, expected)
print(inp)
r = fc089(uid, inp)
print(hex(r), r == expected)
7
First 数组里有两个有用的初始化函数。作了简单的反调和字符串解密。反调函数很容易识别并 patch 掉,字符串解密也容易静态实现。
实现一些字符串解密的 ida 脚本:
import ida_bytes
def patch_add(addr, size, value):
org = ida_bytes.get_bytes(addr, size)
p = bytes([(a+value)&0xff for a in org])
ida_bytes.patch_bytes(addr, p)
print(f"done {hex(addr)} {hex(size)} {value}")
patch_add(0x140026ad0, 8, 0x10)
patch_add(0x140026ac4, 6, 0x10)
patch_add(0x140026aac, 6, 0x10)
patch_add(0x140026A30, 0x11, 0x10)
patch_add(0x140026A80, 0x29, 0x10)
patch_add(0x140026B28, 0x11, 0x10)
patch_add(0x140026B40, 13, 0x10)
patch_add(0x140026AB8, 11, 0x10)
第二个有用的初始化函数里面修改了main
函数的地址,真实地址为1400017A0
。
刚开始做这题时,并没有认真看sub_1400013B0
函数。发现只需要输入和程序由时间戳生成而来的一些常量满足一些简单的算数关系即可。通过trace获取生成的量,构造满足条件的输入后本地就过了,但远程连挂了三组(没错,又要等第二天了)。
后面认真看了下sub_1400013B0
函数,发现是椭圆曲线的点加操作,初始化函数里也是在初始化一个阶小于200的非退化的椭圆曲线。于是猜测对输入数据实际上还有一些额外的要求。将输入的最后两个数(用作椭圆曲线的a、p参数改成和题目生成的一样),再去构造剩余输入,就过了。
EXP:
A = [
(0xFFFFFFFFFFFFE12E, 0x237),
]
a, p = 0x40, 0x80F
b = A[0][1]
A = [a for a, _ in A]
print('001001'*16 + ("%06d"%a) + ("%06d"%p))
uid = 0
import time
t = (int(time.time()) // 60) * 60 // 10
t ^= uid
t = str(t * t)[:0x10]
print(t)
key = ""
for i, c in enumerate(t):
v = (ord(c) * b) - (A[i] - (1 << 64))
print(v)
key += "%06d"%v
print(key + ("%06d"%a) + ("%06d"%p))
后记:
第一天连挂三组时,站短戳了出题人,但奈何我只能发两条短消息,沟通完全没起到效果。然后搜出了出题人的邮箱,继续发邮件追问。出题人没有看邮箱的习惯,但好在过了第一天12点后,我利用新的提交flag机会试出了出题人的意图。后面补发邮件说明情况后没再继续纠结这回事。
8
一个不支持 x86 的 Android APP ,native 层加了不弱的 MBA 混淆,我能逆吗,我逆不动。
2/5 放题后小看了一会,在研究怎么装 unidbg 环境。ubuntu+vscode+maven,跑官方 test 报各种错,因为有活马上到DDL了,遂放弃。直到元宵那天晚上才腾出手了继续做题,这次切到 windows 直接解决了 unidbg 的环境问题。
虽然有 mba 混淆,但不影响在 JNI_OnLoad
->sub_13260
里面找到 RegisterNatives。
通过追踪输入数据(GetStringUTFChars),可以大致看出sub_18A00
在作加密算法的初始化(输入不参与),sub_18454
在作加密。加密函数大致有如下结构:
x = sub_16CA0(a1, x, 0LL)
x = sub_16CA0(a1+128, x, 1LL)
x = sub_16CA0(a1+256, x, 0LL)
且x
为8字节,很难不让我联想到 3des 。经过进一步比对基本确认。
剩下两个问题:
sub_184A8
对输入作了什么;
- 3des 的密钥是否能恢复(程序里只有扩展密钥,虽然已经够了,但如果能恢复密钥,一方面是验证了它确实是des,另一方面也更方便写代码)。
关于1,我真没猜出来,只看出是逐字节加密的(看其它wp说是bit倒序)。于是用 unidbg trace 打了个表:
private void buildmap() {
Module module = emulator.getMemory().findModule(moduleName);
long target = module.base + 0x184A8;
for (long i = 0; i< 0x100; i += 1) {
Number result = emulator.eFunc(target, i << 56);
System.out.print("(" + Long.toHexString(i) + "," + Long.toHexString(result.longValue()) + "),");
}
}
关于2,可以用Stark/des_keyschedule基于trace的任意一组明密文+第一轮密钥爆破出原始密钥。
最终 exp:
from Crypto.Cipher import DES
key1 = bytes.fromhex('2bd7449f82c5b200')
key2 = bytes.fromhex('952d48114881ff48')
key3 = key1
ciphers = [
DES.new(key1, DES.MODE_ECB),
DES.new(key2, DES.MODE_ECB),
DES.new(key3, DES.MODE_ECB)
]
def tdes_enc(ciphers, pt):
x = pt
x = ciphers[0].encrypt(x)
x = ciphers[1].decrypt(x)
x = ciphers[2].encrypt(x)
return x
def tdes_dec(ciphers, ct):
x = ct
x = ciphers[2].decrypt(x)
x = ciphers[1].encrypt(x)
x = ciphers[0].decrypt(x)
return x
bmap = dict([(0x0,0x0),(0x1,0x80),(0x2,0x40),(0x3,0xc0),(0x4,0x20),(0x5,0xa0),(0x6,0x60),(0x7,0xe0),(0x8,0x10),(0x9,0x90),(0xa,0x50),(0xb,0xd0),(0xc,0x30),(0xd,0xb0),(0xe,0x70),(0xf,0xf0),(0x10,0x8),(0x11,0x88),(0x12,0x48),(0x13,0xc8),(0x14,0x28),(0x15,0xa8),(0x16,0x68),(0x17,0xe8),(0x18,0x18),(0x19,0x98),(0x1a,0x58),(0x1b,0xd8),(0x1c,0x38),(0x1d,0xb8),(0x1e,0x78),(0x1f,0xf8),(0x20,0x4),(0x21,0x84),(0x22,0x44),(0x23,0xc4),(0x24,0x24),(0x25,0xa4),(0x26,0x64),(0x27,0xe4),(0x28,0x14),(0x29,0x94),(0x2a,0x54),(0x2b,0xd4),(0x2c,0x34),(0x2d,0xb4),(0x2e,0x74),(0x2f,0xf4),(0x30,0xc),(0x31,0x8c),(0x32,0x4c),(0x33,0xcc),(0x34,0x2c),(0x35,0xac),(0x36,0x6c),(0x37,0xec),(0x38,0x1c),(0x39,0x9c),(0x3a,0x5c),(0x3b,0xdc),(0x3c,0x3c),(0x3d,0xbc),(0x3e,0x7c),(0x3f,0xfc),(0x40,0x2),(0x41,0x82),(0x42,0x42),(0x43,0xc2),(0x44,0x22),(0x45,0xa2),(0x46,0x62),(0x47,0xe2),(0x48,0x12),(0x49,0x92),(0x4a,0x52),(0x4b,0xd2),(0x4c,0x32),(0x4d,0xb2),(0x4e,0x72),(0x4f,0xf2),(0x50,0xa),(0x51,0x8a),(0x52,0x4a),(0x53,0xca),(0x54,0x2a),(0x55,0xaa),(0x56,0x6a),(0x57,0xea),(0x58,0x1a),(0x59,0x9a),(0x5a,0x5a),(0x5b,0xda),(0x5c,0x3a),(0x5d,0xba),(0x5e,0x7a),(0x5f,0xfa),(0x60,0x6),(0x61,0x86),(0x62,0x46),(0x63,0xc6),(0x64,0x26),(0x65,0xa6),(0x66,0x66),(0x67,0xe6),(0x68,0x16),(0x69,0x96),(0x6a,0x56),(0x6b,0xd6),(0x6c,0x36),(0x6d,0xb6),(0x6e,0x76),(0x6f,0xf6),(0x70,0xe),(0x71,0x8e),(0x72,0x4e),(0x73,0xce),(0x74,0x2e),(0x75,0xae),(0x76,0x6e),(0x77,0xee),(0x78,0x1e),(0x79,0x9e),(0x7a,0x5e),(0x7b,0xde),(0x7c,0x3e),(0x7d,0xbe),(0x7e,0x7e),(0x7f,0xfe),(0x80,0x1),(0x81,0x81),(0x82,0x41),(0x83,0xc1),(0x84,0x21),(0x85,0xa1),(0x86,0x61),(0x87,0xe1),(0x88,0x11),(0x89,0x91),(0x8a,0x51),(0x8b,0xd1),(0x8c,0x31),(0x8d,0xb1),(0x8e,0x71),(0x8f,0xf1),(0x90,0x9),(0x91,0x89),(0x92,0x49),(0x93,0xc9),(0x94,0x29),(0x95,0xa9),(0x96,0x69),(0x97,0xe9),(0x98,0x19),(0x99,0x99),(0x9a,0x59),(0x9b,0xd9),(0x9c,0x39),(0x9d,0xb9),(0x9e,0x79),(0x9f,0xf9),(0xa0,0x5),(0xa1,0x85),(0xa2,0x45),(0xa3,0xc5),(0xa4,0x25),(0xa5,0xa5),(0xa6,0x65),(0xa7,0xe5),(0xa8,0x15),(0xa9,0x95),(0xaa,0x55),(0xab,0xd5),(0xac,0x35),(0xad,0xb5),(0xae,0x75),(0xaf,0xf5),(0xb0,0xd),(0xb1,0x8d),(0xb2,0x4d),(0xb3,0xcd),(0xb4,0x2d),(0xb5,0xad),(0xb6,0x6d),(0xb7,0xed),(0xb8,0x1d),(0xb9,0x9d),(0xba,0x5d),(0xbb,0xdd),(0xbc,0x3d),(0xbd,0xbd),(0xbe,0x7d),(0xbf,0xfd),(0xc0,0x3),(0xc1,0x83),(0xc2,0x43),(0xc3,0xc3),(0xc4,0x23),(0xc5,0xa3),(0xc6,0x63),(0xc7,0xe3),(0xc8,0x13),(0xc9,0x93),(0xca,0x53),(0xcb,0xd3),(0xcc,0x33),(0xcd,0xb3),(0xce,0x73),(0xcf,0xf3),(0xd0,0xb),(0xd1,0x8b),(0xd2,0x4b),(0xd3,0xcb),(0xd4,0x2b),(0xd5,0xab),(0xd6,0x6b),(0xd7,0xeb),(0xd8,0x1b),(0xd9,0x9b),(0xda,0x5b),(0xdb,0xdb),(0xdc,0x3b),(0xdd,0xbb),(0xde,0x7b),(0xdf,0xfb),(0xe0,0x7),(0xe1,0x87),(0xe2,0x47),(0xe3,0xc7),(0xe4,0x27),(0xe5,0xa7),(0xe6,0x67),(0xe7,0xe7),(0xe8,0x17),(0xe9,0x97),(0xea,0x57),(0xeb,0xd7),(0xec,0x37),(0xed,0xb7),(0xee,0x77),(0xef,0xf7),(0xf0,0xf),(0xf1,0x8f),(0xf2,0x4f),(0xf3,0xcf),(0xf4,0x2f),(0xf5,0xaf),(0xf6,0x6f),(0xf7,0xef),(0xf8,0x1f),(0xf9,0x9f),(0xfa,0x5f),(0xfb,0xdf),(0xfc,0x3f),(0xfd,0xbf),(0xfe,0x7f),(0xff,0xff),])
rmap = {}
for k, v in bmap.items():
rmap[v] = k
if __name__ == '__main__':
ct = bytes.fromhex('15317A952E8B1A7CE65DFC6235E1434B5D943FE93A104683')
pt = b""
for i in range(0, len(ct), 8):
tpt = tdes_dec(ciphers, bytes(reversed(ct[i:i+8])))
pt += tpt
pt = bytes([rmap[i] for i in pt])
print(pt)