一开始打算静态逆,结果还是天真了,在错误的道路上奔跑了太久,不调试很难做出来吧。
这个题的难点主要有
- 在
libexecute_table.so
中有反调试,动态库被加载时会创建线程读取/proc/self/status
中的TracerPid
检测是否被调试。
- 动态库被加载时会读取
/proc/self/maps
获取自身的基地址,然后通过分析程序头和.dynamic
段,修改符号表,改变JNI_OnLoad
的地址。
- 动态库在加载时,使用
mprotect
改变内存权限,使用自修改代码来生成新的JNI_OnLoad
。
- 在程序里面搞了很多死分支,也就是根本不会被执行到的分支,影响分析。
- C++的string类真的很烦,函数重载导致一个功能的函数有好几个。
Java层
Java层比较简单,就是调用native函数lkdakjudajndn
,如果lkdakjudajndn
返回1,则成功。
libexecute_table.so
修复库函数名及识别关键函数
拖到IDA Pro里,首先是ELF文件.plt.got
部分的函数没有正确命名,不知道为啥,写一个小脚本命名一下,然后就可以得到正确的库函数名。
import idaapi
import idc
rel_start_addr = 0x5D00
rel_end_addr = 0x5EE8
func_start_addr = 0x5EFC
rel_addr = rel_start_addr
func_addr = func_start_addr
print 'start renaming ...'
while rel_addr < rel_end_addr:
cmt = idc.get_cmt(rel_addr, False)
func_name = '_' + cmt.split(' ')[1]
print func_name
idc.set_name(func_addr, func_name)
rel_addr += 8
func_addr += 12
print 'done'
修复后的结果如下
然后还有4个直接用系统调用实现的函数,sys_openat
, sys_read
, sys_cloase
, sys_mprotect
。
还有一个关键的函数初始化一个结构体,结构体中是函数指针
这些函数很关键。
调试
程序里有明文字符串,所以可以猜想有反调试,然后原JNI_OnLoad
函数看起来不正常,应该有自修改代码。所以在sys_openat
和sys_mprotect
处下断点。
从sys_openat
处的断点,可以发现sys_openat
会打开两个文件,/proc/self/status
和/proc/self/maps
。
/proc/self/status
/proc/self/maps
其中,打开/proc/self/maps
是用来获取libexecute_table.so
的基地址,以便修改JNI_OnLoad
的符号表。
打开/proc/self/status
是为了读取TracerPid
用于反调试,要patch一下这个函数,使其反调试失效。然后用patch过的so文件换掉原so文件。
从sys_mprotect
处的断点,可以找到修改符号表的地方。
真正的JNI_OnLoad
函数是sub_D374B260
(这个地址只有260是准确的)。
在真正的JNI_OnLoad
中,可以看到函数lkdakjudajndn
的注册过程
其地址为0xD374BC98
,同样,调试的时候高位地址会随机化。
从sys_mprotect
处的断点,还可以发现,有自修改代码,但是我没有研究其过程,让它默默的改就好了。
lkdakjudajndn
lkdakjudajndn
函数的验证过程本身没有那么烦,但是弄了一堆条件分支就很烦了,不过发现规律后可以很明确的判断程序会走哪个分支。
比方说下图中v14
的取值,很明显只能是1。
可以看到其验证流程是把输入做一些变换,然后与3ww3U53wOAWG333wwPZ56GGw0PO02OUW
对比,如果相同则正确。
其变换过程并不复杂,都是一些异或、置换、替换之类的操作,但是描述起来很麻烦,就不详细写了。直接把解题的IDAPython代码贴出来,其中有几个地址需要根据实际情况替换一下。代码很乱,不要吐槽我,不想整理了。
import idaapi
input = '1234567891234567'
print 'input %s' % input
input = input[1:] + input[0]
base = idaapi.get_dword(0xD3776CF8)
print 'base %x' % base
print 'base+33 %x' % (base+33)
out1 = ''
for c in input:
out1 += chr(idaapi.get_byte(base + 33 + ord(c)))
print 'out1 %s' % out1.encode('hex')
out2 = ''
for c in out1:
out2 += chr( ((ord(c)&0xf) << 4) | (ord(c) >> 4) )
print 'out2 %s' % out2.encode('hex')
out3 = ''
for i in range(16):
out3 += chr( ord(out2[i]) ^ idaapi.get_byte(0xD3777098 + i%4) )
print 'out3 %s' % out3.encode('hex')
out4 = ''
for i in range(8):
out4 += out3[2*i+1] + out3[2*i]
print 'out4 %s' % out4.encode('hex')
out5 = ''
for c in out4:
out5 += chr(idaapi.get_byte(base + 33 + ord(c)))
print 'out5 %s' % out5.encode('hex')
out5 = out5[1:] + out5[0]
table = 'A3Cw6Gb0OZWPU52s'
out6_1 = ''
out6_2 = ''
for c in out5:
out6_1 += table[ ord(c) >> 4]
out6_2 = table[ ord(c) & 0xf] + out6_2
out6 = out6_1 + out6_2
print out6
# 1234567891234567 ->
# target = 'Uww3C3sG6sCAb55sPAOPUA0O6OCA25AO'
print 'decoding .....'
target = '3ww3U53wOAWG333wwPZ56GGw0PO02OUW' # '3ww3U53wOAWG333'
print target
out5 = ''
out6_1 = target[:16]
out6_2 = target[16:]
for i in range(16):
out5 += chr( (table.index(out6_1[i]) << 4) | (table.index(out6_2[15-i])) )
out5 = out5[-1] + out5[:-1]
print 'out5 %s' % out5.encode('hex')
base = 0xd3748fc2
table = []
print 'start'
for i in range(256):
e = idaapi.get_byte(base + i)
if e in table:
print '*******************************dup'
table.append(e)
out4 = ''
for c in out5:
out4 += chr(table.index(ord(c)))
print 'out4 %s' % out4.encode('hex')
out3 = ''
for i in range(8):
out3 += out4[2*i+1] + out4[2*i]
print 'out3 %s' % out3.encode('hex')
out2 = ''
for i in range(16):
out2 += chr( ord(out3[i]) ^ idaapi.get_byte(0xD3777098 + i%4) )
print 'out2 %s' % out2.encode('hex')
out1 = ''
for c in out2:
out1 += chr( ((ord(c)&0xf) << 4) | (ord(c) >> 4) )
print 'out1 %s' % out1.encode('hex')
input = ''
for c in out1:
input += chr(table.index(ord(c)))
input = input[-1] + input[:-1]
print input
最后,flag为C0ngRa7U1AtIoN2U
。
我感觉我要弃赛了。
[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界
最后于 2018-6-26 14:23
被iweizime编辑
,原因: