【整理】HITBCTF 部分 WriteUp

目录


MIsc

  • IRC checkin

  • tpyx

  • pix

  • read file


PWN

  • once

  • babypwn

  • d

  • gundam


Web

  • upload

  • Python's revenge


Reverse

  • hacku


Crypto

  • base


Mobile

  • multicheck

  • ivysimple





Misc


IRC checkin

签到题,加入IRC flag写在公告里就行了
HITBXCTF{W3lcome_To_HITBXCTF_2018_Online_Qualifications}

tpyx

拿到图片第一反应是丢进Stegsolve发现打不开,用winhex打开通过观察发现是IDAT的length存在问题,对长度进行修复。然后使用binwalk分析

发现存在一个zlib,对其进行分离得到两个新文件

继续对29进行分析发现还存在一个zlib,继续分离

得到两个新文件

打开28D28D,发现里面存在一串十六进制,通过分析发现这是一个7z文件,用winhex构造7z文件

密码写在注释里面,解压得到flag
HITB{0c88d56694c2fb3bcc416e122c1072eb}

pix

下载后得到文件: aee487a2-49cd-4f1f-ada6-b2d398342d99.SteinsGate
执行file命令,发现是一个png图片

用pngcheck检查执行,没发现错误,binwalk一下也没任何发现,就直接用stegslove打开,选择数据导出:

保存后用file命令查看一下,如下:

通过以上关键字搜索到如下信息:

将从图片中导出的文件重命名为1.kdbx,通过以下链接https://fileinfo.com/extension/kdbx下载keepass,打开1.kdbx,发现需要输入密码,由hint可知密码为hint+6位数字
参考如下链接:https://bbs.ichunqiu.com/thread-36593-1-1.html 生成了相应的字典进行爆破,得到密码为hitb180408

hashcat64.exe -a 0 -m 13400 c:UserswangztDesktoppix.hash
c:UserswangztDesktopsuperdic1.txt --force

hashcat64.exe -a 0 -m 13400 c:UserswangztDesktoppix.hash
c:UserswangztDesktopsuperdic1.txt --force --show

read file

echo $input | grep -o "[[:punct:]]*"
从表面上看是只显示标点符号,但当输入$$时,输出了pid:

由此可以判断他实际过滤的是输入,即只能输入标点符号,利用通配符寻到了flag的位置:

但不能用直接输入cat之类的,于是用相同方法找到/bin/cat位置,用$()执行里面的命令,
最后payload:

$(~/../../???/??? ~/../????????????/????.???)

在文件底部找到flag:


Pwn


once
程序主体

程序实现了一个双链表,允许改变一次链表节点 ,选项4进入子选项允许申请一次非fastbin堆块,允许写入一次,释放一次。

  • 漏洞:

    • 当输入选项不符合要求是会打印puts地址

    • 一次输入一次

    • 双链表解连时存在一次任意内存写,内容不可控。

  • 漏洞利用

    • 首先通过打印puts获得libc地址

    • 通过一次内存任意写改global_max_fast,

    • 此时申请堆块大小都会被计算为fastbin,寻找fastbin时能够得到libcdata段地址,通过改写file vtabl getshell

  • 最终脚本

Babypwn

  • fmt盲pwn

  • 通过格式串dump bin文件

  • 修复ida解析得到printf got表

  • 通过对比发现libc和once相同,格式串改写printf got getshell

  • 最终脚本

d

  • 程序主体

  • 实现功能

    puts("1. read message"); puts("2. edit message"); puts("3. wipe message"); puts("4. exit");
  • 漏洞

    • 存在单字节溢出

  • 利用

    • 通过单字节溢出漏洞制造unlink拿到指向bss自身指针

    • 通过指针更改got内容完成info leak 和getshell

    • 输入长度由strlen控制需要将strlen改为alarm_plt+6使输入长度变大

  • 最终脚本

gundam

  • 程序主体

  • 结构体 struct { int inuse; char* name; char type[0x18] }

  • 程序在bss段保存列表和计数

  • libc版本位2.26

  • 漏洞

    • 在选项3删除时bss列表中的指针没有清空,可以double free

  • 利用

    • 通过多次删除堆块,满足tcahce的上限(7),将堆块放入arena的bin中得到libc中得地址,完成地址泄露

    • 利用tcache实现任意地址写

  • 最终脚本


Web

Upload

经过扫描开放的web端口的指纹,得知服务器为windows,IIS7,php。这里考

察的是windows下<<符号可以,用于替代路径上的未知字符。
1.上传test.pHp

