chen_null 发表于 2020-1-23 10:23

Insomni'hack teaser 2020 签到,逆向Writeup

# 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 保存。

```python
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] = 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。

```python
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())
    if h in dic.keys():
      print(f'{h} Found {dic}')
      s.send(f'{dic}\n'.encode())
      print(s.recv(128))
    else:
      print(f'{h} Not found')
    s.close()
```



## 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` 知道他连接上了内网的一台机器。

```bash
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` 导出了文件

```bash
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`,然后再通过其他方式调用了解密函数将 `sk` 和 `ep` 进行异或,`((unsigned __int64)(0x8421084210842109LL * (unsigned __int128)(v4 >> 1) >> 64) >> 4)`是被优化的取模运算,可以带入 python 中跑一下就知道了。

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

```bash
memory:
0xffffffffc094308000 00 00 00 00 00 00 00 88 30 94 c0 ff ff ff ff   .........0......
0xffffffffc094309088 30 94 c0 ff ff ff ff 72 6b 69 74 00 00 00 00   .0......rkit....
IDA:
000000000000098000 00 00 00 00 00 00 0000 00 00 00 00 00 00 00................
000000000000099000 00 00 00 00 00 00 0072 6B 69 74 00 00 00 00........rkit....
base = 0xffffffffc0943090 - 0x990 -> 0xffffffffc0942700
```

```python
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 ^ ep) for i in range(62)))
```

## kaboom

> Defuse the bomb!

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

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

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



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



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

```asm
UPX2:0046F0A8               pusha
UPX2:0046F0A9               xor   ecx, ecx
UPX2:0046F0AB               mov   eax, fs:
UPX2:0046F0AF               mov   eax,
UPX2:0046F0B2               mov   esi,
UPX2:0046F0B5               lodsd
UPX2:0046F0B6               xchg    eax, esi
UPX2:0046F0B7               lodsd
UPX2:0046F0B8               mov   ebx,
UPX2:0046F0BB               mov   edx,
UPX2:0046F0BE               add   edx, ebx
UPX2:0046F0C0               mov   edx,
UPX2:0046F0C3               add   edx, ebx
UPX2:0046F0C5               mov   esi,
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 , 43746547h
UPX2:0046F0D6               jnz   short loc_46F0CC
UPX2:0046F0D8               cmp   dword ptr , 616D6D6Fh
UPX2:0046F0DF               jnz   short loc_46F0CC
UPX2:0046F0E1               cmp   dword ptr , 694C646Eh
UPX2:0046F0E8               jnz   short loc_46F0CC
UPX2:0046F0EA               cmp   dword ptr , offset word_41656E
UPX2:0046F0F1               jnz   short loc_46F0CC
UPX2:0046F0F3               mov   esi,
UPX2:0046F0F6               add   esi, ebx
UPX2:0046F0F8               mov   cx,
UPX2:0046F0FC               dec   ecx
UPX2:0046F0FD               mov   esi,
UPX2:0046F100               add   esi, ebx
UPX2:0046F102               mov   edx,
UPX2:0046F105               add   edx, ebx
UPX2:0046F107               call    edx             ; 调用 GetCommandLineA
UPX2:0046F109               jmp   short loc_46F10C
```

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

```asm
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 , 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,
UPX2:0046F12A               cmp   cl, ; 比较
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   , 0 ; 比较字符串是否结束
UPX2:0046F137               jnz   short loc_46F125
```

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

```asm
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
UPX2:0046F145               mov   byte ptr ds:word_46D1A7, al
UPX2:0046F14B               mov   ds:byte_46F160, 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( ̄ε ̄*)

```python
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 - encoded) & 0xFF) + decoded
print(decoded)

```

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



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

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

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

if ( argc >= 2
    && !strcmp((int)a2, (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;
}
```

这是输出

```powershell
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>

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

Zeaf 发表于 2020-2-4 12:17

说实话,从申请板块来的,发现前面这么多申请的只有您通过了{:1_893:}

chen_null 发表于 2020-2-13 19:45

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

审核还是比较严的吧,主要是之前免费注册的号都被回收了:rggrg
页: [1]
查看完整版本: Insomni'hack teaser 2020 签到,逆向Writeup