nlp学习3-BERT系列学习

RNN

循环神经网络

编码器和解码器

编码器即把自然语言转化为向量(一般为二维),解码器即把特征向量转化为人类可阅读的语言

Attention

注意力机制到底是什么——基于常识的基本结构介绍 - 李鹏宇的文章 - 知乎

用于自动学习中计算数据对输出数据的贡献大小,即有选择性地处理信号

特征工程就是选择和目标最有关系的特征,减少工作量。一般的特征工程为各个特征赋予固定权值,而注意力机制让模型自己学习如何分配注意力(特征加权)。

注意力机制位于编码器和解码器之间,用于计算各个输出向量对解码器每一个输出的影响程度


$W_1 = (w_{1,1},w_{1,2},w_{1,3},w_{1,4})$

$w_{i,j}=g(y_i,z_j)$

$C_1=\begin{bmatrix}W_1\W_1\W_1\W_1\end{bmatrix}*\begin{bmatrix}z1\z2\z3\z4\end{bmatrix}$

$y_1=f_{de}(c1,y_0)$

直到解码器输出了<end>标签。

让解码器根据自身历史输出计算$y_i$与$Z$的相关性,即可得到$W$矩阵

可以看出,注意力机制增加了额外的运算量,降低了推理速度。

Transformer

BERT大火却不懂Transformer?读这一篇就够了 - 数据汪的文章 - 知乎
https://zhuanlan.zhihu.com/p/54356280

用放大镜看Transformer——总体和各个模块的结构到底是什么样的 - 李鹏宇的文章 - 知乎
https://zhuanlan.zhihu.com/p/122977440

Transformer结构示意图

编码-解码器结构,其中编码器和解码器都利用了类似RNN的时间循环结构。由编码组件、解码组件和它们之间的连接构成。

Transformer会利用上一时刻的输出和当前时刻的输入

编码组件由一堆编码器构成,解码组件由相同数量的解码器构成。

编码器结构上相同,之间未共享参数。每个编码器都可以分解成两个子层:前馈神经网络层和自注意力层。

输入是向量列表,列表大小是可以设置的超参数。

自注意力曾中,各个词有互相连接,互相影响;前馈神经网络曾无相互连接和相互影响,可以并行进行。每一个单词的前馈神经网络完全相同。

自注意力机制会将所有相关单词的理解融入到我们正在处理的单词中。

计算自注意力

  1. 使用输入词嵌入后的向量$X$承三个矩阵生成 查询向量、键向量和值向量$Q,K,V$
  2. 被打分查询向量$K_s$与每一个键向量$Q_i$点积即得到对应分数
  3. 将分数除以维数的平方根,之后使用softmax归一化
  4. 将每个值向量$V_i$乘softmax分数
  5. 对加权值的向量求和

可以用矩阵运算以加快运算速度

这样得到的向量就可以传给前馈网络了

注意力头:使用几个不同的查询/键/值矩阵,计算不同的结果,然后使用一定的方法合并输入到前馈层

使用位置编码表示序列的顺序

使用正弦函数进行位置编码

残差连接

每个编码器中每个子层之间都有一个残差连接,并且都跟随着一个层归一化步骤。相应地,解码器也有这些结构。

解码器

编码器通过处理输入序列开启工作。顶端编码器的输出之后会变转化为一个包含向量K(键向量)和V(值向量)的注意力向量集 。这些向量将被每个解码器用于自身的“编码-解码注意力层”,而这些层可以帮助解码器关注输入序列哪些位置合适

解码器最终输出一个实数,然后会进行线性变换,再到softmax层W

GPT

GPT——生成式预训练Transformer - 李鹏宇的文章 - 知乎
https://zhuanlan.zhihu.com/p/125139937

BERT

基于Transformer的双向编码器表示(BERT)——结构和训练 - 李鹏宇的文章 - 知乎
https://zhuanlan.zhihu.com/p/125655365

linux常用命令

通用相关

alias

1
alias alia="cmd"

为某个命令指定别名,可以带参数

1
alias gnome-screenshot="gnome-screenshot -i"

让截图工具默认以交互方式截图而不是直接截取全屏

arander图形化的显示器调整工具

xrander命令行显示器调整

latex相关

1
2
pacman -S texlive-most texlive-lang
yay -S texlive-local-manager-git # 包管理器

i3 相关

1
2
# Moving workspaces between screens
bindsym $mod+p move workspace to output right

这条配置可以更改workspace所在屏幕

hws冬令营选拔赛部分wp

就做了一道题拿了1血,后面就忘记做了,复现学到东西的题

[rev]babyre

hook了一个从ring3到ring0的函数,所以v9调用的其实是自己的函数。

通过调试没找到那个函数,只能在函数列表一个一个寻找(也可以过滤函数长度,找了一下不是库函数就是直接jmp的函数,所以应该很容易过滤)

找到了sub_412A20,其中有ZwSetInformatinoThread反调试,这个函数如果执行到最后恰好返回0x7ff,我tm直接nop反调试

然后加载了一个资源文件并且解密,然后是sm4加密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

import hashlib
import base64
import marshal
import dis
from Crypto.Cipher import AES
from Crypto.Util.number import long_to_bytes,bytes_to_long
from pysm4 import decrypt

cipher = bytes([234, 99, 88, 183, 140, 226, 161, 233, 197, 41, 143, 83, 232, 8, 50, 89, 175, 27, 103, 174, 217, 218, 207, 196, 114, 255, 177, 236, 118, 115, 243, 6])
key = b'Ez_5M4_C1pH@r!!!'
p = decrypt(bytes_to_long(cipher[:16]),bytes_to_long(key))
p2 = decrypt(bytes_to_long(cipher[16:]),bytes_to_long(key))
print(hex(p),hex(p2))
print(long_to_bytes(p)+long_to_bytes(p2))

[rev]Enigma

异常虚拟机

通过SetUnhandledExceptionFilter进行了无效指令的处理和反调试,SetUnhandledExceptionFilter主要通过ZwQueryInformationProcess查询调试器,可以patch掉ZwQueryInformationProcess的代码,具体是在ZwQueryInformationProcess下断点,断下时如果第二个参数为7,infoClass=7,就回溯到调用者函数,把je改为jmp

https://www.cnblogs.com/zpchcbd/p/12079166.html

反汇编脚本

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
50
51
52
53

import hashlib
import base64
import marshal
import dis

opcs = [85, 139, 236, 83, 86, 87, 104, 48, 22, 64, 0, 255, 21, 0, 32, 68, 0, 199, 255, 4, 1, 0, 51, 201, 131, 249, 32, 125, 23, 199, 255, 0, 1, 17, 199, 255, 4, 1, 31, 137, 4, 141, 112, 122, 69, 0, 199, 255, 2, 3, 235, 228, 51, 201, 131, 249, 32, 125, 45, 139, 28, 141, 112, 122, 69, 0, 139, 20, 141, 116, 122, 69, 0, 138, 130, 76, 122, 69, 0, 136, 131, 224, 121, 69, 0, 138, 131, 76, 122, 69, 0, 136, 130, 224, 121, 69, 0, 199, 255, 0, 3, 2, 235, 206, 51, 201, 131, 249, 32, 125, 53, 138, 153, 224, 121, 69, 0, 199, 255, 4, 2, 31, 199, 255, 7, 2, 3, 139, 241, 70, 131, 230, 31, 138, 150, 224, 121, 69, 0, 128, 226, 224, 129, 226, 255, 0, 0, 0, 199, 255, 8, 4, 5, 10, 218, 136, 153, 4, 122, 69, 0, 65, 235, 198, 160, 4, 122, 69, 0, 162, 40, 122, 69, 0, 185, 1, 0, 0, 0, 131, 249, 32, 125, 40, 138, 153, 4, 122, 69, 0, 139, 241, 199, 255, 3, 5, 50, 158, 4, 122, 69, 0, 139, 241, 199, 255, 4, 5, 3, 50, 158, 240, 104, 69, 0, 136, 153, 40, 122, 69, 0, 65, 235, 211, 95, 94, 91, 93, 195]
ip = 0
start=True
regs = ['invalid','eax','ebx','ecx','edx','esi']
while ip<len(opcs):
rip = ip+0x4018f0
dip = 0
if opcs[ip]==0xc7 and ip+1<len(opcs) and opcs[ip+1]==0xff:
opc = opcs[ip+2]
op1 = opcs[ip+3]
op2 = opcs[ip+4]
print('%08x:'%rip,end="")

if opc==0:
dip=5
print('add %s,%d'%(regs[op1],op2))
elif opc==1:
dip=5
print('sub %s,%d'%(regs[op1],op2))
elif opc==2:
dip=4
print('inc %s'%regs[op1])
elif opc==3:
print('dec %s'%regs[op1])
dip=4
elif opc==5:
print('and %s,%d'%(regs[op1],op2))
dip=5
elif opc==6:
print('or %s,%d'%(regs[op1],op2))
dip=5
elif opc==7:
print('shl %s,%d'%(regs[op1],op2))
dip=5
elif opc==8:
print('shr %s,%d'%(regs[op1],op2))
dip=5
else:
print('nop')
dip=2
start=True
else:
if start:
print('%08x:%x:valid code'%(rip,opcs[ip]))
start=False
dip=1
ip+=dip

patch程序方法:可以先都patch成0x90,这样就可以patch 汇编了。更好的方法当然是使用一个汇编引擎(已知pwntools)

patch之后就可以直接f5了(~ ̄▽ ̄)~(~ ̄▽ ̄)~(~ ̄▽ ̄)~(~ ̄▽ ̄)~(~ ̄▽ ̄)~

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
char sub_4018F0()
{
int v0; // eax
int i; // ecx
int j; // ecx
int v3; // ebx
int v4; // edx
int k; // ecx
char result; // al
int l; // ecx

SetUnhandledExceptionFilter(TopLevelExceptionFilter);
LOBYTE(v0) = 0;
for ( i = 0; i < 32; ++i )
{
v0 = ((_BYTE)v0 + 17) & 0x1F;
dword_457A70[i] = v0;
}
for ( j = 0; j < 32; j += 2 )
{
v3 = dword_457A70[j];
v4 = dword_457A74[j];
byte_4579E0[v3] = byte_457A4C[v4];
byte_4579E0[v4] = byte_457A4C[v3];
}
for ( k = 0; k < 32; ++k )
byte_457A04[k] = ((unsigned __int8)(byte_4579E0[((_BYTE)k + 1) & 0x1F] & 0xE0) >> 5) | (8 * (byte_4579E0[k] & 0x1F));
result = byte_457A04[0];
byte_457A28[0] = byte_457A04[0];
for ( l = 1; l < 32; ++l )
byte_457A28[l] = aBier[l & 3] ^ byte_457A04[l - 1] ^ byte_457A04[l];
return result;
}

首先是一个群上面的移位,然后整体循环左移3位,最后是简单的异或处理

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

import hashlib
import base64
import marshal
import dis
import unicorn

s = '938b8f431268f7907a4b6e421301b42120738d68cb19fcf8b26bc4abc89b8d22'
arr = [int(s[i:i+2],base=16) for i in range(0,len(s),2)]
xord = [i for i in b'Bier']
for i in range(1,len(arr)):
arr[i]=arr[i]^arr[i-1]^xord[i%4]
narr = [0]*len(arr)
for i in range(len(arr)):
narr[i]=(arr[i]>>3)|(arr[(i-1)&0x1f]<<5)
narr[i]&=0xff
ind = [17]+[0]*31
print(bytes(narr))
for i in range(1,31):
ind[i]=(ind[i-1]+17)&0x1f
for i in range(0,len(narr),2):
tmp = narr[ind[i]]
narr[ind[i]]=narr[ind[i+1]]
narr[ind[i+1]]=tmp
print(bytes(narr))

[rev]child_protect

既然是child_protect,直接找CreateProcess,交叉引用找到了父进程主函数sub_413670,然后pid交叉引用找到了DebugActiveProcess,sub_413D10下面是主进程对子进程进行处理的函数sub_411415

查了一下GetLastError的ErrorCode,183恰好是文件已存在,也就是说父子进程通过这个判断自己的身份

根据父进程patch子进程,得到流程是先异或再byteswap再tea再byteswap

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>
#include <math.h>
// #include "defs.h"
// #include <setimp.h>
int key[8];
void printArray(const char *name,uint8_t *v,size_t len)
{
printf("========%s=========\n",name);
for(size_t i=0;i<len;i++){
printf("0x%02X,",v[i]);
}
printf("\n=================\n");
}
int sub_412FB0()
{
int result;
// eax
int k;
// [esp+D0h] [ebp-E4h]
int j;
// [esp+DCh] [ebp-D8h]
int i;
// [esp+E8h] [ebp-CCh]
int v4[10];
// [esp+11Ch] [ebp-98h]
int v5[10];
// [esp+144h] [ebp-70h]
int v6[8];
// [esp+16Ch] [ebp-48h]
int v7[9];
// [esp+18Ch] [ebp-28h]
v6[0] = 2130706575;
v6[1] = 1799636093;
v6[2] = 1435019980;
v6[3] = -1351037338;
v6[4] = -1720747942;
v6[5] = -212893566;
v6[6] = -1812289297;
v6[7] = -381726905;
v7[0] = -449338949;
v7[1] = 1166434102;
v7[2] = -1917020786;
v7[3] = -603841535;
v7[4] = 1193330650;
v7[5] = -295274829;
v7[6] = 2021562342;
v7[7] = -1511690567;
v5[0] = -557566690;
v5[1] = 854106805;
v5[2] = 827418122;
v5[3] = 2071911752;
v5[4] = 1115606310;
v5[5] = 452882457;
v5[6] = 1918099577;
v5[7] = 421130192;
v4[0] = 445079886;
v4[1] = 2056584266;
v4[2] = -357270445;
v4[3] = 1148562038;
v4[4] = 1253939174;
v4[5] = 1597303268;
v4[6] = -598138600;
v4[7] = -13445532;
result = 0;
for ( i = 0; i < 16; ++i )
{
key[i % 8] += v6[i] ^ 0x54647484;
for ( j = 0; j < 8; ++j )
{
key[j] = (v7[j] ^ v6[j]) << j;
v6[j] += v7[j] ^ v5[j];
v7[j] -= v6[j] ^ v5[7 - j];
}
key[i % 8] += v5[i % 8] ^ (v4[i % 8] + v6[i]);
for ( k = 0; k < 8; ++k )
{
v5[k] *= v4[k];
v4[k] += v5[k];
}
result = i + 1;
}
return result;
}
void encrypt (uint32_t* v, uint32_t* k)
{
uint32_t v0=v[0], v1=v[1], sum=0, i;
/* set up */
uint32_t delta=0x9e3779b9;
/* a key schedule constant */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3];
/* cache key */
for (i=0; i < 32; i++)
{
/* basic cycle start */
sum += delta;
v0 += ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
v1 += ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
}
/* end cycle */
v[0]=v0;
v[1]=v1;
}
void decrypt (uint32_t* v, uint32_t* k)
{
uint32_t v0=v[0], v1=v[1], sum=0xC6EF3720, i;
/* set up */
uint32_t delta=0x9e3779b9;
/* a key schedule constant */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3];
/* cache key */
for (i=0; i<32; i++)
{
/* basic cycle start */
v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
sum -= delta;
}
/* end cycle */
v[0]=v0;
v[1]=v1;
}
uint32_t byteSwap(uint32_t x){
return ((x>>8)&0xff00)|((x<<8)&0xff0000)|((x>>24)&0xff)|((x<<24)&0xff000000);
}
int main()
{
sub_412FB0();
// printArray("key",(uint8_t*)key,32);
printf("0x%08x,0x%08x,0x%08x,0x%08x\n",key[0],key[1],key[2],key[3]);
uint8_t v3[33];
v3[0] = 237;
v3[1] = 233;
v3[2] = 139;
v3[3] = 59;
v3[4] = 210;
v3[5] = 133;
v3[6] = 231;
v3[7] = 235;
v3[8] = 81;
v3[9] = 22;
v3[10] = 80;
v3[11] = 122;
v3[12] = 177;
v3[13] = 220;
v3[14] = 93;
v3[15] = 9;
v3[16] = 69;
v3[17] = 174;
v3[18] = 185;
v3[19] = 21;
v3[20] = 77;
v3[21] = 141;
v3[22] = 255;
v3[23] = 80;
v3[24] = 222;
v3[25] = 224;
v3[26] = 188;
v3[27] = 139;
v3[28] = 155;
v3[29] = 188;
v3[30] = 254;
v3[31] = 225;
uint32_t v[2];
uint32_t k[4];
uint32_t xord = 0x73FF8CA6;
uint32_t dif = 0x50FFE544;
for(size_t i=0;i<32;i+=8){
v[0]=byteSwap(*(uint32_t*)&v3[i]);
v[1]=byteSwap(*(uint32_t*)&v3[i+4]);
printf("0x%08x,0x%08x\n",v[0],v[1]);
decrypt(v,key);
v[0]^=xord;
xord-=dif;
v[1]^=xord;
xord-=dif;
printArray("v",(uint8_t*)v,8);
*(uint32_t*)&v3[i]=byteSwap(v[0]);
*(uint32_t*)&v3[i+4]=byteSwap(v[1]);
}
printf("%s\n",v3);
return 0;
}

网络爬虫入门

主要使用requests和BeautifulSoup库,BeautifulSoup使用CSS过滤器

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
50
51
52
53
54
55
56
57
58
59
60
61
# encoding=utf-8
import requests
import json
from bs4 import BeautifulSoup
# from bs4.element import Tag
import pandas as pd
import time

def getOk(base):
print(base)
res = []
response = requests.get(base)
soup = BeautifulSoup(response.text,'lxml')
for i in soup.select('.zg_spbt'):
c = i.children
next(c)
aTag = next(c)
href = aTag.get('href')
title = aTag.get_text()
# print(href,title)
response1 = requests.get(href)
response1.encoding='gbk'
soup = BeautifulSoup(response1.text,'lxml')
title = title.replace('\n','',-1)
title = title.replace('\r','',-1)
scrText = soup.select('.zg_list_zi')[0].get_text()
if ('人力' in scrText or '人事' in scrText) and ('本科' in scrText or ('硕士' not in scrText and '研究生' not in scrText)):
print(href,title)
res.append({
'链接':href,
'title':title,
'文章过短需要二次查看':False,
'文章长度':len(scrText)
})
elif len(scrText)<1000 and '研究生' not in scrText and '硕士' not in scrText: # 有的文章只配了图片
print(href,title)
res.append({
'链接':href,
'title':title,
'文章过短需要二次查看':True,
'文章长度':len(scrText)
})
time.sleep(0.5)
return res
def main():
preUrl = 'http://www.zggqzp.com/zpxx/'
pagesUrl = ['60_60_2_0.html']
res = []
for i in range(2,9): # 2020,2021招聘信息只有前8页有
pagesUrl.append('60_60_2_%d.html'%i)
for i in pagesUrl:
res+=getOk(preUrl+i)
print(res)
# print(minLen,minHref)
df = pd.DataFrame(res)
df.to_excel('work.xlsx',columns=['链接','title','文章过短需要二次查看','文章长度'])
if __name__=='__main__':
main()
print('ok')
# for i in soup.select('.zg_spsj'):
# print(i)

情感分析论文阅读

Emotion Recognition by Textual Tweets Classification Using Voting Classifier (LR-SGD)

其中tokenize后每个词用一个向量表示,一个句子是一个二维矩,使用TF-IDF提取出句子中的关键词,所以训练的时候输入到每个句子中就是一个一维向量

最后投票的方式就是认为是正面的概率两个模型求平均,认为是负面的求平均,就实现了投票操作

NLP入门笔记阅读

https://github.com/NLP-LOVE/Introduction-NLP/blob/master/chapter/

中文分词方法

  • 基于词典的分词方法
    1. 正向最长匹配
    2. 逆向最长匹配
    3. 双向最长匹配
    4. 字典树,DAT最长匹配

去掉无意义的词可以优化分词效果

  • 基于机器学习的分词方法
    1. 隐含马尔科夫模型
    2. 基于结构化感知机的中文分词
    3. 条件随机场模型

词性标注

序列标注模型

命名实体识别

序列标注模型

数据集可以区tweeter上爬取,貌似有官方API。数据集进行了一些预处理,去除了无关紧要的符号和链接等。

测试数据集参考文献.M. Mohammad, F. Bravo-Marquez, M. Salameh, S. Kiritchenko, SemEval-
2018 task 1: Affect in tweets, in: Proceedings of International Workshop
on Semantic Evaluation (SemEval-2018), New Orleans, LA, USA, 2018.

采用Best–Worst Scaling方法对数据进行手动标注,避免数据内部和数据之间的矛盾。但是还有个强度量使用0-1之间的数值进行标注。

训练时数据集分三份,一份训练,一份development(用于评估模型的性能),一份test-gold,test-gold用于排名各个队伍。

使用Sentence BERT提取属性,使用CrystalFeel情感分析。优于一元分词(一个字看成一个向量)和二元分词(使用条件概率进行分词)。

使用Pearson correlations(皮尔逊相关系数)评测模型准确率。

这篇论文还测试了逻辑回归、随机森林、SVM等情感分类方法。对于葡萄牙语,另外使用了使用朴素贝叶斯,多层感知器,梯度上升等方式。

使用GSDMM识别topic,给出一个topic集合。一个消息只能有一个topic。topic分类的同时指出句子中关于这个topic的单词。

fastText文本分类方法。

架构

Attitude of Chinese public towards municipal solid waste sorting policy: A text mining study

有用的参考文献

Liu, X., Hu, W., 2019. Attention and sentiment of Chinese public toward green buildings based on Sina Weibo. Sustain. Cities Soc. 44, 550–558. https://doi.org/10.1016/j.scs.2018.10.047

Wang, Y., Li, H., Wu, Z., 2019. Attitude of the Chinese public toward off-site construction: a text mining study. J. Clean. Prod. 238, 117926. https://doi.org/10.1016/j.jclepro.2019.117926

语言处理步骤

  1. R 3.5.3(R语言)对数据进行去噪处理
  2. jiebaR用于分词
  3. NLPIR-Parser用于情感分析
  4. LDA topic model用于提取话题,LDAvis包用于可视化结果

What factors affect consumers’ dining sentiments and their ratings: Evidence from restaurant online review data

使用软件Linguistic Inquiry and Word Count (LIWC)进行分词、还原词根等预处理操作

没有正则化文本,而是正则化了情感分析的结果数值

使用lexion-based方法提取关键词

Pennebaker, J. W., & Francis, M. E. (1996). Cognitive, emotional, and language processes in disclosure. Cognition & Emotion, 10(6), 601–626

通过寻找表达情感的词的数量来进行情感分析,字典也是用LIWC的。$\frac{积极/消极情感词数量}{单词总量}$

语种识别技术–知乎

https://zhuanlan.zhihu.com/p/165863653

  1. out-of-space
  2. 朴素贝叶斯
  3. 多类别逻辑回归

非技术也能看懂的 NLP 入门科普.pdf

https://easyai.tech/ai-definition/

NLP两个任务:NLU(机器理解人),NLG(人理解机器)

NLU

NLU处理流程

传统
语料预处理>特征工程>选择分类器

深度学习
预料预处理>设计模型>训练模型

