This file is indexed.

/usr/bin/unhide.rb is in unhide.rb 13-1.

This file is owned by root:root, with mode 0o755.

The actual contents of the file can be viewed below.

  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
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
#!/usr/bin/ruby -w

# Try to find running processes using different methods, and report
# processes found through some means but not through others.
#
# Exit code 2 means something fishy was detected.
#
# Exit code 1 means something went wrong.

# Copyright 2009 by Johan Walles, johan.walles@gmail.com
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.

require 'set'
require 'dl/import'
require 'dl/struct'

# Support for libc functions not covered by the standard Ruby
# libraries
module LibC
  extend DL::Importable
  dlload "libc.so.6"

  # PID scanning functions
  extern "int getsid(int)"
  extern "int sched_getscheduler(int)"
  extern "int sched_getparam(int, void*)"
  extern "int sched_rr_get_interval(int, void*)"
  extern "int sched_getaffinity(int, int, void*)"

  # We want to ask sysinfo about the number of active PIDs.  This
  # result struct has been taken from the sysinfo(2) Linux man page.
  SysInfoData = struct [
     "long uptime",
     "long loads[3]",
     "long totalram",
     "long freeram",
     "long sharedram",
     "long bufferram",
     "long totalswap",
     "long freeswap",
     "short procs",
     "long padding[42]"
   ]
   extern "int sysinfo(void *)"
end

# A piece of scratch memory where system calls can fill in
# information.  What's written here is not interesting, it's just that
# some of the PID scanning functions need a memory area to write into.
scratch = "\0" * 1024

# Make sure our scratch buffer is large enough for sched_getaffinity()
while LibC.sched_getaffinity(0, scratch.length, scratch) == -1
  scratch = "\0" * (scratch.length * 2)
end

$proc_parent_pids = nil
$proc_tasks = nil
$ps_pids = nil
def setup
  # List all parent processes pointed out by anybody in /proc
  $proc_parent_pids = Set.new
  proc_dir = Dir.new("/proc")
  proc_dir.each do |proc_entry|
    next unless File.directory?("/proc/" + proc_entry)
    next unless proc_entry =~ /^[0-9]+$/
    status_file = File.new("/proc/#{proc_entry}/status")
    begin
      status_file.each_line do |line|
        line.chomp!
        next unless line =~ /^ppid:\s+([0-9]+)$/i
        ppid = $1.to_i
        $proc_parent_pids << ppid
        break
      end
    ensure
      status_file.close
    end
  end
  proc_dir.close

  # List all thread pids in /proc
  $proc_tasks = {}
  proc_dir = Dir.new("/proc")
  proc_dir.each do |proc_entry|
    next unless File.directory?("/proc/" + proc_entry)
    next unless proc_entry =~ /^[0-9]+$/
    task_dir = Dir.new("/proc/#{proc_entry}/task")
    begin
      task_dir.each do |task_pid|
        next unless task_pid =~ /^[0-9]+$/

        # "true" means we *have* an executable
        exe = true
        begin
          # Store the *name* of the executable
          exe = File.readlink("/proc/#{proc_entry}/task/#{task_pid}/exe")
        rescue Errno::EACCES, Errno::ENOENT
          # This block intentionally left blank
        end
        $proc_tasks[task_pid.to_i] = exe
      end
    ensure
      task_dir.close
    end
  end
  proc_dir.close

  # List all PIDs displayed by ps
  $ps_pids = {}
  $ps_pids.default = false
  ps_stdout = IO.popen("ps axHo lwp,cmd", "r")
  ps_pid = ps_stdout.pid
  ps_stdout.each_line do |ps_line|
    ps_line.chomp!
    next unless ps_line =~ /^\s*([0-9]+)\s+([^ ]+).*$/
    pid = $1.to_i
    exe = $2
    $ps_pids[pid] = $2
  end
  ps_stdout.close
  # Remove the ps process itself from our pid list
  $ps_pids.delete ps_pid
end

# This array contains named PID detectors. Given a PID to examine they
# can say:
# * true (it exists)
# * "some string" (it exists, and here is its name)
# * false (it doesn't exist)
# * nil (don't know)
$pid_detectors = [
                 ["ps", proc { |pid|
                    # Does "ps" list this pid?
                    $ps_pids[pid]
                  }],

                 ["/proc", proc { |pid|
                    # Is there a /proc entry for this pid?
                    unless File.directory?("/proc/#{pid}")
                      break
                    end

                    begin
                      File.readlink("/proc/#{pid}/exe")
                    rescue Errno::ENOENT, Errno::EACCES
                      # Process exists, but we cannot read its exe symlink
                      true
                    end
                  }],

                 ["/proc tasks", proc { |pid|
                    # Is there a /proc/1234/tasks/pid directory for
                    # this pid?
                    $proc_tasks[pid]
                  }],

                 ["/proc parent", proc { |pid|
                    # Does any /proc entry point this pid out as a
                    # parent pid?
                    if $proc_parent_pids.include? pid
                      true
                    else
                      nil
                    end
                  }],

                 ["getsid()", proc { |pid|
                    LibC.getsid(pid) != -1
                  }],

                 ["getpgid()", proc { |pid|
                    exists = true
                    begin
                      Process.getpgid(pid)
                    rescue
                      exists = false
                    end
                    exists
                  }],

                 ["getpriority()", proc { |pid|
                    exists = true
                    begin
                      Process.getpriority(Process::PRIO_PROCESS, pid)
                    rescue
                      exists = false
                    end
                    exists
                  }],

                 ["sched_getparam()", proc { |pid|
                    LibC.sched_getparam(pid, scratch) != -1
                  }],

                 ["sched_getaffinity()", proc { |pid|
                    LibC.sched_getaffinity(pid, scratch.length, scratch) != -1
                  }],

                 ["sched_getscheduler()", proc { |pid|
                    LibC.sched_getscheduler(pid) != -1
                  }],

                 ["sched_rr_get_interval()", proc { |pid|
                    LibC.sched_rr_get_interval(pid, scratch) != -1
                  }]
                ]

found_something = false

# Scan PIDs and report those found by some means but not others
#
# Returns a map of pids->warning strings
def get_suspicious_pids(pids_to_scan = nil)
  if pids_to_scan == nil
    pid_max = File.new("/proc/sys/kernel/pid_max").gets.to_i
    pids_to_scan = (1..pid_max)
  end

  return_me = Hash.new
  pids_to_scan.each do |pid|
    pid_exists = {}
    $pid_detectors.each do |pid_detector|
      detector_name = pid_detector[0]
      detector_proc = pid_detector[1]

      pid_exists[detector_name] = detector_proc.call(pid)
    end

    # Is there consensus about the existence of this process?
    suspicious = false
    existence_consensus = nil
    pid_exists.values.each do |existence|
      # Always over-write "don't know"
      existence_consensus = existence if existence_consensus == nil

      # This one is undecisive, skip it
      next if existence == nil

      # Does the result of this test match the consensus?
      if existence_consensus == false
        unless existence == false
          suspicious = true
          break
        end
      else
        # Anything but "false" is considered to be true, can be a string
        # with a process name in it for example
        if existence == false
          suspicious = true
          break
        end
      end
    end

    if suspicious
      # Put output in a string and add it to the return result
      message = "Suspicious PID #{pid}:"
      $pid_detectors.each do |detector|
        detector_name = detector[0]
        detector_result = pid_exists[detector_name]
        next if detector_result == nil

        description = ""
        description =
          " (\"#{detector_result}\")" if detector_result.class == String
        message +=
          sprintf("\n  %s %s%s",
                  detector_result ? "Seen by" : "Not seen by",
                  detector_name,
                  description)
      end
      return_me[pid] = message
    end
  end

  return return_me
end


##
## Main program starts here
##


puts "Scanning for hidden processes..."

setup
# Verify PID count between ps and sysinfo()
sysinfo = LibC::SysInfoData.malloc
if LibC.sysinfo(sysinfo) == -1
  STDERR.puts "Error: failed calling sysinfo()"
  exit 1
end
if sysinfo.procs != $ps_pids.size
  $stderr.puts "ps and sysinfo() process count mismatch:"
  $stderr.puts "  ps: #{$ps_pids.size} processes"
  $stderr.puts "  sysinfo(): #{sysinfo.procs} processes"
  found_something = true
end

suspicious_pids = get_suspicious_pids

unless suspicious_pids.empty?
  # Filter out false positives by testing all positives again.  False
  # positives occur when we race with processes starting up or
  # shutting down.
  setup
  still_suspicious_pids = get_suspicious_pids(suspicious_pids.keys)

  still_suspicious_pids.keys.sort.each do |still_suspicious_pid|
    warning_text = suspicious_pids[still_suspicious_pid]
    next unless warning_text

    found_something = true
    $stderr.puts warning_text
  end
end

if found_something
  exit 2
else
  puts "No hidden processes found!"
end