Created
April 12, 2025 17:18
-
-
Save 0xHamy/f54f672ddf49e41e550350448e4c93a9 to your computer and use it in GitHub Desktop.
@0xvpr Fixed remote stack overflow for: https://github.com/LemonOSProject/LemonOS/blob/master/Applications/Steal/main.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <vector> | |
| #include <map> | |
| #include "URL.h" | |
| #include <algorithm> | |
| #include <cctype> | |
| #include <memory> | |
| #include <unistd.h> | |
| #include <sys/socket.h> | |
| #include <arpa/inet.h> | |
| #include <netdb.h> | |
| #include <openssl/ssl.h> | |
| #include <openssl/bio.h> | |
| #include <openssl/err.h> | |
| #include <stdio.h> | |
| #include <string.h> | |
| #include <getopt.h> | |
| #include <assert.h> | |
| int help = false; | |
| int verbose = false; | |
| char* outFile = nullptr; | |
| enum HTTPStatusCode { | |
| Continue = 100, | |
| SwitchingProtocols = 101, | |
| OK = 200, | |
| Created = 201, | |
| Accepted = 202, | |
| NonAuthoritativeInformation = 203, | |
| NoContent = 204, | |
| ResetContent = 205, | |
| PartialContent = 206, | |
| MultipleChoices = 300, | |
| MovedPermanently = 301, | |
| Found = 302, | |
| SeeOther = 303, | |
| NotModified = 304, | |
| UseProxy = 305, | |
| SwitchProxy = 306, | |
| TemporaryRedirect = 307, | |
| PermanentRedirect = 308, | |
| BadRequest = 400, | |
| Unauthorized = 401, | |
| PaymentRequired = 402, | |
| Forbidden = 403, | |
| NotFound = 404, | |
| MethodNotAllowed = 405, | |
| NotAcceptable = 406, | |
| ProxyAuthenticationRequired = 407, | |
| RequestTimeout = 408, | |
| Conflict = 409, | |
| Gone = 410, | |
| LengthRequired = 411, | |
| InternalServerError = 500, | |
| NotImplemented = 501, | |
| BadGateway = 502, | |
| ServiceUnavailable = 503, | |
| GatewayTimeout = 504, | |
| HTTPVersionNotSupported = 505, | |
| VariantAlsoNegotiates = 506, | |
| }; | |
| struct IOObject { | |
| virtual ~IOObject() = default; | |
| virtual ssize_t Read(void* buffer, size_t len) = 0; | |
| virtual ssize_t Write(void* buffer, size_t len) = 0; | |
| }; | |
| struct RawSocket final : IOObject { | |
| RawSocket(int fd) | |
| : sock(fd) {} | |
| ssize_t Read(void* buffer, size_t len){ | |
| return recv(sock, buffer, len, 0); | |
| } | |
| ssize_t Write(void* buffer, size_t len){ | |
| return send(sock, buffer, len, 0); | |
| } | |
| int sock; | |
| }; | |
| struct TLSSocket : IOObject { | |
| BIO* tlsIO; | |
| TLSSocket(BIO* io) | |
| : tlsIO(io) {} | |
| ssize_t Read(void* buffer, size_t len){ | |
| ssize_t rlen = BIO_read(tlsIO, buffer, len); | |
| while(rlen <= 0){ | |
| if(!BIO_should_retry(tlsIO)){ | |
| ERR_print_errors_fp(stderr); | |
| return -1; | |
| } | |
| rlen = BIO_read(tlsIO, buffer, len); | |
| } | |
| return rlen; | |
| } | |
| ssize_t Write(void* buffer, size_t len){ | |
| ssize_t rlen = BIO_write(tlsIO, buffer, len); | |
| while(rlen <= 0){ | |
| if(!BIO_should_retry(tlsIO)){ | |
| ERR_print_errors_fp(stderr); | |
| return -1; | |
| } | |
| rlen = BIO_write(tlsIO, buffer, len); | |
| } | |
| return rlen; | |
| } | |
| }; | |
| class HTTPResponse { | |
| public: | |
| std::string status; | |
| HTTPStatusCode statusCode; | |
| std::map<std::string, std::string> fields; | |
| }; | |
| void UpdateProgress(size_t recievedData, size_t dataLeft){ | |
| if(outFile){ | |
| size_t total = recievedData + dataLeft; | |
| printf("\033[1K\r %lu / %lu KB [", recievedData / 1024, total / 1024); | |
| if(total){ | |
| size_t pBarCount = ((recievedData * 40) / total); | |
| size_t fillCount = 40 - pBarCount; | |
| for(unsigned i = 0; i < pBarCount; i++){ | |
| putc('#', stdout); | |
| } | |
| for(unsigned i = 0; i < fillCount; i++){ | |
| putc(' ', stdout); | |
| } | |
| } | |
| putc(']', stdout); | |
| fflush(stdout); | |
| } | |
| } | |
| int RecieveResponse(IOObject* sock, HTTPResponse& response){ | |
| std::string line = ""; | |
| response.fields.clear(); | |
| response.status.clear(); | |
| char c; | |
| ssize_t ret; | |
| while((ret = sock->Read(&c, sizeof(char))) == 1){ | |
| if(verbose){ | |
| fputc(c, stdout); | |
| } | |
| if(c == '\r'){ | |
| continue; // We don't care about CR | |
| } else if(c == '\n'){ | |
| if(!line.length()){ | |
| break; | |
| } | |
| if(!response.status.length()){ | |
| response.status = std::move(line); // We do not need the data in line anymore | |
| size_t pos = response.status.find(' '); | |
| if(pos == std::string::npos){ | |
| return 20; // Bad response | |
| } | |
| response.statusCode = (HTTPStatusCode)std::stoi(response.status.substr(pos + 1, 3)); // Get response code | |
| } else if(size_t pos = line.find(':'); pos != std::string::npos){ | |
| std::transform(line.begin(), line.begin() + pos, line.begin(), tolower); // Convert field name to lowercase | |
| auto& value = response.fields[line.substr(0, pos)]; | |
| value = line.substr(pos + 2); // Insert field into map | |
| if(value.front() == ' '){ // Remove any leading whitespace | |
| value.erase(0, 1); | |
| } | |
| } else { | |
| return 20; // Invalid field | |
| } | |
| line.clear(); | |
| } else { | |
| line += c; | |
| } | |
| } | |
| if(ret == 0){ | |
| return 12; // Unexpected | |
| } else if(ret < 0) { | |
| printf("steal: Error (%i) recieving data: %s\n", errno, strerror(errno)); | |
| return 12; | |
| } | |
| return 0; | |
| } | |
| int HTTPGet(IOObject* sock, const std::string& host, const std::string& resource, HTTPResponse& response, FILE* stream){ | |
| { | |
| char request[4096]{}; | |
| int reqLength = snprintf(request, sizeof(request)-1, "GET /%s HTTP/1.1\r\n\ | |
| Host: %s\r\n\ | |
| User-Agent: curl/0.1\r\n\ | |
| Accept: */*\r\n\ | |
| Accept-Encoding: identity\r\n\ | |
| \r\n", resource.c_str(), host.c_str()); | |
| if(verbose){ | |
| printf("\n%s---\n\n", request); | |
| } | |
| if(ssize_t len = sock->Write(request, reqLength); len != reqLength){ | |
| if(len < 0){ | |
| perror("steal: Failed to send data"); | |
| } else { | |
| printf("steal: Failed to send all %i bytes (only sent %li).\n", reqLength, len); | |
| } | |
| return 11; | |
| } | |
| } | |
| std::string line; | |
| response.status.clear(); | |
| response.fields.clear(); | |
| if(int e = RecieveResponse(sock, response); e){ | |
| return e; | |
| } | |
| if(response.statusCode != HTTPStatusCode::OK){ | |
| return 0; | |
| } | |
| ssize_t contentLength = -1; | |
| enum { | |
| TransferNormal, | |
| TransferChunked, | |
| TransferUnknown | |
| } transferEncoding = TransferNormal; | |
| auto fieldsEnd = response.fields.end(); | |
| if(auto it = response.fields.find("transfer-encoding"); it != fieldsEnd){ | |
| if(!it->second.compare("chunked")){ | |
| transferEncoding = TransferChunked; | |
| } else if(!it->second.compare("identity")){ | |
| transferEncoding = TransferNormal; | |
| } else { | |
| transferEncoding = TransferUnknown; | |
| printf("steal: Unknown transfer encoding '%s'.\n", it->second.c_str()); | |
| return 21; | |
| } | |
| } | |
| if(auto it = response.fields.find("content-length"); it != fieldsEnd){ | |
| contentLength = std::stol(it->second); | |
| } | |
| if(transferEncoding == TransferNormal){ | |
| if(contentLength <= 0){ | |
| printf("steal: Content length not specified and transfer encoding is not 'chunked'.\n"); | |
| return 22; | |
| } | |
| ssize_t readSize = 4096; | |
| ssize_t expectedData = contentLength; | |
| char receiveBuffer[4096]{}; | |
| while(1){ | |
| if(readSize > expectedData){ | |
| readSize = expectedData; | |
| } | |
| ssize_t len = sock->Read(receiveBuffer, readSize); | |
| if(len < 0){ | |
| printf("steal: Error (%i) recieving data: %s\n", errno, strerror(errno)); | |
| return 12; | |
| } | |
| if(len > expectedData){ | |
| len = expectedData; | |
| } | |
| fwrite(receiveBuffer, 1, len, stream); | |
| expectedData -= len; | |
| UpdateProgress(expectedData, contentLength - expectedData); | |
| if(expectedData <= 0){ | |
| break; // If expectedData is zero and we have received the header then assume that all the data has been received | |
| } | |
| } | |
| } else if(transferEncoding == TransferChunked) { | |
| auto getSocketChar = [sock]() -> int { | |
| char c; | |
| if(ssize_t ret = sock->Read(&c, 1); ret <= 0){ | |
| return -1; | |
| } | |
| return c; | |
| }; | |
| auto getChunkSize = [getSocketChar]() -> ssize_t { | |
| char buf[33]{}; | |
| int bufIndex = 0; | |
| while(1){ | |
| int c = getSocketChar(); | |
| if(c <= 0){ // Read error | |
| printf("steal: Error (%i) recieving data: %s\n", errno, strerror(errno)); | |
| return 12; | |
| } else if(c == '\r'){ // Ignore carriage return | |
| continue; | |
| } else if(c == '\n'){ | |
| if(bufIndex == 0){ | |
| continue; // Leading CRLF? | |
| } | |
| break; | |
| } else if(c == ';'){ // Marks start of extra information | |
| fprintf(stderr, "steal: Warning: Ignoring extra chunk information.\n"); // We don't care about the extra chunk information | |
| while((c = getSocketChar()) != '\n'){ // Discard everything until newline | |
| if(c <= 0){ | |
| printf("steal: Error (%i) recieving data: %s\n", errno, strerror(errno)); | |
| return 12; | |
| } | |
| } | |
| break; | |
| } else if(bufIndex >= 32){ | |
| printf("steal: Error: chunk size field >= 32 characters\n"); | |
| return 23; | |
| } else { | |
| buf[bufIndex++] = c; | |
| } | |
| } | |
| buf[bufIndex] = 0; | |
| return std::stoll(buf, nullptr, 16); | |
| }; | |
| while(1) { | |
| ssize_t chunkSize = getChunkSize(); | |
| if(chunkSize < 0){ | |
| return chunkSize; | |
| } else if (chunkSize == 0) { | |
| return 1; | |
| } | |
| std::unique_ptr<char *> receiveBuffer = std:: make_unique<char *>(new char[chunkSize]); | |
| memset(*receiveBuffer, 0, chunkSize); | |
| ssize_t received = 0; | |
| while (received < chunkSize) { | |
| ssize_t ret = sock->Read(*receiveBuffer + received, chunkSize - received); | |
| if (ret <= 0) { | |
| printf("steal: Error (%i) recieving data: %s\n", errno, strerror(errno)); | |
| return 12; | |
| } | |
| received += ret; | |
| } | |
| fwrite(*receiveBuffer, 1, received, stream); | |
| } | |
| } else { | |
| assert(!"Invalid transfer encoding!"); | |
| } | |
| fflush(stream); | |
| return 0; | |
| } | |
| int ResolveHostname(const std::string& host, uint32_t& ip){ | |
| if(inet_pton(AF_INET, host.c_str(), &ip) > 0){ // Check if it is an IP address | |
| } else { | |
| addrinfo hint; | |
| memset(&hint, 0, sizeof(addrinfo)); | |
| hint = { .ai_family = AF_INET, .ai_socktype = SOCK_STREAM }; | |
| addrinfo* result; | |
| if(getaddrinfo(host.c_str(), nullptr, &hint, &result)){ | |
| printf("steal: Failed to resolve host '%s': %s.\n", host.c_str(), strerror(errno)); | |
| return 4; | |
| } | |
| ip = reinterpret_cast<sockaddr_in*>(result->ai_addr)->sin_addr.s_addr; | |
| } | |
| return 0; | |
| } | |
| void hacked() { | |
| printf("Hacked!\n"); | |
| exit(0); | |
| } | |
| int main(int argc, char** argv){ | |
| option opts[] = { | |
| {"help", no_argument, &help, true}, | |
| {"verbose", no_argument, nullptr, 'v'}, | |
| {"output", required_argument, nullptr, 'o'}, | |
| {nullptr, 0, nullptr, 0}, | |
| }; | |
| int option; | |
| while((option = getopt_long(argc, argv, "v", opts, nullptr)) >= 0){ | |
| switch(option){ | |
| case 'v': | |
| verbose = true; | |
| break; | |
| case 'o': | |
| outFile = strdup(optarg); | |
| break; | |
| case '?': | |
| default: | |
| printf("Usage: %s [options] <url>\nSee --help\n", argv[0]); | |
| return 1; | |
| } | |
| } | |
| if(argc - optind < 1){ | |
| printf("Usage: %s [options] <url>\nSee --help\n", argv[0]); | |
| return 1; | |
| } | |
| if(help){ | |
| printf("steal [options] <url>\n"); | |
| printf("Steal data from <url> (Default HTTP)\n\n"); | |
| printf("--help Show Help\n"); | |
| printf("-v, --verbose Show extra information\n"); | |
| return 0; | |
| } | |
| Lemon::URL url(argv[optind]); | |
| printf("URL: '%s'\n", argv[optind]); | |
| printf("Host: '%s'\n", url.Host().c_str()); | |
| printf("Valid: %d\n", url.IsValid()); | |
| if (!url.IsValid() || !url.Host().length()) { | |
| printf("steal: Invalid/malformed URL.\n"); | |
| return 2; | |
| } | |
| enum { | |
| ProtocolHTTP, | |
| ProtocolHTTPS, | |
| } protocol = ProtocolHTTP; | |
| if(url.Protocol().length()){ | |
| if(!url.Protocol().compare("http")){ | |
| protocol = ProtocolHTTP; | |
| } else if(!url.Protocol().compare("https")){ | |
| protocol = ProtocolHTTPS; | |
| } else { | |
| printf("steal: Unsupported protocol: '%s'.\n", url.Protocol().c_str()); | |
| return 2; | |
| } | |
| } | |
| FILE* out = nullptr; | |
| if(outFile){ | |
| FILE* file = fopen(outFile, "w"); | |
| if(!file){ | |
| perror("steal: Error creating file for output"); | |
| return 3; | |
| } | |
| fseek(file, 0, SEEK_SET); | |
| out = file; | |
| } else { | |
| out = stdout; | |
| } | |
| bool sslLoaded = false; | |
| SSL_CTX* sslContext; | |
| const SSL_METHOD* method; | |
| redirect: | |
| HTTPResponse response; | |
| if(protocol == ProtocolHTTP){ | |
| int sock = socket(AF_INET, SOCK_STREAM, 0); | |
| uint32_t ip; | |
| if(int e = ResolveHostname(url.Host(), ip)){ | |
| return e; | |
| } | |
| sockaddr_in address; | |
| address.sin_addr.s_addr = ip; | |
| address.sin_family = AF_INET; | |
| address.sin_port = htons(80); | |
| if(int e = connect(sock, reinterpret_cast<sockaddr*>(&address), sizeof(sockaddr_in)); e){ | |
| perror("steal: Failed to connect"); | |
| return 10; | |
| } | |
| RawSocket io(sock); | |
| if(int e = HTTPGet(&io, url.Host(), url.Resource(), response, out); e){ | |
| return e; | |
| } | |
| close(sock); | |
| } else if(protocol == ProtocolHTTPS){ | |
| if(!sslLoaded){ | |
| SSL_load_error_strings(); | |
| SSL_library_init(); | |
| ERR_load_BIO_strings(); | |
| OpenSSL_add_all_algorithms(); | |
| method = TLSv1_2_client_method(); | |
| sslContext = SSL_CTX_new(method); | |
| } | |
| if(!method){ | |
| perror("steal: TLS Error obtaining SSL method"); | |
| printf("steal: Error setting TLS connection hostname:"); | |
| ERR_print_errors_fp(stderr); | |
| return 50; | |
| } | |
| SSL *ssl = SSL_new(sslContext); | |
| assert(ssl); | |
| SSL_set_connect_state(ssl); | |
| SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); | |
| BIO* sslBasicIO = BIO_new(BIO_f_ssl()); | |
| assert(sslBasicIO); | |
| BIO_set_ssl(sslBasicIO, ssl, BIO_CLOSE); | |
| BIO* basicIO; | |
| if(url.Port().length()){ | |
| basicIO = BIO_new_connect((url.Host() + url.Port()).c_str()); | |
| } else { | |
| basicIO = BIO_new_connect((url.Host() + ":443").c_str()); | |
| } | |
| assert(basicIO); | |
| if(BIO_do_connect(basicIO) <= 0){ | |
| BIO_free_all(sslBasicIO); | |
| BIO_free_all(basicIO); | |
| printf("steal: (BIO_do_connect) Failed to establish TLS connection to '%s'.", url.Host().c_str()); | |
| ERR_print_errors_fp(stdout); | |
| return 42; | |
| } | |
| BIO_push (sslBasicIO, basicIO); | |
| TLSSocket io(sslBasicIO); | |
| if(int e = HTTPGet(&io, url.Host(), url.Resource(), response, out); e){ | |
| BIO_free_all(sslBasicIO); | |
| BIO_free_all(basicIO); | |
| return e; | |
| } | |
| BIO_free_all(sslBasicIO); | |
| BIO_free_all(basicIO); | |
| } | |
| if(response.statusCode == HTTPStatusCode::MovedPermanently){ | |
| auto it = response.fields.find("location"); | |
| if(it == response.fields.end()){ | |
| printf("steal: Invalid redirect: No location field"); | |
| return 13; | |
| } | |
| std::string& redirectURL = it->second; | |
| size_t pos = redirectURL.find_first_not_of(' '); | |
| url = Lemon::URL(redirectURL.substr(pos).c_str()); | |
| if(!url.IsValid()){ | |
| printf("steal: Invalid redirect: Invalid URL '%s'.\n", redirectURL.c_str()); | |
| return 14; | |
| } | |
| if(!url.Protocol().compare("http")){ | |
| protocol = ProtocolHTTP; | |
| } else if(!url.Protocol().compare("https")){ | |
| protocol = ProtocolHTTPS; | |
| } else { | |
| printf("steal: Invalid redirect: Unknown URL protocol '%s'.\n", url.Protocol().c_str()); | |
| return 15; | |
| } | |
| if(verbose){ | |
| printf("steal: Redirecting to '%s' (%s).\n", url.Host().c_str(), url.Protocol().c_str()); | |
| } | |
| goto redirect; | |
| } else if(response.statusCode != HTTPStatusCode::OK){ | |
| printf("steal: HTTP %d.\n", response.statusCode); | |
| return 20; | |
| } | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment