内容简介:正常的逆向(大雾)好像就只有两个。。另外还有一个从题目就可以看出来这是个go语言的题目,打开一看是去符号表的逆向,IDA里面通过脚本恢复符号表开始看代码
正常的逆向(大雾)好像就只有两个。。另外还有一个 apk
、一个 wasm
、还有一个 MBR
。。。这三个后续慢慢来补,需要的知识可能有点多,先把两个相对简单的题写一下。
easygo
从题目就可以看出来这是个 go 语言的题目,打开一看是去符号表的逆向,IDA里面通过脚本恢复符号表开始看代码
https://github.com/sibears/IDAGolangHelper
跟到 main.main
看到关键的比较函数:
gdb跟一下,在 0x495318
地址处下断点即可获得明文的flag:
bbvvmm
一道让人头疼的题目,根据题目名称大概猜到是一道vm的题目,也就是在前一天刚刚正在研究 reversing.kr
上的另一道vm题目,有点头大。先说说这个题目把。
拖进ida里面,先分析一下:
整个程序我们分为两部分来看,一部分校验用户名,结果存储在 v5
中,一部分校验密码,结果存储在 *(ptr+25)
即 v8
里面。最后两者检验均通过之后链接上服务器就能打印 flag
。
先看对用户名的验证,简单跟一下我们的输入经过了这么些函数 sub_4066C0(encode_hex) -> sub_4018C4(sm4_en) -> sub_4067BD(encode_hex) -> sub_400AA6(base64) -> strcmp
首先 sub_4066C0
将我们的输入进行了 encode('hex')
操作,然后使用sm4加密算法进行加密,而密钥就是中间一长串赋值操作,从 v17-v32
,共16字节的秘钥,然后加密之后进入了一个魔改的 base64
函数,方式没变,替换了原本 base64
的编码表。然后和 RVYtG85NQ9OPHU4uQ8AuFM+MHVVrFMJMR8FuF8WJQ8Y=
进行比较,那我们就可以逆着来就能得到用户名,脚本如下:
import binascii base64_charset = 'IJLMNOPKABDEFGHCQRTUVWXSYZbcdefa45789+/6ghjklmnioprstuvqwxz0123y' def b64_decode(base64_str): base64_bytes = ['{:0>6}'.format(str(bin(base64_charset.index(s))).replace('0b', '')) for s in base64_str if s != '='] resp = bytearray() nums = len(base64_bytes) // 4 remain = len(base64_bytes) % 4 integral_part = base64_bytes[0:4 * nums] while integral_part: tmp_unit = ''.join(integral_part[0:4]) tmp_unit = [int(tmp_unit[x: x + 8], 2) for x in [0, 8, 16]] for i in tmp_unit: resp.append(i) integral_part = integral_part[4:] if remain: remain_part = ''.join(base64_bytes[nums * 4:]) tmp_unit = [int(remain_part[i * 8:(i + 1) * 8], 2) for i in range(remain - 1)] for i in tmp_unit: resp.append(i) return resp from sm4 import encrypt, decrypt from Crypto.Util.number import bytes_to_long, long_to_bytes mk=0xda98f1da312ab753a5703a0bfd290dd6 data=int("0x"+b64_decode('RVYtG85NQ9OPHU4uQ8AuFM+MHVVrFMJMR8FuF8WJQ8Y=').decode('utf-8'),16) print long_to_bytes(decrypt(data,mk)).decode('hex')
其中的sm4就是从 https://github.com/yang3yen/pysm4
这里随便拿的一个sm4.py脚本。
得到用户名为 badrer12
。
用户名的逆向相对简单,但是密码的逆向就麻烦了。main函数调用了 sub_405B25
,初始化了一个 handler
,这里一个byte对应一个函数
然后 sub_406607
开始扫描执行,执行的就是从 0x6090e0
开始的这部分代码:
而在这些函数中势必有读取我们输入的密码,
所以需要开始分析虚拟机指令对应的函数。不过学弟他们是用angr一会儿就能跑出来。另外想了下似乎可以patch掉 system('exit')
,然后让它返回到输入来,这样就是本地70亿次的爆破,似乎是可行的。
那既然是复现还是分析下虚拟机。
一下午加一晚上过去了,我分析虚拟机回来了。。。。
分析的脑壳都大了,就是得耐着性子挨个指令看,vm的题做的比较少,所以这道题多花了心血认真分析了。几个比较核心的点,首先 0x609240
处存储目前的栈顶指针, 0x60a4f0
则是直接的栈底指针,核心的handler的结构体 0x60a010
,结果存储在 *(0x60a010+0x24)+0x64
的地方,也就是最后验证的结果。
整个逻辑分析下来很简单,就是六位输入满足 x1^0x78+x2^0x79+x3^0x7a+x4^0x7b+x5^0x7c+x6^0x7d
即可,所以 x1-x6
分别为 0x78-0x7d
即可,所以输入为 xyz{|}
贴一下笔记吧:
//part 0 B0 19 00 00 00 B5 0A B2 0B B4 09 B0 1A 00 00 00 B5 0A 04 0B 09 B0 1A 00 00 00 B5 0A B2 0B B4 09 90 C2 00 00 00 91 //jmp part 2 //part 1 01 1A 00 00 00 0A // a1+0x28 = 0x1A 02 09 00 //*(a1)=*(*(a1+0x24)+0x68) 10 09 30 00 00 00 01 // judge(*a1+0x24)!=0 ? *(a1+4)=0x0060b5c0 : *(a1+4)=0x0060b530 B2 01 //*a1+4 入栈 B2 00 //*a1 入栈 C0 //judge(*a1)!=0,*a1=4**a1+*a1+4 : *a1+=*a1+4 B5 00 //*a1 <- 栈顶 B0 F4 FF FF FF // -c 入栈 B5 0A //a1+0x28= -c B1 00 // password逐个取到栈顶 B5 01 // a1+0x4=password 01 1A 00 00 00 0A // a1+0x28 = 0x1A B1 09 // *[*(a1+0x24)+0x68] 入栈 B5 00 //*a1 <- 栈顶 10 00 78 00 00 00 00 //judge(*a1)!=0? *a1+=4*0x78:*a1+=0x78; 70 00 FF 00 00 00 00 //*a1=*a1 & 0xff 50 00 18 00 00 00 00 //*a1=*a1 << 0x18 B2 00 // *a1 入栈 B0 18 00 00 00 // 0x18 入栈 C8 // *a1 >> 0x18 B5 00 // *a1 <- 栈顶 B2 01 // *a1+0x4 入栈(password) B2 00 // *a1 入栈 C3 // *a1 ^ *a1+0x4 B5 00 // *a1 <- 栈顶 50 00 18 00 00 00 00 // *a1=*a1 << 0x18 B2 00 // *a1 入栈 B0 18 00 00 00 // 0x18 入栈 C8 // *a1 >> 0x18 B5 00 // *a1 <- 栈顶 70 00 FF 00 00 00 01 //*a1+4=*a1 & 0xff 01 19 00 00 00 0A //*a1+0x28=0x19 02 09 00 //*(a1)=*(*(a1+0x24)+0x64) 11 01 00 00 // judge(*(a1+4))!=0? *a1=*(a1+4) + 4**a1: *a1=*(a1+4) +*a1 B0 19 00 00 00 // 0x19 入栈 B5 0A // *a1+0x28 = 0x19 B2 00 // *a1 入栈 B4 09 // *[*(a1+0x24)+0x64]=栈顶 01 1A 00 00 00 0A // a1+0x28 = 0x1A B1 09 //*[*(a1+0x24)+0x68] 入栈 B5 00 // *a1 <- 栈顶 10 00 01 00 00 00 00 // judge(*a1)!=0? *a1+=4*0x01:*a1+=0x01; 01 1A 00 00 00 0A // a1+0x28 = 0x1A 04 00 09 //*[*(a1+0x24)+0x68]=*a1 //part 2 B0 1A 00 00 00 // 0x1A 入栈 B5 0A // *a1+0x28 = 0x1A 02 09 00 // *(a1)=*(*(a1+0x24)+0x64) 86 00 06 00 00 00 00 // *a1 = len < 6 ;限定长度为6,即循环6次就ok 88 00 26 00 00 00 91 //jmp part 1 FF
B0: *tmp=*(pc+1);tmp+=4;pc+=5; push 立即数指令 B5: *(a1+4*op1)=*tmp;tmp-=4;pc+=2; pop 指令 B2: *tmp=*(a1+4*op1);tmp+=4;pc+=2; push 指令 B4: *(a1+0x28)<=1000 ? tmp-=4; *[*(a1+4*op1) + 4 * [*(a1+0x28)] ] = *tmp : *[*(a1+4*op1) - [*(a1+0x28)] ] =*tmp ; pop指令 04: *(a1+0x28)<=1000 ? *[a1+4*op2 + 4*[*(a1+0x28)]]=*(a1+4*op1):*[*(a1+4*op2) - 4*[*(a1+0x28)]]=*(a1+4*op1) 90: pc+=*(a1+0x30)+op1 02: *(a1+0x28)<=1000 ? *(a1+4*op2)=*[*(a1+4*op1) + 4*[*(a1+0x28)]] :*(a1+4*op2)=*[*(a1+4*op1) - 4*[*(a1+0x28)]] 86: *(a1+4*op6)=*(a1+4*op1) < op2 88: *(a1+4*op1) != 0 ? pc = *(a1+0x30+4*op1) : pc=op6 01: *(a1+4*op5)=op1 10: judge(*(a1+4*op1))!=0 ? *(a1+4*op6)=*(a1+4*op1)+4*op2 : *(a1+4*op6)=*(a1+4*op1)+op2 C0: judge(*(tmp-1))!=0 ? *(tmp-1)=4* *(tmp-1) + *(tmp-2) : *(tmp-1)=*(tmp-1) + *(tmp-2) B1: *(a1+0x28)<=1000 ? tmp=*[a1+4*op1 + 4 * [*(a1+0x28)] ] : tmp=*[a1+4*op1 - 4*[*(a1+0x28)] ] ;tmp+=4; 70: *(a1+4*op6)=*(a1+4*op1) & op2 50: *(a1+4*op6)=*(a1+4*op1) << op2 C8: *(tmp-1) = *(tmp-2) >> *(tmp-1); C3: *(tmp-1) ^= *(tmp-2); 11: judge(*(a1+4*op1))!=0 ? *(a1+4*op3)=*(a1+4*op1)+4**(a1+4*op2) : *(a1+4*op3)=*(a1+4*op1)+*(a1+4*op2)
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。