英文语料预处理

  1. 分词 Tokenization
  2. 词干提取-Stemming cites->city/children->child,推荐Snowball
  3. 词性还原-Lemmatization doing/did/does->do,可以用NLTK的WordNet
  4. 词性标注-Parts of Speech
  5. 命名实体识别-NER
  6. 分块-Chunking

英文语料预处理

  1. 分词
  2. 词性标注
    • 规则
    • 统计
    • 规则+统计
    • 基于深度学习
    • Jieba,SnowNLP,THULAC,StandfordCoreNLP,HanLP,NLTK,SpaCy
  3. 命名实体识别
  4. 去除停用词

Transformer是目前NLP最先进的方法,用于语义特征提取

分词是非结构化数据结构化的第一步,词是表达完整含义的最小单位,字含的信息太少,句子信息量太大。

中英文分词有一定的区别

分词方法

  1. 基于词典
  2. 基于统计
  3. 基于深度学习
    中文分词工具star排名
  4. Hanlp
  5. Stanford分词
  6. ansj分词
  7. 哈工大LTP
  8. KCWS分词
  9. jieba
  10. IK
  11. 清华THULAC
  12. ICTCLAS
    英文分词工具排名
  13. Keras
  14. Spacy
  15. Gensim
  16. NLTK

NLG

步骤

  1. 内容确定
  2. 文本结构
  3. 句子聚合
  4. 语法化
  5. 参考表达生成
  6. 语言实现

还有个停用词去除,即去除无关紧要的词。

Linux ptrace注入教程

注入技术在外挂、热修复技术中非常常见,由于Android使用Linux内核,所以先实现Linux下ptrace注入,后面再逐步改为Android版本

被注入的程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <dlfcn.h>
#include <sys/mman.h>
#include <unistd.h>

int main(){
printf("HelloWorld\n");
//mmap(0,0x1000,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_ANONYMOUS|MAP_PRIVATE,0,0);
printf("pid:%d\n",getpid());
printf("mmap:%p\ndlopen%p\ndlclose%p\ndlsym%p\ndlerror%p\n",mmap,dlopen,dlclose,dlsym,dlerror);
int a;
while(1){
scanf("%d",&a);
printf("%d\n",a);
// sleep(1);
}
return 0;
}

这里打印各个函数的地址,便于出问题的时候debug

要注入的so文件

1
2
3
4
5
6
7
8
#include <stdio.h>

void testEntry(){
FILE *f = fopen("/tmp/test.txt","w");
fputs("Ok to load",f);
fclose(f);

}

使用下面的命令编译为so文件

1
gcc -o libtest.so test.c -fPIC -shared

注入器

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <sys/ptrace.h>
#include <sys/user.h>
#include <memory.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <dlfcn.h>
#include <sys/ptrace.h>
#include <dirent.h>
#include <sys/types.h>
#include <string.h>
#define MAX_PATH 0x512
char libcPath[]="/usr/lib/libc-2.32.so";

void ptraceWriteData(pid_t pid,void*addr,const char*data,size_t len){
size_t i=0;
for(;i<len;i+=sizeof(long)){
ptrace(PTRACE_POKETEXT,pid,addr+i,(void*)*(long*)&data[i]);
}
// 每次写sizeof(long)字节,直接对齐
}
void ptraceReadData(pid_t pid,void*addr,char*data,size_t len){
size_t i=0;
long rdata;
for(;i<len;i+=sizeof(long)){
rdata=ptrace(PTRACE_PEEKTEXT,pid,addr+i,NULL);
*(long*)&data[i]=rdata;
}
}
void ptraceAttach(pid_t pid){
if(ptrace(PTRACE_ATTACH,pid,NULL,NULL)==-1){
printf("[INJECT F]Failed to attach:%d\n",pid);
}
int stat=0;
waitpid(pid,&stat,WUNTRACED);
}
void ptraceGetRegs(pid_t pid,struct user_regs_struct*regs_addr){
if(ptrace(PTRACE_GETREGS,pid,NULL,(void*)regs_addr)==-1){
printf("Get regs error\n");
}
}
void ptraceSetRegs(pid_t pid,struct user_regs_struct*regs_addr){
if(ptrace(PTRACE_SETREGS,pid,NULL,(void*)regs_addr)==-1){
printf("Set regs error\n");
}
}
void ptraceDetach(pid_t pid){
ptrace(PTRACE_DETACH,pid,NULL,NULL);
}
void ptraceContinue(pid_t pid){
if(ptrace(PTRACE_CONT,pid,NULL,NULL)==-1){
printf("ptrace continue error\n");
}
}
void *getModuleBaseAddr(pid_t pid,const char*moduleName){
if(pid==-1)pid=getpid();
// 通过解析/proc/pid/maps 获得基址
char filepath[MAX_PATH];
void*moduleBaseAddr=NULL;
snprintf(filepath,MAX_PATH,"/proc/%d/maps",pid);
FILE *f = fopen(filepath,"r");
char line[MAX_PATH];
char base[MAX_PATH],name[MAX_PATH];
size_t cnt,start;
while(!feof(f)){
memset(line,0,MAX_PATH);
memset(name,0,MAX_PATH);
fgets(line,MAX_PATH,f);
cnt=0;
while(line[cnt]!='/')cnt++;
start=cnt;
while(line[cnt]){
name[cnt-start]=line[cnt];
cnt++;
}
name[cnt-start-1]=0;

if(strncmp(name,moduleName,MAX_PATH))continue;
memset(base,0,MAX_PATH);
cnt=0;
while(line[cnt]!='-'){
base[cnt]=line[cnt];
cnt++;
}
base[cnt]=0;
sscanf(base,"%llx",(long long*)(&moduleBaseAddr));
printf("[INJECT] GotBaseAddr %p of %s\n",moduleBaseAddr,moduleName);
break;
}
fclose(f);
return moduleBaseAddr;
}
void *getRemoteFuncAddr(pid_t pid,const char *moduleName,void *localFuncAddr){
void *localModuleAddr,*remoteModuleAddr,*remoteFuncAddr;
// 通过计算指定函数的偏移量获取目标函数地址
localModuleAddr = getModuleBaseAddr(-1,moduleName);
remoteModuleAddr = getModuleBaseAddr(pid,moduleName);
remoteFuncAddr=localFuncAddr-localModuleAddr+remoteModuleAddr;
printf("[INJECT] GotFuncAddr:%p\n",remoteFuncAddr);
return remoteFuncAddr;
}
int ptraceCall(pid_t pid,void* funcAddr,long*paras,long paraLen,struct user_regs_struct *regs){

// 多于6个参数通过栈传递
regs->rsp-=sizeof(void*);
size_t errRet=0;
// 返回地址弄成0导致错误停止
ptraceWriteData(pid,(void*)regs->rsp,(char*)&errRet,sizeof(void*));
if(paraLen>6){
regs->rsp-=(paraLen-6)*sizeof(long);
ptraceWriteData(pid,(void*)regs->rsp,(char*)&paras[6],sizeof(long)*(paraLen-6));
}
// 前6个参数通过寄存器传递
switch(paraLen){
case 6:
regs->r9=paras[5];
case 5:
regs->r8=paras[4];
case 4:
regs->rcx=paras[3];
case 3:
regs->rdx=paras[2];
case 2:
regs->rsi=paras[1];
case 1:
regs->rdi=paras[0];
break;

}
// 调用函数
regs->rip=(unsigned long long)funcAddr;
ptraceSetRegs(pid,regs);
int stat=0;
while(stat!=0xb7f){
ptraceContinue(pid);
waitpid(pid,&stat,WUNTRACED);
printf("[INJECT] substatus: %x\n",stat);
}

ptraceGetRegs(pid,regs);
return 0;
}
void inject(pid_t pid,const char*libname,const char*funcName){
struct user_regs_struct oldRegs;
struct user_regs_struct regs;
long paras[6];
char realLibPath[MAX_PATH];
realpath(libname,realLibPath);
printf("[INJECT]Real path of lib found:%s\n",realLibPath);

ptraceAttach(pid);
// 保存寄存器环境
ptraceGetRegs(pid,&oldRegs);
memcpy(&regs,&oldRegs,sizeof(struct user_regs_struct));
// 获取mmap地址
void *mmapAddr = getRemoteFuncAddr(pid,libcPath,mmap);
// 调用mmap
paras[0]=0;
paras[1]=0x1000;
paras[2]=PROT_READ|PROT_WRITE|PROT_EXEC;
paras[3]=MAP_ANONYMOUS|MAP_PRIVATE;
paras[4]=0;
paras[5]=0;
ptraceCall(pid,mmapAddr,paras,6,&regs);
void *remoteMemAddr=(void*)regs.rax;
printf("[INJECT] remote mmaped addr:%p\n",remoteMemAddr);
// 调用dlopen
void *dlopenAddr=getRemoteFuncAddr(pid,libcPath,dlopen);
void *dlcloseAddr=getRemoteFuncAddr(pid,libcPath,dlclose);
void *dlErrorAddr=getRemoteFuncAddr(pid,libcPath,dlerror);
ptraceWriteData(pid,remoteMemAddr,realLibPath,strlen(realLibPath)+1); // 作为dlopen的参数
//debug start
char buf[MAX_PATH];
memset(buf,0,MAX_PATH);
ptraceReadData(pid,remoteMemAddr,buf,strlen(realLibPath)+3);
printf("%s\n",buf);
//debug end
paras[0]=(long)remoteMemAddr;
paras[1]=RTLD_NOW|RTLD_GLOBAL;
ptraceCall(pid,dlopenAddr,paras,2,&regs);
void*libBaseAddr=(void*)regs.rax;
printf("[INJECT]remote lib base:0x%llx of %s\n",(long long)libBaseAddr,realLibPath);
// 调用dlsym
void*dlsymAddr=getRemoteFuncAddr(pid,libcPath,dlsym);
ptraceWriteData(pid,remoteMemAddr,funcName,strlen(funcName)+1);
//debug start
memset(buf,0,MAX_PATH);
ptraceReadData(pid,remoteMemAddr,buf,strlen(funcName)+3);
printf("%s\n",buf);
//debug end
paras[0]=(long)libBaseAddr;
paras[1]=(long)remoteMemAddr;
ptraceCall(pid,dlsymAddr,paras,2,&regs);
void*remoteFuncAddr = (void*)regs.rax;
printf("[INJECT]addr:0x%llx of func %s\n",(long long)remoteFuncAddr,funcName);
// 调用目标函数
ptraceCall(pid,remoteFuncAddr,paras,0,&regs);
// 恢复寄存器环境
ptraceSetRegs(pid,&oldRegs);
ptraceDetach(pid);
}
pid_t findPIdByName(const char *name){
pid_t pid = -1;
int pDirId = 0; // process dir id
FILE *f;
char filename[MAX_PATH];
char cmdline[MAX_PATH];
struct dirent *entry=NULL;
int cnt;
if(name==NULL){
return -1;
}
DIR *dir = opendir("/proc");
if(dir==NULL){
return -1;
}
while((entry=readdir(dir))!=NULL){
pDirId=atoi(entry->d_name);
if(pDirId!=0){
snprintf(filename,MAX_PATH,"/proc/%d/cmdline",pDirId);
f = fopen(filename,"r");
if(f){
fgets(cmdline,sizeof(cmdline),f);
cnt = (int)strlen(cmdline);
while(cnt>=0&&cmdline[cnt]!='/')cnt--; // cmdline是完整路径,这里只找最后一个/之后的,就是可执行文件的文件名
cnt++;
if(!strncmp(name,&cmdline[cnt],strlen(name))){
pid=pDirId;
break;
}
}
fclose(f);
}
}
closedir(dir);
return pid;
}

