DASCTF八月-RE&Crypto

本文分享了作者在LittleJunk RE挑战中的破解经历,涉及Python编码解谜、APK逆向分析、Tea加密解密、easymath和rsa挑战的解法。通过实际操作和学习过程,深入探讨了基础技巧和策略,包括代码逆编、Android逆向、密码学原理应用。
摘要由CSDN通过智能技术生成


littlejunk太痛苦了,在学长的精心提示下还搞了一天半,小丑就是我自己了,ida欺负我就算了,dev也给我卡bug。

RE

1、py签到

经典python打包的exe程序,pyinstxtractor解包后,对名称为py的无后缀文件修复文件头,再用uncompyle6直接反编译出源码。

def encode(s):
    str = ''
    for i in range(len(s)):
        res = ord(s[i]) ^ 32
        res += 31
        str += chr(res)

    return str


m = 'ek`fz13b3c5e047b`bd`0/c268e600e7c5d1`|'
strings = ''
strings = input('Input:')
if encode(strings) == m:
    print 'Correct!'
else:
    print 'Try again!'
# okay decompiling py.pyc

简单逆运算即可

m='ek`fz13b3c5e047b`bd`0/c268e600e7c5d1`|'
for i in range(len(m)):
  print(chr((ord(m[i])-31)^32),end='')

2、apkre

Android逆向题,自己目前就会常见的思路,拿到apk文件,直接解压,找到class.dex文件,用d2j-dex2jar.bat把dex反编译为jar文件,之后用jd-gui查看源码。 Android killer有点慢…
在这里插入图片描述native层的逆向,主要就是一个mycheck函数来进行检验。
去lib文件夹内找到x86下的so文件直接拖入ida,在函数窗口搜索mycheck就能定位。
在这里插入图片描述
在对s盒初始化的时候有很多sse指令,之后可以学一学,但当时看到类似rc4,之后又秘钥有明文直接套个脚本就出了。不过在线网站有点香,手速题。
在这里插入图片描述

总结

Android逆向现在一知半解,还要了解一下结构和常见逆向思路什么的,对于这题调试一下出流秘钥那就更快了,目前我不是太会。

3、littlejunk

①动静态分析

比赛期间没做出来,单纯认为是个Tea加密,最后key还搞错了,不过这个题还是有很多坑的,搞了1天半才摸索出。
在这里插入图片描述首先在main函数里面就有俩花指令,比较常见的一种类型,因为是跳转到一个标号+1,所以标号处的第一个字节始终不起作用,所以可以nop掉。

接着发现一个比较可疑的地方,有ctf的字样,并且函数1对一个地址处的字节做了异或操作。
在这里插入图片描述
函数1
在这里插入图片描述
根据做题经验,这是通过异或掩盖了函数的内容,只有动调到这里才能看到函数原本的面貌。听学长说这是一种smc,自修改技术,没怎么了解,别骂了,太懒了。

所以我们要动调到这,让他自己异或,看一看异或后的函数内容。
在这里插入图片描述当然,直接调的话会有个system函数,就认为他是一种比较low的反调试吧,下个断点,修改eip为箭头指向的地方,或者其他语句的开始即可,可别乱改写中间,ida会崩的。
在这里插入图片描述
当然,在要观察的函数附近下个断点,绕过system就直接F9过去。

通过调试发现,这个ipAddress就是指向loc_14000函数的第一个字节,正如猜测他是修改的下面的函数。
修改前: 啥也不是,不要乱nop。在这里插入图片描述F8单步以后,进去把数据按C转代码,再p重新改一下这个函数。
在这里插入图片描述
非代码段里面放了代码,第一次见挺吓人的。

这里要注意,这个函数里面没有一点花指令,全是有用代码,可能不能直接c,或者c不动的话,先按u进行分析,或者d转数据之后就可以按c了!!!
一开始我就是在这里面不能c的都nop了,结果越看越迷。
在这里插入图片描述
刚开始弄完是这样,有点奇怪,后面会解决,先单步跟进来看看是什么情况。

