吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 4263|回复: 1
收起左侧

[CTF] Insomni'hack teaser 2020 签到,逆向Writeup

[复制链接]
chen_null 发表于 2020-1-23 10:23

Insomni'hack teaser 2020 签到,逆向Writeup

这次比赛我们获得了...好吧就签了个道...比赛出的题很有新意,还是很值得一做的。

warmup

This year we added a Proof of Work to some of our challenges. Just run python pow.py \<target\>, were target is the value provided by the server and get the flag.

签到题,类似加密货币的挖矿环节,服务器给出一个哈希值的 0-6 字节,如果我们给出一段文字的哈希值的前 6 位与之相符,就可以拿到 Flag。简而言之就是哈希碰撞。

由给出的程序知道这个需要我们碰撞的 hash 算法是 md5。下面是我写的生成的 hash 和被 hash 值对应的 dict 的脚本,将 dict 用 pickle 保存。

import hashlib
import pickle

dic = {}

def save(obj):
    with open('cache.pkl', 'wb') as f:
        pickle.dump(dic, f)

i = 0
try:
    while i < 1000000:
        m = hashlib.md5()
        m.update(str(i).encode())
        h = m.hexdigest()
        dic[h[:6]] = str(i)
        if len(dic.items()) % 10000 == 0:
            print(len(dic.items()))
        i += 1
    save(dic)
except KeyboardInterrupt as e:
    save(dic)

然后用循环爆破,如果在 dict 里找到了对应的字符串及其 hash,就将字符串传给服务器以拿到 Flag。

import socket
import re
import pickle

regex = re.compile('with "(.+)"')
host = 'welcome.insomnihack.ch'
port = 1337
with open('cache.pkl', 'rb') as f:
    dic = pickle.load(f)

while True:
    s = socket.socket()
    s.connect((host, port))
    res = s.recv(1024)
    h = regex.findall(res.decode())[0]
    if h in dic.keys():
        print(f'{h} Found {dic[h]}')
        s.send(f'{dic[h]}\n'.encode())
        print(s.recv(128))
    else:
        print(f'{h} Not found')
    s.close()

Annotation 2020-01-18 200525.png

getdents

Oh shit! Data have been stolen from my computer... I looked for malicious activity but found nothing suspicious. Could ya give me a hand and find the malware and how it's hiding?

题目给了 profile 和 .vmem,应该是分析 Linux 内存镜像找出病毒的题。所以这题上了 volatility。需要注意的是 Linux 的 profile 需要放到 volatility 项目目录下的 volatility\plugins\overlays\linux 文件夹,例如我的 volatility 放在了F:\volatility\,那么打包好的 profile (zip 文件)应该放在 F:\volatility\volatility\plugins\overlays\linux,我因为放错了位置导致没搞出来...这是官方文档:https://github.com/volatilityfoundation/volatility/wiki/Linux#Linux-Profiles。按照官方文档放好 profile 后就可以使用 profile 进行分析了。

使用命令 python vol.py -f memory.vmem --profile=LinuxUbuntu_4_15_0-72-generic_profilex64 linux_pslist 于是尝试 dump meterpreter,但是发现 meterpreter 是 stageless 的,只能通过 netscan 知道他连接上了内网的一台机器。

volatility > python vol.py -f memory.vmem --profile=LinuxUbuntu_4_15_0-72-generic_profilex64 linux_psaux
Volatility Foundation Volatility Framework 2.6.1
Pid    Uid    Gid    Arguments
...
1750   0      0      sudo ./meterpreter
1751   0      0      ./meterpreter
...
2950   1000   1000   /usr/lib/deja-dup/deja-dup-monitor
2964   0      0      /bin/sh
volatility > python vol.py -f memory.vmem --profile=LinuxUbuntu_4_15_0-72-generic_profilex64 linux_netscan
Volatility Foundation Volatility Framework 2.6.1
...
8a9df81f6000 TCP      192.168.180.132 :51934 192.168.180.131 : 1337 ESTABLISHED

于是看看文件 python vol.py -f memory.vmem --profile=LinuxUbuntu_4_15_0-72-generic_profilex64 linux_enumerate_file | less 。看见了看见了 home 目录下的 rkit.ko。根据后缀名判断这是个内核模块。使用python vol.py -f memory.vmem --profile=LinuxUbuntu_4_15_0-72-generic_profilex64 linux_find_file -i 0xffff8a9dd42755e8 -O rkit.ko 导出了文件

