2
27
2011
9

Wine QQ隐私保护器

本文来自依云's Blog,转载请注明。

鉴于QQ 4 Linux实在太不好用,有些不得不使用QQ的Linux用户使用Wine来运行QQ,我经过努力也成功了,但依旧不用,因为在去年的3Q大战中我们看到腾讯实在难以信任。想想我的ssh密钥、GPG密钥、getmail和msmtp的配置文件等等非常重要的文件QQ皆可轻易扫描并上传……

想过使用另外的低权限用户来wine QQ。然而即使这样,我自己的众多文件QQ依然可以看到,心中还是不安,而且这样作为QQ的重要功能之一——互传文件——将很不方便。于是终于有一天,我开始折腾chroot了。为了不需要总是sudo,我用C写的,可以使用Linux的suid特性,同时也练习下C编程。在man相关系统调用的时候,我还发现了unshare这个调用,竟然可以把挂载的文件系统设置成只在新的挂载命名空间(mount namespace)中可见。这样就可以把为了chroot而mount --bind的项目全部隐藏起来,免得mount输出一大堆不想看到的东西。至于umount嘛,不管了,反正只是mount --bind的。

/* ===================================================================== *
 * chrooted wine QQ
 * ===================================================================== */
#define _GNU_SOURCE
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<sys/mount.h>
#include<linux/limits.h>
#include<sched.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#define ROOT "/tmp/.rwine"
#define PROGRAM "你的QQ的目录"
/* 文件共享目录;若不存在会自动创建 */
#define SHAREDIR "哪个目录作为共享?"
#define BIN "Bin/QQ.exe"
#define ROOTD(dir) ROOT dir
#define MKDIR(dir) mkdir(ROOTD(dir), 0777)
#define MOUNTBIND(dir) mountbind(dir, ROOTD(dir))
int mountbind(const char *source, const char *target);
/* --------------------------------------------------------------------- */
int main(int argc, char **argv){
  int ret;
  ret = unshare(CLONE_NEWNS);
  if(ret){
    perror("unshare");
    exit(1);
  }
  seteuid(getuid());
  ret = mkdir(ROOT, 0777);
  seteuid(0);
  if(ret && errno != EEXIST){
    perror("mkdir new root dir");
    exit(1);
  }

  seteuid(getuid());
  mkdir(SHAREDIR, 0700);
  seteuid(0);
  setgid(0);
  MKDIR("/lib");
  MKDIR("/usr");
  MKDIR("/usr/share");
  MKDIR("/usr/bin");
  MKDIR("/usr/lib");
  MKDIR("/etc");
  MKDIR("/etc/fonts");
  MKDIR("/program");
  MKDIR("/dev");
  MKDIR("/tmp");
  MKDIR("/tmp/share");
  chmod(ROOTD("/tmp"), 01777);

  ret = MOUNTBIND("/lib")
    || MOUNTBIND("/usr/share")
    || MOUNTBIND("/usr/bin")
    || MOUNTBIND("/usr/lib")
    || MOUNTBIND("/etc/fonts")
    || MOUNTBIND("/dev")
    || mountbind(PROGRAM, ROOTD("/program"))
    || mountbind(SHAREDIR, ROOTD("/tmp/share"));
  if(ret){
    perror("mount --bind");
    exit(1);
  }

  char path[PATH_MAX];
  char path2[PATH_MAX];
  char *p;
  strcpy(path, getenv("HOME"));
  strcat(path, "/.wine");
  strcpy(path2, ROOT);
  p = strtok(path, "/");
  seteuid(getuid());
  while(p){
    strcat(path2, "/");
    strcat(path2, p);
    mkdir(path2, 0777);
    p = strtok(NULL, "/");
  }
  seteuid(0);
  /* path changed by strtok */
  strcpy(path, getenv("HOME"));
  strcat(path, "/.wine");
  strcpy(path2, ROOT);
  strcat(path2, path);
  ret = mountbind(path, path2);
  if(ret){
    perror("mount --bind user's wine dir");
    exit(1);
  }
  strcpy(path, getenv("HOME"));
  strcat(path, "/.fonts");
  if(access(path, X_OK) == 0){
    strcpy(path2, ROOT);
    strcat(path2, path);
    seteuid(getuid());
    mkdir(path2, 0777);
    seteuid(0);
    ret = mountbind(path, path2);
    if(ret){
      perror("mount --bind user's font dir");
      exit(1);
    }
  }
  strcpy(path, getenv("HOME"));
  strcat(path, "/.fontconfig");
  if(access(path, X_OK) == 0){
    strcpy(path2, ROOT);
    strcat(path2, path);
    seteuid(getuid());
    mkdir(path2, 0777);
    seteuid(0);
    ret = mountbind(path, path2);
    if(ret){
      perror("mount --bind user's fontconfig dir");
      exit(1);
    }
  }

  ret = chroot(ROOT);
  if(ret){
    perror("chroot");
    exit(1);
  }
  chdir("/tmp");
  ret = setuid(getuid());
  if(ret){
    perror("setuid");
    exit(1);
  }
  execl("/usr/bin/wine", "/usr/bin/wine", "/program/"BIN, NULL);
  /* execl("/usr/bin/wine", "/usr/bin/wine", "notepad", NULL); */
  perror("execl");
  return 1;
}
int mountbind(const char *source, const char *target){
  return mount(source, target, NULL, MS_BIND | MS_NOSUID, NULL);
}
/* ===================================================================== *
 * vim modeline                                                          *
 * vim:se fdm=expr foldexpr=getline(v\:lnum)=~'^\\S.*{'?'>1'\:1:         *
 * ===================================================================== */

程序名为rwine。这里的“r”可不是rsync或者rcp中的“r”,而是“rbash”、“rzsh”、“rvim”中的“r”的意思。在代码的最开头有几个宏是用来配置路径的。suid程序嘛,像这种东西还是硬编码进去好了。因为只是自己使用,所以没有做太多的错误检查之类的,健壮性应该不怎么样。对于这个程序我只要没有安全漏洞就OK了。

另外说个小插曲。在调试这段代码的时候,我发现用户的路径总是挂载得不对,检查了好久,才注意到strtok的声明是

char *strtok(char *str, const char *delim);

第一个参数那里没有const!再仔细看看man文档,果然,它把传进去的参数给改了。

Category: Linux | Tags: C代码 linux QQ 腾讯 | Read Count: 5580
lainme 说:
Feb 27, 2011 11:51:21 PM

我想webqq应该没这个顾虑吧...

Avatar_small
依云 说:
Feb 28, 2011 01:24:11 AM

是的,webqq 是安全的,但是,它除了文本聊天之外其它的都不好用(图片经常失败、传文件少有成功、不支持远程协助)。而且,由于同在浏览器中,我会因为输入法状态的混乱而经常搞错。

PS:你留言重复了,我删掉了一个。

o(∩∩)o...哈哈 说:
May 04, 2011 10:59:50 PM

哇卡,,你太能折腾啦我直接vbox+xp就ok了。。嘿嘿

Avatar_small
依云 说:
May 04, 2011 11:11:20 PM

那个太耗资源了……我的内存不够,CPU散热也不好。。。

而且,我不是想学下 Linux C 编程么?

Avatar_small
VT 说:
Sep 02, 2011 01:22:39 AM

Linux 3.0-ARCH x86_64

rwine.c:26:3: 警告:隐式声明函数‘unshare’ [-Wimplicit-function-declaration]
rwine.c:26:17: 错误:‘CLONE_NEWNS’未声明(在此函数内第一次使用)
rwine.c:26:17: 附注:每个未声明的标识符在其出现的函数内只报告一次

Avatar_small
依云 说:
Sep 02, 2011 11:44:37 AM

已更新,要 #define _GNU_SOURCE。

shellexy 说:
Sep 08, 2011 02:57:26 PM

其实可以用现成的 fakechroot 来干,
或者用 python-fuse 简单改下默认例子来过滤文件系统,
再不使用 LD_PRELOAD 也行(过滤掉无关目录的 open)

不过,既然是 wine 程序,其实最简单还是弄一个独立的 WINEPREFIX 目录,然后改掉里边的 C: Z: 之类盘符映射

Avatar_small
依云 说:
Sep 08, 2011 03:16:38 PM

当时没想到用 LD_PRELOAD 机制呢。不过:

fakechroot should not be used as a tool for enhancing system security i.e. by separating (sandboxing) applications. It is very easy to escape from a fake chroot environment.

似乎不够安全呢。而且也同样需要 mount --bind 以及 unshare,所以还得用 sudo。

自己写 LD_PRELOAD 库也不错,不过在这好久以后我才会写呢。

Wine 的配置我就从来没研究过了。没事不用浪费时间在它上面。

shellexy 说:
Sep 08, 2011 03:37:46 PM

不需要特地去看配置的,简单执行下
ls -lh ~/.wine/dosdevices/
就知道怎么回事了。

而独立 WINEPREFIX 就是加这个环境变量指定别的目录,

比如

env WINEPREFIX=~/.wine-qq/ wine notepad

(然后看 ~/.wine-qq/dosdevices/ 下的软链


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter

部分静态文件存储由又拍云存储提供。 | Theme: Aeros 2.0 by TheBuckmaker.com