Anonymous RAT with global observer
Preface
Remote access can be a very dangerous software function, or a very useful one – it all depends on the context, intentions, tasks and purposes for which such programs will be used. The same situation with anonymity and anonymous communications in general. They can both hide malicious activity and hide legitimate activity from outsiders, who may benefit from its detection for both financial and political reasons. As a result, the technology remains neutral, just like any useful, and at the same time potentially dangerous thing.
The abbreviation RAT will be understood as Remote Access Tool, and not Remote Access Trojan, since the purpose of this article is not to write any malicious software, although there are not many differences in terms of functionality between these two designations.
Introduction
With a global observer, anonymous communications are very limited in their capabilities. Firstly, the communications themselves become very slow, sending and receiving information can take seconds and minutes. Secondly, such communications do not scale well and will be limited to small groups. Exceeding the group will lead to an increase in the processor load, which will lead to the disconnection of the weakest participants. Reducing the processor load will lead to an increase in spam and overload of the network channel. Due to such functional limitations, a number of application limitations also arise, such as the impossibility or complexity of writing streaming services, transferring large files, implementing synchronization systems, etc. Fortunately, remote access programs do not belong to this class of limitations, and therefore we can implement them quite correctly, guided by theoretically provable anonymity.
Theoretically provable anonymity
Anonymous networks with theoretically provable anonymity are considered to be closed, fully eavesdropped systems in which it becomes impossible to carry out any passive attacks (including in the presence of a global observer) aimed at deanonymizing the fact of sending and/or receiving information, or at deanonymizing the connection between the sender and the recipient with minimal conditions on the number of nodes not subject to collusion. In other words, from the point of view of a passive attacker, a posteriori knowledge obtained as a result of observations should remain equal to a priori knowledge, before observations, thereby preserving the equiprobability of deanonymization by N-th a multitude of network entities.
At the current time, there are three anonymization problems with theoretical provability: DC (Dining Cryptographers, the problem of dining cryptographers), EI (Entropy Increase, the problem of increasing entropy), QB (Queue Based, the problem based on queues). Anonymous networks Herbivore, Dissent, PriFi belong to DC, the Hidden Lake, MA networks belong to QB, EI has no representatives. From this list, only the Hidden Lake network remains alive today. We will try to write a remote access program based on it.
A little bit about the QB task
I have written quite a lot about the QB task in my previous articles and works. The most detailed description of this task can be found in the work “Anonymous network “Hidden Lake”” with all the features, advantages and disadvantages. If we talk about practice in short, then the essence of the task will be reduced to the implementation of the following steps:
Each message is encrypted with the recipient's key,
The message is sent during the period = T to all network participants,
Period T one participant is independent of periods T1T2…, Tn other participants,
If for a period T the message does not exist, then a false message is sent to the network without a recipient,
Each participant tries to decrypt the message received from the network.
![Three-member QB network Three-member QB network](https://habrastorage.org/getpro/habr/upload_files/6db/017/760/6db0177601751144ff3469e874d535a4.png)
The QB problem is so called because before sending a message to the network, each node first stores it in its queue and only after a period of time equal to T does it take the message from the queue and send it to the network.
QB networks can successfully resist global observers, especially if they are passive listeners of traffic. For example, it is easy enough to show that a global observer does not receive any information from the fact that he is able to listen to the input and output traffic of the entire network. He only sees a picture in which each node sends an encrypted message in its specified period of time to all network participants, without understanding whether the message is false or true. In other words, a QB network can be imagined as a generator of false traffic with the function of short-term replacement of a certain part of the noise with true traffic. Anonymity is formed on this property.
However, the QB problem has a number of shortcomings – one of them is that QB networks poorly anonymize the connections between subscribers. In other words, it is assumed that the sender and the recipient know each other and trust each other. This problem is based on the absence of information polymorphism in the QB problem, i.e. its variability as it is transmitted from one node to another. As a result, if one of the subscribers is an attacker and has a global observer as an ally, then deanonymization of the second subscriber, or more precisely, linking his IP (actual network address) with his public key (cryptographic address), will be considered a trivial task. However, such deanonymization will only link the addresses of one subscriber, but will not say anything about the connection of this subscriber with other participants in the network, which is what the QB problem assumes.
Information polymorphism
Information polymorphism is a property of variability of the transmitted object during multiple routing by several network subjects, which delimits the connection of subjects by analyzing the object. For example, if there are three network subjects {A, B, C} and object Pwhich is transmitted from A To B and from B To C accordingly, the appearance of the information P1 And P2 should be defined as [P1 = (A → B)] ≠ [P2 = (B → C)]Where P ∉ {P1, P2} And P1 ≠ P2(B does not bind {P1, P2} With P) And (A does not bind {P1, P} With P2) and/or (C does not bind {P2, P} With P1). In most cases, information polymorphism is achieved multiple encryption object: [E2(E1(P)) = (A → B)] ≠ [E1(P) = (B → C)](Where E — encryption function), in which the interstitial, intermediate subject B becomes unable to bind {E2(E1(P)), E1(P)} With Pand the subject C unable to connect {E1(P), P} With E2(E1(P)).
A little about the Hidden Lake network
Anonymous network Hidden Lake (HL) is a decentralized F2F (friend-to-friend) anonymous network with theoretical provability. Unlike well-known anonymous networks, such as Tor, I2P, Mixminion, Crowds, etc., the HL network is able to withstand global observer attacks. The Hidden Lake network does not need such criteria as: 1) to anonymize its traffic. level of network centralization2) number of nodes3) arrangement of nodes and 4) communication between nodes in the network. Based on such properties, HL is capable of being implemented into already existing centralized systems, thereby creating anonymity for its users, as demonstrated in the article “Anonymous P2P network inside a centralized HTTPS server: we sew parasitic traffic by hook or by crook.”
![](https://habrastorage.org/getpro/habr/upload_files/13d/b3e/d99/13db3ed99f12453db4b12fb875afd292.png)
The Hidden Lake network is based on a microservice architecture. This means that each new functionality, each new application is added to the network by implementing a new service. The advantage of such an architecture is the flexibility of implementation, because you can add the necessary and remove unnecessary functions without editing or changing the network code. Plus, new services can be written in any convenient technology and programming language, thereby easily integrating into the overall architecture.
The core of the network is the application HLS (HIdden Lake Service). It performs two functions: anonymizes traffic and routes requests. Traffic anonymization is based on the QB task. Request routing is tied to redirecting incoming traffic to application services and outgoing traffic to the external network. Thus, to write a new application, like a remote access program, we need to work with the HLS API.
Writing RAT over Hidden Lake
We will call the future application program that performs the remote access function in short HLR (Remoter), similar to other applications (HLM – Messenger, HLF – Filesharer) in the Hidden Lake network.
![](https://habrastorage.org/getpro/habr/upload_files/fe1/2a5/323/fe12a5323ce46d53ee308d0975911bc5.png)
In order for HLR to be more secure, it is also necessary to implement an additional authentication layer on top of the authentication used in HLS by public keys. passwordswhich can be sent in HTTP headers. All this needs to be done for two reasons:
HLS can be tied to the execution of several applications. If there were no password, then the intended authentication, for example, for a messenger or file sharing service, would also affect remote access, which would not be correct. HLR could implicitly affect both HLM and HLF, violating their isolation and security,
HLS can be started with the F2F option disabled. This is a perfectly acceptable action for some application uses, allowing network clients to connect and immediately send messages/requests without prior negotiation. If the password did not exist, HLR could not function securely with the F2F option disabled.
To implement HLR, first of all, it is necessary to write an HTTP handler that will receive a request from the outside, extract the request body and execute the command located in the body.
// С целью упрощения из кода удалены строки с логгированием.
func HandleIncomingExecHTTP(pCtx context.Context, pConfig config.IConfig) http.HandlerFunc {
return func(pW http.ResponseWriter, pR *http.Request) {
// Говорим HLS, что наш обработчик должен отвечать
// на принимаемый запрос.
pW.Header().Set(hls_settings.CHeaderResponseMode, hls_settings.CHeaderResponseModeON)
// Устанавливаем, что команды мы можем принимать только
// по HTTP методу POST.
if pR.Method != http.MethodPost {
_ = api.Response(pW, http.StatusMethodNotAllowed, "failed: incorrect method")
return
}
// Проверяем корректность пароля к функции удалённого доступа.
sett := pConfig.GetSettings()
if pR.Header.Get(hlr_settings.CHeaderPassword) != sett.GetPassword() {
_ = api.Response(pW, http.StatusForbidden, "failed: request forbidden")
return
}
// Читаем тело запроса.
cmdBytes, err := io.ReadAll(pR.Body)
if err != nil {
_ = api.Response(pW, http.StatusConflict, "failed: response message")
return
}
// Проверяем, что в теле запроса все символы читаемы,
// отсутствуют бинарные данные.
cmdStr := string(cmdBytes)
if chars.HasNotGraphicCharacters(cmdStr) {
_ = api.Response(pW, http.StatusBadRequest, "failed: has not graphic characters")
return
}
// Устанавливаем время за которое должна быть исполнена команда.
execTimeout := time.Duration(sett.GetExecTimeoutMS()) * time.Millisecond
ctx, cancel := context.WithTimeout(pCtx, execTimeout)
defer cancel()
// Разделяем команду по вызову конкретной программы и её аргументам.
// И далее исполняем команду с выводом результата.
cmdSplited := strings.Split(cmdStr, hlr_settings.CExecSeparator)
out, err := exec.CommandContext(ctx, cmdSplited[0], cmdSplited[1:]...).Output() // nolint: gosec
if err != nil {
_ = api.Response(pW, http.StatusInternalServerError, "failed: "+err.Error())
return
}
// Возвращаем полученный результат команды в качестве ответа.
_ = api.Response(pW, http.StatusOK, out)
}
}
Functions, interfaces and constants used
Response
const (
CTextPlain = "text/plain"
CApplicationJSON = "application/json"
CApplicationOctetStream = "application/octet-stream"
)
func Response(
pW http.ResponseWriter,
pRet int,
pRes interface{},
) error {
var (
contentType string
respBytes []byte
)
switch x := pRes.(type) {
case []byte:
contentType = CApplicationOctetStream
respBytes = x
case string:
contentType = CTextPlain
respBytes = []byte(x)
default:
contentType = CApplicationJSON
respBytes = encoding.SerializeJSON(x)
}
pW.Header().Set("Content-Type", contentType)
pW.WriteHeader(pRet)
if _, err := io.Copy(pW, bytes.NewBuffer(respBytes)); err != nil {
return utils.MergeErrors(ErrCopyBytes, err)
}
return nil
}
HasNotGraphicCharacters
func HasNotGraphicCharacters(pS string) bool {
for _, c := range pS {
if !unicode.IsGraphic(c) {
return true
}
}
return false
}
IConfig
type IConfig interface {
GetSettings() IConfigSettings
// <...>
}
type IConfigSettings interface {
GetExecTimeoutMS() uint64
GetPassword() string
}
Constants
const (
CHeaderPassword = "Hl-Remoter-Password"
CExecSeparator = "[@remoter-separator]"
)
And that's all the code. All that's left is to link it to the route to the path /exec
run the received service and bind its address to HLS. You can do the last step by simply adding to the file hls.yml (which is created when HLS is first launched) the path to the address of our HLR application.
# <...>
# Конфигурационный файл HLS
services:
hidden-lake-remoter:
# (HLR был запущен на port=9532)
host: localhost:9532
# <...>
Now, all that remains for us is to test the operation of HLR. For this, I prepared a simple docker-compose script with all the configured configuration files in the directories recv_hlc And send_hls. You can watch it at this link.
![The example uses a similar scheme of interaction between services. The differences are only in two details. First: instead of two application applications - HLM, HLF, one is used - HLR. Second: instead of one HLT, three HLTs are used in a chain. The example uses a similar scheme of interaction between services. The differences are only in two details. First: instead of two application applications - HLM, HLF, one is used - HLR. Second: instead of one HLT, three HLTs are used in a chain.](https://habrastorage.org/getpro/habr/upload_files/bcc/fa2/2d6/bccfa22d693183d39f1138a087b63696.png)
In short, recv_hlc is a node Bobwhich has HLR running. The send_hls node is a node Alicesending a command to the recv_hlc node over anonymized traffic. Both nodes are connected to each other via a chain of three relays (HLT – Traffic). Alice connects to the first relay, Bob to the third, the first and third relays are connected via the second. This does not affect the functionality or quality of anonymity in any way. All this is done only to check the correctness of the operation under the condition when the nodes are not connected directly.
Configuration files
On the recv_hlc side (Bob)
# hls.yml
settings:
message_size_bytes: 8192
work_size_bits: 22
key_size_bits: 4096
fetch_timeout_ms: 60000
queue_period_ms: 5000
rand_queue_period_ms: 5000
rand_message_size_bytes: 4096
logging:
- info
- warn
- erro
services:
hidden-lake-remoter:
host: recv_hlc:9532
connections:
- middle_hlt_3:6581
friends:
Alice: >-
PubKey{3082020A0282020100C17B6FA53983050B0339A0AB60D20A8A5FF5F8210564464C45CD2FAC2F266E8DDBA3B36C6F356AE57D1A71EED7B612C4CBC808557E4FCBAF6EDCFCECE37494144F09D65C7533109CE2F9B9B31D754453CA636A4463594F2C38303AE1B7BFFE738AC57805C782193B4854FF3F3FACA2C6BF9F75428DF6C583FBC29614C0B3329DF50F7B6399E1CC1F12BED77F29F885D7137ADFADE74A43451BB97A32F2301BE8EA866AFF34D6C7ED7FF1FAEA11FFB5B1034602B67E7918E42CA3D20E3E68AA700BE1B55A78C73A1D60D0A3DED3A6E5778C0BA68BAB9C345462131B9DC554D1A189066D649D7E167621815AB5B93905582BF19C28BCA6018E0CD205702968885E92A3B1E3DB37A25AC26FA4D2A47FF024ECD401F79FA353FEF2E4C2183C44D1D44B44938D32D8DBEDDAF5C87D042E4E9DAD671BE9C10DD8B3FE0A7C29AFE20843FE268C6A8F14949A04FF25A3EEE1EBE0027A99CE1C4DC561697297EA9FD9E23CF2E190B58CA385B66A235290A23CBB3856108EFFDD775601B3DE92C06C9EA2695C2D25D7897FD9D43C1AE10016E51C46C67F19AC84CD25F47DE2962A48030BCD8A0F14FFE4135A2893F62AC3E15CC61EC2E4ACADE0736C9A8DBC17D439248C42C5C0C6E08612414170FBE5AA6B52AE64E4CCDAE6FD3066BED5C200E07DBB0167D74A9FAD263AF253DFA870F44407F8EF3D9F12B8D910C4D803AD82ABA136F93F0203010001}
# hlr.yml
settings:
exec_timeout_ms: 5000
password: DpxJFjAlrs4HOWga0wk14mZqQSBo9DxK
logging:
- info
- warn
- erro
address:
incoming: :9532
On the send_hls side (Alice)
# hls.yml
settings:
message_size_bytes: 8192
work_size_bits: 22
key_size_bits: 4096
fetch_timeout_ms: 60000
queue_period_ms: 5000
rand_queue_period_ms: 5000
rand_message_size_bytes: 4096
logging:
- info
- warn
- erro
address:
http: :7572
connections:
- middle_hlt_1:6581
friends:
Bob: >-
PubKey{3082020A0282020100B752D35E81F4AEEC1A9C42EDED16E8924DD4D359663611DE2DCCE1A9611704A697B26254DD2AFA974A61A2CF94FAD016450FEF22F218CA970BFE41E6340CE3ABCBEE123E35A9DCDA6D23738DAC46AF8AC57902DDE7F41A03EB00A4818137E1BF4DFAE1EEDF8BB9E4363C15FD1C2278D86F2535BC3F395BE9A6CD690A5C852E6C35D6184BE7B9062AEE2AFC1A5AC81E7D21B7252A56C62BB5AC0BBAD36C7A4907C868704985E1754BAA3E8315E775A51B7BDC7ACB0D0675D29513D78CB05AB6119D3CA0A810A41F78150E3C5D9ACAFBE1533FC3533DECEC14387BF7478F6E229EB4CC312DC22436F4DB0D4CC308FB6EEA612F2F9E00239DE7902DE15889EE71370147C9696A5E7B022947ABB8AFBBC64F7840BED4CE69592CAF4085A1074475E365ED015048C89AE717BC259C42510F15F31DA3F9302EAD8F263B43D14886B2335A245C00871C041CBB683F1F047573F789673F9B11B6E6714C2A3360244757BB220C7952C6D3D9D65AA47511A63E2A59706B7A70846C930DCFB3D8CAFB3BD6F687CACF5A708692C26B363C80C460F54E59912D41D9BB359698051ABC049A0D0CFD7F23DC97DA940B1EDEAC6B84B194C8F8A56A46CE69EE7A0AEAA11C99508A368E64D27756AD0BA7146A6ADA3D5FA237B3B4EDDC84B71C27DE3A9F26A42197791C7DC09E2D7C4A7D8FCDC8F9A5D4983BB278FCE9513B1486D18F8560C3F31CC70203010001}
Repeater 1
settings:
message_size_bytes: 8192
key_size_bits: 4096
work_size_bits: 22
messages_capacity: 2048
rand_message_size_bytes: 4096
logging:
- info
- warn
- erro
address:
tcp: :6581
Repeater 2
settings:
message_size_bytes: 8192
key_size_bits: 4096
work_size_bits: 22
messages_capacity: 2048
rand_message_size_bytes: 4096
logging:
- info
- warn
- erro
address:
tcp: :6581
connections:
- middle_hlt_1:6581
Repeater 3
settings:
message_size_bytes: 8192
key_size_bits: 4096
work_size_bits: 22
messages_capacity: 2048
rand_message_size_bytes: 4096
logging:
- info
- warn
- erro
address:
tcp: :6581
connections:
- middle_hlt_2:6581
Now, we need to somehow send a request from Alice to Bob. For this, we can use a simple bash script. With it, we will try to create file.txt
on Bob's side with the contents hello, world
and then read it.
#!/bin/bash
BASE64_BODY="$(\
echo -n 'bash[@remoter-separator]-c[@remoter-separator]echo 'hello, world' > file.txt && cat file.txt' | \
base64 -w 0 \
)";
PUSH_FORMAT='{
"receiver":"Bob",
"req_data":{
"method":"POST",
"host":"hidden-lake-remoter",
"path":"/exec",
"head":{
"Hl-Remoter-Password": "DpxJFjAlrs4HOWga0wk14mZqQSBo9DxK"
},
"body":"'${BASE64_BODY}'"
}
}';
d="$(date +%s)";
curl -i -X POST -H 'Accept: application/json' http://localhost:7572/api/network/request --data "${PUSH_FORMAT}";
echo && echo "Request took $(($(date +%s)-d)) seconds";
We launch docker containers:
cd examples/anonymity/remoter/routing
make
> docker-compose build
> docker-compose up
Run the script:
cd examples/anonymity/remoter
./_request/request.sh
We get the answer:
HTTP/1.1 200 OK
Content-Type: text/plain
Date: Sun, 21 Jul 2024 20:43:45 GMT
Content-Length: 93
{"code":200,"head":{"Content-Type":"application/octet-stream"},"body":"aGVsbG8sIHdvcmxkCg=="}
Request took 9 seconds
We decode base64 from the response body, thereby obtaining the required string:
echo "aGVsbG8sIHdvcmxkCg" | base64 -d INT ✘
> hello, world
Conclusion
As a result of all that we wrote, a remote access program was implemented – HLR over the anonymous Hidden Lake network. Among the advantages of such a program, in comparison with, for example, SSH, we can highlight the concealment of real activity for a global observer, as well as the ability to act as a client, without worrying about raising a server on a specific address and port.
All source codes of what I have written can be found here. You can check the functionality of the HLR service locally here. Articles, presentations, books, diagrams and other documentation on the Hidden Lake network can be found Here.