Mq-b / Loser-HomeWork

卢瑟们的作业展示,答案讲解,以及一些C++知识
https://mq-b.github.io/Loser-HomeWork/
Apache License 2.0
613 stars 130 forks source link

14题解 #229

Closed BlueArc0 closed 7 months ago

BlueArc0 commented 7 months ago

^_^

BlueArc0 commented 7 months ago

测试编译器:x86-64 gcc 13.2,x86-64 clang 17.0.1,MinGW gcc 13.2.0,x64 msvc v19.37.32824

BlueArc0 commented 7 months ago

适配编译器适配的头大>_<

BlueArc0 commented 7 months ago

call_todo函数将该函数的返回地址(调用该函数时call指令压入栈中的地址)传递给todotodo从该地址开始搜索以8B开头的mov指令,并计算出ss::a的地址(ss::a的引用即[i+[i+2]+6],其中i是该条mov指令的开始位置,[i+2]mov指令中存储的偏移地址,6是该条指令的长度)。 由于std::cout << ss::a << '\n';代码读取了ss::a,我就希望从这段代码入手获取ss::a的地址,经过测试发现,读取ss::a的通常是一条以8B开头的mov指令,于是就有了todo函数; call_todo是为了避免CWG2811中不得使用main函数的规定,通过返回地址间接获取到main函数中mov指令前的地址,其中函数参数void*、局部变量void* p2是用来对齐不同编译器栈中内存布局不同,此时*(&p+3)即该函数的返回地址。 由于编译器不一定把读取ss::a编译成以8B开头的mov指令,以及由于栈中内存布局不同而导致*(&p+3)并不是函数的返回地址,所以这段代码不支持所有编译器,以下是测试能够正确运行的编译器: x86-64 gcc 13.2,x86-64 clang 17.0.1,MinGW gcc 13.2.0,x64 msvc v19.37.32824

BlueArc0 commented 7 months ago

最后把那个0xCC_b改成0xC3_b是因为在gcc上函数结束使用nop(90)而不是msvc的int 3(CC),所以改成了ret(C3)。这是失败路径防止意外修改的代码。

MrShinshi commented 7 months ago

思路挺个性,但你最好把这玩意儿写项目里去

Mq-b commented 7 months ago

我其实根本不懂这写法,不过看起来你写的很不错。

Mq-b commented 7 months ago

call_todo函数将该函数的返回地址(调用该函数时call指令压入栈中的地址)传递给todotodo从该地址开始搜索以8B开头的mov指令,并计算出ss::a的地址(ss::a的引用即[i+[i+2]+6],其中i是该条mov指令的开始位置,[i+2]mov指令中存储的偏移地址,6是该条指令的长度)。 由于std::cout << ss::a << '\n';代码读取了ss::a,我就希望从这段代码入手获取ss::a的地址,经过测试发现,读取ss::a的通常是一条以8B开头的mov指令,于是就有了todo函数; call_todo是为了避免CWG2811中不得使用main函数的规定,通过返回地址间接获取到main函数中mov指令前的地址,其中函数参数void*、局部变量void* p2是用来对齐不同编译器栈中内存布局不同,此时*(&p+3)即该函数的返回地址。 由于编译器不一定把读取ss::a编译成以8B开头的mov指令,以及由于栈中内存布局不同而导致*(&p+3)并不是函数的返回地址,所以这段代码不支持所有编译器,以下是测试能够正确运行的编译器: x86-64 gcc 13.2,x86-64 clang 17.1.0,MinGW gcc 13.2.0,x64 msvc v19.37.32824

Markdown 别写一坨,稍微列举分类一下,我看着都麻烦。

BlueArc0 commented 7 months ago

等我整理一下注释,没想好怎么塞代码里

Matrix-A commented 7 months ago

在 WSL 和虚拟机上测试未通过,系统均为 Ubuntu22.04 LTS ,编译器为 G++11.4

(gdb) p &p
$1 = (void **) 0x7fffffffdb98
(gdb) x /10x &p
0x7fffffffdb98: 0x00000000      0x00000000      0x00000000      0x00000000
0x7fffffffdba8: 0xe7777300      0xc38ba66e      0xffffdbc0      0x00007fff
0x7fffffffdbb8: 0x55555305      0x00005555
(gdb) p main
$2 = {int (void)} 0x5555555552d5 <main()>
(gdb)
rsp4jack commented 7 months ago

由于整个仓库的 git history 被改写,请更新 fork(rebase)。

BlueArc0 commented 7 months ago

在 WSL 和虚拟机上测试未通过,系统均为 Ubuntu22.04 LTS ,编译器为 G++11.4

(gdb) p &p
$1 = (void **) 0x7fffffffdb98
(gdb) x /10x &p
0x7fffffffdb98: 0x00000000      0x00000000      0x00000000      0x00000000
0x7fffffffdba8: 0xe7777300      0xc38ba66e      0xffffdbc0      0x00007fff
0x7fffffffdbb8: 0x55555305      0x00005555
(gdb) p main
$2 = {int (void)} 0x5555555552d5 <main()>
(gdb)

看起来把*(&p+3)改成*(&p+4)就能解决,这个位置确实挺玄学的;或者直接使用todo(reinterpret_cast<std::byte*>(main));而不是call_todo(nullptr);

Mq-b commented 7 months ago

@rsp4jack 我其实很好奇,你改写了啥,让这个 pr 直接有了 701 个提交。

rsp4jack commented 7 months ago

@Mq-b 从整个 git history 里删了一些很大的文件,然后从上传那个文件的 commit 到 HEAD 之间的所有 commit 都被修改了。

Mq-b commented 7 months ago

哦,,,你直接删了 Git 的历史是吧。