CustomShell
终于是把这个reversing.kr做完了。。。。
➜ file CustomShell
CustomShell: ELF 32-bit LSB executable, Atmel AVR 8-bit, version 1 (SYSV), statically linked, stripped
AVR架构的单片机程序,去除了符号表
在做题之前先记录一下环境搭建的过程:
- AVR Studio: https://www.microchip.com/mplab/avr-support/avr-and-sam-downloads-archive
- hapsim: http://www.helmix.at/hapsim/#hapsimdownload
两者安装好了之后,运行AVR Studio
,直接open我们的CustomShell
,然后如下
这里选择AVR Simulator
平台,关于设备的选择,首先我们的程序是8位的,所以查阅资料之后选择ATmega128
设备,是ATMEL公司的8位系列单片机的最高配置的一款单片机。
大概这样的界面:
然后打开hapsim
,File - 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
则是打印login
和password
字符串的,
再往下看:
很自然就会想到这个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
的输出就是和输入一一对应,再分析round2
和round3
,可以得到最终结果:
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:
具体读的方式开始已经说了需要下断点修改一下内存,这里不赘述了,具体的怎么加密的我就没有进行分析了。。。到此为止。