点进去第一个和第二个函数看一看,里面是print 让你输入flag,接着就是输入flag,并且flag必须是38位,然后格式flag{}.
在这里插入图片描述
用python构造出一个flag即可,也方便跟踪和计算的。
flag{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} //根据个人喜好构造flag
在这里插入图片描述
下一个函数跟进后,又是一个自解密的函数,结构这么怪就与这里有关。
单步完这个自修改函数后,进入第四个函数,里面蛮花的,汇编窗口按照u+c整理整理。
在这里插入图片描述
整理后:注意重新定义一下函数p,没有花指令
在这里插入图片描述一个类似RC4的函数,但最后的算法有一丢丢怪。

接着来到外面,F5一下,出现了一个爆红的指令,是自修改后出现的部分,汇编窗口table跟进,继续整理。

在这里插入图片描述
主要是在sub_14000函数标号的地方,u或d一下之后再p一下,直到出现上图的情况算是正常,再提示一遍,这里没有花指令!!!
在这里插入图片描述
这是1400函数,我改了改函数名,就是先输入flag然后进行了一个类似rc4的加密,反正是异或只要调出key就可以了。

直接F8单步,看一下rc4 return的全局变量,这里还有一个坑!!!已经不知道是第几个了。
在这里插入图片描述进去一看很懵逼,处理的字符串变成了一个,注意下面有个align100,按一下u。
在这里插入图片描述
一共32个,每个取第一个字节,这里5其实是高位的,但是那个转大端序的函数把5放到了第四个地址也就是变成了低字节。这里获得的32个数据是key[i] ^ord(‘a’),在异或一下ord(‘a’)就拿到了rc4的key。

接着是最后一个函数,跟进去也是乱的,这里又有两处花指令,和main函数处的原理一样。
在这里插入图片描述不在多做解释,自己更改即可,注意修改后重新定义一下函数。
在这里插入图片描述Tea加密,进化版,可知v13和v11是一组,v12和v14是一组,delta没有改是0x9E3779B9,但是原本的Tea加密是明文两个4字节,秘钥4个四字节。这个是明文两个8字节,并且写在了一个循环里,秘钥4个8字节,我愿称之为巨型Tea。

在这里插入图片描述
动调到a2这里,能看到a2的地址,0x12ffe20,在hex窗口G到这个地址。

在这里插入图片描述
为了证明ida的障眼法,手机拍下了证据,真正的秘钥key是从0x12ffe20开始的4个8字节,注意小端序要转成正确顺序,从内存中提取出的key才是真正的key。

0x54466076484c5476 , 0x4550504f765f4344 , 0x5a796f755f6d6179 , 0x5f6e6565645f7468
在这里插入图片描述
这是密文,也是最终的check,不过他这个256bit的Tea加密最终的比较是第一个数是低位第二个数是高位,两两一组就能拼出最中加密后的4个值,之后再两两一组进行解密即可。

②写脚本的辛酸经历

#include<iostream>
#include<iomanip>
#include<cstring>
using namespace std;
void tea_decode(unsigned __int64*s,unsigned __int64 *key);
int main(){

	unsigned __int64  m[2]={0x2FDE61CCEFC70FF8,0x56BC19E119C8B07B};
	//第一组:0xE990A522BE80F786  
	
	unsigned __int64 key[4]={0x54466076484c5476 , 0x4550504f765f4344 , 0x5a796f755f6d6179 , 0x5f6e6565645f7468};
    tea_decode(m,key);
    
	unsigned long long a=0x9e3779b9L * 0x20L;//测试 
	
	cout<<hex<<a<<endl; //测试 
	return 0;
} 