0xffff8a9dc38422e8                   2363716 /home/julien/Downloads
               0x0 ------------------------- /home/julien/Downloads/.hidden
0xffff8a9dd42755e8                   2359303 /home/julien/Downloads/rkit.ko
0xffff8a9d948151a8                   2367790 /home/julien/Downloads/meterpreter

接下来就是逆向了。大致猜测程序先使用 base64 解密了某一个字符串,然后通过网络获取数据将 pk 进行第一次 xor 解密得到 sk,然后再通过其他方式调用了解密函数将 skep 进行异或,((unsigned __int64)(0x8421084210842109LL * (unsigned __int128)(v4 >> 1) >> 64) >> 4)是被优化的取模运算,可以带入 python 中跑一下就知道了。

通过 volshell 提取出数据。首先通过 IDA 中显示的偏移和内存中相同数据的位置算出 module 的基地址 '0xffffffffc0942700',然后就可以通过这个基地址 + 偏移计算出程序不同数据的位置,就可以进行数据提取了。

memory:
0xffffffffc0943080  00 00 00 00 00 00 00 00 88 30 94 c0 ff ff ff ff   .........0......
0xffffffffc0943090  88 30 94 c0 ff ff ff ff 72 6b 69 74 00 00 00 00   .0......rkit....
IDA:
0000000000000980  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
0000000000000990  00 00 00 00 00 00 00 00  72 6B 69 74 00 00 00 00  ........rkit....
base = 0xffffffffc0943090 - 0x990 -> 0xffffffffc0942700
sk = [0xe5, 0xd1, 0xa6, 0xf8, 0xc1, 0xf0, 0xd5, 0xdd, 0xf9, 0xe6, 0xca, 0xc6, 0xdb, 0xf4, 0xf6, 0xe1,
      0xda, 0xd4, 0xe7, 0xd9, 0xfd, 0xc7, 0xa5, 0xfc, 0xc8, 0xc4, 0xe4, 0xde, 0xe3, 0xa2, 0xf7, 0xc5,
      0xfe, 0xa3, 0xff, 0xd0, 0xc3, 0xe0, 0xab, 0xc2, 0xa7, 0xd8, 0xd7, 0xe2, 0xdf, 0xeb, 0xdc, 0xaa,
      0xa1, 0xa0, 0xd3, 0xcb, 0xa4, 0xf1, 0xfa, 0xc0, 0xfb, 0xf5, 0xd6, 0xf3, 0xea, 0xe8, 0x00, 0x00,
      0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x12, 0xfc, 0xa1, 0x9d, 0x8a, 0xff, 0xff,
      0x40, 0x02, 0x60, 0xab, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]

ep = [0xac, 0x9f, 0xf5, 0x83, 0x93, 0xc0, 0xe5, 0xa9, 0xb2, 0xd7, 0xbe, 0x80, 0xeb, 0x86, 0xa4, 0x8e,
      0xea, 0xbf, 0x8e, 0xbc, 0x8e, 0xba, 0x00, 0x00, 0x60, 0xda, 0xaf, 0xaa, 0xff, 0xff, 0xff, 0xff,
      0x7a, 0x73, 0x84, 0xf8, 0x09, 0xcc, 0xdd, 0xc4, 0xd0, 0x9b, 0xaa, 0xaa, 0xff, 0xff, 0xff, 0xff,
      0xb0, 0x9b, 0xaa, 0xaa, 0xff, 0xff, 0xff, 0xff, 0xb0, 0x4e, 0xb0, 0xaa, 0xff, 0xff, 0xff, 0xff,
      0xc0, 0xf0, 0x68, 0xc0, 0x51, 0xd7, 0xff, 0xff, 0x80, 0xf0, 0x68, 0xc0, 0x51, 0xd7, 0xff, 0xff,
      0x00, 0xf1, 0x68, 0xc0, 0x51, 0xd7, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]

print(''.join(chr(sk[i] ^ ep[i]) for i in range(62)))

kaboom

Defuse the bomb!

这个程序一打开就输出了 "KABOOM!",也没什么输入之类的,非常符合题目描述:拆弹。

当时在和队友讨论时就发现未脱壳的原程序不对劲,未脱壳程序中出现了两个 "INS" (Flag 开头)的字符串。一般情况下 UPX 会把字符串也一起压缩,出现字符串说明可能有其他程序藏在了未脱壳程序外面。

和队友讨论时候想到了上面的情况,但是没往那个方向做...

Annotation 2020-01-20 155959.png

上面是 UPX 加壳程序和题目的部分对比,可以发现最大的特点在于题目的代码开始在 UPX2 段,然后 jmp 到 UPX1 段上继续解密。在下图中的代码中跳转到了原本的解压函数,两个程序就非常相似了。

Annotation 2020-01-20 160122.png

那么重点放在不一样的代码上,直接啃汇编发现一部分代码是在解析 PE 文件以动态调用 GetCommandLineA 获取命令行参数。

UPX2:0046F0A8                 pusha
UPX2:0046F0A9                 xor     ecx, ecx
UPX2:0046F0AB                 mov     eax, fs:[ecx+30h]
UPX2:0046F0AF                 mov     eax, [eax+0Ch]
UPX2:0046F0B2                 mov     esi, [eax+14h]
UPX2:0046F0B5                 lodsd
UPX2:0046F0B6                 xchg    eax, esi
UPX2:0046F0B7                 lodsd
UPX2:0046F0B8                 mov     ebx, [eax+10h]
UPX2:0046F0BB                 mov     edx, [ebx+3Ch]
UPX2:0046F0BE                 add     edx, ebx
UPX2:0046F0C0                 mov     edx, [edx+78h]
UPX2:0046F0C3                 add     edx, ebx
UPX2:0046F0C5                 mov     esi, [edx+20h]
UPX2:0046F0C8                 add     esi, ebx
UPX2:0046F0CA                 xor     ecx, ecx
UPX2:0046F0CC
UPX2:0046F0CC loc_46F0CC:                             ; CODE XREF: start+2E↓j
UPX2:0046F0CC                                         ; start+37↓j ...
UPX2:0046F0CC                 inc     ecx
UPX2:0046F0CD                 lodsd
UPX2:0046F0CE                 add     eax, ebx
UPX2:0046F0D0                 cmp     dword ptr [eax], 43746547h
UPX2:0046F0D6                 jnz     short loc_46F0CC
UPX2:0046F0D8                 cmp     dword ptr [eax+4], 616D6D6Fh
UPX2:0046F0DF                 jnz     short loc_46F0CC
UPX2:0046F0E1                 cmp     dword ptr [eax+8], 694C646Eh
UPX2:0046F0E8                 jnz     short loc_46F0CC
UPX2:0046F0EA                 cmp     dword ptr [eax+0Ch], offset word_41656E
UPX2:0046F0F1                 jnz     short loc_46F0CC
UPX2:0046F0F3                 mov     esi, [edx+24h]
UPX2:0046F0F6                 add     esi, ebx
UPX2:0046F0F8                 mov     cx, [esi+ecx*2]
UPX2:0046F0FC                 dec     ecx
UPX2:0046F0FD                 mov     esi, [edx+1Ch]
UPX2:0046F100                 add     esi, ebx
UPX2:0046F102                 mov     edx, [esi+ecx*4]
UPX2:0046F105                 add     edx, ebx
UPX2:0046F107                 call    edx             ; 调用 GetCommandLineA
UPX2:0046F109                 jmp     short loc_46F10C

一部分则是在计算命令行参数的长度,然后跳转到命令行参数的末尾,从尾往头检查命令行参数。

UPX2:0046F10B
UPX2:0046F10B loc_46F10B:                             ; CODE XREF: start+67↓j
UPX2:0046F10B                 inc     eax
UPX2:0046F10C
UPX2:0046F10C loc_46F10C:                             ; CODE XREF: start+61↑j
UPX2:0046F10C                 cmp     byte ptr [eax], 0 ; 计算命令行参数的长度
UPX2:0046F10F                 jnz     short loc_46F10B
UPX2:0046F111                 push    0
UPX2:0046F113                 push    33E377FDh
UPX2:0046F118                 push    0D7831BBAh
UPX2:0046F11D                 push    4CE1B463h
UPX2:0046F122                 push    42h
UPX2:0046F124                 pop     ecx
UPX2:0046F125
UPX2:0046F125 loc_46F125:                             ; CODE XREF: start+8F↓j
UPX2:0046F125                 xor     edx, edx
UPX2:0046F127                 dec     eax
UPX2:0046F128                 add     cl, [eax]
UPX2:0046F12A                 cmp     cl, [esp+10h+var_10] ; 比较
UPX2:0046F12D                 jz      short loc_46F132
UPX2:0046F12F                 or      dl, 1
UPX2:0046F132
UPX2:0046F132 loc_46F132:                             ; CODE XREF: start+85↑j
UPX2:0046F132                 inc     esp
UPX2:0046F133                 cmp     [esp+10h+var_10], 0 ; 比较字符串是否结束
UPX2:0046F137                 jnz     short loc_46F125

如果上面的检查结束发现命令行参数成功匹配 (dl 寄存器为 0),则将二进制文件中被压缩的假 Flag 替换成被压缩的真 Flag,然后返回 UPX 解压部分代码。

UPX2:0046F139                 pop     ecx
UPX2:0046F13A                 cmp     dl, 0
UPX2:0046F13D                 jnz     short loc_46F15B
UPX2:0046F13F
UPX2:0046F13F loc_46F13F:                             ; CODE XREF: start+B1↓j
UPX2:0046F13F                 mov     al, ds:byte_46F160[ecx]
UPX2:0046F145                 mov     byte ptr ds:word_46D1A7[ecx], al
UPX2:0046F14B                 mov     ds:byte_46F160[ecx], 0
UPX2:0046F152                 inc     ecx
UPX2:0046F153                 cmp     ecx, 11A1h
UPX2:0046F159                 jb      short loc_46F13F
UPX2:0046F15B
UPX2:0046F15B loc_46F15B:                             ; CODE XREF: start+95↑j
UPX2:0046F15B                 jmp     loc_46E351

用 Python 很容易逆向程序思路写出解密脚本,就是将后一个数的值减去前一个数的值。脚本运行后输出部分命令行参数 "Plz&Thank-Q"

LazyIDA 真好用 o( ̄ε ̄*)

encoded = b"\x42"
encoded += b"\x63\xB4\xE1\x4C"
encoded += b"\xBA\x1B\x83\xD7"
encoded += b"\xFD\x77\xE3\x33"

decoded = ''
for i in range(0, len(encoded) - 1):
    decoded = chr((encoded[i+1] - encoded[i]) & 0xFF) + decoded
print(decoded)

根据字符串可以定位到输出 "KABOOM!" 的地方,通过在 x32dbg 中添加命令行参数,在输出点下断点就可以看到 x32dbg 显示出真正的 Flag 了。

Annotation 2020-01-20 161058.png

但是这其实还没完,根据 WP,在主函数中的第一个检查是命令行参数的个数,如果个数少于 2,第二个参数不是 "defuse",就不输出 Flag。所以 "拆弹" 的真正方式是在 Powershell 中以 ".\kaboom.exe defuse Plz`&Thank-Q!" (要转义 &)启动程序。

静态编译的程序 + 取出符号表是逆向噩梦啊

signed int __cdecl print_result(signed int argc, char **a2)
{
  int v2; // eax
  signed int result; // eax

  if ( argc >= 2
    && !strcmp((int)a2[1], (int)aDefuse)
    && (v2 = sub_401523((int)aIns), !sub_4011AE((int)aIns_0, (int)aHttpsWwwYoutub, v2)) )
  {
    sub_401393(aCongratsTheFla, (unsigned int)aHttpsWwwYoutub_0);
    result = 0;
  }
  else
  {
    sub_401393(aS, (unsigned int)aKaboom);
    result = 1;
  }
  return result;
}

这是输出

PS C:\Users\chenx> .\kaboom-origin.exe defuse Plz`&Thank-Q!
Congrats! The flag is INS{GG EZ clap PogU 5Head B) Kreygasm <3<3}

refs

写完后发现原来已经有更详细的 WriteUp 了,这篇文章里面的一些地方也重新参考了其他 WP...

https://ctftime.org/writeup/17984

https://github.com/Insomnihack/Teaser-2020/tree/master/kaboom

大佬说这篇申请区文章发到这个版里

免费评分

参与人数 3威望 +2 吾爱币 +101 热心值 +2 收起 理由
Hmily + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
Id!0tNe + 1 我很赞同!
Zeaf + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

Zeaf 发表于 2020-2-4 12:17
说实话,从申请板块来的,发现前面这么多申请的只有您通过了
 楼主| chen_null 发表于 2020-2-13 19:45
Zeaf 发表于 2020-2-4 12:17
说实话,从申请板块来的,发现前面这么多申请的只有您通过了

审核还是比较严的吧,主要是之前免费注册的号都被回收了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-15 12:51

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表