2.访问

http://47.90.97.18:9999/pic.php?filename=default.jpg

3.使用绕过gd库的脚本生成图片马,再次上传,一样有出现 width和height。

有上述步骤确认,可以上传为后缀为pHp,文件,目录与default.jpg 同级。

于是开始爆破default.jpg 所在目录。

得知

wwwroot/87194f13726af7cee27ba2cfe97b60df/default.jpg

访问第一次上传的pHp

使用kali自带的weevely生成php后门,可getshell

Python's revenge

这里考察的就是pickle反序列化的问题,我们可以控制反序列化的内容,就可

以直接构造__reduce__魔术方法任意命令执行

但这里加了一个hook装饰器对callback进行过滤,然后用的是黑名单过滤,发

现可以用map函数去绕过黑名单的限制

整理一下大概思路就两步,关键主要是黑名单绕过吧:

  1. 访问/reminder生成location, 本地爆破secret,得到secret : hitb

  2. 利用map函数绕过黑名单任意命令执行, 构造恶意的location, 访问首页即可触发反序列化漏洞

没有回显的命令执行,直接用weblog方式带出来


Reverse


Hacku

解压缩包得到一个流量包和一个chm

Chm里很显然包含着后门执行程序

通过hh.exe可以将其解包,在doc.htm中发现了后门程序的执行

通过powershell隐蔽执行了一段b64,解密后发现是一段Unicode

又是一段b64,解b64后又用了DeflateStream解压缩

查了一下zlib什么的乱七八糟很麻烦,干脆直接去掉IEX即可

得到一段脚本

观察发现通过nslookup对x.hacku.org发起解析,返回的字符串解b64后执行

于是去流量包里找DNS

将流量包dump出来并清洗后解析,得到下一段脚本

仅混淆了函数名,通读一遍后标记即可

逻辑为向cc.php发包,再解析返回的包来执行指定的命令

发包和收包都是先b64,再逐字符加密,再b64一次

于是分别写出发包和收包的解密函数,从流量包中dump出data,然后解密

Recv中可以看到data有两种,指定传输moliboom文件和下载并执abs_stage3

Moliboom的发包中可以解出path和txt,在fix后的数据中找到flag的前半段

Abs_stage3返回的字符串被ps执行了,所以显然也是脚本

但是混淆很严重,包括大小写混杂、逆序、replace等

粗看一遍大概能发现是写入了某个exe并执行,然后删除

由于是exe,因此一定会落地在磁盘里,在ISE中调试即可捕捉到

在执行前断住,然后通过路径和文件名去找这个文件

抓住以后进行反编译

值得一提的是,sub_401050是个花指令控制函数,通过堆栈的操作使得ret跳到[eax]个字节之后的地址。需要手动/脚本将其后的

花指令nop掉才能使IDA正常反编译

程序开启了磁盘的交互

对主扇区的512和1024处进行了写入

这里是MBR(主引导程序)的地址,因此实际上是个勒索病毒吧233

分别将418DB0和4187b0dump下来

查了一下,MBR是直接送给CPU执行的汇编程序,因此同样按照x86反汇编

Dump下来以后用IDA打开即可查看

注意MBR是16bit模式

16bit模式无法使用F5,所以只能生啃汇编

好消息是MBR的空间有限,程序不会太过复杂

猜测应该是简单的加密以后直接对硬编码进行cmp

因此通过查找cmp可以在一定程度上加快速度

里面的读写调用都是通过int 10/13/16来执行的,具体方法跟ax参数有关

不进行动态调试的话难度有点大,因为没法准确锁定读写的函数

Sub_0x457是input函数,循环接受最多16个字符,值得一提的是没有退格,输入一个算一个。或者遇到0x0d,即回车也会提前结束

Sub_0x494是check函数,对input进来的内容进行一些变换,然后与硬编码cmp

主要逻辑在这里

做题的时候最好还是动态调试,因为猜逻辑太难了233

动态调试主要参考52论坛的@willJ的方法

https://www.52pojie.cn/thread-173889-1-1.html

在此附加一些个人经验,和对本题的特殊之处

首先要重新启动一个ida,在进入界面选择“GO”

直接附加会内存指令无法正常显示

因为MBR的入口点在0x7c00处,该dump程序会自动被装载到0x7c00起点的地方。

自己用汇编程序编译出来的MBR似乎会自动将入口点放在合适的0x7c00,使得动态调试直接命中。

但是dump下来的idb起点是0x00,也许前面放上一些00使其偏移过去也行?