void tea_decode(unsigned __int64 *s,unsigned __int64 *key){ 
	unsigned __int64 v0=s[0];
    unsigned __int64 v1=s[1];
	unsigned __int64 sum=0x13c6ef3720;  //  写的0x9e3779b9*0x20 devc只给我保留低四个字节 
	unsigned __int64 defalt=0x9e3779b9;
	unsigned __int64  k0=key[0],k1=key[1],k2=key[2],k3=key[3];
	for(int i=0;i<0x20;i++){  
		v1-=((v0<<4)+k2)^(v0+sum)^((v0>>5)+k3);
		v0-=((v1<<4)+k0)^(v1+sum)^((v1>>5)+k1); 
		sum-=defalt;
	}
	s[0]=v0;
	s[1]=v1;
    cout<<v0<<","<<v1<<endl; 
} 

注意:Tea解密时的sum直接写算出来的结果0x13c6ef3720,不要写0x9e3779b9*0x20,如果第二种写法则结果只保留低32bit,即便你开辟了8字节的大小,还是会有这个坑。
在这里插入图片描述
Tea解密之后流密码解密

from Crypto.Util.number import *
#print('flag{'+'a'*32+'}')
a=[ 0x05, 0x00, 0x00, 0x00, 0x5A, 0x00, 0x00, 0x00, 0x18, 0x00,
  0x00, 0x00, 0xC6, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00,
  0x4F, 0x00, 0x00, 0x00, 0xCE, 0x00, 0x00, 0x00, 0x83, 0x00,
  0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0xAC, 0x00, 0x00, 0x00,
  0xF3, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x00, 0x00, 0x7F, 0x00,
  0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
  0x05, 0x00, 0x00, 0x00, 0x6C, 0x00, 0x00, 0x00, 0x60, 0x00,
  0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0xD3, 0x00, 0x00, 0x00,
  0x1F, 0x00, 0x00, 0x00, 0xA5, 0x00, 0x00, 0x00, 0xFA, 0x00,
  0x00, 0x00, 0xEB, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00,
  0x63, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x0F, 0x00,
  0x00, 0x00, 0xF6, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
  0x42, 0x00, 0x00, 0x00, 0xA5, 0x00, 0x00, 0x00]
