Reversing.kr Writeup(27)

CustomShell

终于是把这个reversing.kr做完了。。。。

➜  file CustomShell 
CustomShell: ELF 32-bit LSB executable, Atmel AVR 8-bit, version 1 (SYSV), statically linked, stripped

AVR架构的单片机程序,去除了符号表

在做题之前先记录一下环境搭建的过程:

两者安装好了之后,运行AVR Studio,直接open我们的CustomShell,然后如下

这里选择AVR Simulator平台,关于设备的选择,首先我们的程序是8位的,所以查阅资料之后选择ATmega128设备,是ATMEL公司的8位系列单片机的最高配置的一款单片机。
大概这样的界面:

然后打开hapsimFile - New Control - Terminal,打开一个新的终端,随后Options - Terminal Settings,勾选Local Echo,然后就可以选择串口,尝试了一下之后,发现程序从USART1输出,这里可以看出需要输入一个用户名和一个密码:

然后需要定位到登录验证的地方,我们先单步简单跟一下,单步到第一个循环:

利用Run to Cursor跳出循环,然后进入到第二个循环:

同理利用Run to Cursor跳出循环,进而执行地址0x61处的一个跳转指令RCALL,该指令跳转到了地址为0x7E5处的子函数sub_7E5执行,然后依次sub_729-sub_6EC

可以看到sub_729中连续调用两次sub_6EC,猜测是用来分别获取用户名和密码的输入,经过调试确实是的,而且其中的两个sub_89A则是打印loginpassword字符串的,
再往下看:

很自然就会想到这个sub_920是一个验证函数,

这里的ld r24,X+以及ld r0,Z+明显的查表操作,调试跟进一下

即是读取我们的输入,然后和0x659地址进行对比,

所以用户名就是revkr12,然后密码,跟进之后发现有点复杂,尤其是在sub_729中调用了sub_20C对输入的密码进行了一大堆操作:

具体sub_20C就不截图了。

这样有点麻烦,重新整理一下思路,我们回到最开始的sub_7E5函数中调用sub_729的地方:

看到调用完了sub_729之后就进行了一个判断,r24-1是否等于0,调试的时候发现r24其实等于0(因为密码输错了),所以我们先不管密码怎么处理的,我们直接下断点,然后手动修改r24=1

然后运行:

登陆成功!

然后简单操作之后发现读出的文件都是乱码,应该是存在某种加密方式,另外在尝试读tmp目录下的readme的时候报错了:

就这个文件说不存在,那我们跟一下看看到底是怎么回事。

单步跟进之后定位到了一个关键函数sub_6EC,它调用了sub_6F8,单步跟进之后了解到sub_6EC的作用就是获取我们输入的命令。

继续往下看:

之前说了sub_920是验证函数,所以这里对我们的输入进行验证,截图这一段函数就是遍历几个命令和我们输入的命令进行对比,

一旦匹配之后,便跳转到loc_860,然后在地址0x871处进行了一个icall间接寻址到寄存器Z所指向的地址,以cat为例,cat最后跳转到了0x1b5

然后调到0x4e5,在这里从0x4f9-0x516进行了cat要读取的文件名的对比:

这个时候在内存中才看到猫腻为啥读不了readme

readme后面有个空格\x20,但是重新尝试之后发现即使输了空格也不行,那就只能修改内存了,把这个\x20改成\x00,发现可以成功读取readme

但是是乱码,跟进之后知道打印的是地址0x582处的字符串,readme的内容为:

但是一路跟下来没发现加密之类的地方,在ida中看到静态也是同样的值:

然后猛然想起来为啥要研究这个readme为啥读不了…..再回到shell,又研究了一番发现其中没有什么有用的信息,所以再整理一下,应该还是需要破解密码。

然后焦点又回到了最开始跳过的sub_20c….

这个函数分成三个部分,关键地址:0xAD2-0XADB,10位由8位的用户输入生成,也是变换之后的最终值存储的地方,
第一部分0x23B-0X302,循环8次,将8位的输入变成10位值,具体算法变成python如下:

def round1(i):
    #print([hex(i) for i in data_ad2])
    global r4
    r12=i<<2
    r12=r12&0xff
    #if i==1:import ipdb;ipdb.set_trace()
    last_round_ad2=data_ad2.copy()
    data_ad2[0]=data_100[0+r4]^last_round_ad2[2]
    r24=data_ae4[i]&0x05

    data_ad2[1]=data_100[0+r12+sub_1DC(r24)]^last_round_ad2[4]    
    r24=data_ae4[i]&0x0a
    r24=lsr(r24)

    data_ad2[2]=data_100[0+r12+sub_1DC(r24)]^last_round_ad2[7]
    r24=data_ae4[i]&0x50
    r24=swap(r24)

    data_ad2[3]=data_100[0+r12+sub_1DC(r24)]^last_round_ad2[3]
    r24=data_ae4[i]&0xa0
    r24=swap(r24)
    r24=lsr(r24)

    data_ad2[4]=data_100[0+r12+sub_1DC(r24)]^last_round_ad2[1]
    r24=data_ae4[i]&0x05

    data_ad2[5]=data_100[0+r12+sub_1DC(r24)]^last_round_ad2[5]
    r24=data_ae4[i]&0x0a
    r24=lsr(r24)

    data_ad2[6]=data_100[0+r12+sub_1DC(r24)]^last_round_ad2[6]
    r24=data_ae4[i]&0x50
    r24=swap(r24)

    data_ad2[7]=data_100[0+r12+sub_1DC(r24)]^last_round_ad2[2]
    r24=data_ae4[i]&0xa0
    r24=swap(r24)
    r24=lsr(r24)

    data_ad2[8]=data_100[0+r12+sub_1DC(r24)]^last_round_ad2[8]
    r24=data_ae4[i]&0x05

    data_ad2[9]=data_100[0+r12+sub_1DC(r24)]^last_round_ad2[3]
    r4+=4

这部分代码比较简单很容易看懂,看懂之后直接回复就行。

第二大部分,0X304-0X3D2,这部分和上一部分类似甚至比上一部分简单,代码如下:

def round2(i):
    #print([hex(i) for i in data_ad2])
    r12=i<<2
    r12=r12&0xff
    #if i==0:import ipdb;ipdb.set_trace()

    last_round_ad2=data_ad2.copy()

    r24=data_ae4[i]&0x42

    data_ad2[0]=data_100[0+r12+sub_1EC(r24)]^last_round_ad2[0]
    r24=data_ae4[i]&0x81

    data_ad2[1]=data_100[0+r12+sub_1EC(r24)]^last_round_ad2[4]
    r24=data_ae4[i]&0x42

    data_ad2[2]=data_100[0+r12+sub_1EC(r24)]^last_round_ad2[1]
    r24=data_ae4[i]&0x24

    data_ad2[3]=data_100[0+r12+sub_1EC(r24)]^last_round_ad2[8]
    r24=data_ae4[i]&0x18

    data_ad2[4]=data_100[0+r12+sub_1EC(r24)]^last_round_ad2[2]
    r24=data_ae4[i]&0x81

    data_ad2[5]=data_100[0+r12+sub_1EC(r24)]^last_round_ad2[5]
    r24=data_ae4[i]&0x42

    data_ad2[6]=data_100[0+r12+sub_1EC(r24)]^last_round_ad2[6]
    r24=data_ae4[i]&0x24

    data_ad2[7]=data_100[0+r12+sub_1EC(r24)]^last_round_ad2[7]
    r24=data_ae4[i]&0x18

    data_ad2[8]=data_100[0+r12+sub_1EC(r24)]^last_round_ad2[3]
    r24=data_ae4[i]&0x81

    data_ad2[9]=data_100[0+r12+sub_1EC(r24)]^last_round_ad2[4]

最后一部分0X3DD-0X3F6,如下:

def round3():
    data_ad2[1] = ror(data_ad2[1],8-getrorindex(all,8,(all & 0xff00) >> 8 )[1])
    data_ad2[2] = ror(data_ad2[2],8-getrorindex(all,7,(all & 0xff00) >> 8 )[1])
    data_ad2[3] = ror(data_ad2[3],8-getrorindex(all,6,(all & 0xff00) >> 8 )[1])
    data_ad2[4] = ror(data_ad2[4],8-getrorindex(all,5,(all & 0xff00) >> 8 )[1])
    data_ad2[5] = ror(data_ad2[5],8-getrorindex(all,4,(all & 0xff00) >> 8 )[1])
    data_ad2[6] = ror(data_ad2[6],8-getrorindex(all,3,(all & 0xff00) >> 8 )[1])
    data_ad2[7] = ror(data_ad2[7],8-getrorindex(all,2,(all & 0xff00) >> 8 )[1])