按照文章附加上以后在0x7c00处下断,此时该处为空值,无视它继续下断,再F9即会断下

单步运行到check函数中

发现ax中放着输入的字符,先ror3,sub_4df是花指令,无视即可,再依次执行xor0x74, rol5,add0x47

最后按照最低位为0/1来-1/+1

之后与bx+141处的值cmp,全部dump下来后逆向求解即可

脚本:

r = [37, 161, 57, 137, 166, 157, 213, 165, 117, 141, 74, 146, 241, 89, 94, 145]
a = [i+1 if(i&1) else i-1 for i in r]
for i in range(16):

print(chr(rol(ror((a[i]-0x47)&0xff, 5)^0x74, 3)), end='')

cmp成功以后会输出一个”}n”来补齐

与之前流量包中的前半段flag合并后提交完成

Flag:

HITBXCTF{W0rk_1n_th3_dark_ Tu_s4v@thr#1Ig&q}


Crypto


Base
这个一开始没思路,然后就开始爆破,最终爆破出flag。但是题目提示了不用爆破,显然我们使用了非预期算法。


Mobile


Multicheck

反编译发现load了"libcheck.so",但是没有导入native函数

反射了com.a.Check类中的check方法,这个类暂时没有发现

顺手瞟一眼claz.dex,逐字符比较了一个看起来像是flag但显然不是flag的字符串

出题师傅的恶趣味真是~

于是Java层中暂时没什么线索了,反编译so看看

在init_array中发现了一个函数,跟过去往里看

Sub_1380里有一些令人在意的东西

新建了一个文件,往里写了一些内容

把byte_4004里的值异或后dump下来

打开发现是个dex,反编译得到com.a.check类

Check方法里将arg和硬编码b进行了比较

然后一路跟着a进去,是4个同名不同参的重载函数

先整理了个数,将bytes后移,第一个byte存储偏移,之后补0

然后每8个byte一组,转成两个int(大端序)

然后是核心变换,与之前强网杯的hide算法相同

32轮迭代,每轮先对key自增,然后互相运算

逆起来很简单,按着逻辑先把v1自增32次,然后将v2自减,再将v3自减,最后v1自减即可

然后有一个关键问题就是,之前的hide是elf格式的,而这次在安卓中的jvm中执行,写了python脚本怎么跑都不对

由于是不同的dex,jeb也跟不进反射的方法,Xp hook的话又太麻烦。

最后突发奇想复制到java环境里执行,果然得到了正确的逻辑:

Java中>>为带符号的右移,要考虑各个参数的正负,如果是负数>>时应补1。

Python调了半天都没修正好,默认的int是无限宽度的,必须用0xffffffff去修正,而这会导致符号丢失。

调了一万年以后突然想起来何苦非要用python写脚本呢,遂将java的check类逻辑小改,得到flag

解码脚本:

import java.util.Arrays;  public class HelloWorld  {      public static void main(String[] args)      {          int i;          byte[] t = { 99, 124, 101, -23, -114, 81, -47, -39, -102, 79, 22, 52, -39, -94, -66, -72, 101, -18, 73, -27, 53, -5, 46, -20, 97, 11, -56, 36, -19, -49, -112, -75 };          byte[] tt = re(t);             for(i=0;i<tt.length;i++)          {              System.out.print((char)(tt[i]&0xff));              /*System.out.print(", ");              if((i+1)%8==0 && i!=0)              {                  System.out.println();              }*/          }               }      private static int[] a = { -1414812757, -842150451, -269488145, 305419896 };        private static byte[] b = { 99, 124, 101, -23, -114, 81, -47, -39, -102, 79, 22, 52, -39, -94, -66, -72, 101, -18, 73, -27, 53, -5, 46, -20, 97, 11, -56, 36, -19, -49, -112, -75 };               private static byte[] a(int[] paramArrayOfInt, int paramInt)        {          byte[] arrayOfByte = new byte[paramArrayOfInt.length << 2];          int i = 0;          while (paramInt < arrayOfByte.length)          {            arrayOfByte[(paramInt + 3)] = ((byte)(paramArrayOfInt[i] & 0xFF));            arrayOfByte[(paramInt + 2)] = ((byte)(paramArrayOfInt[i] >> 8 & 0xFF));            arrayOfByte[(paramInt + 1)] = ((byte)(paramArrayOfInt[i] >> 16 & 0xFF));            arrayOfByte[paramInt] = ((byte)(paramArrayOfInt[i] >> 24 & 0xFF));            i += 1;            paramInt += 4;          }          return arrayOfByte;        }        private static int a(byte paramByte)        {          int i = paramByte;          if (paramByte < 0) {            i = paramByte + 256;          }          return i;        }        private static int[] b(byte[] paramArrayOfByte, int paramInt)        {          int[] arrayOfInt = new int[paramArrayOfByte.length >> 2];          int i = 0;          while (paramInt < paramArrayOfByte.length)          {            arrayOfInt[i] = (a(paramArrayOfByte[(paramInt + 3)]) | a(paramArrayOfByte[(paramInt + 2)]) << 8 | a(paramArrayOfByte[(paramInt + 1)]) << 16 | paramArrayOfByte[paramInt] << 24);            i += 1;            paramInt += 4;          }          return arrayOfInt;        }                      public static byte[] re(byte[] paramString)        {              return re_a(paramString);        }               public static byte[] re_a(byte[] arrayOfByte)        {          /*int i = 8 - paramArrayOfByte.length % 8;          byte[] arrayOfByte = new byte[paramArrayOfByte.length + i];          arrayOfByte[0] = ((byte)i);          System.arraycopy(paramArrayOfByte, 0, arrayOfByte, i, paramArrayOfByte.length);*/          int i;          byte[] paramArrayOfByte = new byte[arrayOfByte.length];          i = 0;          while (i < paramArrayOfByte.length)          {            System.arraycopy(re_c(arrayOfByte, i, a, 32), 0, paramArrayOfByte, i, 8);            i += 8;          }          return paramArrayOfByte;        }               static byte[] re_c(byte[] paramArrayOfByte, int paramInt1, int[] paramArrayOfInt, int paramInt2)        {          int[] ArraryInt = b(paramArrayOfByte, paramInt1);          int i = ArraryInt[0];          paramInt1 = ArraryInt[1];          //System.out.println(Integer.toHexString(i));          //System.out.println(Integer.toHexString(paramInt1));          int k = 0;          int m = paramArrayOfInt[0];          int n = paramArrayOfInt[1];          int i1 = paramArrayOfInt[2];          int i2 = paramArrayOfInt[3];          int j = 0;          while(j<paramInt2)          {          k -= 1640531527;          j += 1;          }          while (j > 0 )          {            paramInt1 -= ((i << 4) + i1 ^ i + k ^ (i >> 5) + i2);                       i -= ((paramInt1 << 4) + m ^ paramInt1 + k ^ (paramInt1 >> 5) + n);            /*System.out.print((paramInt1));            System.out.println("t");*/            k += 1640531527;            j -= 1;          }          ArraryInt[0] = i;          ArraryInt[1] = paramInt1;          return a(ArraryInt, 0);        }  }

Kivysimple

先查了一下,发现kivy是python开发apk的套件

那么很显然,这种套件的逻辑应该是在安卓中安装python虚拟机,然后将py编译成pyc/pyo来执行

扫了一圈assest和lib,没有发现pyo

不过lib中看到了python27.so,基本可以确定我们的判断

Libmain.so这个名字很吸引人,进去看一眼

查字符串,发现

都是python的原始代码,也就是说虚拟机确实在安卓中解释执行代码了

虽然还是没有发现程序的代码,但是注意到有很多自带的log,于是进行安装运行尝试

发现虚拟机在这个路径里,过去看看

一下就抓到了关键,将它pull出来并反编译

出题师傅的恶趣味真是~x2

显然关键在b64解出来的代码里,于是将eval去掉,查看code object的信息

PS:由于源代码为python2所写,因此必须使用python2来解码。Python3的marshal格式不同,会报错。

可以看到有一个check函数,也是code object

先通过dis模块来解析字节码

啥都没有╮(╯_╰)╭就是调用了check函数而已

于是再解析check

还好没有很复杂的结构,基本上看单词和值就能大差不离的猜出来

s = flag  if(len(s)!=31):         return False  if(s[17]!=’7’):         jmp 54  …

下面都是类似的结构

因此只要把对应的下标和值提取出来即可得到flag

数据清洗使用正则匹配,或者手动复制都可233

得到flag:

HITB{1!F3_1S_&H%r7_v$3_pY7#ON!}


本文作者:白帽100安全攻防实验室

本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/71257.html

Tags:
评论  (0)
快来写下你的想法吧!

白帽100安全攻防实验室

文章数:22 积分: 52

安全问答社区

安全问答社区

脉搏官方公众号

脉搏公众号