key=[]
for i in range(len(a)//4):
    key.append(a[4*i]^97)
#dump出了 rc4的key

#tea解密后
cc1=[5790024677284421248,265414871944557825,3760601914643164344,675928458074199027]
temp=int.to_bytes(cc1[0],8,byteorder='big') #已经帮我们小端转大端了,不许要我们自行转换了
temp+=int.to_bytes(cc1[1],8,byteorder='big')
temp+=int.to_bytes(cc1[2],8,byteorder='big')
temp+=int.to_bytes(cc1[3],8,byteorder='big')

for i in range(len(temp)):
    print(chr(temp[i]^key[i]),end='')

#flag{4a5c0f1b5cc7f60e918611721c87ba07}

在这里插入图片描述
反观这个rc4,其实是做了手脚的,最后本应该算出的v4,原版应该是s[v4]^m[i] 但题中确是m[i]^s[i],中间那三步还是个交换函数,如下python示例。

def fun(a,b):
    a = b & ~a | a & ~b
    b = b & ~a | a & ~b
    a = b & ~a | a & ~b
    print(a,b)

fun(3,4) -> 4 3

当然对于流密码来说,动调能拿到流秘钥后便能快速进行解密。

总结:

其实复现一遍来看,并不是那么复杂,花指令就4处,2处自解密算法,最后跟了一个rc4和一个巨型TEA,其实一开始做题的时候没这么清晰,直接去看了字符串窗口,也能看到很多信息,可以在那些地方下断点什么,当然一道送分题扣了那么久还真是蛮好笑的,但也学到了很多细节问题,比如符号处理,devc的bug,自解密等,拿我的idol的话来说,别总想着套,多去了解,10min出才是基础扎实。haha,慢慢爬叭!

Crypto

整体难度不大,本期的主题就是赛题复现么,不过也挺有意思的

1、easymath

原题,谷歌能直接搜出来,之前校赛也搞过,主要还是求逆元。

assert(len(open('flag.txt', 'rb').read()) < 50)
assert(str(int.from_bytes(open('flag.txt', 'rb').read(), byteorder='big') << 10000).endswith(
    '1862790884563160582365888530869690397667546628710795031544304378154769559410473276482265448754388655981091313419549689169381115573539422545933044902527020209259938095466283008'))

endwith函数是判断是否以指定的字符串结尾,即尾部为括号内的内容,是175位数。

c = (flag<<10000)%(10^175)
左移10000也就是乘 2**10000次方 原式  c = flag*(2**10000) % 10^175 如果2**10000在10^175下有逆元
就直接能解出flag,但2**10000 和 10^175不是互素的,但和5^175互素,而10^175是5^175的倍数,所以
(c%(10^175)) 再%5^175 和 直接c%(5^175)的值是一样的。

直接求个逆元,变能拿到flag

import gmpy2
from Crypto.Util.number import *
d=gmpy2.invert(2**10000,5**175)
c=...
f=long_to_bytes(c*d%(5**175))
print(f)

2、let’s play with rsa~

from sympy import isprime,nextprime
from Crypto.Util.number import getPrime as getprime ,long_to_bytes,bytes_to_long,inverse
flag='flag{***************}'

def play():
    p=getprime(1024)
    q=getprime(1024)

    n=p*q
    e=65537

    print "Hello,let's play rsa~\n"
    print 'Now,I make some numbers,wait a second\n'
    n1=getprime(200)
    n2=getprime(200)
    number=n1*n2
    print "Ok,i will send two numbers to you,one of them was encoded.\n"
    print "Encode n1:%d,\n"%(pow(n1,e,n))
    print "And n2:%d.\n"%n2

    print "Information that can now be made public:the public key (n,e):(%d,%d)\n"%(n,e)
    while True:
        try:
            c=int(raw_input("ok,now,tell me the value of the number (encode it for safe):"))
        except:
            print "Sorry,the input is illeagal, and the integer is accept~"
        else:
            break
    d=inverse(e,(p-1)*(q-1))
    m=pow(c,d,n)
    if m==number:
        print "It's easy and interesting,didn't it?\n"
        print "This is the gift for you :"+flag
    else:
        print "Emmmmm,there is something wrong, bye~\n"

if __name__ == '__main__':
    play()

芜湖,正好三星的ctf的教程里面就有关这个的攻击,大致流程是e和n给你了,然后他自己生产了n1,n2,number=n1*n2,我们拿到了(pow(n1,e,n)) 和 n2,之后让我们转入一个数,并且他进行解密m=pow(c,d,n)后和number进行check。

 ((n1^e)%n * (n2^e)%n)%n =(n1*n2)^e %n = number^e %n
 #就是带模运算 乘法的那个式子,(n1^e)%n n2已经知 直接构造求即可
 print((pow(n2,e,n)*n1)%n)
 nc 连上 传入即得到flag

总结

有些时候,服务器可能会限制我们的输入,比如传入一个hello,里面是个rsa的加密机制,并且会自动加密和自动解密,他要求我们传入的密文解密后加入等于hello,他就会给我们flag。

但是我们直接传入hello进行加密的时候呢,他又进制了这个输入,这时候我们可以把hello,转为数值,进行因式分解,即m1*m2==m,然后我们再传入m1和m2,这个体系就会给我们返还pow(m1,e,n)和pow(m2,e,n),之后将两者的乘积再%n,这样就构造出了hello的密文,之后传入便能拿到flag。

3、ezRSA

from secret import flag
from Crypto.Util.number import *
from random import getrandbits
from hashlib import sha256


class EzRsa:
    def __init__(self):
        self.E = 0x10001
        self.P = getPrime(1024)
        self.Q = getPrime(1024)
        while GCD((self.P-1)*(self.Q-1), self.E) != 1:
            self.Q = getPrime(1024)
        self.N = self.P*self.Q

    def encrypt(self):
        f = getrandbits(32)
        c = pow(f, self.E, self.N)
        return (f, c)

    def encrypt_flag(self, flag):
        f = bytes_to_long(flag)
        c = pow(f, self.E, self.N)
        return c


def proof():
    seed = getrandbits(32)
    print(seed)
    sha = sha256(str(seed).encode()).hexdigest()
    print(f"sha256({seed>>18}...).hexdigest() = {sha}")
    sha_i = input("plz enter seed: ")
    if sha256(sha_i.encode()).hexdigest() != sha:
        exit(0)


if __name__ == "__main__":
    proof()
    print("welcome to EzRsa")
    print("""
    1. Get flag
    2. Encrypt
    3. Insert
    4. Exit
    """)
    A = EzRsa()
    coin = 5
    while coin > 0:
        choose = input("> ")
        if choose == "1":
            print(
                f"pow(flag,e,n) = {A.encrypt_flag(flag)}\ne = 0x10001")
            exit(0)
        elif choose == "2":
            f, c = A.encrypt()
            print(f"plain = {f}\ncipher = {c}")
            coin -= 1
        elif choose == "3":
            q = getrandbits(1024)
            n = A.P*q
            f = getrandbits(32)
            c = pow(f, 0x10001, n)
            print(f"plain = {f}\ncipher = {c}")
            coin -= 1
        elif choose == "4":
            print("bye~")
        else:
            print("wrong input")
    print("Now you get the flag right?")

第一个sha256的check有点离谱,seed都给了,直接复制粘贴一遍便过了。
coin初始为5,每次选择会-1,最多能循环5次。
选1的话,是拿到flag的密文,但选了1就会退出。
选2的话是给你明文和加密后的密文,e的话是0x10001
选3的话,也是给一组明文和密文,e是0x10001不过n和2的不一样,这里的n是取出的P乘上一个随机数。
…谁没事会选4呢???

根据2,由 m^e=c+k*n (m^e -c =k*n) 拿到两组2的话 就能通过求公因数泄露k*n

同理,根据 m^e -c =k*P*q 取公因数可以拿到 k*q

注意,这里一般都是 k*n 或者 k*q k可以在小范围内爆破,毕竟明文比较短,n比较大

所以我们只需取 2组 2来泄露n,取两组3来泄露P就能分解n,进而正常解密rsa了

解密脚本

import gmpy2
from Crypto.Util.number import *
from random import getrandbits
e= 0x10001
p1=1751297511
c1=

p2=3438100521
c2=
#以上数据用于泄露n  连续取2次 2

p3=1584259449
c3=

p4=3829043104
c4=
#泄露p 连续取两次3

enc=

n=gmpy2.gcd(pow(p1,e)-c1,pow(p2,e)-c2) #可能k*n的k也有公因数 可以小范围的缩一下

#for i  in range(2,100):
#    if n%i==0:
#        print(i)
n=n//22  #i求出来是22

p=gmpy2.gcd(pow(p3,e)-c3,pow(p4,e)-c4) #因为3 的q是随机的 所以公因数为k*q
#for i in range(2,100):
#    if p%i==0:
#        print(i)
p=p//3
#if n%p==0:
#    print('yes')
q=n//p
d=gmpy2.invert(e,(p-1)*(q-1))
print(long_to_bytes(pow(enc,d,n)))

漏洞还是比较明显的,主要是gcd预算,n和p在小范围内跑一下有没有k即可。

总结

idol:10min 做不出来就是基础不行,确实都是送分题。
hh对我还挺难受的,对于littlejunk,确实是基础不牢,做题经验也不行,老是采坑,Android逆向也了解不深,rsa有关线代的一些知识还不了解,真是tcl,还要继续加油。
总之,热爱的话就一起爬叭~

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值