那一个完整的加密流程如下:


data_ad2=[0]+input+[0] #输入

for i in range(8):
    round1(i)

data_ad2[0]=all&0xff

for i in range(8):
    round2(i)

data_ad2[9]=(all&0xff00)>>8

round3()

正向恢复完了就得考虑逆向或者爆破解决的问题了。
那这里其实分析完算法之后很容易知道每一位是独立计算的,每一位结果只受一位输入的影响,所以可以逐位爆破,但逐位爆破又需要考虑输入ascii码的总和,所以我们爆破分层,先爆破总和,再逐位爆破。

那至于结果的哪一位受输出的哪一位影响,我们可以分析,连个线就行了,我这里举例把方法是说一下就行了:

比如前四位输入,r1-1代表round1的第一轮,根据round1的算法,round1共8轮,第一轮input[1]影响第四位,但是到第二轮之后input[1]影响第1位,这样依次分析,发现round1的输出就是和输入一一对应,再分析round2round3,可以得到最终结果:

input[1] <- result[4]
input[2] <- result[1]
input[3] <- result[3]
input[4] <- result[2]
input[5] <- result[5]
input[6] <- result[6]
input[7] <- result[7]
input[8] <- result[8]

然后就可以开始逐位爆破,
得到结果如下:

这里附上全部的爆破代码:

data_624=[0x01,0x00,0x00,0x02,0x03]
data_ae4=[0x4a,0x18,0xaf,0xf7,0x81,0x6a,0xd7,0x3a]
data_100=[0x4A,0x16,0x71,0x2C,0x11,0xBB,0xAF,0x1E,0xB8,0x9F,0x68,0xD3,0x37,0xCD,0x55,0x1B,0xB7,0xA8,0x02,0xBD,0x0B,0xFF,0xEE,0x8E,0x30,0xC9,0xD7,0x12,0xE8,0x60,0x0A,0x4B,0x01,0x00,0x00,0x00,0x00,0x00,0x14,0x00,0x58,0x05,0x15,0x00,0x66,0x05,0x16,0x00,0x73,0x05,0x1F,0x00,0x82,0x05,0x28,0x00,0x10,0x06,0x29,0x00,0x10,0x06,0x2A,0x00,0x10,0x06,0x33,0x00,0x91,0x05,0x08,0x00,0x01,0x00,0x01,0x05,0x00,0x00,0x00,0x00,0x2F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0A,0x01,0x01,0x05,0x00,0x00,0x00,0x00,0x65,0x74,0x63,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0x01,0x01,0x05,0x00,0x00,0x00,0x00,0x74,0x6D,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0x01,0x01,0x05,0x00,0x00,0x00,0x00,0x62,0x69,0x6E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0D,0x01,0x01,0x05,0x00,0x00,0x00,0x00,0x76,0x61,0x72,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00]
r4=0
def go(data_ad2,all,guess,index):

    global r4
    r4=0
    def swap(val):
        hexray="{:0>2}".format(hex(val)[2:])
        tmp1=hexray[0]
        tmp2=hexray[1]
        return int("0x"+tmp2+tmp1,16)

    def lsr(val):
        return val>>1

    def sub_1DC(val):
        if val<=0:
            return 0
        if val-1<5:
            return data_624[val-1]
        else:
            return 0

    def sub_1EC(val):
        if val==0x18: 
            return 3
        elif val<0x18:
            if val==4:#4
                return 1
            elif val<4:
                if 1<=val<=2:#12
                    return 1
                else:#03
                    return 0
            elif val>4:
                if val==8:
                    return 1
                else:
                    if val==0x10:
                        return 2
                    else:
                        return 0
        elif val>0x18:
            if val==0x40:
                return 3
            elif val<0x40:
                if val==0x20:
                    return 2
                elif val==0x24:
                    return 3
                else:
                    return 0
            elif val>0x40:
                if val==0x80:
                    return 2
                elif val==0x81:
                    return 3
                elif val==0x42:
                    return 3
                else:
                    return 0

    def round1(i):
        #print([hex(i) for i in data_ad2])
        global r4
        r12=i<<2
        r12=r12&0xff
        #if i==1:import ipdb;ipdb.set_trace()
        last_round_ad2=data_ad2.copy()
        data_ad2[0]=data_100[0+r4]^last_round_ad2[2]
        r24=data_ae4[i]&0x05

        data_ad2[1]=data_100[0+r12+sub_1DC(r24)]^last_round_ad2[4]    
        r24=data_ae4[i]&0x0a
        r24=lsr(r24)

        data_ad2[2]=data_100[0+r12+sub_1DC(r24)]^last_round_ad2[7]
        r24=data_ae4[i]&0x50
        r24=swap(r24)

        data_ad2[3]=data_100[0+r12+sub_1DC(r24)]^last_round_ad2[3]
        r24=data_ae4[i]&0xa0
        r24=swap(r24)
        r24=lsr(r24)

        data_ad2[4]=data_100[0+r12+sub_1DC(r24)]^last_round_ad2[1]
        r24=data_ae4[i]&0x05

        data_ad2[5]=data_100[0+r12+sub_1DC(r24)]^last_round_ad2[5]
        r24=data_ae4[i]&0x0a
        r24=lsr(r24)

        data_ad2[6]=data_100[0+r12+sub_1DC(r24)]^last_round_ad2[6]
        r24=data_ae4[i]&0x50
        r24=swap(r24)

        data_ad2[7]=data_100[0+r12+sub_1DC(r24)]^last_round_ad2[2]
        r24=data_ae4[i]&0xa0
        r24=swap(r24)
        r24=lsr(r24)

        data_ad2[8]=data_100[0+r12+sub_1DC(r24)]^last_round_ad2[8]
        r24=data_ae4[i]&0x05

        data_ad2[9]=data_100[0+r12+sub_1DC(r24)]^last_round_ad2[3]
        r4+=4



    def round2(i):
        #print([hex(i) for i in data_ad2])
        r12=i<<2
        r12=r12&0xff
        #if i==0:import ipdb;ipdb.set_trace()

        last_round_ad2=data_ad2.copy()

        r24=data_ae4[i]&0x42

        data_ad2[0]=data_100[0+r12+sub_1EC(r24)]^last_round_ad2[0]
        r24=data_ae4[i]&0x81

        data_ad2[1]=data_100[0+r12+sub_1EC(r24)]^last_round_ad2[4]
        r24=data_ae4[i]&0x42

        data_ad2[2]=data_100[0+r12+sub_1EC(r24)]^last_round_ad2[1]
        r24=data_ae4[i]&0x24

        data_ad2[3]=data_100[0+r12+sub_1EC(r24)]^last_round_ad2[8]
        r24=data_ae4[i]&0x18

        data_ad2[4]=data_100[0+r12+sub_1EC(r24)]^last_round_ad2[2]
        r24=data_ae4[i]&0x81

        data_ad2[5]=data_100[0+r12+sub_1EC(r24)]^last_round_ad2[5]
        r24=data_ae4[i]&0x42

        data_ad2[6]=data_100[0+r12+sub_1EC(r24)]^last_round_ad2[6]
        r24=data_ae4[i]&0x24

        data_ad2[7]=data_100[0+r12+sub_1EC(r24)]^last_round_ad2[7]
        r24=data_ae4[i]&0x18

        data_ad2[8]=data_100[0+r12+sub_1EC(r24)]^last_round_ad2[3]
        r24=data_ae4[i]&0x81

        data_ad2[9]=data_100[0+r12+sub_1EC(r24)]^last_round_ad2[4]

    def ror(a,b):
        return ((a>>b) ^ (a<<(8-b)))&0xff

    def getrorindex(r24,r22,r9):
        carry = 0
        r25 = r9
        r26 = 0
        r23 = 0
        r27 = 0
        i = 0x11
        while(1):
            r24 = (r24 << 1) + carry
            carry = (r24 & 0x100) >> 8
            r24 &= 0xff

            r25 = (r25 << 1) + carry
            carry = (r25 & 0x100) >> 8
            r25 &= 0xff
            i -= 1
            if i == 0:
                break
            r26 = (r26 << 1) + carry
            carry = (r26 & 0x100) >> 8
            r26 &= 0xff

            r27 = (r27 << 1) + carry
            carry = (r27 & 0x100) >> 8
            r27 &= 0xff

            tmp = r26 - r22
            carry = (tmp & 0x100) >> 8
            tmp &= 0xff

            tmp = r27 - r23 - carry
            carry = (tmp & 0x100) >> 8
            tmp &= 0xff

            if carry == 0:
                r26 = (r26 - r22) & 0xff
        r24 = (0xff-r24)&0xff
        r25 = (0xff-r25)&0xff
        return (r24,r26)

    def round3():
        data_ad2[1] = ror(data_ad2[1],8-getrorindex(all,8,(all & 0xff00) >> 8 )[1])
        data_ad2[2] = ror(data_ad2[2],8-getrorindex(all,7,(all & 0xff00) >> 8 )[1])
        data_ad2[3] = ror(data_ad2[3],8-getrorindex(all,6,(all & 0xff00) >> 8 )[1])
        data_ad2[4] = ror(data_ad2[4],8-getrorindex(all,5,(all & 0xff00) >> 8 )[1])
        data_ad2[5] = ror(data_ad2[5],8-getrorindex(all,4,(all & 0xff00) >> 8 )[1])
        data_ad2[6] = ror(data_ad2[6],8-getrorindex(all,3,(all & 0xff00) >> 8 )[1])
        data_ad2[7] = ror(data_ad2[7],8-getrorindex(all,2,(all & 0xff00) >> 8 )[1])


    for i in range(8):
        round1(i)

    data_ad2[0]=all&0xff

    for i in range(8):
        round2(i)

    data_ad2[9]=(all&0xff00)>>8

    round3()

    print([hex(i) for i in data_ad2])
    if data_ad2[index]==guess:
        return True
    else:
        return False

all_ans=[]
for all in range(0x200,0x300):
    ans=[]
    #result=[0x9a,0x7d,0x72,0x57,0xd5,0x78,0x49,0xe6,0xf2,0x02]

    for i in range(0x7f):
        data_ad2=[0]+[i]*8+[0]
        if go(data_ad2,all,0xd5,4):
            ans.append(i)
            break
    if len(ans)!=1: continue

    for i in range(0x7f):
        data_ad2=[0]+[i]*8+[0]
        if go(data_ad2,all,0x7d,1):
            ans.append(i)
            break
        else: continue
    if len(ans)!=2: continue

    for i in range(0x7f):
        data_ad2=[0]+[i]*8+[0]
        if go(data_ad2,all,0x57,3):
            ans.append(i)
            break
        else: continue
    if len(ans)!=3: continue

    for i in range(0x7f):
        data_ad2=[0]+[i]*8+[0]
        if go(data_ad2,all,0x72,2):
            ans.append(i)
            break
        else: continue
    if len(ans)!=4: continue

    for i in range(0x7f):
        data_ad2=[0]+[i]*8+[0]
        if go(data_ad2,all,0x78,5):
            ans.append(i)
            break
        else: continue
    if len(ans)!=5: continue

    for i in range(0x7f):
        data_ad2=[0]+[i]*8+[0]
        if go(data_ad2,all,0x49,6):
            ans.append(i)
            break
        else: continue
    if len(ans)!=6: continue

    for i in range(0x7f):
        data_ad2=[0]+[i]*8+[0]
        if go(data_ad2,all,0xe6,7):
            ans.append(i)
            break
        else: continue
    if len(ans)!=7: continue

    for i in range(0x7f):
        data_ad2=[0]+[i]*8+[0]
        if go(data_ad2,all,0xf2,8):
            ans.append(i)
            break
        else: continue
    if len(ans)!=8: continue
    tmp=0
    for i in ans:
        tmp+=i
    if tmp != all:
        continue
    print('=============')
    all_ans.append("".join(chr(i) for i in ans))

print(all_ans)

但是提交发现这并不是flag。。有点迷茫,结果登录进去之后发现文件都可以读了不是乱码了,直接读/tmp/readme就拿到flag:

具体读的方式开始已经说了需要下断点修改一下内存,这里不赘述了,具体的怎么加密的我就没有进行分析了。。。到此为止。