int main(int argc,char **argv){
if(argc!=4){
printf("usage: inject processname libexample.so funcname\n");
return 0;
}
pid_t pid = findPIdByName(argv[1]);
inject(pid,argv[2],argv[3]);
return 0;
}

参考

https://xz.aliyun.com/t/5361?spm=5176.12901015.0.i12901015.2ad3525c5zq8Kz 一直跟着这个教程学的

https://man7.org/linux/man-pages/ linux的手册真的挺详细的

多语种语料库搜集

github

https://github.com/qhungngo/EVBCorpus
来自于 https://sites.google.com/a/uit.edu.vn/hungnq/evbcorpus

https://github.com/facebookresearch/XNLI

XNLI

https://github.com/facebookresearch/XNLI

https://www.aclweb.org/anthology/D18-1269/

XNLI 将 NLI 数据集扩展到 15 种语言,包括英语、法语、西班牙语、德语、希腊语、保加利亚语、俄语、土耳其语、阿拉伯语、越南语、泰语、中文、印地语、斯瓦希里语和乌尔都语,并以 NLI 的三分类格式为每种语言分别提供了 7500 个经人工标注的开发和测试实例,合计 112500 个标准句子对

CSDN下载

https://download.csdn.net/download/weixin_41781408/11161362

新闻网站

有些国家提供了中越双语的网页,可以爬取

https://zh.vietnamplus.vn/

https://www.bing.com/search?q=%E4%B8%AD%E6%96%87+site%3Avn&mkt=zh-CN

http://cn.news.chinhphu.vn/

付费

http://catalog.elra.info/en-us/repository/browse/ELRA-W0124/

免费

http://opus.nlpl.eu/Wikipedia.php

https://www.kaggle.com/search?q=Vietnam

这个可以搜索其他的

http://opus.nlpl.eu/

这个网站所好像有所有语言的语料库,有点不懂,难道是自己翻译的?

http://www.language-archives.org/

这个网站也是所有语言都有,但是没看懂怎么下载

http://www.arts.chula.ac.th/ling/tnc/

泰国国家语料库,泰语。 来自于 http://pioneer.chula.ac.th/~awirote/ling/corpuslst.htm

Android下实现ptrace注入&hook

思路

  1. 通过目标进程名找到进程PID
  2. 将so注入到进程空间
  3. 如果hook Native层函数,则有IAT hook,调试hook,inline hook,hotfix hook等多种手段;如果是Java层hook,可以使用反射机制

使用ndk编译linux原生应用程序

建立工程文件夹,工程文件夹内建立jni文件夹

进入jni文件夹

创建源文件,Android.mk,Application.mk

Android.mk

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 一个Android.mk file首先必须定义好LOCAL_PATH变量。
# 它用于在开发树中查找源文件。在这个例子中,宏函数’my-dir’,
# 由编译系统提供,用于返回当前路径(即包含Android.mk file文件的目录)。
LOCAL_PATH := $(call my-dir)
# CLEAR_VARS由编译系统提供,
# 指定让GNU MAKEFILE为你清除许多LOCAL_XXX变量(例如 LOCAL_MODULE, LOCAL_SRC_FILES, LOCAL_STATIC_LIBRARIES, 等等...),除LOCAL_PATH 。这是必要的,
# 因为所有的编译控制文件都在同一个GNU MAKE执行环境中,所有的变量都是全局的。
include $(CLEAR_VARS)
# LOCAL_MODULE变量必须定义,以标识你在Android.mk文件中描述的每个模块。名称必须是唯一的,而且不包含任何空格。
# 注意编译系统会自动产生合适的前缀和后缀,换句话说,一个被命名为'foo'的共享库模块,将会生成'libfoo.so'文件。
LOCAL_MODULE := hello-jni
# LOCAL_SRC_FILES变量必须包含将要编译打包进模块中的C或C++源代码文件。注意,你不用在这里列出头文件和包含文件,
# 因为编译系统将会自动为你找出依赖型的文件;仅仅列出直接传递给编译器的源代码文件就好。
LOCAL_SRC_FILES := hello-jni.c
# BUILD_EXECUTABLE 表示以一个可执行程序的方式进行编译
# BUILD_SHARED_LIBRARY 表示动态链接库的方式进行编译
include $(BUILD_EXECUTABLE)

