Compare commits

...

7 Commits
1.2 ... 1.4.1

Author SHA1 Message Date
AnonymousUser
c632782bc6 Add Scope Column: any 2020-11-11 23:59:27 +08:00
AnonymousUser
503fea6f55 Update: Support request messages 2020-11-11 03:32:42 +08:00
AnonymousUser
db1f8b9cc9 Update 2020-11-11 03:22:31 +08:00
AnonymousUser
1e22b48001 Update 2020-11-11 03:15:12 +08:00
AnonymousUser
9cb5c93fd7 Update: Support request messages 2020-11-11 03:09:32 +08:00
AnonymousUser
8f18079ea6 Add Regular Expression [ Elasticsearch Unauthorized Access ] 2020-11-10 14:39:33 +08:00
AnonymousUser
ad8ebefb63 Update: Optimize speed 2020-10-27 19:38:58 +08:00
6 changed files with 114 additions and 66 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -6,7 +6,7 @@ Read Chinese simplified version ([README_zh](README_zh.md)).
## Introduction ## Introduction
**HaE** is used to highlight HTTP requests and extract information from HTTP response messages. **HaE** is used to highlight HTTP requests and extract information from HTTP `response messages` or `request messages`.
![-w1070](images/16000706401522.jpg) ![-w1070](images/16000706401522.jpg)
@@ -41,7 +41,7 @@ HaE supports three actions:
3. Color upgrade algorithm: **Two regulars expression, the colors are both orange, if the request are matched these, it will be upgraded to red.** 3. Color upgrade algorithm: **Two regulars expression, the colors are both orange, if the request are matched these, it will be upgraded to red.**
4. The configuration file format uses JSON format, the format is 4. The configuration file format uses JSON format, the format is
``` ```
{name: {"loaded": isLoaded,"regex": regexText, "highlight": isHighlight, "extract": isExtract, "color": colorText}} {name: {"loaded": isLoaded,"regex": regexText, "scope": request/response/any, "action": extract/highlight/any, "color": colorText}}
``` ```
5. Built-in simple cache to reduce the stuttering phenomenon in the `multi-regular, big data scenario`. 5. Built-in simple cache to reduce the stuttering phenomenon in the `multi-regular, big data scenario`.
@@ -79,7 +79,7 @@ The mobile phone number required to be matched cannot be a number from 0-9.
### Include Regular Expression List ### Include Regular Expression List
Chinese ID-NumberFrom: https://github.com/gh0stkey/HaE/issues/3: Chinese ID-Number(From: https://github.com/gh0stkey/HaE/issues/3):
``` ```
[^0-9]([1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx])|([1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}[0-9Xx])[^0-9] [^0-9]([1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx])|([1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}[0-9Xx])[^0-9]
@@ -91,3 +91,9 @@ Email Address:
([\w-]+(?:\.[\w-]+)*@(?:[\w](?:[\w-]*[\w])?\.)+[\w](?:[\w-]*[\w])?) ([\w-]+(?:\.[\w-]+)*@(?:[\w](?:[\w-]*[\w])?\.)+[\w](?:[\w-]*[\w])?)
``` ```
Elasticsearch Unauthorized Access(From: https://github.com/gh0stkey/HaE/issues/7):
```
("cluster_uuid"\s*:\s*"[A-Za-z0-9_-]{22}")
```

View File

@@ -6,7 +6,7 @@
![-w1070](images/16000706401522.jpg) ![-w1070](images/16000706401522.jpg)
该插件可以通过自定义正则的方式匹配**响应报文**,可以自行决定符合该自定义正则匹配的相应请求是否需要高亮标记、信息提取。 该插件可以通过自定义正则的方式匹配**响应报文或请求报文**,可以自行决定符合该自定义正则匹配的相应请求是否需要高亮标记、信息提取。
**注**: `HaE`的使用,对测试人员来说需要基本的正则表达式基础,由于`Java`正则表达式的库并没有`Python`的优雅或方便在使用正则的HaE要求使用者必须使用`()`将所需提取的表达式内容包含;例如你要匹配一个**Shiro应用**的响应报文,正常匹配规则为`rememberMe=delete`,如果你要提取这段内容的话就需要变成`(rememberMe=delete)` **注**: `HaE`的使用,对测试人员来说需要基本的正则表达式基础,由于`Java`正则表达式的库并没有`Python`的优雅或方便在使用正则的HaE要求使用者必须使用`()`将所需提取的表达式内容包含;例如你要匹配一个**Shiro应用**的响应报文,正常匹配规则为`rememberMe=delete`,如果你要提取这段内容的话就需要变成`(rememberMe=delete)`
@@ -37,7 +37,7 @@ HaE支持三个动作:
3. 颜色升级算法: 利用下标的方式进行优先级排序当满足2个同颜色条件则以优先级顺序上升颜色。例如: **两个正则,颜色为橘黄色,该请求两个正则都匹配到了,那么将升级为红色** 3. 颜色升级算法: 利用下标的方式进行优先级排序当满足2个同颜色条件则以优先级顺序上升颜色。例如: **两个正则,颜色为橘黄色,该请求两个正则都匹配到了,那么将升级为红色**
4. 简单的配置文件格式选用JSON格式格式为 4. 简单的配置文件格式选用JSON格式格式为
``` ```
{name: {"loaded": isLoaded,"regex": regexText, "highlight": isHighlight, "extract": isExtract, "color": colorText}} {name: {"loaded": isLoaded,"regex": regexText, "scope": request/response/any, "action": extract/highlight/any, "color": colorText}}
``` ```
5. 内置简单缓存,在“多正则、大数据”的场景下减少卡顿现象。 5. 内置简单缓存,在“多正则、大数据”的场景下减少卡顿现象。
@@ -89,7 +89,7 @@ Github项目地址BUG、需求、正则欢迎提交: https://github.com/gh
### 收录正则列表 ### 收录正则列表
身份证号码来自: https://github.com/gh0stkey/HaE/issues/3: 身份证号码(来自: https://github.com/gh0stkey/HaE/issues/3):
``` ```
[^0-9]([1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx])|([1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}[0-9Xx])[^0-9] [^0-9]([1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx])|([1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}[0-9Xx])[^0-9]
@@ -101,3 +101,8 @@ Github项目地址BUG、需求、正则欢迎提交: https://github.com/gh
([\w-]+(?:\.[\w-]+)*@(?:[\w](?:[\w-]*[\w])?\.)+[\w](?:[\w-]*[\w])?) ([\w-]+(?:\.[\w-]+)*@(?:[\w](?:[\w-]*[\w])?\.)+[\w](?:[\w-]*[\w])?)
``` ```
Elasticsearch未授权访问匹配(来自: https://github.com/gh0stkey/HaE/issues/7):
```
("cluster_uuid"\s*:\s*"[A-Za-z0-9_-]{22}")
```

View File

@@ -38,6 +38,7 @@ import javax.swing.SwingUtilities;
import javax.swing.JLabel; import javax.swing.JLabel;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
public class BurpExtender implements IBurpExtender, IHttpListener, IMessageEditorTabFactory, ITab { public class BurpExtender implements IBurpExtender, IHttpListener, IMessageEditorTabFactory, ITab {
@@ -48,9 +49,11 @@ public class BurpExtender implements IBurpExtender, IHttpListener, IMessageEdito
private IBurpExtenderCallbacks callbacks; private IBurpExtenderCallbacks callbacks;
private static String configFilePath = "config.json"; private static String configFilePath = "config.json";
private static String initFilePath = "init.hae"; private static String initFilePath = "init.hae";
private static String initConfigContent = "{\"Email\":{\"loaded\":true,\"highlight\":true,\"regex\":\"([\\\\w-]+(?:\\\\.[\\\\w-]+)*@(?:[\\\\w](?:[\\\\w-]*[\\\\w])?\\\\.)+[\\\\w](?:[\\\\w-]*[\\\\w])?)\",\"extract\":true,\"color\":\"yellow\"}}"; private static String initConfigContent = "{\"Email\":{\"loaded\":true,\"scope\":\"response\",\"regex\":\"([\\\\w-]+(?:\\\\.[\\\\w-]+)*@(?:[\\\\w](?:[\\\\w-]*[\\\\w])?\\\\.)+[\\\\w](?:[\\\\w-]*[\\\\w])?)\",\"action\":\"any\",\"color\":\"yellow\"}}";
private static String endColor = ""; private static String endColor = "";
private static String[] colorArray = new String[] {"red", "orange", "yellow", "green", "cyan", "blue", "pink", "magenta", "gray"}; private static String[] colorArray = new String[] {"red", "orange", "yellow", "green", "cyan", "blue", "pink", "magenta", "gray"};
private static String[] scopeArray = new String[] {"any", "response", "request"};
private static String[] actionArray = new String[] {"any", "extract", "highight"};
private static IMessageEditorTab HaETab; private static IMessageEditorTab HaETab;
private static PrintWriter stdout; private static PrintWriter stdout;
@@ -58,8 +61,10 @@ public class BurpExtender implements IBurpExtender, IHttpListener, IMessageEdito
public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks) public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks)
{ {
this.callbacks = callbacks; this.callbacks = callbacks;
// 设置插件名字 // 设置插件名字和版本
callbacks.setExtensionName("HaE - Highlighter and Extractor"); String version = "1.4.1";
callbacks.setExtensionName(String.format("HaE (%s) - Highlighter and Extractor", version));
// 定义输出 // 定义输出
stdout = new PrintWriter(callbacks.getStdout(), true); stdout = new PrintWriter(callbacks.getStdout(), true);
@@ -137,7 +142,7 @@ public class BurpExtender implements IBurpExtender, IHttpListener, IMessageEdito
panel_2.add(panel_1, BorderLayout.NORTH); panel_2.add(panel_1, BorderLayout.NORTH);
panel_1.setBorder(new TitledBorder(new EtchedBorder(EtchedBorder.LOWERED, null, null), "Actions", TitledBorder.LEADING, TitledBorder.TOP, null, new Color(0, 0, 0))); panel_1.setBorder(new TitledBorder(new EtchedBorder(EtchedBorder.LOWERED, null, null), "Actions", TitledBorder.LEADING, TitledBorder.TOP, null, new Color(0, 0, 0)));
JButton btnReloadRule = new JButton("Reload Rule"); JButton btnReloadRule = new JButton("Reload");
btnReloadRule.addActionListener(new ActionListener() { btnReloadRule.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
fillTable(); fillTable();
@@ -145,7 +150,7 @@ public class BurpExtender implements IBurpExtender, IHttpListener, IMessageEdito
}); });
panel_1.add(btnReloadRule); panel_1.add(btnReloadRule);
JButton btnNewRule = new JButton("New Rule"); JButton btnNewRule = new JButton("New");
btnNewRule.addActionListener(new ActionListener() { btnNewRule.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) { public void actionPerformed(ActionEvent arg0) {
DefaultTableModel dtm = (DefaultTableModel) table.getModel(); DefaultTableModel dtm = (DefaultTableModel) table.getModel();
@@ -154,14 +159,14 @@ public class BurpExtender implements IBurpExtender, IHttpListener, IMessageEdito
rules.add("New Rule"); rules.add("New Rule");
rules.add("New Regex"); rules.add("New Regex");
rules.add("red"); rules.add("red");
rules.add(true); rules.add("response");
rules.add(true); rules.add("any");
dtm.addRow(rules); dtm.addRow(rules);
} }
}); });
panel_1.add(btnNewRule); panel_1.add(btnNewRule);
JButton btnDeleteRule = new JButton("Delete Rule"); JButton btnDeleteRule = new JButton("Delete");
btnDeleteRule.addActionListener(new ActionListener() { btnDeleteRule.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
int selectRows = table.getSelectedRows().length; int selectRows = table.getSelectedRows().length;
@@ -188,7 +193,7 @@ public class BurpExtender implements IBurpExtender, IHttpListener, IMessageEdito
new Object[][] { new Object[][] {
}, },
new String[] { new String[] {
"Loaded", "Name", "Regex", "Color", "isExtract", "isHighlight" "Loaded", "Name", "Regex", "Color", "Scope", "Action"
} }
)); ));
scrollPane.setViewportView(table); scrollPane.setViewportView(table);
@@ -196,8 +201,8 @@ public class BurpExtender implements IBurpExtender, IHttpListener, IMessageEdito
table.getColumnModel().getColumn(2).setPreferredWidth(172); table.getColumnModel().getColumn(2).setPreferredWidth(172);
table.getColumnModel().getColumn(3).setCellEditor(new DefaultCellEditor(new JComboBox(colorArray))); table.getColumnModel().getColumn(3).setCellEditor(new DefaultCellEditor(new JComboBox(colorArray)));
table.getColumnModel().getColumn(0).setCellEditor(new DefaultCellEditor(new JCheckBox())); table.getColumnModel().getColumn(0).setCellEditor(new DefaultCellEditor(new JCheckBox()));
table.getColumnModel().getColumn(4).setCellEditor(new DefaultCellEditor(new JCheckBox())); table.getColumnModel().getColumn(4).setCellEditor(new DefaultCellEditor(new JComboBox(scopeArray)));
table.getColumnModel().getColumn(5).setCellEditor(new DefaultCellEditor(new JCheckBox())); table.getColumnModel().getColumn(5).setCellEditor(new DefaultCellEditor(new JComboBox(actionArray)));
JLabel lblNewLabel = new JLabel("@EvilChen Love YuChen."); JLabel lblNewLabel = new JLabel("@EvilChen Love YuChen.");
lblNewLabel.setHorizontalAlignment(SwingConstants.CENTER); lblNewLabel.setHorizontalAlignment(SwingConstants.CENTER);
@@ -217,8 +222,8 @@ public class BurpExtender implements IBurpExtender, IHttpListener, IMessageEdito
jsonObj1.put("loaded", (boolean) dtm.getValueAt(i, 0)); jsonObj1.put("loaded", (boolean) dtm.getValueAt(i, 0));
jsonObj1.put("regex", (String) dtm.getValueAt(i, 2)); jsonObj1.put("regex", (String) dtm.getValueAt(i, 2));
jsonObj1.put("color", (String) dtm.getValueAt(i, 3)); jsonObj1.put("color", (String) dtm.getValueAt(i, 3));
jsonObj1.put("extract", (boolean) dtm.getValueAt(i, 4)); jsonObj1.put("scope", (String) dtm.getValueAt(i, 4));
jsonObj1.put("highlight", (boolean) dtm.getValueAt(i, 5)); jsonObj1.put("action", (String) dtm.getValueAt(i, 5));
// 添加数据 // 添加数据
jsonObj.put((String) dtm.getValueAt(i, 1), jsonObj1); jsonObj.put((String) dtm.getValueAt(i, 1), jsonObj1);
} }
@@ -261,27 +266,34 @@ public class BurpExtender implements IBurpExtender, IHttpListener, IMessageEdito
@Override @Override
public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo) { public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo) {
// 判断是否是响应且该代码作用域为REPEATER、INTRUDER、PROXY分别对应toolFlag 64、32、4 // 判断是否是响应且该代码作用域为REPEATER、INTRUDER、PROXY分别对应toolFlag 64、32、4
if (!messageIsRequest && (toolFlag == 64 || toolFlag == 32 || toolFlag == 4)) { if (toolFlag == 64 || toolFlag == 32 || toolFlag == 4) {
byte[] content = messageInfo.getResponse(); JSONObject jsonObj = new JSONObject();
JSONObject jsonObj = matchRegex(content); if (messageIsRequest) {
if (jsonObj.length() > 0) { byte[] content = messageInfo.getRequest();
List<String> colorList = new ArrayList<String>(); try {
Iterator<String> k = jsonObj.keys(); String c = new String(content, "UTF-8").intern();
while (k.hasNext()) { } catch (UnsupportedEncodingException e) {
String name = k.next(); e.printStackTrace();
JSONObject jsonObj2 = new JSONObject(jsonObj.get(name).toString()); }
boolean isHighlight = jsonObj2.getBoolean("highlight"); jsonObj = matchRegex(content, "request", "highlight");
if (isHighlight) { } else {
colorList.add(jsonObj2.getString("color")); byte[] content = messageInfo.getResponse();
} try {
} String c = new String(content, "UTF-8").intern();
if (colorList.size() != 0) { } catch (UnsupportedEncodingException e) {
colorUpgrade(getColorKeys(colorList)); e.printStackTrace();
String color = endColor; }
messageInfo.setHighlight(color); jsonObj = matchRegex(content, "response", "highlight");
} }
List<String> colorList = highlightList(jsonObj);
if (colorList.size() != 0) {
colorUpgrade(getColorKeys(colorList));
String color = endColor;
messageInfo.setHighlight(color);
} }
} }
} }
class MarkInfoTab implements IMessageEditorTab { class MarkInfoTab implements IMessageEditorTab {
@@ -305,8 +317,9 @@ public class BurpExtender implements IBurpExtender, IHttpListener, IMessageEdito
@Override @Override
public boolean isEnabled(byte[] content, boolean isRequest) { public boolean isEnabled(byte[] content, boolean isRequest) {
// 先判断是否是请求,再判断是否匹配到内容 if (isRequest && matchRegex(content, "request", "extract").length() != 0) {
if (!isRequest && matchRegex(content).length() != 0) { return true;
} else if (!isRequest && matchRegex(content, "response", "extract").length() != 0) {
return true; return true;
} }
return false; return false;
@@ -332,29 +345,54 @@ public class BurpExtender implements IBurpExtender, IHttpListener, IMessageEdito
*/ */
@Override @Override
public void setMessage(byte[] content, boolean isRequest) { public void setMessage(byte[] content, boolean isRequest) {
if (content.length > 0 && !isRequest) { try {
String result = ""; String c = new String(content, "UTF-8").intern();
JSONObject jsonObj = matchRegex(content); } catch (UnsupportedEncodingException e) {
if (jsonObj.length() != 0) { e.printStackTrace();
Iterator<String> k = jsonObj.keys(); }
while (k.hasNext()) { if (content.length > 0) {
String name = k.next(); if (isRequest) {
JSONObject jsonObj1 = new JSONObject(jsonObj.get(name).toString()); JSONObject jsonObj = matchRegex(content, "request", "extract");
boolean isExtract = jsonObj1.getBoolean("extract"); if (jsonObj.length() != 0) {
if (isExtract) { String result = extractString(jsonObj);
String tmpStr = String.format("[%s]\n%s\n\n", name, jsonObj1.getString("data")).intern(); markInfoText.setText(result.getBytes());
result += tmpStr; }
} } else {
JSONObject jsonObj = matchRegex(content, "response", "extract");
if (jsonObj.length() != 0) {
String result = extractString(jsonObj);
markInfoText.setText(result.getBytes());
} }
} }
markInfoText.setText(result.getBytes());
} }
currentMessage = content; currentMessage = content;
} }
} }
private String extractString(JSONObject jsonObj) {
String result = "";
Iterator<String> k = jsonObj.keys();
while (k.hasNext()) {
String name = k.next();
JSONObject jsonObj1 = new JSONObject(jsonObj.get(name).toString());
String tmpStr = String.format("[%s]\n%s\n\n", name, jsonObj1.getString("data")).intern();
result += tmpStr;
}
return result;
}
private JSONObject matchRegex(byte[] content) { private List<String> highlightList(JSONObject jsonObj) {
List<String> colorList = new ArrayList<String>();
Iterator<String> k = jsonObj.keys();
while (k.hasNext()) {
String name = k.next();
JSONObject jsonObj2 = new JSONObject(jsonObj.get(name).toString());
colorList.add(jsonObj2.getString("color"));
}
return colorList;
}
private JSONObject matchRegex(byte[] content, String scopeString, String actionString) {
JSONObject tabContent = new JSONObject(); JSONObject tabContent = new JSONObject();
// 正则匹配提取内容 // 正则匹配提取内容
try { try {
@@ -368,12 +406,13 @@ public class BurpExtender implements IBurpExtender, IHttpListener, IMessageEdito
JSONObject jsonObj1 = new JSONObject(jsonObj.get(name).toString()); JSONObject jsonObj1 = new JSONObject(jsonObj.get(name).toString());
JSONObject jsonData = new JSONObject(); JSONObject jsonData = new JSONObject();
String regex = jsonObj1.getString("regex"); String regex = jsonObj1.getString("regex");
boolean isHighligth = jsonObj1.getBoolean("highlight");
boolean isExtract = jsonObj1.getBoolean("extract");
boolean isLoaded = jsonObj1.getBoolean("loaded"); boolean isLoaded = jsonObj1.getBoolean("loaded");
String scope = jsonObj1.getString("scope");
String action = jsonObj1.getString("action");
String color = jsonObj1.getString("color"); String color = jsonObj1.getString("color");
List<String> result = new ArrayList<String>(); List<String> result = new ArrayList<String>();
if(isLoaded) {
if(isLoaded && (scope.equals(scopeString) || scope.equals("any")) && (action.equals(actionString) || action.equals("any"))) {
Pattern pattern = Pattern.compile(regex); Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(contentString); Matcher matcher = pattern.matcher(contentString);
while (matcher.find()) { while (matcher.find()) {
@@ -387,8 +426,6 @@ public class BurpExtender implements IBurpExtender, IHttpListener, IMessageEdito
result.addAll(tmpList); result.addAll(tmpList);
if (!result.isEmpty()) { if (!result.isEmpty()) {
jsonData.put("highlight", isHighligth);
jsonData.put("extract", isExtract);
jsonData.put("color", color); jsonData.put("color", color);
jsonData.put("data", String.join("\n", result)); jsonData.put("data", String.join("\n", result));
jsonData.put("loaded", isLoaded); jsonData.put("loaded", isLoaded);
@@ -541,16 +578,16 @@ public class BurpExtender implements IBurpExtender, IHttpListener, IMessageEdito
boolean loaded = jsonObj1.getBoolean("loaded"); boolean loaded = jsonObj1.getBoolean("loaded");
String regex = jsonObj1.getString("regex"); String regex = jsonObj1.getString("regex");
String color = jsonObj1.getString("color"); String color = jsonObj1.getString("color");
boolean isExtract = jsonObj1.getBoolean("extract"); String scope = jsonObj1.getString("scope");
boolean isHighlight = jsonObj1.getBoolean("highlight"); String action = jsonObj1.getString("action");
// 填充数据 // 填充数据
Vector rules = new Vector(); Vector rules = new Vector();
rules.add(loaded); rules.add(loaded);
rules.add(name); rules.add(name);
rules.add(regex); rules.add(regex);
rules.add(color); rules.add(color);
rules.add(isExtract); rules.add(scope);
rules.add(isHighlight); rules.add(action);
dtm.addRow(rules); dtm.addRow(rules);
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 KiB

After

Width:  |  Height:  |  Size: 698 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 223 KiB