fpid是你的目标文件名,1.c是源文件名

Application.mk

1
2
3
4
#### APP_ABI := arm64-v8a 后面接的是需要生成的.so平台文件,
#### 正常手机使用arm64处理器的即可。 all是全平台
#### APP_PLATFORM :=后面接的是使用SDK的最低等级
APP_ABI := all

然后进入工程目录执行

1
ndk-build

在libs目录下可以找到编译好的文件,使用adb push命令可以送到手机

查找PID

主要是利用了linux的文件系统,/proc存储着进程有关信息

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
50
51
52
53
54
55
#include <stdio.h>
#include <sys/ptrace.h>
#include <dirent.h>
#include <sys/types.h>
#include <string.h>
#include <stdlib.h>
#include <inttypes.h>
#define MAX_PATH 256

pid_t findPIdByName(const char *name){
pid_t pid = -1;
int pDirId = 0; // process dir id
FILE *f;
char filename[MAX_PATH];
char cmdline[MAX_PATH];
struct dirent *entry=NULL;
int cnt;
if(name==NULL){
return -1;
}
DIR *dir = opendir("/proc");
if(dir==NULL){
return -1;
}
while((entry=readdir(dir))!=NULL){
pDirId=atoi(entry->d_name);
if(pDirId!=0){
snprintf(filename,MAX_PATH,"/proc/%d/cmdline",pDirId);
f = fopen(filename,"r");
if(f){
fgets(cmdline,sizeof(cmdline),f);
cnt = (int)strlen(cmdline);
while(cnt>=0&&cmdline[cnt]!='/')cnt--; // cmdline是完整路径,这里只找最后一个/之后的,就是可执行文件的文件名
cnt++;
if(!strncmp(name,&cmdline[cnt],strlen(name))){
pid=pDirId;
break;
}
}
fclose(f);
}
}
closedir(dir);
return pid;
}
int main(int argc,char**argv){
if(argc!=2){
printf("usage: fpid processname\n");
return -1;
}
pid_t pid = findPIdByName(argv[1]);
printf("%d\n",pid);
return 0;

}

注入so

步骤

  1. attach到目标进程
  2. 保存寄存器上下文
  3. 调用mmap函数分配内存空间-这里主要为了传递一些字符串参数
  4. 向内存空间写入加载模块名和调用函数
  5. 调用dlopen打开注入模块
  6. 调用dlsym获取调用函数的地址
  7. 调用被注入模块的函数
  8. 恢复寄存器环境
  9. 从远程进程detach

64位注入器

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
#include <stdio.h>
#include <stdlib.h>
#include <sys/user.h>
#include <asm/ptrace.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <dlfcn.h>
#include <dirent.h>
#include <unistd.h>
#include <string.h>
#include <elf.h>
#include <sys/uio.h>

#define pt_regs user_pt_regs

#define uregs regs
#define ARM_pc pc
#define ARM_sp sp
#define ARM_cpsr pstate
#define ARM_lr regs[30]
#define ARM_r0 regs[0]
#ifndef PTRACE_GETREGS
#define PTRACE_GETREGS PTRACE_GETREGSET
#endif
#ifndef PTRACE_SETREGS
#define PTRACE_SETREGS PTRACE_SETREGSET
#endif
#define CPSR_T_MASK (1u<<5)

#define MAX_PATH 512
char libcPath[]="/system/lib64/libc.so";

void ptraceWriteData(pid_t pid,void*addr,const char*data,size_t len){
size_t i=0;
for(;i<len;i+=sizeof(long)){
ptrace(PTRACE_POKETEXT,pid,(long)addr+i,(void*)*(long*)&data[i]);
}
// 每次写sizeof(long)字节,直接对齐
}
void ptraceReadData(pid_t pid,void*addr,char*data,size_t len){
size_t i=0;
long rdata;
for(;i<len;i+=sizeof(long)){
rdata=ptrace(PTRACE_PEEKTEXT,pid,(long)addr+i,NULL);
*(long*)&data[i]=rdata;
}
}
void ptraceAttach(pid_t pid){
if(ptrace(PTRACE_ATTACH,pid,NULL,NULL)==-1){
printf("[INJECT F]Failed to attach:%d\n",pid);
}
int stat=0;
waitpid(pid,&stat,WUNTRACED);
}
void ptraceGetRegs(pid_t pid,struct pt_regs*regs_addr){
struct iovec io;
io.iov_base=regs_addr;
io.iov_len=sizeof(struct pt_regs);
if(ptrace(PTRACE_GETREGS,pid,NT_PRSTATUS,&io)==-1){
printf("Get regs error\n");
}
}
void ptraceSetRegs(pid_t pid,struct pt_regs*regs_addr){
struct iovec io;
io.iov_base = regs_addr;
io.iov_len = sizeof(struct pt_regs);
if(ptrace(PTRACE_SETREGS,pid,NT_PRSTATUS,&io)==-1){
printf("Set regs error\n");
}
}
void ptraceDetach(pid_t pid){
ptrace(PTRACE_DETACH,pid,NULL,NULL);
}
void ptraceContinue(pid_t pid){
if(ptrace(PTRACE_CONT,pid,NULL,NULL)==-1){
printf("ptrace continue error\n");
}
}
void *getModuleBaseAddr(pid_t pid,const char*moduleName){
if(pid==-1)pid=getpid();
// 通过解析/proc/pid/maps 获得基址
char filepath[MAX_PATH];
void*moduleBaseAddr=NULL;
snprintf(filepath,MAX_PATH,"/proc/%d/maps",pid);
FILE *f = fopen(filepath,"r");
char line[MAX_PATH];
char base[MAX_PATH],name[MAX_PATH];
size_t cnt,start;
while(!feof(f)){
memset(line,0,MAX_PATH);
memset(name,0,MAX_PATH);
fgets(line,MAX_PATH,f);
cnt=0;
while(line[cnt]!='/')cnt++;
start=cnt;
while(line[cnt]){
name[cnt-start]=line[cnt];
cnt++;
}
name[cnt-start-1]=0;

if(strncmp(name,moduleName,MAX_PATH))continue;
memset(base,0,MAX_PATH);
cnt=0;
while(line[cnt]!='-'){
base[cnt]=line[cnt];
cnt++;
}
base[cnt]=0;
sscanf(base,"%llx",(long long*)(&moduleBaseAddr));
printf("[INJECT] GotBaseAddr %p of %s\n",moduleBaseAddr,moduleName);
break;
}
fclose(f);
return moduleBaseAddr;
}
void *getRemoteFuncAddr(pid_t pid,const char *moduleName,void *localFuncAddr){
void *localModuleAddr,*remoteModuleAddr,*remoteFuncAddr;
// 通过计算指定函数的偏移量获取目标函数地址
localModuleAddr = getModuleBaseAddr(-1,moduleName);
remoteModuleAddr = getModuleBaseAddr(pid,moduleName);
remoteFuncAddr=(void*)((long)localFuncAddr-(long)localModuleAddr+(long)remoteModuleAddr);
printf("[INJECT] GotFuncAddr:%p\n",remoteFuncAddr);
return remoteFuncAddr;
}
int ptraceCall(pid_t pid,void* funcAddr,long*paras,long paraLen,struct pt_regs *regs){

// 多于8个参数通过栈传递

if(paraLen>8){

regs->ARM_sp-=(paraLen-8)*sizeof(long);
ptraceWriteData(pid,(void*)regs->ARM_sp,(char*)&paras[6],sizeof(long)*(paraLen-6));
}
// 前6个参数通过寄存器传递
for(size_t i=0;i<8;i++){
regs->uregs[i]=paras[i];
}
// 调用函数
regs->ARM_pc=(unsigned long long)funcAddr;
// 判断arm模式还是thumb模式
if(regs->ARM_pc&1){
regs->ARM_pc&=~1;
regs->ARM_cpsr|=CPSR_T_MASK;
}else{
regs->ARM_cpsr&=~CPSR_T_MASK;
}
regs->ARM_lr=0;
ptraceSetRegs(pid,regs);
int stat=0;
while(stat!=0xb7f){
ptraceContinue(pid);
waitpid(pid,&stat,WUNTRACED);
printf("[INJECT] substatus: %x\n",stat);
}

ptraceGetRegs(pid,regs);
return 0;
}
void inject(pid_t pid,const char*libname,const char*funcName){
struct pt_regs oldRegs;
struct pt_regs regs;
long paras[6];
char realLibPath[PATH_MAX];
realpath(libname,realLibPath);
printf("[INJECT]Real path of lib found:%s\n",realLibPath);

ptraceAttach(pid);
// 保存寄存器环境
ptraceGetRegs(pid,&oldRegs);
memcpy(&regs,&oldRegs,sizeof(struct pt_regs));
// 获取mmap地址
void *mmapAddr = getRemoteFuncAddr(pid,libcPath,(void*)mmap);
// 调用mmap
paras[0]=0;
paras[1]=0x1000;
paras[2]=PROT_READ|PROT_WRITE|PROT_EXEC;
paras[3]=MAP_ANONYMOUS|MAP_PRIVATE;
paras[4]=0;
paras[5]=0;
ptraceCall(pid,mmapAddr,paras,6,&regs);
void *remoteMemAddr=(void*)regs.ARM_r0;
printf("[INJECT] remote mmaped addr:%p\n",remoteMemAddr);
// 调用dlopen
void *dlopenAddr=getRemoteFuncAddr(pid,libcPath,(void*)dlopen);
void *dlcloseAddr=getRemoteFuncAddr(pid,libcPath,(void*)dlclose);
void *dlErrorAddr=getRemoteFuncAddr(pid,libcPath,(void*)dlerror);
ptraceWriteData(pid,remoteMemAddr,realLibPath,strlen(realLibPath)+1); // 作为dlopen的参数
//debug start
char buf[MAX_PATH];
memset(buf,0,MAX_PATH);
ptraceReadData(pid,remoteMemAddr,buf,strlen(realLibPath)+3);
printf("%s\n",buf);
//debug end
paras[0]=(long)remoteMemAddr;
paras[1]=RTLD_NOW|RTLD_GLOBAL;
ptraceCall(pid,dlopenAddr,paras,2,&regs);
void*libBaseAddr=(void*)regs.ARM_r0;
printf("[INJECT]remote lib base:0x%llx of %s\n",(long long)libBaseAddr,realLibPath);
// 调用dlsym
void*dlsymAddr=getRemoteFuncAddr(pid,libcPath,(void*)dlsym);
ptraceWriteData(pid,remoteMemAddr,funcName,strlen(funcName)+1);
//debug start
memset(buf,0,MAX_PATH);
ptraceReadData(pid,remoteMemAddr,buf,strlen(funcName)+3);
printf("%s\n",buf);
//debug end
paras[0]=(long)libBaseAddr;
paras[1]=(long)remoteMemAddr;
ptraceCall(pid,dlsymAddr,paras,2,&regs);
void*remoteFuncAddr = (void*)regs.ARM_r0;
printf("[INJECT]addr:0x%llx of func %s\n",(long long)remoteFuncAddr,funcName);
// 调用目标函数
ptraceCall(pid,remoteFuncAddr,paras,0,&regs);
// 恢复寄存器环境
ptraceSetRegs(pid,&oldRegs);
ptraceDetach(pid);
}
pid_t findPIdByName(const char *name){
pid_t pid = -1;
int pDirId = 0; // process dir id
FILE *f;
char filename[MAX_PATH];
char cmdline[MAX_PATH];
struct dirent *entry=NULL;
int cnt;
if(name==NULL){
return -1;
}
DIR *dir = opendir("/proc");
if(dir==NULL){
return -1;
}
while((entry=readdir(dir))!=NULL){
pDirId=atoi(entry->d_name);
if(pDirId!=0){
snprintf(filename,MAX_PATH,"/proc/%d/cmdline",pDirId);
f = fopen(filename,"r");
if(f){
fgets(cmdline,sizeof(cmdline),f);
cnt = (int)strlen(cmdline);
while(cnt>=0&&cmdline[cnt]!='/')cnt--; // cmdline是完整路径,这里只找最后一个/之后的,就是可执行文件的文件名
cnt++;
if(!strncmp(name,&cmdline[cnt],strlen(name))){
pid=pDirId;
break;
}
}
fclose(f);
}
}
closedir(dir);
return pid;
}

int main(int argc,char **argv){
if(argc!=4){
printf("usage: inject processname libexample.so funcname\n");
return 0;
}
pid_t pid = findPIdByName(argv[1]);
printf("[INJECT]found pid:%d of %s\n",pid,argv[1]);
inject(pid,argv[2],argv[3]);
return 0;
}

32位注入器

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
#include <stdio.h>
#include <stdlib.h>
#include <sys/user.h>
#include <asm/ptrace.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <dlfcn.h>
#include <dirent.h>
#include <unistd.h>
#include <string.h>
#include <elf.h>
#include <sys/uio.h>

#define CPSR_T_MASK (1u<<5)

#define MAX_PATH 512
char libcPath[]="/system/lib/libc.so";
char linkerPath[] = "/system/bin/linker";

void ptraceWriteData(pid_t pid,void*addr,const char*data,size_t len){
size_t i=0;
for(;i<len;i+=sizeof(long)){
ptrace(PTRACE_POKETEXT,pid,(long)addr+i,(void*)*(long*)&data[i]);
}
// 每次写sizeof(long)字节,直接对齐
}
void ptraceReadData(pid_t pid,void*addr,char*data,size_t len){
size_t i=0;
long rdata;
for(;i<len;i+=sizeof(long)){
rdata=ptrace(PTRACE_PEEKTEXT,pid,(long)addr+i,NULL);
*(long*)&data[i]=rdata;
}
}
void ptraceAttach(pid_t pid){
if(ptrace(PTRACE_ATTACH,pid,NULL,NULL)==-1){
printf("[INJECT F]Failed to attach:%d\n",pid);
}
int stat=0;
waitpid(pid,&stat,WUNTRACED);
}
void ptraceGetRegs(pid_t pid,struct pt_regs*regs_addr){
// struct iovec io;
// io.iov_base=regs_addr;
// io.iov_len=sizeof(struct pt_regs);
if(ptrace(PTRACE_GETREGS,pid,NULL,regs_addr)==-1){
printf("Get regs error\n");
}
}
void ptraceSetRegs(pid_t pid,struct pt_regs*regs_addr){
// struct iovec io;
// io.iov_base = regs_addr;
// io.iov_len = sizeof(struct pt_regs);
if(ptrace(PTRACE_SETREGS,pid,NULL,regs_addr)==-1){
printf("Set regs error\n");
}
}
void ptraceDetach(pid_t pid){
ptrace(PTRACE_DETACH,pid,NULL,NULL);
}
void ptraceContinue(pid_t pid){
if(ptrace(PTRACE_CONT,pid,NULL,NULL)==-1){
printf("ptrace continue error\n");
}
}
void *getModuleBaseAddr(pid_t pid,const char*moduleName){
if(pid==-1)pid=getpid();
// 通过解析/proc/pid/maps 获得基址
char filepath[MAX_PATH];
void*moduleBaseAddr=NULL;
snprintf(filepath,MAX_PATH,"/proc/%d/maps",pid);
FILE *f = fopen(filepath,"r");
char line[MAX_PATH];
char base[MAX_PATH],name[MAX_PATH];
size_t cnt,start;
while(!feof(f)){
memset(line,0,MAX_PATH);
memset(name,0,MAX_PATH);
fgets(line,MAX_PATH,f);
cnt=0;
while(line[cnt]!='/')cnt++;
start=cnt;
while(line[cnt]){
name[cnt-start]=line[cnt];
cnt++;
}
name[cnt-start-1]=0;

if(strncmp(name,moduleName,MAX_PATH))continue;
memset(base,0,MAX_PATH);
cnt=0;
while(line[cnt]!='-'){
base[cnt]=line[cnt];
cnt++;
}
base[cnt]=0;
sscanf(base,"%x",(unsigned int*)(&moduleBaseAddr));
printf("[INJECT] GotBaseAddr %p of %s\n",moduleBaseAddr,moduleName);
break;
}
fclose(f);
return moduleBaseAddr;
}
void *getRemoteFuncAddr(pid_t pid,const char *moduleName,void *localFuncAddr){
void *localModuleAddr,*remoteModuleAddr,*remoteFuncAddr;
// 通过计算指定函数的偏移量获取目标函数地址
localModuleAddr = getModuleBaseAddr(-1,moduleName);
remoteModuleAddr = getModuleBaseAddr(pid,moduleName);
remoteFuncAddr=(void*)((long)localFuncAddr-(long)localModuleAddr+(long)remoteModuleAddr);
printf("[INJECT] GotFuncAddr:%p\n",remoteFuncAddr);
return remoteFuncAddr;
}
int ptraceCall(pid_t pid,void* funcAddr,long*paras,long paraLen,struct pt_regs *regs){

// 多于4个参数通过栈传递

if(paraLen>4){

regs->ARM_sp-=(paraLen-4)*sizeof(long);
ptraceWriteData(pid,(void*)regs->ARM_sp,(char*)&paras[6],sizeof(long)*(paraLen-6));
}
// 前6个参数通过寄存器传递
for(size_t i=0;i<4;i++){
regs->uregs[i]=paras[i];
}
// 调用函数
regs->ARM_pc=(unsigned long long)funcAddr;
// 判断arm模式还是thumb模式
if(regs->ARM_pc&1){
regs->ARM_pc&=~1;
regs->ARM_cpsr|=CPSR_T_MASK;
}else{
regs->ARM_cpsr&=~CPSR_T_MASK;
}
regs->ARM_lr=0;
ptraceSetRegs(pid,regs);
int stat=0;
while(stat!=0xb7f){
ptraceContinue(pid);
waitpid(pid,&stat,WUNTRACED);
printf("[INJECT] substatus: %x\n",stat);
}

ptraceGetRegs(pid,regs);
return 0;
}
void inject(pid_t pid,const char*libname,const char*funcName){
struct pt_regs oldRegs;
struct pt_regs regs;
long paras[6];
char realLibPath[PATH_MAX];
realpath(libname,realLibPath);
printf("[INJECT]Real path of lib found:%s\n",realLibPath);

ptraceAttach(pid);
// 保存寄存器环境
ptraceGetRegs(pid,&oldRegs);
memcpy(&regs,&oldRegs,sizeof(struct pt_regs));
// 获取mmap地址
void *mmapAddr = getRemoteFuncAddr(pid,libcPath,(void*)mmap);
// 调用mmap
paras[0]=0;
paras[1]=0x1000;
paras[2]=PROT_READ|PROT_WRITE|PROT_EXEC;
paras[3]=MAP_ANONYMOUS|MAP_PRIVATE;
paras[4]=0;
paras[5]=0;
ptraceCall(pid,mmapAddr,paras,6,&regs);
void *remoteMemAddr=(void*)regs.ARM_r0;
if(remoteMemAddr==(void*)-1){
printf("[INJECT ERR]mmapFailed\n");
}
printf("[INJECT] remote mmaped addr:%p\n",remoteMemAddr);
// 调用dlopen
void *dlopenAddr=getRemoteFuncAddr(pid,linkerPath,(void*)dlopen);
void *dlcloseAddr=getRemoteFuncAddr(pid,linkerPath,(void*)dlclose);
void *dlErrorAddr=getRemoteFuncAddr(pid,linkerPath,(void*)dlerror);
ptraceWriteData(pid,remoteMemAddr,realLibPath,strlen(realLibPath)+1); // 作为dlopen的参数
//debug start
char buf[MAX_PATH];
memset(buf,0,MAX_PATH);
ptraceReadData(pid,remoteMemAddr,buf,strlen(realLibPath)+3);
printf("%s\n",buf);
//debug end
paras[0]=(long)remoteMemAddr;
paras[1]=RTLD_NOW|RTLD_GLOBAL;
ptraceCall(pid,dlopenAddr,paras,2,&regs);
void*libBaseAddr=(void*)regs.ARM_r0;
printf("[INJECT]remote lib base:0x%llx of %s\n",(long long)libBaseAddr,realLibPath);
// 调用dlsym
void*dlsymAddr=getRemoteFuncAddr(pid,libcPath,(void*)dlsym);
ptraceWriteData(pid,remoteMemAddr,funcName,strlen(funcName)+1);
//debug start
memset(buf,0,MAX_PATH);
ptraceReadData(pid,remoteMemAddr,buf,strlen(funcName)+3);
printf("%s\n",buf);
//debug end
paras[0]=(long)libBaseAddr;
paras[1]=(long)remoteMemAddr;
ptraceCall(pid,dlsymAddr,paras,2,&regs);
void*remoteFuncAddr = (void*)regs.ARM_r0;
printf("[INJECT]addr:0x%llx of func %s\n",(long long)remoteFuncAddr,funcName);
// 调用目标函数
ptraceCall(pid,remoteFuncAddr,paras,0,&regs);
// 恢复寄存器环境
ptraceSetRegs(pid,&oldRegs);
ptraceDetach(pid);
}
pid_t findPIdByName(const char *name){
pid_t pid = -1;
int pDirId = 0; // process dir id
FILE *f;
char filename[MAX_PATH];
char cmdline[MAX_PATH];
struct dirent *entry=NULL;
int cnt;
if(name==NULL){
return -1;
}
DIR *dir = opendir("/proc");
if(dir==NULL){
return -1;
}
while((entry=readdir(dir))!=NULL){
pDirId=atoi(entry->d_name);
if(pDirId!=0){
snprintf(filename,MAX_PATH,"/proc/%d/cmdline",pDirId);
f = fopen(filename,"r");
if(f){
fgets(cmdline,sizeof(cmdline),f);
cnt = (int)strlen(cmdline);
while(cnt>=0&&cmdline[cnt]!='/')cnt--; // cmdline是完整路径,这里只找最后一个/之后的,就是可执行文件的文件名
cnt++;
if(!strncmp(name,&cmdline[cnt],strlen(name))){
pid=pDirId;
break;
}
}
fclose(f);
}
}
closedir(dir);
return pid;
}

int main(int argc,char **argv){
if(argc!=4){
printf("usage: inject processname libexample.so funcname\n");
return 0;
}
pid_t pid = findPIdByName(argv[1]);
printf("[INJECT]found pid:%d of %s\n",pid,argv[1]);
inject(pid,argv[2],argv[3]);
return 0;
}

要注入的so文件编译

Android.mk

1
2
3
4
5
6
7
8
LOCAL_PATH:=$(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE := hook
LOCAL_SRC_FILES := hook.c
LOCAL_LDLIBS += -llog

include $(BUILD_SHARED_LIBRARY)

Application.mk指定架构即可

hook.c

1
2
3
4
5
6
7
8
9
10
11
12
#include <android/log.h>

#define LOG_TAG "INJECT"
#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG , LOG_TAG, __VA_ARGS__))

void _init(void){
LOGD("Injected!\n");

}
void hookEntry(){
LOGD("Hook Entry called\n");
}

这里init可以作为Android的.init段,在so加载时执行

参考

[1] https://www.jianshu.com/p/c546783ad284

[2] https://xz.aliyun.com/t/5361?spm=5176.12901015.0.i12901015.583e525cW8q5ne

[3] https://gslab.qq.com/portal.php?mod=view&aid=162